e7b9167d22f14d79e0c3d01f06c37bdc412faa67
[aai/schema-service.git] / aai-schema-service / src / main / java / org / onap / aai / schemaservice / nodeschema / NodeIngestor.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.schemaservice.nodeschema;
22
23 import java.io.ByteArrayInputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.nio.charset.StandardCharsets;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Map.Entry;
37 import java.util.Set;
38 import java.util.TreeMap;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42 import javax.xml.XMLConstants;
43 import javax.xml.parsers.DocumentBuilder;
44 import javax.xml.parsers.DocumentBuilderFactory;
45 import javax.xml.parsers.ParserConfigurationException;
46
47 import org.eclipse.persistence.jaxb.JAXBContextProperties;
48 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
49 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory;
50 import org.onap.aai.schemaservice.config.ConfigTranslator;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53 import org.springframework.beans.factory.annotation.Autowired;
54 import org.springframework.stereotype.Component;
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Element;
57 import org.w3c.dom.Node;
58 import org.w3c.dom.NodeList;
59 import org.xml.sax.SAXException;
60
61 import com.google.common.base.CaseFormat;
62 import com.google.common.collect.ArrayListMultimap;
63 import com.google.common.collect.Multimap;
64
65 import jakarta.xml.bind.JAXBException;
66
67 /**
68  * NodeIngestor - ingests A&AI OXM files per given config, serves DynamicJAXBContext per version
69  */
70 @Component
71 public class NodeIngestor {
72
73     private static final Logger LOGGER = LoggerFactory.getLogger(NodeIngestor.class);
74
75     private static final Pattern classNamePattern = Pattern.compile("\\.(v\\d+)\\.");
76     private Map<SchemaVersion, DynamicJAXBContext> versionContextMap = new TreeMap<>();
77     private Map<SchemaVersion, Set<String>> typesPerVersion = new TreeMap<>();
78     private Map<SchemaVersion, Document> schemaPerVersion = new TreeMap<>();
79     private ConfigTranslator translator;
80
81     @Autowired
82     /**
83      * Instantiates the NodeIngestor bean.
84      *
85      * @param translator - ConfigTranslator autowired in by Spring framework which
86      *        contains the configuration information needed to ingest the desired files.
87      */
88     public NodeIngestor(ConfigTranslator translator) {
89         this.translator = translator;
90         Map<SchemaVersion, List<String>> filesToIngest = translator.getNodeFiles();
91
92         try {
93             for (Entry<SchemaVersion, List<String>> verFiles : filesToIngest.entrySet()) {
94                 SchemaVersion v = verFiles.getKey();
95                 List<String> files = verFiles.getValue();
96                 final DynamicJAXBContext ctx = ingest(files);
97                 versionContextMap.put(v, ctx);
98                 typesPerVersion.put(v, getAllNodeTypes(files));
99                 schemaPerVersion.put(v, createCombinedSchema(files, v));
100             }
101         } catch (JAXBException | ParserConfigurationException | SAXException | IOException e) {
102             throw new ExceptionInInitializerError(e);
103         }
104     }
105
106     /**
107      * Ingests the given OXM files into DynamicJAXBContext
108      *
109      * @param files - List<String> of full filenames (ie including the path) to be ingested
110      * @return DynamicJAXBContext including schema information from all given files
111      * @throws FileNotFoundException if an OXM file can't be found
112      * @throws JAXBException if there's an error creating the DynamicJAXBContext
113      */
114     private DynamicJAXBContext ingest(List<String> files)
115         throws FileNotFoundException, JAXBException {
116         List<InputStream> streams = new ArrayList<>();
117
118         for (String name : files) {
119             streams.add(new FileInputStream(new File(name)));
120         }
121
122         Map<String, Object> properties = new HashMap<>();
123         properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, streams);
124         return DynamicJAXBContextFactory.createContextFromOXM(this.getClass().getClassLoader(),
125             properties);
126     }
127
128     private Set<String> getAllNodeTypes(List<String> files)
129         throws ParserConfigurationException, SAXException, IOException {
130         Set<String> types = new HashSet<>();
131         final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
132         docFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
133         docFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
134         docFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
135         docFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
136         docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
137         docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
138         final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
139
140         ArrayList<Node> javaTypes = new ArrayList<>();
141         for (String file : files) {
142             InputStream inputStream = new FileInputStream(file);
143
144             final Document doc = docBuilder.parse(inputStream);
145             final NodeList list = doc.getElementsByTagName("java-type");
146
147             for (int i = 0; i < list.getLength(); i++) {
148                 String type = list.item(i).getAttributes().getNamedItem("name").getNodeValue();
149                 javaTypes.add(list.item(i));
150                 types.add(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, type));
151             }
152         }
153
154         return types;
155     }
156
157     private Document createCombinedSchema(List<String> files, SchemaVersion v)
158         throws ParserConfigurationException, SAXException, IOException {
159         final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
160         docFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
161         docFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
162         docFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
163         docFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
164         docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
165         docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
166         final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
167         DocumentBuilder masterDocBuilder = docFactory.newDocumentBuilder();
168         Document combinedDoc = masterDocBuilder.parse(getShell(v));
169         NodeList masterList = combinedDoc.getElementsByTagName("java-types");
170         Node javaTypesContainer =
171             masterList.getLength() == 0 ? combinedDoc.getDocumentElement() : masterList.item(0);
172
173         Multimap<String, Node> nodeMultimap = ArrayListMultimap.create();
174         LOGGER.debug("Started combining the schema from list of files {} for version {}", files, v);
175
176         for (String file : files) {
177             InputStream inputStream = new FileInputStream(file);
178
179             final Document doc = docBuilder.parse(inputStream);
180             final NodeList list = doc.getElementsByTagName("java-type");
181
182             for (int i = 0; i < list.getLength(); i++) {
183                 Node curNode = list.item(i);
184                 String name = curNode.getAttributes().getNamedItem("name").getNodeValue();
185                 nodeMultimap.put(name, curNode);
186             }
187         }
188
189         Map<String, Collection<Node>> map = nodeMultimap.asMap();
190         createNode(combinedDoc, javaTypesContainer, map);
191
192         LOGGER.debug("Successfully merged all schema files for version {}", v);
193
194         return combinedDoc;
195     }
196
197     private void createNode(Document combinedDoc, Node javaTypesContainer,
198         Map<String, Collection<Node>> map) {
199
200         for (Entry<String, Collection<Node>> entry : map.entrySet()) {
201
202             List<Node> listOfNodes = (List<Node>) entry.getValue();
203             LOGGER.trace("NodeType {} Occurrences {}", entry.getKey(), listOfNodes.size());
204             Node copyOfFirstElement = null;
205             Node javaAttributeElement = null;
206
207             if (listOfNodes.size() > 1) {
208                 for (int index = 0; index < listOfNodes.size(); index++) {
209                     if (index == 0) {
210                         Node currentNode = listOfNodes.get(index);
211                         copyOfFirstElement = combinedDoc.importNode(currentNode, true);
212                         if (copyOfFirstElement.getNodeType() == Node.ELEMENT_NODE) {
213                             Element element = (Element) copyOfFirstElement;
214                             NodeList javaAttributesList =
215                                 element.getElementsByTagName("java-attributes");
216                             for (int javaAttributeIndex = 0; javaAttributeIndex < javaAttributesList
217                                 .getLength(); javaAttributeIndex++) {
218                                 javaAttributeElement = javaAttributesList.item(javaAttributeIndex);
219                             }
220                         }
221                     } else {
222                         Node currentNode = listOfNodes.get(index);
223                         Node copyOfCurrentElement = combinedDoc.importNode(currentNode, true);
224                         if (copyOfCurrentElement.getNodeType() == Node.ELEMENT_NODE) {
225                             Element element = (Element) copyOfCurrentElement;
226                             NodeList javaAttributesList =
227                                 element.getElementsByTagName("java-attributes");
228                             for (int javaAttributeIndex = 0; javaAttributeIndex < javaAttributesList
229                                 .getLength(); javaAttributeIndex++) {
230                                 Node jaElement = javaAttributesList.item(javaAttributeIndex);
231                                 NodeList xmlElementList = jaElement.getChildNodes();
232                                 for (int xmlElementIndex = 0; xmlElementIndex < xmlElementList
233                                     .getLength(); xmlElementIndex++) {
234                                     if (javaAttributeElement != null) {
235                                         Node curElem = xmlElementList.item(xmlElementIndex);
236                                         if (curElem != null) {
237                                             javaAttributeElement
238                                                 .appendChild(curElem.cloneNode(true));
239                                         }
240                                     }
241                                 }
242                             }
243                         }
244
245                     }
246                 }
247                 javaTypesContainer.appendChild(copyOfFirstElement);
248             } else if (listOfNodes.size() == 1) {
249                 javaTypesContainer.appendChild(combinedDoc.importNode(listOfNodes.get(0), true));
250             }
251         }
252     }
253
254     /**
255      * Gets the DynamicJAXBContext for the given version
256      *
257      * @param v the schema version
258      * @return DynamicJAXBContext
259      */
260     public DynamicJAXBContext getContextForVersion(SchemaVersion v) {
261         return versionContextMap.get(v);
262     }
263
264     /**
265      * Determines if the given version contains the given node type
266      *
267      * @param nodeType - node type to check, must be in lower hyphen form (ie "type-name")
268      * @param v - schema version to check against
269      * @return true if node type is contained in the given version
270      */
271     public boolean hasNodeType(String nodeType, SchemaVersion v) {
272         return typesPerVersion.get(v).contains(nodeType);
273     }
274
275     public Set<String> getObjectsInVersion(SchemaVersion v) {
276         return typesPerVersion.get(v);
277     }
278
279     public Document getSchema(SchemaVersion v) {
280         return schemaPerVersion.get(v);
281     }
282
283     private InputStream getShell(SchemaVersion v) {
284         String source = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
285             + "<xml-bindings xmlns=\"http://www.eclipse.org/eclipselink/xsds/persistence/oxm\" package-name=\"inventory.aai.onap.org."
286             + v.toString().toLowerCase() + "\" xml-mapping-metadata-complete=\"true\">\n"
287             + "  <xml-schema element-form-default=\"QUALIFIED\">\n"
288             + "    <xml-ns namespace-uri=\"http://org.onap.aai.inventory/"
289             + v.toString().toLowerCase() + "\" />\n" + "  </xml-schema>\n" + "  <java-types>\n"
290             + "  </java-types>\n" + "</xml-bindings>";
291         return new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8));
292     }
293
294     public SchemaVersion getVersionFromClassName(String classname) {
295         Matcher m = classNamePattern.matcher(classname);
296         String version = null;
297         if (m.find()) {
298             version = m.group(1);
299             return new SchemaVersion(version);
300         } else {
301             return translator.getSchemaVersions().getDefaultVersion();
302         }
303     }
304 }