[MOBY-guts] biomoby commit

Paul Gordon gordonp at dev.open-bio.org
Mon Jun 30 17:42:29 UTC 2008


gordonp
Mon Jun 30 13:42:29 EDT 2008
Update of /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/services/util
In directory dev.open-bio.org:/tmp/cvs-serv9782/src/main/ca/ucalgary/services/util

Modified Files:
	WSDLConfig.java 
Added Files:
	SourceMap.java 
Log Message:
Changes to reflect full SAWSDL support
moby-live/Java/src/main/ca/ucalgary/services/util SourceMap.java,NONE,1.1 WSDLConfig.java,1.2,1.3
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/services/util/WSDLConfig.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/services/util/WSDLConfig.java	2008/06/10 21:59:57	1.2
+++ /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/services/util/WSDLConfig.java	2008/06/30 17:42:29	1.3
@@ -1,5 +1,6 @@
 package ca.ucalgary.services.util;
 
+import org.biomoby.shared.LSIDResolver;
 import org.biomoby.shared.MobyPrefixResolver;
 import org.biomoby.shared.NamespaceContextImpl;
 import org.biomoby.shared.parser.MobyTags;
@@ -8,11 +9,16 @@
 
 import javax.xml.namespace.QName;
 import javax.xml.parsers.*;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.*;
 import javax.xml.xpath.*;
+import javax.xml.ws.*;
 
-import java.io.InputStream;
+import java.io.*;
 import java.net.URL;
 import java.util.*;
