diff --git a/pom.xml b/pom.xml index e0a4066..9734d0b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.wso2 soaptorest - 1.3 + 1.4 diff --git a/src/main/java/org/wso2/soaptorest/OASGenerator.java b/src/main/java/org/wso2/soaptorest/OASGenerator.java index 2ab8137..b941d62 100644 --- a/src/main/java/org/wso2/soaptorest/OASGenerator.java +++ b/src/main/java/org/wso2/soaptorest/OASGenerator.java @@ -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; @@ -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); } } diff --git a/src/main/java/org/wso2/soaptorest/SOAPRequestBodyGenerator.java b/src/main/java/org/wso2/soaptorest/SOAPRequestBodyGenerator.java index 310a2cd..f297d4d 100644 --- a/src/main/java/org/wso2/soaptorest/SOAPRequestBodyGenerator.java +++ b/src/main/java/org/wso2/soaptorest/SOAPRequestBodyGenerator.java @@ -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; @@ -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 */ @@ -116,6 +122,7 @@ public static SOAPtoRESTConversionData generateSOAPtoRESTConversionObjectFromOAS } List parameters = operation.getParameters(); + Map jsonPathAndSchemaMap = new HashMap<>(); if (parameters != null) { for (Parameter parameter : parameters) { String name = parameter.getName(); @@ -131,7 +138,7 @@ 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); } @@ -139,8 +146,9 @@ public static SOAPtoRESTConversionData generateSOAPtoRESTConversionObjectFromOAS } 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)); } @@ -148,9 +156,59 @@ public static SOAPtoRESTConversionData generateSOAPtoRESTConversionObjectFromOAS 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 parameterJsonPathMapping, Map queryPathParamMapping, String namespace, String operationId, OpenAPI openAPI) throws - SOAPToRESTException { + String> queryPathParamMapping, String namespace, String operationId, OpenAPI openAPI, + Map jsonPathAndSchemaMap) throws SOAPToRESTException { DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder; @@ -204,6 +262,21 @@ private static Document createSOAPRequestXMLForOperation(ArrayList 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)) { @@ -255,6 +328,9 @@ private static Document createSOAPRequestXMLForOperation(ArrayList param if (elemPos == length - 1) { element.setTextContent(payloadPrefix + currentJSONPath + "}"); } + if (needIsEmptyCheck) { + element.setAttribute(IS_EMPTY_ATTRIBUTE, "true"); + } if (prevElement != null) { prevElement.appendChild(element); } else { diff --git a/src/main/java/org/wso2/soaptorest/WSDLProcessor.java b/src/main/java/org/wso2/soaptorest/WSDLProcessor.java index b15794d..84a45ef 100644 --- a/src/main/java/org/wso2/soaptorest/WSDLProcessor.java +++ b/src/main/java/org/wso2/soaptorest/WSDLProcessor.java @@ -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; } diff --git a/src/main/java/org/wso2/soaptorest/models/SOAPtoRESTConversionData.java b/src/main/java/org/wso2/soaptorest/models/SOAPtoRESTConversionData.java index efe470e..e832cc9 100644 --- a/src/main/java/org/wso2/soaptorest/models/SOAPtoRESTConversionData.java +++ b/src/main/java/org/wso2/soaptorest/models/SOAPtoRESTConversionData.java @@ -62,4 +62,8 @@ public String getSoapPort() { return soapPort; } + + public OpenAPI getOpenAPI() { + return openAPI; + } } diff --git a/src/main/java/org/wso2/soaptorest/models/XSElement.java b/src/main/java/org/wso2/soaptorest/models/XSElement.java index 69b1d52..9032edb 100644 --- a/src/main/java/org/wso2/soaptorest/models/XSElement.java +++ b/src/main/java/org/wso2/soaptorest/models/XSElement.java @@ -27,6 +27,7 @@ public class XSElement { QName name; QName type; QName refKey; + boolean isOptional; XSDataType inlineComplexType; boolean isArray; @@ -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; + } } diff --git a/src/main/java/org/wso2/soaptorest/utils/ListJSONPaths.java b/src/main/java/org/wso2/soaptorest/utils/ListJSONPaths.java index c162fe0..43ccaa0 100644 --- a/src/main/java/org/wso2/soaptorest/utils/ListJSONPaths.java +++ b/src/main/java/org/wso2/soaptorest/utils/ListJSONPaths.java @@ -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; @@ -96,11 +97,10 @@ private static void readArray(JSONArray array, String jsonPath, ArrayList getJsonPathsFromExample(Example example) { - + public static ArrayList getJsonPathsFromExample(Example example, Map jsonPathSchemaMapping) { ArrayList pathList = new ArrayList<>(); if (example != null) { - listExamples("", example, pathList); + listExamples("", example, pathList, jsonPathSchemaMapping); } return pathList; } @@ -112,14 +112,18 @@ public static ArrayList 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 parameterJsonPathMapping) { + public static void listExamples(String parent, Example example, ArrayList parameterJsonPathMapping + , Map jsonPathSchemaMapping) { if (example != null) { if (SOAPToRESTConstants.OBJECT_TYPE.equals(example.getTypeName())) { Map values = ((ObjectExample) example).getValues(); if (values != null) { for (Map.Entry 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); @@ -132,9 +136,9 @@ public static void listExamples(String parent, Example example, ArrayList - + - + diff --git a/src/test/resources/complex/nested.xsd b/src/test/resources/complex/nested.xsd index 6c53484..d33fadc 100644 --- a/src/test/resources/complex/nested.xsd +++ b/src/test/resources/complex/nested.xsd @@ -2,17 +2,21 @@ - + - + - + - + + + + + @@ -24,13 +28,14 @@ + - +