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