/** * 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.cxf.javascript.service; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import javax.xml.namespace.QName; import org.apache.cxf.binding.soap.SoapBindingConstants; import org.apache.cxf.binding.soap.SoapBindingFactory; import org.apache.cxf.binding.soap.SoapVersion; import org.apache.cxf.binding.soap.model.SoapBindingInfo; import org.apache.cxf.common.i18n.Message; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.common.xmlschema.SchemaCollection; import org.apache.cxf.common.xmlschema.XmlSchemaUtils; import org.apache.cxf.javascript.JavascriptUtils; import org.apache.cxf.javascript.NameManager; import org.apache.cxf.javascript.NamespacePrefixAccumulator; import org.apache.cxf.javascript.ParticleInfo; import org.apache.cxf.javascript.UnsupportedConstruct; import org.apache.cxf.service.ServiceModelVisitor; import org.apache.cxf.service.model.BindingInfo; import org.apache.cxf.service.model.EndpointInfo; import org.apache.cxf.service.model.FaultInfo; import org.apache.cxf.service.model.InterfaceInfo; import org.apache.cxf.service.model.MessageInfo; import org.apache.cxf.service.model.MessagePartInfo; import org.apache.cxf.service.model.OperationInfo; import org.apache.cxf.service.model.ServiceInfo; import org.apache.cxf.wsdl.WSDLConstants; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.XmlSchemaComplexType; import org.apache.ws.commons.schema.XmlSchemaElement; import org.apache.ws.commons.schema.XmlSchemaObject; import org.apache.ws.commons.schema.XmlSchemaSequence; import org.apache.ws.commons.schema.XmlSchemaSequenceMember; import org.apache.ws.commons.schema.XmlSchemaSimpleType; import org.apache.ws.commons.schema.XmlSchemaType; /** * Class to construct the JavaScript corresponding to a service. */ public class ServiceJavascriptBuilder extends ServiceModelVisitor { private static final Logger LOG = LogUtils.getL7dLogger(ServiceJavascriptBuilder.class); private SoapBindingInfo soapBindingInfo; private JavascriptUtils utils; private NameManager nameManager; private StringBuilder code; private String currentInterfaceClassName; private OperationInfo currentOperation; private Set<OperationInfo> operationsWithNameConflicts; private Set<MessageInfo> inputMessagesWithNameConflicts; private Set<MessageInfo> outputMessagesWithNameConflicts; private SchemaCollection xmlSchemaCollection; // When generating from a tool or ?js, we know the endpoint addr and can build it into the javascript. private String endpointAddress; private boolean isWrapped; // facts about the wrapper when there is one. private MessagePartInfo inputWrapperPartInfo; private String inputWrapperClassName; private XmlSchemaElement inputWrapperElement; private XmlSchemaComplexType inputWrapperComplexType; private MessagePartInfo outputWrapperPartInfo; private XmlSchemaElement outputWrapperElement; private XmlSchemaComplexType outputWrapperComplexType; // Javascript parameter names for the input parameters, // derived from the parts. private List<String> inputParameterNames = new ArrayList<>(); // when not wrapped, we use this to keep track of the bits. private List<ParticleInfo> unwrappedElementsAndNames; private NamespacePrefixAccumulator prefixAccumulator; private BindingInfo xmlBindingInfo; private Map<String, OperationInfo> localOperationsNameMap; private Map<String, MessageInfo> localInputMessagesNameMap; private Map<String, MessageInfo> localOutputMessagesNameMap; private String opFunctionPropertyName; private String opFunctionGlobalName; private boolean isInUnwrappedOperation; private boolean nonVoidOutput; private boolean isRPC; /** * Construct builder object. * @param serviceInfo CXF service model description of the service. * @param endpointAddress http:// URL for the service, or null if not known. * @param prefixAccumulator object that keeps track of prefixes through an entire WSDL. * @param nameManager object that generates names for JavaScript objects. */ public ServiceJavascriptBuilder(ServiceInfo serviceInfo, String endpointAddress, NamespacePrefixAccumulator prefixAccumulator, NameManager nameManager) { super(serviceInfo); this.endpointAddress = endpointAddress; code = new StringBuilder(); utils = new JavascriptUtils(code); this.nameManager = nameManager; xmlSchemaCollection = serviceInfo.getXmlSchemaCollection(); this.prefixAccumulator = prefixAccumulator; } public String getCode() { return code.toString(); } @Override public void begin(FaultInfo fault) { } @Override public void begin(InterfaceInfo intf) { code.append("\n// Javascript for " + intf.getName() + "\n\n"); currentInterfaceClassName = nameManager.getJavascriptName(intf.getName()); operationsWithNameConflicts = new HashSet<>(); inputMessagesWithNameConflicts = new HashSet<>(); outputMessagesWithNameConflicts = new HashSet<>(); localOperationsNameMap = new HashMap<>(); localInputMessagesNameMap = new HashMap<>(); localOutputMessagesNameMap = new HashMap<>(); code.append("function " + currentInterfaceClassName + " () {\n"); utils.appendLine("this.jsutils = new CxfApacheOrgUtil();"); utils.appendLine("this.jsutils.interfaceObject = this;"); utils.appendLine("this.synchronous = false;"); if (endpointAddress != null) { utils.appendLine("this.url = '" + endpointAddress + "';"); } else { utils.appendLine("this.url = null;"); } utils.appendLine("this.client = null;"); utils.appendLine("this.response = null;"); generateGlobalElementDictionary(); code.append("}\n\n"); } private void generateGlobalElementDictionary() { // to handle 'any', we need a dictionary of all the global elements of all the schemas. utils.appendLine("this.globalElementSerializers = [];"); utils.appendLine("this.globalElementDeserializers = [];"); for (XmlSchema schemaInfo : xmlSchemaCollection.getXmlSchemas()) { for (Map.Entry<QName, XmlSchemaElement> e : schemaInfo.getElements().entrySet()) { QName name = e.getKey(); XmlSchemaElement element = e.getValue(); // For now, at least, don't handle elements with simple types. // That comes later to improve deserialization. if (JavascriptUtils.notVeryComplexType(element.getSchemaType())) { continue; } // If the element uses a named type, we use the functions for the type. XmlSchemaComplexType elementType = (XmlSchemaComplexType)element.getSchemaType(); if (elementType != null && elementType.getQName() != null) { name = elementType.getQName(); } utils.appendLine("this.globalElementSerializers['" + name.toString() + "'] = " + nameManager.getJavascriptName(name) + "_serialize;"); utils.appendLine("this.globalElementDeserializers['" + name.toString() + "'] = " + nameManager.getJavascriptName(name) + "_deserialize;"); } for (Map.Entry<QName, XmlSchemaType> e : schemaInfo.getSchemaTypes().entrySet()) { QName name = e.getKey(); XmlSchemaType type = e.getValue(); // For now, at least, don't handle simple types. if (JavascriptUtils.notVeryComplexType(type)) { continue; } // the names are misleading, but that's OK. utils.appendLine("this.globalElementSerializers['" + name.toString() + "'] = " + nameManager.getJavascriptName(name) + "_serialize;"); utils.appendLine("this.globalElementDeserializers['" + name.toString() + "'] = " + nameManager.getJavascriptName(name) + "_deserialize;"); } } } private String getFunctionGlobalName(QName itemName, String itemType) { return nameManager.getJavascriptName(itemName) + "_" + itemType; } private<T> String getFunctionPropertyName(Set<T> conflictMap, T object, QName fullName) { boolean needsLongName = conflictMap.contains(object); String functionName; if (needsLongName) { functionName = nameManager.getJavascriptName(fullName); } else { functionName = JavascriptUtils.javaScriptNameToken(fullName.getLocalPart()); } return functionName; } // we do this at the end so we can inventory name conflicts sooner. @Override public void end(OperationInfo op) { // we only process the wrapped operation, not the unwrapped alternative. if (op.isUnwrapped()) { isInUnwrappedOperation = false; return; } isWrapped = op.isUnwrappedCapable(); StringBuilder parameterList = new StringBuilder(); inputParameterNames = new ArrayList<>(); if (isWrapped) { collectWrapperElementInfo(); } else { collectUnwrappedInputInfo(); } buildParameterList(parameterList); MessageInfo outputMessage = op.getOutput(); nonVoidOutput = outputMessage != null && outputMessage.getMessageParts().size() != 0; if (!op.isOneWay()) { buildSuccessFunction(outputMessage); buildErrorFunction(); // fault part some day. } buildOperationFunction(parameterList); createInputSerializer(); if (nonVoidOutput) { createResponseDeserializer(outputMessage); } } /** * visit the input message parts and collect relevant data. */ private void collectUnwrappedInputInfo() { unwrappedElementsAndNames = new ArrayList<>(); if (currentOperation.getInput() != null) { getElementsForParts(currentOperation.getInput(), unwrappedElementsAndNames); } for (ParticleInfo ean : unwrappedElementsAndNames) { inputParameterNames.add(ean.getJavascriptName()); } } private void buildOperationFunction(StringBuilder parameterList) { String responseCallbackParams = ""; if (!currentOperation.isOneWay()) { responseCallbackParams = "successCallback, errorCallback"; } MessageInfo inputMessage = currentOperation.getInput(); code.append("//\n"); code.append("// Operation " + currentOperation.getName() + "\n"); if (!isWrapped) { code.append("// - bare operation. Parameters:\n"); for (ParticleInfo ei : unwrappedElementsAndNames) { code.append("// - " + getElementObjectName(ei) + "\n"); } } else { code.append("// Wrapped operation.\n"); QName contextQName = inputWrapperComplexType.getQName(); if (contextQName == null) { contextQName = inputWrapperElement.getQName(); } XmlSchemaSequence sequence = JavascriptUtils.getSequence(inputWrapperComplexType); XmlSchema wrapperSchema = xmlSchemaCollection.getSchemaByTargetNamespace(contextQName.getNamespaceURI()); for (int i = 0; i < sequence.getItems().size(); i++) { code.append("// parameter " + inputParameterNames.get(i) + "\n"); XmlSchemaSequenceMember sequenceItem = sequence.getItems().get(i); ParticleInfo itemInfo = ParticleInfo.forLocalItem((XmlSchemaObject)sequenceItem, wrapperSchema, xmlSchemaCollection, prefixAccumulator, contextQName); if (itemInfo.isArray()) { code.append("// - array\n"); } XmlSchemaType type = itemInfo.getType(); // null for an any. if (type instanceof XmlSchemaComplexType) { QName baseName; if (type.getQName() != null) { baseName = type.getQName(); } else { baseName = ((XmlSchemaElement)sequenceItem).getQName(); } code.append("// - Object constructor is " + nameManager.getJavascriptName(baseName) + "\n"); } else if (type != null) { code.append("// - simple type " + type.getQName()); } } } code.append("//\n"); code.append("function " + opFunctionGlobalName + "(" + responseCallbackParams + ((parameterList.length() > 0 && !currentOperation.isOneWay()) ? ", " : "") + parameterList + ") {\n"); utils.appendLine("this.client = new CxfApacheOrgClient(this.jsutils);"); utils.appendLine("var xml = null;"); if (inputMessage != null) { utils.appendLine("var args = new Array(" + inputParameterNames.size() + ");"); int px = 0; for (String param : inputParameterNames) { utils.appendLine("args[" + px + "] = " + param + ";"); px++; } utils.appendLine("xml = this." + getFunctionPropertyName(inputMessagesWithNameConflicts, inputMessage, inputMessage.getName()) + "_serializeInput" + "(this.jsutils, args);"); } // we need to pass the caller's callback functions to our callback // functions. if (!currentOperation.isOneWay()) { utils.appendLine("this.client.user_onsuccess = successCallback;"); utils.appendLine("this.client.user_onerror = errorCallback;"); utils.appendLine("var closureThis = this;"); // client will pass itself and the response XML. utils.appendLine("this.client.onsuccess = function(client, responseXml) { closureThis." + opFunctionPropertyName + "_onsuccess(client, responseXml); };"); // client will pass itself. utils.appendLine("this.client.onerror = function(client) { closureThis." + opFunctionPropertyName + "_onerror(client); };"); } utils.appendLine("var requestHeaders = [];"); if (soapBindingInfo != null) { String action = soapBindingInfo.getSoapAction(currentOperation); utils.appendLine("requestHeaders['SOAPAction'] = '" + action + "';"); } // default 'method' by passing null. Is there some place this lives in the // service model? String syncAsyncFlag; if (currentOperation.isOneWay()) { utils.appendLine("this.jsutils.trace('oneway operation');"); syncAsyncFlag = "false"; } else { utils.appendLine("this.jsutils.trace('synchronous = ' + this.synchronous);"); syncAsyncFlag = "this.synchronous"; } utils.appendLine("this.client.request(this.url, xml, null, " + syncAsyncFlag + ", requestHeaders);"); code.append("}\n\n"); code.append(currentInterfaceClassName + ".prototype." + opFunctionPropertyName + " = " + opFunctionGlobalName + ";\n\n"); } private void buildErrorFunction() { String errorFunctionPropertyName = opFunctionPropertyName + "_onerror"; String errorFunctionGlobalName = opFunctionGlobalName + "_onerror"; code.append("function " + errorFunctionGlobalName + "(client) {\n"); utils.startIf("client.user_onerror"); // Is this a good set of parameters for the error function? // Not if we want to process faults, it isn't. To be revisited. utils.appendLine("var httpStatus;"); utils.appendLine("var httpStatusText;"); utils.appendLine("try {"); utils.appendLine(" httpStatus = client.req.status;"); utils.appendLine(" httpStatusText = client.req.statusText;"); utils.appendLine("} catch(e) {"); utils.appendLine(" httpStatus = -1;"); utils.appendLine(" httpStatusText = 'Error opening connection to server';"); utils.appendLine("}"); utils.startIf("client.parseErrorDetails"); utils.appendLine("client.user_onerror(httpStatus, httpStatusText, client.parseErrorDetails(this));"); utils.appendElse(); utils.appendLine("client.user_onerror(httpStatus, httpStatusText);"); utils.endBlock(); utils.endBlock(); code.append("}\n\n"); code.append(currentInterfaceClassName + ".prototype." + errorFunctionPropertyName + " = " + errorFunctionGlobalName + ";\n\n"); } // Note: the response XML that we get from the XMLHttpRequest is the document element, // not the root element. private void buildSuccessFunction(MessageInfo outputMessage) { // Here are the success and error callbacks. They have the job of // calling callbacks provided to the operation function with appropriate // parameters. String successFunctionGlobalName = opFunctionGlobalName + "_onsuccess"; String successFunctionPropertyName = opFunctionPropertyName + "_onsuccess"; String arglist = "(client)"; if (nonVoidOutput) { arglist = "(client, responseXml)"; } code.append("function " + successFunctionGlobalName + arglist + " {\n"); utils.startIf("client.user_onsuccess"); utils.appendLine("var responseObject = null;"); if (nonVoidOutput) { utils.appendLine("var element = responseXml.documentElement;"); utils.appendLine("this.jsutils.trace('responseXml: ' " + "+ this.jsutils.traceElementName(element));"); if (soapBindingInfo != null) { // soap // The following code is probably only right for basic // Doc/Literal/Wrapped services. // the top element should be the Envelope, then the Body, then // the actual response item. utils.appendLine("element = this.jsutils.getFirstElementChild(element);"); utils.appendLine("this.jsutils.trace('first element child: ' " + "+ this.jsutils.traceElementName(element));"); // loop to find the body. utils.startWhile("!this.jsutils.isNodeNamedNS(element, " + "'http://schemas.xmlsoap.org/soap/envelope/', 'Body')"); utils.appendLine("element = this.jsutils.getNextElementSibling(element);"); utils.startIf("element == null"); utils.appendLine("throw 'No env:Body in message.'"); utils.endBlock(); utils.endBlock(); // Go down one more from the body to the response item. utils.appendLine("element = this.jsutils.getFirstElementChild(element);"); utils.appendLine("this.jsutils.trace('part element: ' " + "+ this.jsutils.traceElementName(element));"); } String deserializerFunctionName = outputDeserializerFunctionName(outputMessage); utils.appendLine("this.jsutils.trace('calling " + deserializerFunctionName + "');"); utils.appendLine("responseObject = " + deserializerFunctionName + "(this.jsutils, element);"); } utils.appendLine("client.user_onsuccess(responseObject);"); utils.endBlock(); code.append("}\n\n"); code.append(currentInterfaceClassName + ".prototype." + successFunctionPropertyName + " = " + successFunctionGlobalName + ";\n\n"); } private void buildParameterList(StringBuilder parameterList) { for (String param : inputParameterNames) { parameterList.append(param); parameterList.append(", "); } // trim last comma. if (parameterList.length() > 2) { parameterList.setLength(parameterList.length() - 2); } } private String outputDeserializerFunctionName(MessageInfo message) { return getFunctionGlobalName(message.getName(), "deserializeResponse"); } // This ignores 'wrapped', because it assumes one part that we can use one way or // the other. For simple cases, this is certainly OK. private void createResponseDeserializer(MessageInfo outputMessage) { List<MessagePartInfo> parts = outputMessage.getMessageParts(); if (parts.size() != 1) { unsupportedConstruct("MULTIPLE_OUTPUTS", outputMessage.getName().toString()); } List<ParticleInfo> elements = new ArrayList<>(); String functionName = outputDeserializerFunctionName(outputMessage); code.append("function " + functionName + "(cxfjsutils, partElement) {\n"); getElementsForParts(outputMessage, elements); ParticleInfo element = elements.get(0); XmlSchemaType type = null; if (isRPC) { utils.appendLine("cxfjsutils.trace('rpc element: ' + cxfjsutils.traceElementName(partElement));"); utils.appendLine("partElement = cxfjsutils.getFirstElementChild(partElement);"); utils.appendLine("cxfjsutils.trace('rpc element: ' + cxfjsutils.traceElementName(partElement));"); } type = element.getType(); if (!element.isEmpty()) { if (type instanceof XmlSchemaComplexType) { String typeObjectName = nameManager.getJavascriptName(element.getControllingName()); utils .appendLine("var returnObject = " + typeObjectName + "_deserialize (cxfjsutils, partElement);\n"); utils.appendLine("return returnObject;"); } else if (type instanceof XmlSchemaSimpleType) { XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType)type; utils.appendLine("var returnText = cxfjsutils.getNodeText(partElement);"); utils.appendLine("var returnObject = " + utils.javascriptParseExpression(simpleType, "returnText") + ";"); utils.appendLine("return returnObject;"); } else if (type != null) { utils.appendLine("// Unsupported construct " + type.getClass()); } else { utils.appendLine("// No type for element " + element.getXmlName()); } } code.append("}\n"); } private String getElementObjectName(ParticleInfo element) { XmlSchemaType type = element.getType(); if (!element.isEmpty()) { if (type instanceof XmlSchemaComplexType) { return nameManager.getJavascriptName(element.getControllingName()); } else { return "type " + type.getQName(); // could it be anonymous? } } else { return "empty element?"; } } private void createInputSerializer() { // If are working on a wrapped method, then we use the wrapper element. // If we are working on an unwrapped method, we will have to work from the unwrapped parts. MessageInfo message = currentOperation.getInput(); String serializerFunctionGlobalName = getFunctionGlobalName(message.getName(), "serializeInput"); String serializerFunctionPropertyName = getFunctionPropertyName(inputMessagesWithNameConflicts, message, message.getName()) + "_serializeInput"; code.append("function " + serializerFunctionGlobalName + "(cxfjsutils, args) {\n"); String wrapperXmlElementName = null; // for the wrapped case, we can name the object for Javascript after whatever we like. // we could use the wrapped part, or we could use a conventional name. if (isWrapped) { wrapperXmlElementName = prefixAccumulator.xmlElementString(inputWrapperPartInfo.getConcreteName()); utils.appendLine("var wrapperObj = new " + inputWrapperClassName + "();"); int px = 0; for (String param : inputParameterNames) { utils.appendLine("wrapperObj.set" + StringUtils.capitalize(param) + "(args[" + px + "]);"); px++; } } else if (isRPC) { //make sure the rpc element has a valid prefix prefixAccumulator.xmlElementString(currentOperation.getName()); } if (soapBindingInfo != null) { SoapVersion soapVersion = soapBindingInfo.getSoapVersion(); assert soapVersion.getVersion() == 1.1; utils.appendLine("var xml;"); utils.appendLine("xml = cxfjsutils.beginSoap11Message(\"" + prefixAccumulator.getAttributes() + "\");"); } else { // other alternative is XML, which isn't really all here yet. unsupportedConstruct("XML_BINDING", currentInterfaceClassName, xmlBindingInfo.getName()); } utils.setXmlStringAccumulator("xml"); if (isWrapped) { ParticleInfo elementInfo = ParticleInfo.forPartElement(inputWrapperElement, xmlSchemaCollection, "wrapperObj", wrapperXmlElementName); elementInfo.setContainingType(null); utils.generateCodeToSerializeElement(elementInfo, "", xmlSchemaCollection); } else { String operationXmlElement = null; if (isRPC) { operationXmlElement = prefixAccumulator.xmlElementString(currentOperation.getName()); // RPC has a level of element for the entire operation. // we might have some schema to model this, but the following seems // sufficient. utils.appendString("<" + operationXmlElement + ">"); } int px = 0; // Multiple parts for document violates WS-I, but we can still do them. // They are normal for RPC. // Parts are top-level elements. As such, they cannot, directly, be arrays. // If a part is declared as an array type, the schema has a non-array element // with a complex type consisting of an element with array bounds. We don't // want the JavasSript programmer to have to concoct an extra level of object // (though if the same sort of thing happens elsewhere due to an XmlRootElement, // the JavaScript programmer is stuck with the situation). for (ParticleInfo ean : unwrappedElementsAndNames) { String savedjsName = ean.getJavascriptName(); try { ean.setJavascriptName("args[" + px + "]"); utils.generateCodeToSerializeElement(ean, "", xmlSchemaCollection); px++; } finally { ean.setJavascriptName(savedjsName); } } if (isRPC) { utils.appendString("</" + operationXmlElement + ">"); } } utils.appendLine("xml = xml + cxfjsutils.endSoap11Message();"); utils.appendLine("return xml;"); code.append("}\n\n"); code.append(currentInterfaceClassName + ".prototype." + serializerFunctionPropertyName + " = " + serializerFunctionGlobalName + ";\n\n"); } private XmlSchemaSequence getTypeSequence(XmlSchemaComplexType type, QName parentName) { if (!(type.getParticle() instanceof XmlSchemaSequence)) { unsupportedConstruct("NON_SEQUENCE_PARTICLE", type.getQName() != null ? type.getQName() : parentName); } return (XmlSchemaSequence)type.getParticle(); } private boolean isEmptyType(XmlSchemaType type, QName parentName) { if (type instanceof XmlSchemaComplexType) { XmlSchemaComplexType complexType = (XmlSchemaComplexType)type; if (complexType.getParticle() == null) { return true; } XmlSchemaSequence sequence = getTypeSequence(complexType, parentName); if (sequence.getItems().size() == 0) { return true; } } return false; } /** * Collect information about the parts of an unwrapped message. * @param parts * @param elements */ private void getElementsForParts(MessageInfo message, List<ParticleInfo> elements) { for (MessagePartInfo mpi : message.getMessageParts()) { XmlSchemaElement element = null; XmlSchemaType type = null; QName diagnosticName = mpi.getName(); if (mpi.isElement()) { element = (XmlSchemaElement)mpi.getXmlSchema(); if (element == null) { element = XmlSchemaUtils.findElementByRefName(xmlSchemaCollection, mpi.getElementQName(), serviceInfo.getTargetNamespace()); } diagnosticName = element.getQName(); type = element.getSchemaType(); if (type == null) { type = getElementType(xmlSchemaCollection, null, element, null); } } else { // RPC (!isElement) type = (XmlSchemaType)mpi.getXmlSchema(); if (type == null) { type = xmlSchemaCollection.getTypeByQName(mpi.getTypeQName()); diagnosticName = type.getQName(); } } boolean empty = isEmptyType(type, diagnosticName); String partJavascriptVar = JavascriptUtils.javaScriptNameToken(mpi.getConcreteName().getLocalPart()); String elementXmlRef = prefixAccumulator.xmlElementString(mpi.getConcreteName()); ParticleInfo elementInfo = ParticleInfo.forPartElement(element, xmlSchemaCollection, partJavascriptVar, elementXmlRef); // the type may have been recalculated above. elementInfo.setType(type); elementInfo.setEmpty(empty); elements.add(elementInfo); } } // This function finds all the information for the wrapper. private void collectWrapperElementInfo() { if (currentOperation.getInput() != null) { inputWrapperPartInfo = currentOperation.getInput().getMessagePart(0); List<MessagePartInfo> unwrappedParts = currentOperation.getUnwrappedOperation().getInput().getMessageParts(); for (MessagePartInfo mpi : unwrappedParts) { String jsParamName = JavascriptUtils.javaScriptNameToken(mpi.getName().getLocalPart()); inputParameterNames.add(jsParamName); } inputWrapperPartInfo = currentOperation.getInput().getMessagePart(0); assert inputWrapperPartInfo.isElement(); inputWrapperElement = (XmlSchemaElement)inputWrapperPartInfo.getXmlSchema(); if (inputWrapperElement == null) { inputWrapperElement = XmlSchemaUtils.findElementByRefName(xmlSchemaCollection, inputWrapperPartInfo.getElementQName(), serviceInfo.getTargetNamespace()); } inputWrapperComplexType = (XmlSchemaComplexType)inputWrapperElement.getSchemaType(); // the null name is probably something awful in RFSB. if (inputWrapperComplexType == null) { inputWrapperComplexType = (XmlSchemaComplexType) getElementType(xmlSchemaCollection, serviceInfo.getTargetNamespace(), inputWrapperElement, null); } if (inputWrapperComplexType == null) { unsupportedConstruct("MISSING_WRAPPER_TYPE", currentOperation.getInterface().getName(), currentOperation.getName(), inputWrapperPartInfo.getName()); } if (inputWrapperComplexType.getQName() == null) { // we should be ignoring this for zero-argument wrappers. if (inputWrapperPartInfo.isElement()) { inputWrapperClassName = nameManager. getJavascriptName(inputWrapperPartInfo.getElementQName()); } else { unsupportedConstruct("NON_ELEMENT_ANON_TYPE_PART", inputWrapperPartInfo.getMessageInfo().getName(), inputWrapperPartInfo.getName()); } } else { inputWrapperClassName = nameManager.getJavascriptName(inputWrapperComplexType.getQName()); } } if (currentOperation.getOutput() != null) { outputWrapperPartInfo = currentOperation.getOutput().getMessagePart(0); assert outputWrapperPartInfo.isElement(); outputWrapperElement = (XmlSchemaElement)outputWrapperPartInfo.getXmlSchema(); if (outputWrapperElement == null) { outputWrapperElement = XmlSchemaUtils.findElementByRefName(xmlSchemaCollection, outputWrapperPartInfo.getElementQName(), serviceInfo.getTargetNamespace()); } outputWrapperComplexType = (XmlSchemaComplexType)outputWrapperElement.getSchemaType(); if (outputWrapperComplexType == null) { outputWrapperComplexType = (XmlSchemaComplexType) getElementType(xmlSchemaCollection, serviceInfo.getTargetNamespace(), outputWrapperElement, null); } } } @Override public void begin(ServiceInfo service) { code.append("//\n"); code.append("// Definitions for service: " + service.getName().toString() + "\n"); code.append("//\n"); BindingInfo xml = null; // assume only one soap binding. // until further consideration. // hypothetically, we could generate two different JavaScript classes, // one for each. for (BindingInfo bindingInfo : service.getBindings()) { // there is a JIRA about the confusion / profusion of URLS here. if (SoapBindingConstants.SOAP11_BINDING_ID.equals(bindingInfo.getBindingId()) || SoapBindingConstants.SOAP12_BINDING_ID.equals(bindingInfo.getBindingId()) || SoapBindingFactory.SOAP_11_BINDING.equals(bindingInfo.getBindingId()) || SoapBindingFactory.SOAP_12_BINDING.equals(bindingInfo.getBindingId()) ) { SoapBindingInfo sbi = (SoapBindingInfo)bindingInfo; if (WSDLConstants.NS_SOAP11_HTTP_TRANSPORT.equals(sbi.getTransportURI()) || WSDLConstants.NS_SOAP12_HTTP_BINDING.equals(sbi.getTransportURI()) // we may want this for testing. // we do NOT want a dependency on the local transport. || "http://cxf.apache.org/transports/local".equals(sbi.getTransportURI())) { soapBindingInfo = sbi; break; } } else if (WSDLConstants.NS_BINDING_XML.equals(bindingInfo.getBindingId())) { xml = bindingInfo; } } // For now, we use soap if its available, and XML if it isn't.\ if (soapBindingInfo == null && xml == null) { unsupportedConstruct("NO_USABLE_BINDING", service.getName()); } if (soapBindingInfo != null) { isRPC = WSDLConstants.RPC.equals(soapBindingInfo.getStyle()); } else if (xml != null) { xmlBindingInfo = xml; } } @Override public void end(FaultInfo fault) { } @Override public void end(InterfaceInfo intf) { } @Override public void end(MessageInfo msg) { } @Override public void end(MessagePartInfo part) { } @Override public void end(ServiceInfo service) { LOG.finer(getCode()); } private void unsupportedConstruct(String messageKey, Object... args) { Message message = new Message(messageKey, LOG, args); throw new UnsupportedConstruct(message); } @Override public void begin(OperationInfo op) { if (op.isUnwrapped()) { isInUnwrappedOperation = true; return; } currentOperation = op; OperationInfo conflict = localOperationsNameMap.get(op.getName().getLocalPart()); if (conflict != null) { operationsWithNameConflicts.add(conflict); operationsWithNameConflicts.add(op); } localOperationsNameMap.put(op.getName().getLocalPart(), op); opFunctionPropertyName = getFunctionPropertyName(operationsWithNameConflicts, op, op.getName()); opFunctionGlobalName = getFunctionGlobalName(op.getName(), "op"); } @Override public void begin(MessageInfo msg) { if (isInUnwrappedOperation) { return; } LOG.fine("Message " + msg.getName().toString()); Map<String, MessageInfo> nameMap; Set<MessageInfo> conflicts; if (msg.getType() == MessageInfo.Type.INPUT) { nameMap = localInputMessagesNameMap; conflicts = inputMessagesWithNameConflicts; } else { nameMap = localOutputMessagesNameMap; conflicts = outputMessagesWithNameConflicts; } MessageInfo conflict = nameMap.get(msg.getName().getLocalPart()); if (conflict != null) { conflicts.add(conflict); conflicts.add(msg); } nameMap.put(msg.getName().getLocalPart(), msg); } @Override public void begin(EndpointInfo endpointInfo) { String address = endpointInfo.getAddress(); String portClassName = currentInterfaceClassName + "_" + nameManager.getJavascriptName(endpointInfo.getName()); code.append("function " + portClassName + " () {\n"); code.append(" this.url = '" + address + "';\n"); code.append("}\n"); code.append(portClassName + ".prototype = new " + currentInterfaceClassName + ";\n"); } /** * Follow a chain of references from element to element until we can obtain * a type. * * @param element */ public static XmlSchemaType getElementType(SchemaCollection xmlSchemaCollection, String referencingURI, XmlSchemaElement element, XmlSchemaType containingType) { assert element != null; if (element.getSchemaTypeName() != null) { XmlSchemaType type = xmlSchemaCollection.getTypeByQName(element.getSchemaTypeName()); if (type == null) { Message message = new Message("ELEMENT_TYPE_MISSING", LOG, element.getQName(), element.getSchemaTypeName().toString()); throw new UnsupportedConstruct(message); } return type; } // The referencing URI only helps if there is a schema that points to // it. // It might be the URI for the wsdl TNS, which might have no schema. if (xmlSchemaCollection.getSchemaByTargetNamespace(referencingURI) == null) { referencingURI = null; } if (referencingURI == null && containingType != null) { referencingURI = containingType.getQName().getNamespaceURI(); } XmlSchemaElement originalElement = element; while (element.getSchemaType() == null && element.isRef()) { /* * This code assumes that all schemas are in the collection. */ XmlSchemaElement nextElement = element.getRef().getTarget(); assert nextElement != null; element = nextElement; } if (element.getSchemaType() == null) { unsupportedConstruct("ELEMENT_HAS_NO_TYPE", originalElement.getName(), containingType.getQName(), containingType); } return element.getSchemaType(); } public static void unsupportedConstruct(String messageKey, String what, QName subjectName, XmlSchemaObject subject) { Message message = new Message(messageKey, LOG, what, subjectName == null ? "anonymous" : subjectName, cleanedUpSchemaSource(subject)); LOG.severe(message.toString()); throw new UnsupportedConstruct(message); } public static void unsupportedConstruct(String messageKey, XmlSchemaType subject) { Message message = new Message(messageKey, LOG, subject.getQName(), cleanedUpSchemaSource(subject)); LOG.severe(message.toString()); throw new UnsupportedConstruct(message); } public static String cleanedUpSchemaSource(XmlSchemaObject subject) { if (subject == null || subject.getSourceURI() == null) { return ""; } else { return subject.getSourceURI() + ":" + subject.getLineNumber(); } } }