+import java.util.logging.*;
 
 /**
  * Used to expose a WSDL file enhanced with SAWSDL and Moby markup as a Moby service.
@@ -29,7 +35,9 @@
     public static final String SAWSDL_MODEL_ATTR = "modelReference";
     public static final String SAWSDL_INMAP_ATTR = "loweringSchemaMapping";
     public static final String SAWSDL_OUTMAP_ATTR = "liftingSchemaMapping";
+    public static final String MOBY_SECONDARY_SOURCE_ATTR = "secondaryParamSource";
 
+    private final String ARRAY_SENTINEL = "applyToEachXSIArrayItem";
     private final String SERVICENAME_ATTR_XPATH = "//sawsdl:attrExtensions"; 
     // note that XPath //@moby:"+SERVICE_NAME_ATTR doesn't work, not sure why at the moment, so we don't support WSDL2.0 yet...
 
@@ -40,11 +48,15 @@
 
     private Map<String,QName> mobyServiceName2Service;
     private Map<String,QName> mobyServiceName2Port;
-    private Map<String,String> mobyServiceName2OpInput;
-    private Map<String,String> mobyServiceName2OpOutput;
+    private Map<String,String> mobyServiceName2TargetNamespaceURI;
+    private Map<String,QName> mobyServiceName2OpInput;
+    private Map<String,QName> mobyServiceName2OpOutput;
     private Map<String,String> mobyServiceName2Op;
     private Map<String,Map<String,String>> mobyServiceName2InputXSDTypes;
     private Map<String,Map<String,String>> mobyServiceName2OutputXSDTypes;
+    private LSIDResolver lsidResolver;
+
+    private static Logger logger = Logger.getLogger("ca.ucalgary.services.util.WSDLConfig");
 
     static{
         XPathFactory xPathFactory = XPathFactory.newInstance();
@@ -69,12 +81,14 @@
 
 	mobyServiceName2Service = new HashMap<String,QName>();
 	mobyServiceName2Port = new HashMap<String,QName>();
+	mobyServiceName2TargetNamespaceURI = new HashMap<String,String>();
 	mobyServiceName2Op = new HashMap<String,String>();
-	mobyServiceName2OpInput = new HashMap<String,String>();
-	mobyServiceName2OpOutput = new HashMap<String,String>();
+	mobyServiceName2OpInput = new HashMap<String,QName>();
+	mobyServiceName2OpOutput = new HashMap<String,QName>();
 	mobyServiceName2InputXSDTypes = new HashMap<String,Map<String,String>>();
 	mobyServiceName2OutputXSDTypes = new HashMap<String,Map<String,String>>();
 
+	lsidResolver = new LSIDResolver();
 	parse(wsdlURL.openStream());
     }
 
@@ -103,8 +117,8 @@
     public void parseSOAPMessageSpecs() throws Exception{
 	for(String serviceName: getServiceNames()){
 	    setCurrentService(serviceName);
-	    String currentInputMessage = getOperationInputName();
-	    String currentOutputMessage = getOperationOutputName();
+	    String currentInputMessage = getOperationInputQName().getLocalPart();
+	    String currentOutputMessage = getOperationOutputQName().getLocalPart();
 
 	    NodeList messageElements = wsdlDoc.getDocumentElement().getElementsByTagNameNS(MobyPrefixResolver.WSDL_NAMESPACE, 
 											   "message");
@@ -125,6 +139,7 @@
 	Map<String,String> inputs = new HashMap<String,String>();
 	Map<String,String> inputMappings = new HashMap<String,String>();
 	Map<String,String> inputTypes = new HashMap<String,String>();
+	Map<String,String> secondaryInputs = new HashMap<String,String>();
 	Map<String,String> outputs = new HashMap<String,String>();
 	Map<String,String> outputMappings = new HashMap<String,String>();
 	Map<String,String> outputTypes = new HashMap<String,String>();
@@ -152,9 +167,21 @@
 		    throw new Exception("Part element " + partName + " of WSDL message " + 
 					messageName + " has no SAWSDL " + SAWSDL_INMAP_ATTR + " attribute defined");
 		}
-		inputs.put(partName, wsdlParam2MobyParam(partName, partType, modelReference));
-		inputTypes.put(partName, partType);
-		inputMappings.put(partName, schemaMapping);
+		String mobyParam = wsdlParam2MobyParam(partName, partType, modelReference);
+		if(mobyParam == null){
+		    // Check if it's a secondary
+		    mobyParam = wsdlParam2MobySecondaryParam(partName, partType, modelReference, partElement);
+		    if(mobyParam == null){
+			throw new Exception("The LSID '" + modelReference + 
+					    "' is not a properly formed Moby primary or secondary param LSID as required");
+		    }
+		    secondaryInputs.put(partName, mobyParam);
+		}
+		else{
+		    inputs.put(partName, mobyParam);
+		    inputTypes.put(partName, partType);
+		    inputMappings.put(partName, schemaMapping);
+		}
 	    }
 	    else{
 		schemaMapping = partElement.getAttributeNS(MobyPrefixResolver.SAWSDL_NAMESPACE, SAWSDL_OUTMAP_ATTR);
@@ -171,6 +198,7 @@
 	    setPrimaryInputs(inputs);
 	    setPrimaryInputFormats(inputMappings);
 	    setInputXSDTypes(inputTypes);
+	    setSecondaryInputs(secondaryInputs);
 	}
 	else{ // it's output
 	    setPrimaryOutputs(outputs);
@@ -179,13 +207,12 @@
 	}
     }
 
-    public String wsdlParam2MobyParam(String paramName, String paramXSDType, String dataTypeLSID) throws Exception{
+    public String wsdlParam2MobyParam(String paramName, String paramXSDType, String dataTypeLSID){
 	String[] dataTypeLSIDFields = dataTypeLSID.split(":");
 	if(dataTypeLSIDFields.length != 5 && dataTypeLSIDFields.length != 6 ||
 	   !dataTypeLSIDFields[0].equals("urn") || !dataTypeLSIDFields[1].equals("lsid") ||
 	   (!dataTypeLSIDFields[3].equals("objectclass") && !dataTypeLSIDFields[3].equals("namespacetype"))){
-	    throw new Exception("The LSID '" + dataTypeLSID + 
-				"' is not a properly formed Moby objectclass LSID as required");
+	    return null;
 	}
 	// Special case, if the LSID is a namespace, spec out a base object in the given namespace
 	if(dataTypeLSIDFields[3].equals("namespacetype")){
@@ -196,6 +223,350 @@
 	}
     }
 
+    public String wsdlParam2MobySecondaryParam(String paramName, String paramXSDType, String dataTypeLSID, Element partElement) throws Exception{
+	String[] dataTypeLSIDFields = dataTypeLSID.split(":");
+	if(dataTypeLSIDFields.length != 5 && dataTypeLSIDFields.length != 6 ||
+	   !dataTypeLSIDFields[0].equals("urn") || !dataTypeLSIDFields[1].equals("lsid") ||
+	   !dataTypeLSIDFields[3].equals("secondaryParamClass")){
+	    return null;
+	}   
+	// the format for secondaries is "name:type:defaultValue:[min,max]", where [min,max] can be an enumeration as well
+	String restrictionsSpec = getSecondaryParamRestrictions(partElement);
+	return paramName+":"+dataTypeLSIDFields[4]+":"+restrictionsSpec;
+    }
+
+    /**
+     * Checks out the Moby secondary parameter source attribute.  If it's a reference to another service, 
+     * with a nullary c-tor, that service is executed and the returned values are used in the secondary param spec
+     * valid value range.  The value could also be a literal pointed to by a Java-acceptable URL.
+     * 
+     * The returned value is of the form "defaultValue:[min,max]", where [min,max] can be an enumeration as well
+     */
+    public String getSecondaryParamRestrictions(Element partElement) throws Exception{
+	String secondaryValueSource = partElement.getAttributeNS(MobyPrefixResolver.MOBY_XML_NAMESPACE, 
+								 MOBY_SECONDARY_SOURCE_ATTR);
+	try{
+	    URL u = new URL(secondaryValueSource);
+	    return getSecondaryParamRestrictionsFromURL(u);
+	} catch(Exception e){
+	    // TODO: handle LSID case?
+	}
+	// we get here if the source was not a URL or LSID, assume it's a service (WSDL operation)
+	return getSecondaryParamRestrictionsFromService(partElement, secondaryValueSource);
+    }
+
+    public String getSecondaryParamRestrictionsFromURL(URL u){
+	// TODO
+	return "";
+    }
+
+    public String getSecondaryParamRestrictionsFromService(Element partElement, String opName) throws Exception{
+	String defaultValue = "";
+
+	Element wsdlRoot = partElement.getOwnerDocument().getDocumentElement();
+	// Find the operation element defining the op specified
+	Element opElement = null;
+	NodeList ops = wsdlRoot.getElementsByTagNameNS(MobyPrefixResolver.WSDL_NAMESPACE, "operation");
+	if(ops.getLength() == 0){
+	    ops = wsdlRoot.getElementsByTagNameNS(MobyPrefixResolver.WSDL20_NAMESPACE, "operation");
+	}
+	for(int i = 0; i < ops.getLength(); i++){
+	    Element opE = (Element) ops.item(i);
+	    if(opName.equals(opE.getAttribute("name"))){
+		opElement = opE;
+		break;
+	    }
+	}
+	if(opElement == null){
+	    logger.log(Level.SEVERE,
+		       "While trying to find a WSDL operation providing secondary parameter values, " +
+		       "either the WSDL or the specified parameter source operation (" + opName + ") is wrong");
+	    throw new IllegalArgumentException("While trying to find a WSDL operation providing secondary parameter values, " +
+		       "either the WSDL or the specified parameter source operation (" + opName + ") is wrong");
+	}
+
+	QName[] specs = getServiceAndPortFromOperation(opElement, opName);  //backtracks from the op to the qnames JAX-WS needs
+
+	Service service = null;
+	try{
+	    service = Service.create(wsdlURL, specs[0]);
+	} catch(Exception e){
+	    logger.log(Level.SEVERE,
+		       e.getClass().getName() + " while using JAX-WS to create a handle for" +
+		       "a service (" + specs[0] + ") providing secondary parameter values, either the WSDL or the " +
+		       "specified parameter source operation (" + opName + ") is wrong",
+		       e);
+	    throw e;
+	}
+
+	Dispatch<Source> dispatch = null;
+	try{
+	    dispatch = service.createDispatch(specs[1],
+					      Source.class,
+					      Service.Mode.PAYLOAD);
+	} catch(Exception e){
+	    logger.log(Level.SEVERE,
+		       e.getClass().getName() + " while using JAX-WS to create a dispatch for a port on " +
+		       "the service " + specs[0] + ", either the WSDL or the WSDLConfig's " +
+		       "portQName parsed (" + specs[1] + ") is wrong",
+		       e);
+	}
+	Map<String,Object> context = dispatch.getRequestContext();
+	context.put(Dispatch.SOAPACTION_USE_PROPERTY, Boolean.TRUE);
+	context.put(Dispatch.SOAPACTION_URI_PROPERTY, specs[0].getNamespaceURI()+"#"+opName);
+	
+	URL liftingSchema = getLiftingSchemaFromOperation(opElement, opName);
+	// Cheating slightly, since we can't return a URL and a boolean, the ref part of 
+        // the URL is set if we want to indicate the rule should be applied to all members of an XSI array
+	boolean isArrayRule = false;
+	if(ARRAY_SENTINEL.equals(liftingSchema.getRef())){
+	    isArrayRule = true;
+	}
+
+	// we expect the method to have a nullary signature, so we just create a blank input map
+	SourceMap source = new SourceMap(new QName(specs[0].getNamespaceURI(), opName)); 
+
+	Source resultSource = dispatch.invoke(source);
+	// Parse the results into the datatype required
+	//System.err.println("Class of response is " + resultSource.getClass().getName());
+	
+	String rangeValues = convertSourceToMobySecondaryValues(resultSource, liftingSchema, isArrayRule);
+	//System.err.println("Range values are:\n"+rangeValues);
+
+	if(rangeValues == null || rangeValues.length() == 0){
+	    throw new Exception("No values for the Moby secondary paramater could " +
+				"be found from nullary operation "+opName);
+	}
+	if(rangeValues.indexOf(',') == -1){
+	    defaultValue = rangeValues;  // only one choice anyway
+	}
+	// Pick the first value as the default value, for lack of a better strategy
+	else{
+	    defaultValue = rangeValues.substring(0, rangeValues.indexOf(','));
+	}
+	return defaultValue + ":[" + rangeValues + "]";
+    }
+
+    /**
+     * Goes from an operation, determines the output datatype, then sees if that datatype has a lifting schema
+     * that'll turn the response into a moby secondary param format.
+     */
+    protected URL getLiftingSchemaFromOperation(Element opElement, String opName) throws Exception{
+	QName[] ioMsgNames = getInputAndOutputMessageNamesFromOperation(opElement);
+	// ioMsgNames[1] is the output message name
+	String targetMessageName = ioMsgNames[1].getLocalPart();
+
+	// Now find the message definition in the WSDL
+	Element wsdlRoot = opElement.getOwnerDocument().getDocumentElement();
+	NodeList messageElements = wsdlRoot.getElementsByTagNameNS(MobyPrefixResolver.WSDL_NAMESPACE, 
+								   "message");
+	Element partElement = null; 
+	for(int i = 0; i < messageElements.getLength(); i++){
+	    String messageName = ((Element) messageElements.item(i)).getAttribute("name");
+
+	    if(targetMessageName.equals(messageName)){
+		// Find the message part, and its datatype
+		NodeList partElements = ((Element) messageElements.item(i)).getElementsByTagNameNS(MobyPrefixResolver.WSDL_NAMESPACE, 
+												   "part");
+		if(partElements.getLength() != 1){
+		    throw new Exception("The WSDL message (" + messageName+") containing the values " +
+					"for a Moby secondary param (operation "+opName+") did not have one part " +
+					"as expected, but rather "+partElements.getLength());
+		}
+		partElement = (Element) partElements.item(0);
+		break;
+	    }
+	}
+	if(partElement == null){
+	    throw new Exception("Could not find a message definition (" + targetMessageName + 
+				") for operation "+ opName);
+	}
+	// The lifting may be defijned at the "part" tag level, as it is for services
+	String schemaMapping = partElement.getAttributeNS(MobyPrefixResolver.SAWSDL_NAMESPACE, SAWSDL_OUTMAP_ATTR);
+	if(schemaMapping != null && schemaMapping.length() != 0){
+	    return new URL(schemaMapping);
+	}
+
+	String partType = partElement.getAttribute("type");
+	if(partType == null || partType.length() == 0){
+	    throw new Exception("The WSDL message (" + targetMessageName+") containing the values " +
+				"for a Moby secondary param (operation "+opName+") has a part element " +
+				"as expected, but no defined data type (type attribute missing)");
+	}
+	// See if the name is namespace qualified, and separate the local part if so
+	String partTypeNamespaceURI = "";
+	if(partType.contains(":")){
+	    String nsPrefix = partType.substring(0, partType.indexOf(":"));  //XML NS prefix
+	    partType = partType.substring(partType.indexOf(":")+1); //local part
+	    partTypeNamespaceURI = partElement.lookupNamespaceURI(nsPrefix);  //prefix->URI
+	}
+
+	// Now find the definition of the part's data type, and see if it has a schema lifting mapping
+	// Usually, <xsd:schema> -> <xsd:complexType name="partType"/>
+	NodeList schemaElements = wsdlRoot.getElementsByTagNameNS(MobyPrefixResolver.XSD_NAMESPACE, 
+								  "schema");
+	for(int i = 0; i < schemaElements.getLength(); i++){
+	    if(!partTypeNamespaceURI.equals(((Element) schemaElements.item(i)).getAttribute("targetNamespace"))){
+		continue; // only look as schema definitions in the correct namespace
+	    }
+
+	    NodeList schemaDefElements = ((Element) schemaElements.item(i)).getChildNodes();
+	    for(int j = 0; j < schemaDefElements.getLength(); j++){
+
+		if(!(schemaDefElements.item(j) instanceof Element)){
+		    continue;
+		}
+
+		Element schemaDefElement = (Element) schemaDefElements.item(j);
+		// If it's an XML Schema definition element with the same name as our part type, we're good to go...
+		if((MobyPrefixResolver.XSD_NAMESPACE.equals(schemaDefElement.getNamespaceURI())) &&
+		   partType.equals(schemaDefElement.getAttribute("name"))){
+		    // See if the SAWSDL lifting schema attribute is defined
+		    schemaMapping = partElement.getAttributeNS(MobyPrefixResolver.SAWSDL_NAMESPACE, SAWSDL_OUTMAP_ATTR);
+		    if(schemaMapping == null || schemaMapping.length() == 0){
+			// As a last-ditch effort, if the data type is just an array of another datatype, 
+			// look up the other data type to see if it has a lifting schema mapping, and
+			// we will take care of the array iteration part opf the transformation
+			// The XML must look something like:
+			//<xsd:complexType name="ArrayOfThing">
+			// <xsd:complexContent>
+			//  <xsd:restriction base="soapenc:Array">
+			//   <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="typens:Thing[]"/>
+			// </xsd:restriction></xsd:complexContent></xsd:complexType>
+			NodeList contentElements = schemaDefElement.getElementsByTagNameNS(MobyPrefixResolver.XSD_NAMESPACE, 
+											   "complexContent");
+			if(contentElements.getLength() == 1){
+			    NodeList restrictionElements = 
+				((Element) contentElements.item(0)).getElementsByTagNameNS(MobyPrefixResolver.XSD_NAMESPACE, 
+											   "restriction");
+			    if(restrictionElements.getLength() == 1){
+				NodeList attributeElements = ((Element) restrictionElements.item(0)).getElementsByTagNameNS(MobyPrefixResolver.XSD_NAMESPACE, 
+															    "attribute");
+				if(attributeElements.getLength() == 1){
+				    // NOTE: WSDL 1.1 only for now
+				    String arrayType = ((Element) attributeElements.item(0)).getAttributeNS(MobyPrefixResolver.WSDL_NAMESPACE, 
+													    "arrayType");
+				    if(arrayType.contains(":")){
+					arrayType = arrayType.substring(arrayType.indexOf(":")+1); //local part
+				    }
+				    if(!arrayType.endsWith("[]")){
+					throw new Exception("The definition of XML Schema type " + partType + 
+							    " used as the output of " + 
+							    opName + " has no SAWSDL " + SAWSDL_OUTMAP_ATTR + 
+							    " attribute defined, nor is" +
+							    "it simply an array of a type with a lifting schema");
+				    }
+				    arrayType = arrayType.substring(0, arrayType.length()-2); // remove the array square brackets
+				    for(int k = 0; k < schemaDefElements.getLength(); k++){
+					if(!(schemaDefElements.item(k) instanceof Element)){
+					    continue;
+					}
+					Element sde = (Element) schemaDefElements.item(k);
+					if((MobyPrefixResolver.XSD_NAMESPACE.equals(sde.getNamespaceURI())) &&
+					   arrayType.equals(sde.getAttribute("name"))){
+					    // We're at the array's data type.  This is the last chance to find a schema lifting mapping
+					    schemaMapping = sde.getAttributeNS(MobyPrefixResolver.SAWSDL_NAMESPACE, SAWSDL_OUTMAP_ATTR);
+					    if(schemaMapping != null && schemaMapping.length() != 0){
+						// The ARRAY_SENTINEL as a ref part of the URL indicates the rule should be
+						// applied to each members of the incoming array, not the once to the whole array
+						if(lsidResolver.isLSID(schemaMapping)){
+						    return new URL(lsidResolver.resolveDataURL(schemaMapping).toString()+
+								   "#"+ARRAY_SENTINEL);
+						}
+						else{
+						    return new URL(schemaMapping+"#"+ARRAY_SENTINEL);
+						}
+					    }
+					    throw new Exception("Neither the array datatype ("+partType + ") nor the datatype " +
+								"it stores (" + arrayType + ") has a SAWSDL " + SAWSDL_OUTMAP_ATTR +
+								" attribute");
+					}
+				    }
+				    throw new Exception("Could not find XML Schema type definition for " + arrayType);
+				}
+			    }
+			}
+
+			throw new Exception("The definition of XML Schema type " + partType + " used as the output of " + 
+					    opName + " has no SAWSDL " + SAWSDL_OUTMAP_ATTR + " attribute defined, nor is" +
+					    "it simply an array of a type with a lifting schema");
+		    }
+		    if(lsidResolver.isLSID(schemaMapping)){
+			return lsidResolver.resolveDataURL(schemaMapping);
+		    }
+		    else{
+			return new URL(schemaMapping);
+		    } 
+		}
+	    }
+	}
+	throw new Exception("Could not find the data type definition for " + partType + " in the WSDL");
+	    
+    }
+
+    protected String convertSourceToMobySecondaryValues(Source source, URL liftingSchema, boolean arrayRule) throws Exception{
+	// The source will contain an XML document, lets convert it to a string of the form val1,val2,val3 using a stylesheet
+	TransformerFactory transformerFactory = TransformerFactory.newInstance();
+	Transformer transformer = null;
+	//System.err.println("The lifting schema is " + liftingSchema);
+	try{
+	    // Create the transformer that'll turn the soap payload into a moby secondary spec string
+	    transformer = transformerFactory.newTransformer(new StreamSource(liftingSchema.openStream()));
+	} catch (TransformerConfigurationException tce){
+	    logger.log(Level.SEVERE,
+		       "Could not create an XSLT transformer: " + tce,
+		       tce);
+	}
+	StringWriter stringWriter = new StringWriter();
+	// Apply the rule to each member of the array, rather than just once to the array
+	if(arrayRule){
+	    // To simplify life (at the cost of inefficiency of processing), 
+            // turn any source into a stream source (via a null XSLT transform) 
+            // so we can parse it uniformily
+	    if(!(source instanceof StreamSource)){
+		StringWriter verbatim = new StringWriter();
+		transformerFactory.newTransformer().transform(source, new StreamResult(verbatim));
+                //System.err.println("Verbatim source is:\n" + verbatim);
+		source = new StreamSource(new StringReader(verbatim.toString()));
+	    }
+
+	    Document arrayDoc = null;
+	    if(((StreamSource) source).getInputStream() != null){
+		arrayDoc = docBuilder.parse(((StreamSource) source).getInputStream());
+	    }
+	    else if(((StreamSource) source).getReader() != null){
+		arrayDoc = docBuilder.parse(new org.xml.sax.InputSource(((StreamSource) source).getReader()));
+	    }
+	    else{
+		throw new Exception("Neither the InputStream or Reader was available from " +
+				    "the StreamSource, cannot process the source");
+	    }
+	    NodeList arrayElements = arrayDoc.getElementsByTagName("item");
+	    for(int i = 0; i < arrayElements.getLength(); i++){
+		if(i != 0){
+		    stringWriter.write(',');  //join values with a comma
+		}
+		transformer.transform(new DOMSource(arrayElements.item(i)), new StreamResult(stringWriter));
+	    }
+	}
+	// Not an array rule, just apply the stylesheet as-is
+	else{
+	    transformer.transform(source, new StreamResult(stringWriter));
+	}
+	//System.err.println("Response payload:\n"+stringWriter.toString());
+
+	return stringWriter.toString();
+    }
+
+    public static String join(Iterable i, String delimiter) {
+        StringBuffer buffer = new StringBuffer();
+	for(Object item: i){
+            buffer.append(item.toString());
+	    buffer.append(delimiter);
+        }
+        return buffer.substring(0, buffer.length()-delimiter.length());
+    }
+
     // Finds out the soap operations to wrap and the associated Moby metadata such as service name, authority, etc.
     public void parseMobyServiceSpecs() throws Exception{
 	NodeList serviceNameAttrs = (NodeList) xPath.evaluate(SERVICENAME_ATTR_XPATH,
@@ -206,14 +577,6 @@
 				"Either this is not a Moby-oriented SAWSDL file, or the Moby namespace is not properly defined.");
 	}
 	for(int i = 0; i < serviceNameAttrs.getLength(); i++){
-// 	    Node resultNode = serviceNameAttrs.item(i);
-// 	    if(!(resultNode instanceof Attr)){
-// 		throw new Exception("The XPath to retrieve Moby serviceName attributes returned a non-Attr (" +
-// 				    resultNode.getClass().getName() + ").  XPath programming error?");
-// 	    }
-// 	    Attr serviceAttr = (Attr) resultNode;
-// 	    // Make sure the service name is Moby Kosher
-// 	    String serviceName = serviceAttr.getValue();
 	    Element el = (Element) serviceNameAttrs.item(i);
 	    String serviceName = el.getAttributeNS(MobyPrefixResolver.MOBY_XML_NAMESPACE, SERVICE_NAME_ATTR);
 	    if(serviceName == null || serviceName.trim().length() == 0){
@@ -304,7 +667,21 @@
 				"of Moby service " + currentService + " is missing or blank");
 	}
 	soapOpName = soapOpName.trim();
