Skip to content

Commit

Permalink
Add optional value support
Browse files Browse the repository at this point in the history
Add optional value support to WSDL
Add required properties to OAS and wrap optional elements  with <#if> elements in
freemarker template
Fixes wso2/api-manager/issues/1985
  • Loading branch information
GDLMadushanka committed Jul 13, 2023
1 parent 091e892 commit 2f64f98
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 18 deletions.
10 changes: 10 additions & 0 deletions src/main/java/org/wso2/soaptorest/OASGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.wso2.soaptorest.utils.SOAPToRESTConstants;

import javax.xml.namespace.QName;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -265,6 +266,15 @@ private static void processXSSequence(XSSequence xsSequence, Schema<?> parentSch
for (XSElement xsElement : xsElementList) {
Schema<?> innerSchema = getSchemaForXSElement(xsElement, isElementFormDefaultQualified);
if (innerSchema != null) {
// if element is not optional, add it to the required list of parent schema
boolean isRef = xsElement.getRefKey() != null && xsElement.getName() == null;
if (!xsElement.isOptional() && !isRef) {
if (parentSchema.getRequired() != null) {
parentSchema.getRequired().add(xsElement.getName().getLocalPart());
} else {
parentSchema.setRequired(Arrays.asList(xsElement.getName().getLocalPart()));
}
}
parentSchema.addProperties(innerSchema.getName(), innerSchema);
}
}
Expand Down
84 changes: 80 additions & 4 deletions src/main/java/org/wso2/soaptorest/SOAPRequestBodyGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.wso2.soaptorest.exceptions.SOAPToRESTException;
import org.wso2.soaptorest.models.SOAPRequestElement;
import org.wso2.soaptorest.models.SOAPtoRESTConversionData;
Expand All @@ -51,6 +52,11 @@
import java.util.List;
import java.util.Map;

import static org.wso2.soaptorest.utils.SOAPToRESTConstants.ATTRIBUTE_PLACEHOLDER;
import static org.wso2.soaptorest.utils.SOAPToRESTConstants.IF_PLACEHOLDER;
import static org.wso2.soaptorest.utils.SOAPToRESTConstants.IS_EMPTY_ATTRIBUTE;
import static org.wso2.soaptorest.utils.SOAPToRESTConstants.QUESTION_MARK_PLACEHOLDER;

/**
* Class that reads OpenAPI and generate soap request payloads
*/
Expand Down Expand Up @@ -116,6 +122,7 @@ public static SOAPtoRESTConversionData generateSOAPtoRESTConversionObjectFromOAS
}

List<Parameter> parameters = operation.getParameters();
Map<String,String> jsonPathAndSchemaMap = new HashMap<>();
if (parameters != null) {
for (Parameter parameter : parameters) {
String name = parameter.getName();
Expand All @@ -131,26 +138,77 @@ public static SOAPtoRESTConversionData generateSOAPtoRESTConversionObjectFromOAS
operation.getRequestBody().getContent().get(SOAPToRESTConstants.
DEFAULT_CONTENT_TYPE).getSchema();
Example example = ExampleBuilder.fromSchema(model, openAPI.getComponents().getSchemas());
parameterJsonPathMapping = ListJSONPaths.getJsonPathsFromExample(example);
parameterJsonPathMapping = ListJSONPaths.getJsonPathsFromExample(example, jsonPathAndSchemaMap);
} catch (Exception e) {
throw new SOAPToRESTException("Cannot generate JSON body from the OpenAPI", e);
}

}

Document soapRequestBody = createSOAPRequestXMLForOperation(parameterJsonPathMapping, queryParameters,
namespace, operationId, openAPI);
namespace, operationId, openAPI, jsonPathAndSchemaMap );

iterateChildNodes(soapRequestBody.getDocumentElement(), soapRequestBody);
requestBodies.put(operationId, new SOAPRequestElement(soapRequestBody, soapAction, namespace,
soapNamespace));
}
}
return new SOAPtoRESTConversionData(openAPI, requestBodies, soapService, soapPort);
}

/**
* Iterate through the given document and wrap the possible empty elements with <#if> statements.
* @param node Current node
* @param document Root document
*/
private static void iterateChildNodes(Node node, Document document) {
// Get the child nodes of the current node
NodeList childNodes = node.getChildNodes();

// Iterate over the child nodes
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);

// Check if the element has the attribute "addIsEmptyCheck" with value "true"
if (childNode instanceof Element) {
Element element = (Element) childNode;
if (element.hasAttribute(IS_EMPTY_ATTRIBUTE) &&
element.getAttribute(IS_EMPTY_ATTRIBUTE).equals("true")) {
// Create a new element <#if>
String value = element.getTextContent();
String nodeValue = element.getNodeName();
Element nextSibling = (Element) element.getNextSibling();

// copy the element without attributes
Element modified = document.createElement(nodeValue);
modified.setTextContent(value);

Element newElement = document.createElement(IF_PLACEHOLDER);
// remove ${} from the value and append has_content check
newElement.setAttribute(ATTRIBUTE_PLACEHOLDER, value.substring(2,value.length()-1) +
QUESTION_MARK_PLACEHOLDER + "has_content");
newElement.appendChild(modified);

Node parentNode = element.getParentNode();
parentNode.removeChild(element);
if (nextSibling == null) {
parentNode.appendChild(newElement);
} else {
parentNode.insertBefore(newElement, nextSibling);
}
iterateChildNodes(newElement, document);
}
}

