Reduce the number of problems in schema-service
[aai/schema-service.git] / aai-schema-gen / src / main / java / org / onap / aai / schemagen / genxsd / OxmFileProcessor.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.aai.schemagen.genxsd;
22
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.StringReader;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 import javax.xml.XMLConstants;
36 import javax.xml.parsers.DocumentBuilder;
37 import javax.xml.parsers.DocumentBuilderFactory;
38 import javax.xml.parsers.ParserConfigurationException;
39
40 import org.onap.aai.edges.EdgeIngestor;
41 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
42 import org.onap.aai.nodes.NodeIngestor;
43 import org.onap.aai.setup.SchemaVersion;
44 import org.onap.aai.setup.SchemaVersions;
45 import org.w3c.dom.Attr;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.NamedNodeMap;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.NodeList;
51 import org.xml.sax.InputSource;
52 import org.xml.sax.SAXException;
53
54 public abstract class OxmFileProcessor {
55
56     public static final String LINE_SEPARATOR = System.getProperty("line.separator");
57     public static final String DOUBLE_LINE_SEPARATOR =
58         System.getProperty("line.separator") + System.getProperty("line.separator");
59     protected static int annotationsStartVersion = 9; // minimum version to support annotations in
60     // xsd
61     protected static int annotationsMinVersion = 6; // lower versions support annotations in xsd
62     protected static int swaggerSupportStartsVersion = 1; // minimum version to support swagger
63     // documentation
64     protected static int swaggerDiffStartVersion = 1; // minimum version to support difference
65     protected static int swaggerMinBasepath = 6; // minimum version to support difference
66     static List<String> nodeFilter = createNodeFilter();
67     protected Set<String> namespaceFilter;
68     protected File oxmFile;
69     protected String xml;
70     protected SchemaVersion v;
71     protected Document doc = null;
72     protected String apiVersion = null;
73     protected SchemaVersions schemaVersions;
74     protected Map<String, Integer> combinedJavaTypes;
75     protected String apiVersionFmt = null;
76     protected List<String> topLevelPaths = new ArrayList<String>();
77     protected HashMap<String, String> generatedJavaType = new HashMap<String, String>();
78     protected HashMap<String, String> appliedPaths = new HashMap<String, String>();
79     protected NodeList javaTypeNodes = null;
80     protected Map<String, String> javaTypeDefinitions = createJavaTypeDefinitions();
81     EdgeIngestor ei;
82     NodeIngestor ni;
83
84     public OxmFileProcessor(SchemaVersions schemaVersions, NodeIngestor ni, EdgeIngestor ei) {
85         this.schemaVersions = schemaVersions;
86         this.ni = ni;
87         this.ei = ei;
88     }
89
90     private static List<String> createNodeFilter() {
91         return Arrays.asList("search", "actions", "aai-internal", "nodes");
92     }
93
94     private Map<String, String> createJavaTypeDefinitions() {
95         StringBuilder aaiInternal = new StringBuilder();
96         StringBuilder nodes = new StringBuilder();
97         Map<String, String> javaTypeDefinitions = new HashMap<String, String>();
98         // update to use platform portable line separator
99         aaiInternal.append("  aai-internal:").append(LINE_SEPARATOR);
100         aaiInternal.append("    properties:").append(LINE_SEPARATOR);
101         aaiInternal.append("      property-name:").append(LINE_SEPARATOR);
102         aaiInternal.append("        type: string").append(LINE_SEPARATOR);
103         aaiInternal.append("      property-value:").append(LINE_SEPARATOR);
104         aaiInternal.append("        type: string").append(LINE_SEPARATOR);
105         // javaTypeDefinitions.put("aai-internal", aaiInternal.toString());
106         nodes.append("  nodes:").append(LINE_SEPARATOR);
107         nodes.append("    properties:").append(LINE_SEPARATOR);
108         nodes.append("      inventory-item-data:").append(LINE_SEPARATOR);
109         nodes.append("        type: array").append(LINE_SEPARATOR);
110         nodes.append("        items:").append(LINE_SEPARATOR);
111         nodes.append("          $ref: \"#/definitions/inventory-item-data\"")
112             .append(LINE_SEPARATOR);
113         javaTypeDefinitions.put("nodes", nodes.toString());
114         return javaTypeDefinitions;
115     }
116
117     public void setOxmVersion(File oxmFile, SchemaVersion v) {
118         this.oxmFile = oxmFile;
119         this.v = v;
120     }
121
122     public void setXmlVersion(String xml, SchemaVersion v) {
123         this.xml = xml;
124         this.v = v;
125     }
126
127     public void setVersion(SchemaVersion v) {
128         this.oxmFile = null;
129         this.v = v;
130     }
131
132     public void setNodeIngestor(NodeIngestor ni) {
133         this.ni = ni;
134     }
135
136     public void setEdgeIngestor(EdgeIngestor ei) {
137         this.ei = ei;
138     }
139
140     public SchemaVersions getSchemaVersions() {
141         return schemaVersions;
142     }
143
144     public void setSchemaVersions(SchemaVersions schemaVersions) {
145         this.schemaVersions = schemaVersions;
146     }
147
148     protected void getTopLevelPaths(XSDElement elem) {
149         NodeList parentNodes;
150         Element parentElement;
151         NodeList xmlElementNodes;
152
153         parentNodes = elem.getElementsByTagName("java-attributes");
154         if (parentNodes.getLength() == 0) {
155             return;
156         }
157         parentElement = (Element) parentNodes.item(0);
158         xmlElementNodes = parentElement.getElementsByTagName("xml-element");
159         if (xmlElementNodes.getLength() <= 0) {
160             return;
161         }
162
163         XSDElement xmlElementElement;
164
165         for (int i = 0; i < xmlElementNodes.getLength(); ++i) {
166             xmlElementElement = new XSDElement((Element) xmlElementNodes.item(i));
167             if (!xmlElementElement.getParentNode().isSameNode(parentElement)) {
168                 continue;
169             }
170             String topLevel = xmlElementElement.getAttribute("type");
171             topLevel = topLevel.substring(topLevel.lastIndexOf('.') + 1);
172             if (!topLevelPaths.contains(topLevel)) {
173                 if ("Nodes".equals(topLevel) || "AaiInternal".equals(topLevel)) {
174                     continue;
175                 }
176                 topLevelPaths.add(topLevel);
177             }
178         }
179     }
180
181     protected boolean checkTopLevel(String topLevel, boolean ignoreActionsSearch) {
182         // when ignoreActionsSearch is set to true, with a topLevel that matches one of the values
183         // to ignore, the logic will handle those values, as if they are not at the top level.
184         // this was done when refactoring checks that may or may not include these top levels.
185         // Using this API allows new top levels to be added to the schema file and
186         // included in the generated yaml without changing this generation logic.
187         if (ignoreActionsSearch) {
188             if ("Actions".equals(topLevel) || "Search".equals(topLevel)) {
189                 return false;
190             }
191         }
192         return topLevelPaths.contains(topLevel);
193     }
194
195     protected void init()
196         throws ParserConfigurationException, SAXException, IOException, EdgeRuleNotFoundException {
197         if (this.xml != null || this.oxmFile != null) {
198             createDocument();
199         }
200         if (this.doc == null) {
201             this.doc = ni.getSchema(v);
202         }
203         namespaceFilter = new HashSet<>();
204
205         NodeList bindingsNodes = doc.getElementsByTagName("xml-bindings");
206         Element bindingElement;
207         NodeList javaTypesNodes;
208         Element javaTypesElement;
209
210         if (bindingsNodes == null || bindingsNodes.getLength() == 0) {
211             throw new SAXException("OXM file error: missing <binding-nodes> in " + oxmFile);
212         }
213
214         bindingElement = (Element) bindingsNodes.item(0);
215         javaTypesNodes = bindingElement.getElementsByTagName("java-types");
216         if (javaTypesNodes.getLength() < 1) {
217             throw new SAXException(
218                 "OXM file error: missing <binding-nodes><java-types> in " + oxmFile);
219         }
220         javaTypesElement = (Element) javaTypesNodes.item(0);
221
222         javaTypeNodes = javaTypesElement.getElementsByTagName("java-type");
223         if (javaTypeNodes.getLength() < 1) {
224             throw new SAXException(
225                 "OXM file error: missing <binding-nodes><java-types><java-type> in " + oxmFile);
226         }
227     }
228
229     private void createDocument() throws ParserConfigurationException, SAXException, IOException {
230         DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
231         dbFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
232         dbFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
233         dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
234         dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
235         dbFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
236         dbFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
237         DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
238
239         if (xml == null) {
240             doc = dBuilder.parse(oxmFile);
241         } else {
242             InputSource isInput = new InputSource(new StringReader(xml));
243             doc = dBuilder.parse(isInput);
244         }
245     }
246
247     public abstract String getDocumentHeader();
248
249     public abstract String process() throws ParserConfigurationException, SAXException, IOException,
250         FileNotFoundException, EdgeRuleNotFoundException;
251
252     public String getXMLRootElementName(Element javaTypeElement) {
253         String xmlRootElementName = null;
254         NamedNodeMap attributes;
255
256         NodeList valNodes = javaTypeElement.getElementsByTagName("xml-root-element");
257         Element valElement = (Element) valNodes.item(0);
258         attributes = valElement.getAttributes();
259         for (int i = 0; i < attributes.getLength(); ++i) {
260             Attr attr = (Attr) attributes.item(i);
261             String attrName = attr.getNodeName();
262
263             String attrValue = attr.getNodeValue();
264             if ("name".equals(attrName)) {
265                 xmlRootElementName = attrValue;
266             }
267         }
268         return xmlRootElementName;
269     }
270
271     public String getXmlRootElementName(String javaTypeName) {
272         String attrName, attrValue;
273         Attr attr;
274         Element javaTypeElement;
275         for (int i = 0; i < javaTypeNodes.getLength(); ++i) {
276             javaTypeElement = (Element) javaTypeNodes.item(i);
277             NamedNodeMap attributes = javaTypeElement.getAttributes();
278             for (int j = 0; j < attributes.getLength(); ++j) {
279                 attr = (Attr) attributes.item(j);
280                 attrName = attr.getNodeName();
281                 attrValue = attr.getNodeValue();
282                 if ("name".equals(attrName) && attrValue.equals(javaTypeName)) {
283                     NodeList valNodes = javaTypeElement.getElementsByTagName("xml-root-element");
284                     Element valElement = (Element) valNodes.item(0);
285                     attributes = valElement.getAttributes();
286                     for (int k = 0; k < attributes.getLength(); ++k) {
287                         attr = (Attr) attributes.item(k);
288                         attrName = attr.getNodeName();
289
290                         attrValue = attr.getNodeValue();
291                         if ("name".equals(attrName)) {
292                             return (attrValue);
293                         }
294                     }
295                 }
296             }
297         }
298         return null;
299     }
300
301     public Map<String, Integer> getCombinedJavaTypes() {
302         return combinedJavaTypes;
303     }
304
305     public void setCombinedJavaTypes(Map<String, Integer> combinedJavaTypes) {
306         this.combinedJavaTypes = combinedJavaTypes;
307     }
308
309     public Element getJavaTypeElementSwagger(String javaTypeName) {
310
311         String attrName, attrValue;
312         Attr attr;
313         Element javaTypeElement;
314
315         List<Element> combineElementList = new ArrayList<Element>();
316         for (int i = 0; i < javaTypeNodes.getLength(); ++i) {
317             javaTypeElement = (Element) javaTypeNodes.item(i);
318             NamedNodeMap attributes = javaTypeElement.getAttributes();
319             for (int j = 0; j < attributes.getLength(); ++j) {
320                 attr = (Attr) attributes.item(j);
321                 attrName = attr.getNodeName();
322                 attrValue = attr.getNodeValue();
323                 if ("name".equals(attrName) && attrValue.equals(javaTypeName)) {
324                     combineElementList.add(javaTypeElement);
325                 }
326             }
327         }
328         if (combineElementList.size() == 0) {
329             return (Element) null;
330         } else if (combineElementList.size() > 1) {
331             return combineElements(javaTypeName, combineElementList);
332         }
333         return combineElementList.get(0);
334     }
335
336     public boolean versionSupportsSwaggerDiff(String version) {
337         int ver = Integer.parseInt(version.substring(1));
338         return ver >= HTMLfromOXM.swaggerDiffStartVersion;
339     }
340
341     public boolean versionSupportsBasePathProperty(String version) {
342         int ver = Integer.parseInt(version.substring(1));
343         return ver <= HTMLfromOXM.swaggerMinBasepath;
344     }
345
346     protected void updateParentXmlElements(Element parentElement, NodeList moreXmlElementNodes) {
347         Element xmlElement;
348         NodeList childNodes;
349         Node childNode;
350
351         Node refChild = null;
352         // find childNode with attributes and no children, insert children before that node
353         childNodes = parentElement.getChildNodes();
354         if (childNodes == null || childNodes.getLength() == 0) {
355             // should not happen since the base parent was chosen if it had children
356             return;
357         }
358
359         for (int i = 0; i < childNodes.getLength(); ++i) {
360             refChild = childNodes.item(i);
361             if (refChild.hasAttributes() && !refChild.hasChildNodes()) {
362                 break;
363             }
364
365         }
366
367         for (int i = 0; i < moreXmlElementNodes.getLength(); ++i) {
368             xmlElement = (Element) moreXmlElementNodes.item(i);
369             childNode = xmlElement.cloneNode(true);
370             parentElement.insertBefore(childNode, refChild);
371         }
372     }
373
374     protected Node getXmlPropertiesNode(Element javaTypeElement) {
375         NodeList nl = javaTypeElement.getChildNodes();
376         Node child;
377         for (int i = 0; i < nl.getLength(); ++i) {
378             child = nl.item(i);
379             if ("xml-properties".equals(child.getNodeName())) {
380                 return child;
381             }
382         }
383         return null;
384     }
385
386     protected Node merge(NodeList nl, Node mergeNode) {
387         NamedNodeMap nnm = mergeNode.getAttributes();
388         Node childNode;
389         NamedNodeMap childNnm;
390
391         String mergeName = nnm.getNamedItem("name").getNodeValue();
392         String mergeValue = nnm.getNamedItem("value").getNodeValue();
393         String childName;
394         String childValue;
395         for (int j = 0; j < nl.getLength(); ++j) {
396             childNode = nl.item(j);
397             if ("xml-property".equals(childNode.getNodeName())) {
398                 childNnm = childNode.getAttributes();
399                 childName = childNnm.getNamedItem("name").getNodeValue();
400                 childValue = childNnm.getNamedItem("value").getNodeValue();
401                 if (childName.equals(mergeName)) {
402                     // attribute exists
403                     // keep, replace or update
404                     if (childValue.contains(mergeValue)) {
405                         return null;
406                     }
407                     if (mergeValue.contains(childValue)) {
408                         childNnm.getNamedItem("value").setTextContent(mergeValue);
409                         return null;
410                     }
411                     childNnm.getNamedItem("value").setTextContent(mergeValue + "," + childValue);
412                     return null;
413                 }
414             }
415         }
416         childNode = mergeNode.cloneNode(true);
417         return childNode;
418     }
419
420     protected void mergeXmlProperties(Node useChildProperties, NodeList propertiesToMerge) {
421         NodeList nl = useChildProperties.getChildNodes();
422         Node childNode;
423         Node newNode;
424         for (int i = 0; i < propertiesToMerge.getLength(); ++i) {
425             childNode = propertiesToMerge.item(i);
426             if ("xml-property".equals(childNode.getNodeName())) {
427                 newNode = merge(nl, childNode);
428                 if (newNode != null) {
429                     useChildProperties.appendChild(newNode);
430                 }
431             }
432
433         }
434     }
435
436     protected void combineXmlProperties(int useElement, List<Element> combineElementList) {
437         // add or update xml-properties to the referenced element from the combined list
438         Element javaTypeElement = combineElementList.get(useElement);
439         NodeList nl = javaTypeElement.getChildNodes();
440         Node useChildProperties = getXmlPropertiesNode(javaTypeElement);
441         int cloneChild = -1;
442         Node childProperties;
443         if (useChildProperties == null) {
444             // find xml-properties to clone
445             for (int i = 0; i < combineElementList.size(); ++i) {
446                 if (i == useElement) {
447                     continue;
448                 }
449                 childProperties = getXmlPropertiesNode(combineElementList.get(i));
450                 if (childProperties != null) {
451                     useChildProperties = childProperties.cloneNode(true);
452                     javaTypeElement.appendChild(useChildProperties);
453                     cloneChild = i;
454                 }
455             }
456         }
457         NodeList cnl;
458         // find other xml-properties
459         for (int i = 0; i < combineElementList.size(); ++i) {
460             if (i == useElement || (cloneChild >= 0 && i <= cloneChild)) {
461                 continue;
462             }
463             childProperties = getXmlPropertiesNode(combineElementList.get(i));
464             if (childProperties == null) {
465                 continue;
466             }
467             cnl = childProperties.getChildNodes();
468             mergeXmlProperties(useChildProperties, cnl);
469         }
470
471     }
472
473     protected Element combineElements(String javaTypeName, List<Element> combineElementList) {
474         Element javaTypeElement;
475         NodeList parentNodes;
476         Element parentElement = null;
477         NodeList xmlElementNodes;
478
479         int useElement = -1;
480         if (combinedJavaTypes.containsKey(javaTypeName)) {
481             return combineElementList.get(combinedJavaTypes.get(javaTypeName));
482         }
483         for (int i = 0; i < combineElementList.size(); ++i) {
484             javaTypeElement = combineElementList.get(i);
485             parentNodes = javaTypeElement.getElementsByTagName("java-attributes");
486             if (parentNodes.getLength() == 0) {
487                 continue;
488             }
489             parentElement = (Element) parentNodes.item(0);
490             xmlElementNodes = parentElement.getElementsByTagName("xml-element");
491             if (xmlElementNodes.getLength() <= 0) {
492                 continue;
493             }
494             useElement = i;
495             break;
496         }
497         boolean doCombineElements = true;
498         if (useElement < 0) {
499             useElement = 0;
500             doCombineElements = false;
501         } else if (useElement == combineElementList.size() - 1) {
502             doCombineElements = false;
503         }
504         if (doCombineElements) {
505             // get xml-element from other javaTypeElements
506             Element otherParentElement = null;
507             for (int i = 0; i < combineElementList.size(); ++i) {
508                 if (i == useElement) {
509                     continue;
510                 }
511                 javaTypeElement = combineElementList.get(i);
512                 parentNodes = javaTypeElement.getElementsByTagName("java-attributes");
513                 if (parentNodes.getLength() == 0) {
514                     continue;
515                 }
516                 otherParentElement = (Element) parentNodes.item(0);
517                 xmlElementNodes = otherParentElement.getElementsByTagName("xml-element");
518                 if (xmlElementNodes.getLength() <= 0) {
519                     continue;
520                 }
521                 // xml-element that are not present
522                 updateParentXmlElements(parentElement, xmlElementNodes);
523
524             }
525         }
526         // need to combine xml-properties
527         combineXmlProperties(useElement, combineElementList);
528         combinedJavaTypes.put(javaTypeName, useElement);
529         return combineElementList.get(useElement);
530     }
531 }