-	
+	QName[] names = getServiceAndPortFromOperation(opElement, soapOpName);
+	QName[] ioMsgNames = getInputAndOutputMessageNamesFromOperation(opElement);
+
+	setServiceQName(names[0]);
+	setPortQName(names[1]);
+	setTargetNamespaceURI(names[0].getNamespaceURI());
+	setOperationName(soapOpName);
+	setOperationInputQName(ioMsgNames[0]);
+	setOperationOutputQName(ioMsgNames[1]);
+    }
+
+    /**
+     * @return a two element array, with input message name, then output message name
+     */
+    protected QName[] getInputAndOutputMessageNamesFromOperation(Element opElement) throws Exception{
 	// Now that we have the operation, find the input and output children we need to note
 	NodeList inputElements = opElement.getElementsByTagNameNS(MobyPrefixResolver.WSDL_NAMESPACE, "input");
 	if(inputElements == null || inputElements.getLength() == 0){
@@ -318,15 +695,6 @@
 	    throw new Exception("More than one WSDL input element is defined for the operation " +
 				"element defining the Moby service " + currentService);
 	}
-	// Should have element or message attr for WSDL 2.0 and 1.1 respectively
-	String inputName = ((Element) inputElements.item(0)).getAttribute("message");
-	if(inputName == null || inputName.trim().length() == 0){
-	    inputName = ((Element) inputElements.item(0)).getAttribute("element");
-	}
-	if(inputName == null || inputName.trim().length() == 0){
-	    throw new Exception("Could not find the message or element attribute associated with " +
-				"the WSDL input element for the Moby service " + currentService);
-	}
 	
 	NodeList outputElements = opElement.getElementsByTagNameNS(MobyPrefixResolver.WSDL_NAMESPACE, "output");
 	if(outputElements == null || outputElements.getLength() == 0){
@@ -340,6 +708,24 @@
 	    throw new Exception("More than one WSDL output element is defined for the operation " +
 				"element defining the Moby service " + currentService);
 	}
+
+	// Should have element or message attr for WSDL 2.0 and 1.1 respectively
+	String inputName = ((Element) inputElements.item(0)).getAttribute("message");
+	if(inputName == null || inputName.trim().length() == 0){
+	    inputName = ((Element) inputElements.item(0)).getAttribute("element");
+	}
+	if(inputName == null || inputName.trim().length() == 0){
+	    throw new Exception("Could not find the message or element attribute associated with " +
+				"the WSDL input element for the Moby service " + currentService);
+	}
+	String nsURI = null;
+	if(inputName.contains(":")){  // convert ns:label to QName object
+	    String nsPrefix = inputName.substring(0, inputName.indexOf(":"));  //XML NS prefix
+	    inputName = inputName.substring(inputName.indexOf(":")+1); //local part
+	    nsURI = inputElements.item(0).lookupNamespaceURI(nsPrefix);  //prefix->URI
+	}
+	QName inputQName = new QName(nsURI, inputName);
+
 	// Should have element or message attr for WSDL 2.0 and 1.1 respectively
 	String outputName = ((Element) outputElements.item(0)).getAttribute("message");
 	if(outputName == null || outputName.trim().length() == 0){
@@ -349,7 +735,21 @@
 	    throw new Exception("Could not find the message or element attribute associated with " +
 				"the WSDL output element for the Moby service " + currentService);
 	}
-	
+	if(outputName.contains(":")){  // convert ns:label to QName object
+	    String nsPrefix = outputName.substring(0, outputName.indexOf(":"));  //XML NS prefix
+	    outputName = outputName.substring(outputName.indexOf(":")+1); //local part
+	    nsURI = outputElements.item(0).lookupNamespaceURI(nsPrefix);  //prefix->URI
+	}
+	QName outputQName = new QName(nsURI, outputName);
+
+	return new QName[]{inputQName, outputQName};
+    }
+
+    /**
+     * @return a two element array, with service, then port
+     */
+    protected QName[] getServiceAndPortFromOperation(Element opElement, String soapOpName) throws Exception{
+
 	// Now go up the DOM to find the port type (WSDL 1.1) or interface (WSDL 2.0) enclosing the operation
 	Element porttypeInterfaceElement = (Element) opElement.getParentNode();
 	for(; porttypeInterfaceElement != null; 
@@ -443,7 +843,7 @@
 	if(serviceName == null || serviceName.length() == 0){
 	    throw new Exception("The WSDL service element enclosing the port " + portName + 
 				" does not have a name attribute");
-	}
+	}	
 
 	// find the target namespace for the service and port, as JAX-WS will need these later
 	String targetNamespace = wsdlDoc.getDocumentElement().getAttributeNS(MobyPrefixResolver.WSDL_NAMESPACE, 
@@ -455,11 +855,10 @@
 	    throw new Exception("No targetNamespace attribute was found in the root element of the WSDL document");
 	}
 
-	setServiceQName(new QName(targetNamespace, serviceName));
-	setPortQName(new QName(targetNamespace, portName));
-	setOperationName(soapOpName);
-	setOperationInputName(inputName);
-	setOperationOutputName(outputName);
+	QName[] specs = new QName[2];
+	specs[0] = new QName(targetNamespace, serviceName);
+	specs[1] = new QName(targetNamespace, portName);
+	return specs;
     }
 
     public String getOperationName(){
@@ -470,6 +869,14 @@
 	mobyServiceName2Op.put(currentService, opName);
     }
 