// Recursively iterate child nodes of non-matched elements
if (childNode instanceof Element && !childNode.getNodeName().equals(IF_PLACEHOLDER)) {
iterateChildNodes(childNode, document);
}
}
}
private static Document createSOAPRequestXMLForOperation(ArrayList<String> parameterJsonPathMapping, Map<String,
String> queryPathParamMapping, String namespace, String operationId, OpenAPI openAPI) throws
SOAPToRESTException {
String> queryPathParamMapping, String namespace, String operationId, OpenAPI openAPI,
Map<String,String> jsonPathAndSchemaMap) throws SOAPToRESTException {

DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder;
Expand Down Expand Up @@ -204,6 +262,21 @@ private static Document createSOAPRequestXMLForOperation(ArrayList<String> param
isNamespaceQualified = true;
}
}
Schema<?> parentSchema = null;
boolean needIsEmptyCheck = false;
// Check parent schema for required fields and wrap with isEmpty check if required
if (prevElement != null) {
String parentElementName = prevElement.getNodeName();
if (parentElementName.contains(SOAPToRESTConstants.NAMESPACE_SEPARATOR)) {
parentElementName = parentElementName.split(SOAPToRESTConstants.NAMESPACE_SEPARATOR)[1];
}
parentSchema = openAPI.getComponents().getSchemas().get(jsonPathAndSchemaMap.get(parentElementName));
if (parentSchema != null && parentSchema.getRequired() != null &&
!parentSchema.getRequired().contains(parameterTreeNode)) {
needIsEmptyCheck = true;
}
}

