/* * _____________________________________________________________________________ * * INAF - OATS National Institute for Astrophysics - Astronomical Observatory of * Trieste INAF - IA2 Italian Center for Astronomical Archives * _____________________________________________________________________________ * * Copyright (C) 2017 Istituto Nazionale di Astrofisica * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License Version 3 as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package it.inaf.ia2.tsm.model; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.bind.JAXB; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; /** * Class used to parse TASMAN XML configuration files used to defining schemata * structure and convert them into instances of {@link SchemaModel}. * * @author Sonia Zorba {@literal } */ public class XMLModelsLoader { private static final Logger LOG = LoggerFactory.getLogger(XMLModelsLoader.class); private final String[] xmlModelFileNames; private final File[] xmlModelExternalFiles; // key: doc version private final Map documents; private final Map inheritanceGraph; private final Map> inheritanceLevels; public XMLModelsLoader(String[] xmlModelFileNames) { this(xmlModelFileNames, new File[]{}); } public XMLModelsLoader(String[] xmlModelFileNames, File[] xmlModelExternalFiles) { this.xmlModelFileNames = xmlModelFileNames; this.xmlModelExternalFiles = xmlModelExternalFiles; this.documents = new HashMap<>(); inheritanceGraph = new HashMap<>(); inheritanceLevels = new HashMap<>(); } /** * Loads the XML files and obtains schemata models. * * @return a {@code Map} having versions as keys and {@link SchemaModel}s * as values. */ public Map load() { return load(XMLModelsLoader.class.getClassLoader()); } /** * This variant is used for testing (specifying a {@code ClassLoader} it is * possible to use test resource files instead of real application files). */ public Map load(ClassLoader classLoader) { try { loadDocumentsMap(classLoader); // It is necessary to apply inheritance in specific level order. (Example: // if v2 extends from v1 and v1 extends from v0 it is necessary to merge v1 // with v0 first and then merge the result with v2). // So this inheritance data structures are built. buildInheritanceGraph(); buildInheritanceLevels(); for (int i = 0; i < inheritanceLevels.size(); i++) { for (Document doc : inheritanceLevels.get(i)) { applyInheritance(doc, null); } } return getModelsMap(); } catch (IOException | ParserConfigurationException | SAXException e) { throw new ModelLoadingException(e); } } private void loadDocumentsMap(ClassLoader classLoader) throws IOException, SAXException, ParserConfigurationException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dbf.newDocumentBuilder(); for (String xmlModelFileName : xmlModelFileNames) { try (InputStream in = classLoader.getResourceAsStream(xmlModelFileName)) { loadFile(builder, in); } } for (File xmlModelExternalFile : xmlModelExternalFiles) { try (FileInputStream fis = new FileInputStream(xmlModelExternalFile)) { loadFile(builder, fis); } } } private void loadFile(DocumentBuilder builder, InputStream in) throws IOException, SAXException { Document doc = builder.parse(in); Element root = doc.getDocumentElement(); String version = root.getAttribute("version"); // TODO: XML Model validation // Documents loaded in a single XMLModelsLoader instance must // have different versions. assert documents.get(version) == null; documents.put(version, doc); } private void buildInheritanceGraph() { for (Document document : documents.values()) { Element root = document.getDocumentElement(); String version = root.getAttribute("version"); String extendsFrom = root.getAttribute("extends"); if (extendsFrom == null || extendsFrom.isEmpty()) { extendsFrom = null; } inheritanceGraph.put(version, extendsFrom); } } private void buildInheritanceLevels() { for (Document document : documents.values()) { String version = document.getDocumentElement().getAttribute("version"); int level = getInheritanceLevel(version, 0); List levelDocs = inheritanceLevels.get(level); if (levelDocs == null) { levelDocs = new ArrayList<>(); inheritanceLevels.put(level, levelDocs); } levelDocs.add(document); } } private int getInheritanceLevel(String version, int count) { String inheritsFrom = inheritanceGraph.get(version); if (inheritsFrom == null) { return count; } else { return getInheritanceLevel(inheritsFrom, count + 1); } } private void applyInheritance(Document doc, Set applied) throws ParserConfigurationException { String version = doc.getDocumentElement().getAttribute("version"); String inheritFrom = inheritanceGraph.get(version); if (inheritFrom != null) { if (applied == null) { applied = new HashSet<>(); } if (!applied.contains(inheritFrom)) { Document inheritedDocument = documents.get(inheritFrom); XMLMerger merger = new XMLMerger(inheritedDocument, doc); Document merged = merger.getMergedDocument(); documents.put(version, merged); applied.add(inheritFrom); applyInheritance(merged, applied); } } } /** * Public exposed only for testing. */ public Map getDocuments() { return documents; } private Map getModelsMap() { Map models = new HashMap<>(); for (Map.Entry entry : documents.entrySet()) { DOMSource source = new DOMSource(entry.getValue().getDocumentElement()); SchemaModel model = JAXB.unmarshal(source, SchemaModel.class); models.put(entry.getKey(), model); } return models; } }