/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.airavata.workflow.model.gpel.script;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.apache.airavata.common.exception.UtilsException;
import org.apache.airavata.common.utils.WSConstants;
import org.apache.airavata.common.utils.WSDLUtil;
import org.apache.airavata.common.utils.XMLUtil;
import org.apache.airavata.workflow.model.component.ws.WSComponentPort;
import org.apache.airavata.workflow.model.exceptions.WorkflowRuntimeException;
import org.apache.airavata.workflow.model.graph.DataPort;
import org.apache.airavata.workflow.model.graph.GraphException;
import org.apache.airavata.workflow.model.graph.Node;
import org.apache.airavata.workflow.model.graph.system.InputNode;
import org.apache.airavata.workflow.model.graph.system.OutputNode;
import org.apache.airavata.workflow.model.graph.system.ParameterNode;
import org.apache.airavata.workflow.model.graph.system.ReceiveNode;
import org.apache.airavata.workflow.model.graph.system.SystemDataPort;
import org.apache.airavata.workflow.model.graph.util.GraphUtil;
import org.apache.airavata.workflow.model.graph.ws.WSGraph;
import org.apache.airavata.workflow.model.utils.WorkflowConstants;
import org.apache.airavata.workflow.model.utils.ApplicationVersion;
import org.apache.airavata.workflow.model.wf.Workflow;
import org.xmlpull.infoset.XmlComment;
import org.xmlpull.infoset.XmlElement;
import org.xmlpull.infoset.XmlNamespace;
import xsul5.XmlConstants;
import xsul5.wsdl.WsdlDefinitions;
import xsul5.wsdl.WsdlDocumentation;
import xsul5.wsdl.WsdlMessage;
import xsul5.wsdl.WsdlPortType;
import xsul5.wsdl.WsdlPortTypeOperation;
import xsul5.wsdl.plnk.PartnerLinkRole;
import xsul5.wsdl.plnk.PartnerLinkType;
public class WorkflowWSDL {
/**
* Run
*/
private String workflowOperationName;
private static final String INPUT_MESSAGE_ELEMENT_SUFFIX = "";
private static final String OUTPUT_MESSAGE_ELEMENT_SUFFIX = "Response";
/**
* Run
*/
private String workflowInputMessageElelmentName;
/**
* RunOutput
*/
private String workflowOutputMessageElementName;
private static final String INPUT_MESSAGE_SUFFIX = "InputMessage";
private static final String OUTPUT_MESSAGE_SUFFIX = "OutputMessage";
/**
* RunInputMessage
*/
private String workflowInputMessageName;
/**
* RunOutputMessage
*/
private String workflowOutputMessageName;
/**
* input
*/
public static final String INPUT_PART_NAME = "input";
/**
* output
*/
public static final String OUTPUT_PART_NAME = "output";
private static final String TARGET_NS_NAME_PREFIX = WorkflowConstants.NS_URI_XBAYA;
private static final String TYPE_SUFFIX = "Type";
private Workflow workflow;
private WSGraph graph;
private WsdlDefinitions definitions;
private XmlNamespace targetNamespace;
private XmlNamespace typesNamespace;
private QName portTypeQName;
private Map<QName, PartnerLinkRole> partnerLinkRoleMap;
/**
* Constructs a WorkflowWsdl.
*
* @param workflow
* @param operationName
*/
public WorkflowWSDL(Workflow workflow, String operationName) {
this.workflow = workflow;
this.graph = workflow.getGraph();
this.partnerLinkRoleMap = new HashMap<QName, PartnerLinkRole>();
workflowOperationName = operationName;
workflowInputMessageElelmentName = workflowOperationName + INPUT_MESSAGE_ELEMENT_SUFFIX;
workflowOutputMessageElementName = workflowOperationName + OUTPUT_MESSAGE_ELEMENT_SUFFIX;
workflowInputMessageName = workflowOperationName + INPUT_MESSAGE_SUFFIX;
workflowOutputMessageName = workflowOperationName + OUTPUT_MESSAGE_SUFFIX;
}
/**
* @return the WSLD definitions
*/
public WsdlDefinitions getWsdlDefinitions() {
return this.definitions;
}
/**
* @return The target namespace.
*/
public XmlNamespace getTargetNamespace() {
return this.targetNamespace;
}
/**
* @return The types namespace. typens:"http://..../xsd/"
*/
public XmlNamespace getTypesNamespace() {
return this.typesNamespace;
}
/**
* @return The portType QName.
*/
public QName getPortTypeQName() {
return this.portTypeQName;
}
/**
* Creates WSDL.
*
* @throws GraphException
*/
public void create() throws GraphException {
try {
String targetNSName = TARGET_NS_NAME_PREFIX + this.graph.getID() + "/";
this.targetNamespace = XmlConstants.BUILDER.newNamespace(WSConstants.TARGET_NS_PREFIX, targetNSName);
String typesNSName = targetNSName + "xsd/";
this.typesNamespace = XmlConstants.BUILDER.newNamespace(WSConstants.TYPE_NS_PREFIX, typesNSName);
this.definitions = new WsdlDefinitions(targetNSName);
this.definitions.xml().setAttributeValue(WSConstants.NAME_ATTRIBUTE, this.graph.getID());
this.definitions.xml().declareNamespace(this.targetNamespace);
this.definitions.xml().declareNamespace(this.typesNamespace);
this.definitions.xml().declareNamespace(WSConstants.XSD_NS);
this.definitions.xml().declareNamespace(PartnerLinkType.NS);
addDocumentation();
addTypes();
WsdlMessage inputMessage = createInputMessage();
WsdlMessage outputMessage = createOutputMessage();
createPortType(inputMessage, outputMessage);
addComment();
} catch (RuntimeException e) {
throw new GraphException(e);
}
}
/**
* @param servicePortTypeQName
* @return PartnerLinkRole
*/
public PartnerLinkRole getPartnerRoll(QName servicePortTypeQName) {
return this.partnerLinkRoleMap.get(servicePortTypeQName);
}
/**
* Adds a partnerLinkType.
*
* This method is called by BPELScript.
*
* @param partnerLinkTypeName
* @param partnerRollName
* @param servicePortTypeQName
* @return PartnerLinkRole
*/
public PartnerLinkRole addPartnerLinkTypeAndRoll(String partnerLinkTypeName, String partnerRollName,
QName servicePortTypeQName) {
PartnerLinkType partnerLinkType = new PartnerLinkType(partnerLinkTypeName);
PartnerLinkRole partnerRoll = new PartnerLinkRole(partnerRollName, servicePortTypeQName);
partnerLinkType.addRole(partnerRoll);
declareNamespaceIfNecessary("p", servicePortTypeQName.getNamespaceURI(), true);
this.definitions.xml().addElement(partnerLinkType.xml());
this.partnerLinkRoleMap.put(servicePortTypeQName, partnerRoll);
return partnerRoll;
}
/**
* @param operationName
* @param receiveNode
* @return The portType added.
*/
public WsdlPortType addReceivePortType(String operationName, ReceiveNode receiveNode) {
//
// <types>
//
// <types> and <schema> have been defined.
XmlElement types = this.definitions.getTypes();
XmlElement schema = types.element(WSConstants.SCHEMA_TAG);
XmlElement sequence = setupParameterType(operationName, null, schema);
for (DataPort outputPort : receiveNode.getOutputPorts()) {
addParameter(receiveNode, (SystemDataPort) outputPort, sequence, schema);
}
//
// <message>
//
String messageName = operationName + INPUT_MESSAGE_SUFFIX;
String partName = INPUT_PART_NAME;
String messageElementName = operationName + INPUT_MESSAGE_ELEMENT_SUFFIX;
WsdlMessage inputMessage = createMessage(messageName, partName, messageElementName);
String portTypeName = operationName;
WsdlPortType portType = createPortType(portTypeName, operationName, inputMessage, null);
return portType;
}
private void addComment() {
XmlComment comment = this.definitions.xml().newComment(
"\nThis document is automatically generated by " + WorkflowConstants.APPLICATION_NAME + " "
+ ApplicationVersion.VERSION + ".\n");
this.definitions.xml().insertChild(0, "\n");
this.definitions.xml().insertChild(0, comment);
this.definitions.xml().insertChild(0, "\n");
}
/**
* Sets the documentation element.
*/
private void addDocumentation() {
String description = this.workflow.getDescription();
if (description != null) {
WsdlDocumentation documentation = new WsdlDocumentation(description);
this.definitions.setDocumentation(documentation);
}
}
/**
* Adds the types element.
*
* @return The types element
*/
private XmlElement addTypes() {
XmlElement types = this.definitions.getOrCreateTypes();
XmlElement schema = types.addElement(WSConstants.SCHEMA_TAG);
schema.setAttributeValue(WSConstants.TARGET_NAMESPACE_ATTRIBUTE, this.typesNamespace.getName());
schema.setAttributeValue(WSConstants.XMLNS, WSConstants.XSD_NS_URI);
schema.setAttributeValue(WSConstants.ELEMENT_FORM_DEFAULT_ATTRIBUTE, WSConstants.UNQUALIFIED_VALUE);
List<InputNode> inputNodes = GraphUtil.getInputNodes(this.graph);
XmlElement inputMetadata = this.graph.getInputMetadata();
addParameters(workflowInputMessageElelmentName, inputMetadata, inputNodes, schema);
List<OutputNode> outputNodes = GraphUtil.getOutputNodes(this.graph);
XmlElement outputMetadata = this.graph.getOutputMetadata();
addParameters(workflowOutputMessageElementName, outputMetadata, outputNodes, schema);
return types;
}
private void addParameters(String name, XmlElement appinfo, List<? extends ParameterNode> nodes, XmlElement schema) {
XmlElement sequence = setupParameterType(name, appinfo, schema);
for (ParameterNode node : nodes) {
addParameter(node, sequence, schema);
}
}
/**
* @param name
* @param appinfo
* @param schema
* @return The sequence element.
*/
private XmlElement setupParameterType(String name, XmlElement appinfo, XmlElement schema) {
XmlElement element = schema.addElement(WSConstants.ELEMENT_TAG);
element.setAttributeValue(WSConstants.NAME_ATTRIBUTE, name);
String type = name + TYPE_SUFFIX;
element.setAttributeValue(WSConstants.TYPE_ATTRIBUTE, WSConstants.TYPE_NS_PREFIX + ":" + type);
// add metadata
if (appinfo != null) {
XmlElement annotation = element.addElement(WSConstants.ANNOTATION_TAG);
try {
annotation.addElement(XMLUtil.deepClone(appinfo));
} catch (UtilsException e) {
e.printStackTrace();
}
}
XmlElement complex = schema.addElement(WSConstants.COMPLEX_TYPE_TAG);
complex.setAttributeValue(WSConstants.NAME_ATTRIBUTE, type);
XmlElement sequence = complex.addElement(WSConstants.SEQUENCE_TAG);
return sequence;
}
/**
* Adds the parameter element.
*
* @param node
* @param sequence
* @param schema
* @return The parameter element
*/
private XmlElement addParameter(ParameterNode node, XmlElement sequence, XmlElement schema) {
XmlElement element;
SystemDataPort port = node.getPort();
element = addParameter(node, port, sequence, schema);
//
// Annotation
//
String description = node.getDescription();
XmlElement appinfo = node.getMetadata();
// description
if (description != null && description.trim().length() != 0) {
XmlElement annotation = element.element(null, WSConstants.ANNOTATION_TAG, true);
XmlElement documentation = annotation.addElement(WSConstants.DOCUMENTATION_TAG);
documentation.setText(node.getDescription());
}
// appinfo
if (appinfo != null) {
XmlElement annotation = element.element(null, WSConstants.ANNOTATION_TAG, true);
try {
annotation.addElement(XMLUtil.deepClone(appinfo));
} catch (UtilsException e) {
e.printStackTrace();
}
}
//
// Add default value if it's input.
//
if (node instanceof InputNode) {
InputNode inputNode = (InputNode) node;
Object value = inputNode.getDefaultValue();
if (value instanceof String) {
element.setAttributeValue(WSConstants.DEFAULT_ATTRIBUTE, (String) value);
} else if (value instanceof XmlElement) {
// Add the default value in <annotation><default> because there
// is no standard way.
XmlElement valueElement = null;
try {
valueElement = XMLUtil.deepClone((XmlElement) value);
} catch (UtilsException e) {
e.printStackTrace();
}
XmlElement annotation = element.element(null, WSConstants.ANNOTATION_TAG, true);
XmlElement defaultElement = annotation.addElement(WSComponentPort.DEFAULT);
defaultElement.addElement(valueElement);
}
}
return element;
}
private XmlElement addParameter(Node node, SystemDataPort port, XmlElement sequence, XmlElement schema) {
XmlElement element = sequence.addElement(WSConstants.ELEMENT_TAG);
element.setAttributeValue(WSConstants.NAME_ATTRIBUTE, node.getID());
//
// type
//
QName type = port.getType();
WSComponentPort componentPort = port.getWSComponentPort();
WsdlDefinitions wsdl = null;
if (componentPort != null) {
wsdl = componentPort.getComponent().getWSDL();
type = declareTypeIfNecessary(wsdl, type);
}
int arrayDimension = port.getArrayDimension();
if (arrayDimension == 1) {
String typeName = declareArrayType(schema, type, wsdl);
type = new QName(this.typesNamespace.getName(), typeName);
} else if (arrayDimension > 1) {
// TODO
throw new WorkflowRuntimeException("multi-dimentional arrays are not supported yet.");
}
if (WSConstants.XSD_ANY_TYPE.equals(type) && componentPort != null) {
XmlElement elementElement = componentPort.getElement();
if (elementElement == null) {
// Types are not defined anywhare. Leave it as xsd:any.
setTypeAttribute(element, type);
} else {
// Copy the embedded type defition.
XmlElement clonedElementElement = null;
try {
clonedElementElement = XMLUtil.deepClone(elementElement);
} catch (UtilsException e) {
e.printStackTrace();
}
String typeString = clonedElementElement.attributeValue(WSConstants.TYPE_ATTRIBUTE);
if (typeString == null) {
for (Object child : clonedElementElement.children()) {
if (child instanceof XmlElement) {
((XmlElement) child).setParent(null);
}
element.addChild(child);
}
} else {
// The case when type is really xsd:any
setTypeAttribute(element, type);
}
}
} else {
// The normal case.
setTypeAttribute(element, type);
}
return element;
}
private void setTypeAttribute(XmlElement element, QName type) {
String namespaceURI = type.getNamespaceURI();
XmlNamespace namespace = element.lookupNamespaceByName(namespaceURI);
element.setAttributeValue(WSConstants.TYPE_ATTRIBUTE, namespace.getPrefix() + ":" + type.getLocalPart());
}
/**
* @param serviceWSDL
* @param paramType
* @return The QName of the type. This QName always has prefix.
*/
private QName declareTypeIfNecessary(WsdlDefinitions serviceWSDL, QName paramType) {
if (WSConstants.XSD_NS_URI.equals(paramType.getNamespaceURI())) {
// No need to define
return new QName(WSConstants.XSD_NS_URI, paramType.getLocalPart(), WSConstants.XSD_NS_PREFIX);
}
// check if this type already exists in the workflow WSDL.
XmlElement typeDefinition = null;
try {
typeDefinition = WSDLUtil.getTypeDefinition(this.definitions, paramType);
if (typeDefinition == null) {
// now lets check whether there is an import in the service wsdl schema
// that would import this type,
// if so we would be done by just importing that schema
typeDefinition = WSDLUtil.findTypeDefinitionInImports(serviceWSDL, paramType);
if (typeDefinition != null) {
XmlElement importEle = WSDLUtil.getImportContainingTypeDefinition(serviceWSDL, paramType);
addImportIfNecessary(importEle);
String prefix = declareNamespaceIfNecessary(paramType.getPrefix(), paramType.getNamespaceURI(),
false);
return new QName(paramType.getNamespaceURI(), paramType.getLocalPart(), prefix);
}
// copy the type defition and use it.
// Need to copy the whole schema because it might have different
// targetNamespace.
XmlElement newSchema = WSDLUtil.getSchema(serviceWSDL, paramType);
if (newSchema == null) {
// This should have been caught in WSComponent
throw new WorkflowRuntimeException("could not find definition for type " + paramType + " in "
+ WSDLUtil.getWSDLQName(serviceWSDL));
}
this.definitions.getTypes().addChild(XMLUtil.deepClone(newSchema));
String prefix = declareNamespaceIfNecessary(paramType.getPrefix(), paramType.getNamespaceURI(), false);
return new QName(paramType.getNamespaceURI(), paramType.getLocalPart(), prefix);
} else {
XmlNamespace namespace = this.definitions.xml().lookupNamespaceByName(paramType.getNamespaceURI());
return new QName(paramType.getNamespaceURI(), paramType.getLocalPart(), namespace.getPrefix());
}
} catch (UtilsException e) {
e.printStackTrace();
}
return null;
}
private void addImportIfNecessary(XmlElement importEle) {
XmlElement schema = this.definitions.getTypes().element(WSConstants.SCHEMA_TAG);
Iterable<XmlElement> imports = schema.elements(null, WSConstants.IMPORT_TAG);
for (XmlElement importElement : imports) {
if (importElement.attributeValue("namespace").equals(importEle.attributeValue("namespace"))
&& importElement.attributeValue("schemaLocation")
.equals(importEle.attributeValue("schemaLocation"))) {
return;
}
}
schema.addChild(0, importEle);
}
private String declareArrayType(XmlElement schema, QName valueType, WsdlDefinitions serviceWSDL) {
XmlElement complexType = schema.addElement(WSConstants.COMPLEX_TYPE_TAG);
String typeName = valueType.getLocalPart() + "ArrayType";
// TODO check if this typeName is already used.
complexType.setAttributeValue(WSConstants.NAME_ATTRIBUTE, typeName);
XmlElement sequence = complexType.addElement(WSConstants.SEQUENCE_TAG);
XmlElement element = sequence.addElement(WSConstants.ELEMENT_TAG);
element.setAttributeValue(WSConstants.MIN_OCCURS_ATTRIBUTE, "0");
element.setAttributeValue(WSConstants.MAX_OCCURS_ATTRIBUTE, WSConstants.UNBOUNDED_VALUE);
element.setAttributeValue(WSConstants.NAME_ATTRIBUTE, "value");
valueType = declareTypeIfNecessary(serviceWSDL, valueType);
element.setAttributeValue(WSConstants.TYPE_ATTRIBUTE, valueType.getPrefix() + ":" + valueType.getLocalPart());
return typeName;
}
/**
* Creates the input message.
*
* @return The input message
*/
private WsdlMessage createInputMessage() {
return createMessage(workflowInputMessageName, INPUT_PART_NAME, workflowInputMessageElelmentName);
}
/**
* Creates the output message.
*
* @return The output message
*/
private WsdlMessage createOutputMessage() {
return createMessage(workflowOutputMessageName, OUTPUT_PART_NAME, workflowOutputMessageElementName);
}
private WsdlMessage createMessage(String messageName, String partName, String messageElementName) {
WsdlMessage outMessage = this.definitions.addMessage(messageName);
outMessage.addPartWithElement(partName, new QName(this.typesNamespace.getName(), messageElementName,
this.typesNamespace.getPrefix()));
return outMessage;
}
/**
* Creates the portType.
*
* @param inputMessage
* @param outputMessage
* @return The portType
*/
private WsdlPortType createPortType(WsdlMessage inputMessage, WsdlMessage outputMessage) {
String portTypeName = this.graph.getID();
this.portTypeQName = new QName(this.targetNamespace.getName(), portTypeName);
return createPortType(this.graph.getID(), workflowOperationName, inputMessage, outputMessage);
}
private WsdlPortType createPortType(String portTypeName, String operationName, WsdlMessage inputMessage,
WsdlMessage outputMessage) {
WsdlPortType portType = this.definitions.addPortType(portTypeName);
WsdlPortTypeOperation operation = portType.addOperation(operationName);
if (inputMessage != null) {
operation.setInput(inputMessage);
}
if (outputMessage != null) {
operation.setOutput(outputMessage);
}
return portType;
}
private String declareNamespaceIfNecessary(String prefixCandidate, String uri, boolean alwaysUseSuffix) {
XmlNamespace namespace = this.definitions.xml().lookupNamespaceByName(uri);
if (namespace == null) {
return declareNamespace(prefixCandidate, uri, alwaysUseSuffix);
} else {
return namespace.getPrefix();
}
}
/**
* @param prefixCandidate
* @param uri
* @param alwaysUseSuffix
* @return The prefix actually used.
*/
private String declareNamespace(String prefixCandidate, String uri, boolean alwaysUseSuffix) {
if (prefixCandidate == null || prefixCandidate.length() == 0) {
prefixCandidate = "a";
}
String prefix = prefixCandidate;
if (alwaysUseSuffix) {
prefix += "0";
}
if (this.definitions.xml().lookupNamespaceByPrefix(prefix) != null) {
int i = 1;
prefix = prefixCandidate + i;
while (this.definitions.xml().lookupNamespaceByPrefix(prefix) != null) {
i++;
}
}
this.definitions.xml().declareNamespace(prefix, uri);
return prefix;
}
/**
* Returns the workflowOperationName.
*
* @return The workflowOperationName
*/
public String getWorkflowOperationName() {
return this.workflowOperationName;
}
/**
* Sets workflowOperationName.
*
* @param workflowOperationName
* The workflowOperationName to set.
*/
public void setWorkflowOperationName(String workflowOperationName) {
this.workflowOperationName = workflowOperationName;
}
/**
* Returns the workflowInputMessageName.
*
* @return The workflowInputMessageName
*/
public String getWorkflowInputMessageName() {
return this.workflowInputMessageName;
}
/**
* Sets workflowInputMessageName.
*
* @param workflowInputMessageName
* The workflowInputMessageName to set.
*/
public void setWorkflowInputMessageName(String workflowInputMessageName) {
this.workflowInputMessageName = workflowInputMessageName;
}
/**
* Returns the workflowOutputMessageName.
*
* @return The workflowOutputMessageName
*/
public String getWorkflowOutputMessageName() {
return this.workflowOutputMessageName;
}
/**
* Sets workflowOutputMessageName.
*
* @param workflowOutputMessageName
* The workflowOutputMessageName to set.
*/
public void setWorkflowOutputMessageName(String workflowOutputMessageName) {
this.workflowOutputMessageName = workflowOutputMessageName;
}
/**
* Returns the workflowInputMessageElelmentName.
*
* @return The workflowInputMessageElelmentName
*/
public String getWorkflowInputMessageElelmentName() {
return this.workflowInputMessageElelmentName;
}
/**
* Sets workflowInputMessageElelmentName.
*
* @param workflowInputMessageElelmentName
* The workflowInputMessageElelmentName to set.
*/
public void setWorkflowInputMessageElelmentName(String workflowInputMessageElelmentName) {
this.workflowInputMessageElelmentName = workflowInputMessageElelmentName;
}
/**
* Returns the workflowOutputMessageElementName.
*
* @return The workflowOutputMessageElementName
*/
public String getWorkflowOutputMessageElementName() {
return this.workflowOutputMessageElementName;
}
/**
* Sets workflowOutputMessageElementName.
*
* @param workflowOutputMessageElementName
* The workflowOutputMessageElementName to set.
*/
public void setWorkflowOutputMessageElementName(String workflowOutputMessageElementName) {
this.workflowOutputMessageElementName = workflowOutputMessageElementName;
}
}