String payloadPrefix = "${";
if (StringUtils.isNotBlank(parameterTreeNode)) {
if (SOAPToRESTConstants.ATTR_CONTENT_KEYWORD.equalsIgnoreCase(parameterTreeNode)) {
Expand Down Expand Up @@ -255,6 +328,9 @@ private static Document createSOAPRequestXMLForOperation(ArrayList<String> param
if (elemPos == length - 1) {
element.setTextContent(payloadPrefix + currentJSONPath + "}");
}
if (needIsEmptyCheck) {
element.setAttribute(IS_EMPTY_ATTRIBUTE, "true");
}
if (prevElement != null) {
prevElement.appendChild(element);
} else {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/wso2/soaptorest/WSDLProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ private XSElement processXmlSchemaElement(XmlSchemaElement xmlSchemaElement) {
} else {
log.warn("Data type for the child element " + xmlSchemaElement.getName() + "did " + "not processed");
}
if (xmlSchemaElement.getMinOccurs() == 0) {
xsElement.setOptional(true);
}
return xsElement;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ public String getSoapPort() {

return soapPort;
}

public OpenAPI getOpenAPI() {
return openAPI;
}
}
9 changes: 9 additions & 0 deletions src/main/java/org/wso2/soaptorest/models/XSElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class XSElement {
QName name;
QName type;
QName refKey;
boolean isOptional;
XSDataType inlineComplexType;
boolean isArray;

Expand Down Expand Up @@ -77,4 +78,12 @@ public boolean isArray() {
public void setArray(boolean array) {
isArray = array;
}

public boolean isOptional() {
return isOptional;
}

public void setOptional(boolean optional) {
isOptional = optional;
}
}
18 changes: 11 additions & 7 deletions src/main/java/org/wso2/soaptorest/utils/ListJSONPaths.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.wso2.soaptorest.utils;

import io.swagger.v3.oas.models.OpenAPI;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
Expand Down Expand Up @@ -96,11 +97,10 @@ private static void readArray(JSONArray array, String jsonPath, ArrayList<String
* @param example example object
* @return the arraylist of the available json paths
*/
public static ArrayList<String> getJsonPathsFromExample(Example example) {

public static ArrayList<String> getJsonPathsFromExample(Example example, Map<String, String> jsonPathSchemaMapping) {
ArrayList<String> pathList = new ArrayList<>();
if (example != null) {
listExamples("", example, pathList);
listExamples("", example, pathList, jsonPathSchemaMapping);
}
return pathList;
}
Expand All @@ -112,14 +112,18 @@ public static ArrayList<String> getJsonPathsFromExample(Example example) {
* @param parameterJsonPathMapping list of json paths
* @return the arraylist of the available json paths
*/
public static void listExamples(String parent, Example example, ArrayList<String> parameterJsonPathMapping) {
public static void listExamples(String parent, Example example, ArrayList<String> parameterJsonPathMapping
, Map<String, String> jsonPathSchemaMapping) {
if (example != null) {
if (SOAPToRESTConstants.OBJECT_TYPE.equals(example.getTypeName())) {
Map<String, Example> values = ((ObjectExample) example).getValues();
if (values != null) {
for (Map.Entry<String, Example> entry : values.entrySet()) {
String childKey = parent.isEmpty() ? entry.getKey() : parent + "." + entry.getKey();
listExamples(childKey, entry.getValue(), parameterJsonPathMapping);
if (entry.getValue() != null && entry.getValue().getName() != null) {
jsonPathSchemaMapping.put(entry.getKey(), entry.getValue().getName());
}
listExamples(childKey, entry.getValue(), parameterJsonPathMapping, jsonPathSchemaMapping);
}
} else if (StringUtils.isNotBlank(parent)) {
parameterJsonPathMapping.add(parent);
Expand All @@ -132,9 +136,9 @@ public static void listExamples(String parent, Example example, ArrayList<String
Example exampleItem = exampleArray.get(i);
jsonPath = parent + "[" + i + "]";
if (SOAPToRESTConstants.OBJECT_TYPE.equals(exampleItem.getTypeName())) {
listExamples(jsonPath, exampleItem, parameterJsonPathMapping);
listExamples(jsonPath, exampleItem, parameterJsonPathMapping, jsonPathSchemaMapping);
} else if (SOAPToRESTConstants.ARRAY_TYPE.equals(exampleItem.getTypeName())) {
listExamples(jsonPath, exampleItem, parameterJsonPathMapping);
listExamples(jsonPath, exampleItem, parameterJsonPathMapping, jsonPathSchemaMapping);
} else {
parameterJsonPathMapping.add(jsonPath);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,10 @@ public class SOAPToRESTConstants {
public static final String OAS_ALL_MEDIA_TYPE = "*/*";
public static final String OBJECT_TYPE = "object";
public static final String ARRAY_TYPE = "array";
public static final String IF_PLACEHOLDER = "ifPlaceholder";
public static final String ELSE_PLACEHOLDER = "elsePlaceholder";
public static final String QUESTION_MARK_PLACEHOLDER = "questionPlaceholder";
public static final String ATTRIBUTE_PLACEHOLDER = "attributePlaceholder";
public static final String IS_EMPTY_ATTRIBUTE = "addIsEmptyCheck";

}
11 changes: 11 additions & 0 deletions src/test/java/org/wso2/soaptorest/ComplexWSDLTestCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -100,4 +101,14 @@ void testChoiceWSDL() throws SOAPToRESTException {
assertEquals(soaPtoRESTConversionData.getSoapService(), "choiceSampleService");
assertEquals(soaPtoRESTConversionData.getSoapPort(), "choiceSamplePort");
}

@Test
void testWSDLWithOptional() throws SOAPToRESTException {
SOAPtoRESTConversionData soaPtoRESTConversionData = SOAPToRESTConverter.getSOAPtoRESTConversionData(
"src/test/resources" + "/complex/nested.wsdl", "Test API", "1.0.0");
List requiredValues = soaPtoRESTConversionData.getOpenAPI().getComponents()
.getSchemas().get("innerType2").getRequired();
assertTrue(requiredValues.contains("inner3"));
assertTrue(requiredValues.contains("int3"));
}
}
4 changes: 2 additions & 2 deletions src/test/resources/complex/nested.wsdl
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
</xsd:schema>
</types>
<message name="inputMessage">
<part name="parameters" element="tns:inputModel"/>
<part name="parameters" element="tns:inputModelBla"/>
</message>
<message name="outputMessage">
<part name="parameters" element="tns:outputModel"/>
<part name="parameters" element="tns:outputModelBla"/>
</message>
<portType name="nestedSample">
<operation name="inputOperation">
Expand Down
15 changes: 10 additions & 5 deletions src/test/resources/complex/nested.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
<xs:schema xmlns:tns="http://example.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0"
targetNamespace="http://example.com/">

<xs:element name="inputModel" type="tns:inputModel"/>
<xs:element name="inputModelBla" type="tns:inputModel"/>

<xs:element name="outputModel" type="tns:outputModel"/>
<xs:element name="outputModelBla" type="tns:outputModel"/>

<xs:complexType name="inputModel">
<xs:sequence>
<xs:element name="inner1" type="tns:innerType1"/>
<xs:element name="int1" type="xs:int"/>
<xs:element name="int1" type="xs:int" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:simpleType name="languageCode">
<xs:restriction base="xs:string">
<xs:length value="3"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="innerType1">
<xs:sequence>
<xs:element name="inner2" type="tns:innerType2"/>
Expand All @@ -24,13 +28,14 @@
<xs:sequence>
<xs:element name="inner3" type="tns:innerType3"/>
<xs:element name="int3" type="xs:int"/>
<xs:element minOccurs="0" name="languageCode" type="tns:languageCode"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="innerType3">
<xs:sequence>
<xs:element name="intA" type="xs:int"/>
<xs:element name="intB" type="xs:int"/>
<xs:element name="intB" type="xs:int" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

Expand Down

0 comments on commit 2f64f98

Please sign in to comment.