+    public String getTargetNamespaceURI(){
+	return mobyServiceName2TargetNamespaceURI.get(currentService);
+    }
+
+    public void setTargetNamespaceURI(String nsURI){
+	mobyServiceName2TargetNamespaceURI.put(currentService, nsURI);
+    }
+
     public QName getPortQName(){
 	return mobyServiceName2Port.get(currentService);
     }
@@ -486,25 +893,19 @@
 	mobyServiceName2Service.put(currentService, serviceName);
     }
 
-    public String getOperationInputName(){
+    public QName getOperationInputQName(){
 	return mobyServiceName2OpInput.get(currentService);
     }
 
-    public void setOperationInputName(String inputName){
-	if(inputName.contains(":") && !inputName.endsWith(":")){
-	    inputName = inputName.substring(inputName.lastIndexOf(":")+1);
-	}
+    public void setOperationInputQName(QName inputName){
 	mobyServiceName2OpInput.put(currentService, inputName);
     }
 
-    public String getOperationOutputName(){
+    public QName getOperationOutputQName(){
 	return mobyServiceName2OpOutput.get(currentService);
     }
 
-    public void setOperationOutputName(String outputName){
-	if(outputName.contains(":") && !outputName.endsWith(":")){
-	    outputName = outputName.substring(outputName.lastIndexOf(":")+1);
-	}
+    public void setOperationOutputQName(QName outputName){
 	mobyServiceName2OpOutput.put(currentService, outputName);
     }
 




More information about the MOBY-guts mailing list