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