/******************************************************************************* * ALMA - Atacama Large Millimeter Array * Copyright (c) ESO - European Southern Observatory, 2011 * (in the framework of the ALMA collaboration). * All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *******************************************************************************/ package alma.acs.cdbChecker; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import com.sun.xml.internal.xsom.XSComplexType; import com.sun.xml.internal.xsom.XSContentType; import com.sun.xml.internal.xsom.XSElementDecl; import com.sun.xml.internal.xsom.XSModelGroup; import com.sun.xml.internal.xsom.XSParticle; import com.sun.xml.internal.xsom.XSSchema; import com.sun.xml.internal.xsom.XSSchemaSet; import com.sun.xml.internal.xsom.XSTerm; import com.sun.xml.internal.xsom.parser.XSOMParser; /** * Checks for proper inheritance from CharacteristicComponent schema, * see http://jira.alma.cl/browse/COMP-3752. * <p> * For schema parsing we use the <code>com.sun.xml.internal.xsom</code> classes. * If Oracle removes them, we have to resort to some other library, e.g. * com.sun.org.apache.xerces.internal.xs.XSLoader, http://ws.apache.org/commons/XmlSchema/index.html, * http://www.eclipse.org/modeling/mdt/?project=xsd#xsd * <p> * Note that there is a bug in XSOM (checked up to jdk1.7.0_07) that prevents us from first parsing all XSD files * and then validating the baci standards. Thus we parse and validate each schema file separately, * which brings about unnecessary repeated parsing of imported schemas. * The problem is that XSOM does not skip parsing an XSD file when that XSD was already parsed as an imported schema before, * and the other way around. It does however skip an imported XSD that was imported already by a previous schema. * What's broken is the skipping-coordination between imported XSDs and directly parsed XSDs. * The error would be a SAXParseException about "...type... is already defined". * * @author hsommer */ public class BaciSchemaChecker { private final File xsdFile; private final Logger logger; private final XSSchemaSet schemaSet; private XSComplexType baciPropertyBaseType; private XSComplexType baciCharacteristicComponentType; public BaciSchemaChecker(File xsdFile, EntityResolver resolver, ErrorHandler errorHandler, Logger logger) throws SAXException, MalformedURLException { this.xsdFile = xsdFile; this.logger = logger; XSOMParser parser = new XSOMParser(); parser.setErrorHandler(errorHandler); parser.setEntityResolver(resolver); // we use the URL format just so that in debug logs it is the same format // as the xsd system-id given to the schema resolver. URL url = xsdFile.toURI().toURL(); logger.fine("About to parse " + url); parser.parse(url); schemaSet = parser.getResult(); // use this to explore include/import relationships, if needed // Set<SchemaDocument> xsdDocs = parser.getDocuments(); // String msg = "Total parsed XSDs: "; // for (SchemaDocument schemaDocument : xsdDocs) { // msg += schemaDocument.getSystemId() + ", "; // } // System.out.println(msg); // String msg = "Parsed schema file '" + xsdFile.getAbsolutePath() + "' using XSOMParser and got the following schema NSs: "; // if (schemaSet != null) { // for (XSSchema schema : schemaSet.getSchemas()) { // msg += schema.getTargetNamespace() + ", "; // } // } // logger.info(msg); } /** * This method should be used only for unit testing. */ XSSchemaSet getSchemaSet() { return schemaSet; } public static class BaciPropertyLocator { public BaciPropertyLocator(String fileUri, String elementName, String propertyName, String propertyTypeName) { this.fileUri = fileUri; this.elementName = elementName; this.propertyName = propertyName; this.propertyTypeName = propertyTypeName; } public String fileUri; public String elementName; public String propertyName; public String propertyTypeName; public String toString() { return "Property " + elementName + "#" + propertyName + " (type=" + propertyTypeName + ") in " + fileUri; } } public List<BaciPropertyLocator> findBaciPropsOutsideCharacteristicComp() { List<BaciPropertyLocator> ret = new ArrayList<BaciPropertyLocator>(); if (schemaSet == null) { return ret; } final String baciNsUri = "urn:schemas-cosylab-com:BACI:1.0"; XSSchema baciSchema = schemaSet.getSchema(baciNsUri); if (baciSchema != null) { extractBaciTypes(baciSchema); for (XSSchema schema : schemaSet.getSchemas()) { logger.finer("Checking XSD file " + schema.getLocator().getSystemId()); for (XSElementDecl elem : schema.getElementDecls().values()) { logger.finer("Checking XSD element " + elem.getName() + ", NS=" + elem.getTargetNamespace()); if (elem.getType().isComplexType()) { XSComplexType elemType = elem.getType().asComplexType(); XSParticle particle = elemType.getContentType().asParticle(); checkElementsRecursively(elem, particle, ret); } } } } else { logger.finer("No baci types used in " + xsdFile.toURI()); } return ret; } /** * Recursion over elements and complex types, inspired from * http://stackoverflow.com/questions/10320814/how-to-get-max-depth-of-a-xsd-using-xsom-dom-jaxb?rq=1. */ private void checkElementsRecursively(XSElementDecl parentElem, XSParticle xsp, List<BaciPropertyLocator> badProperties) { if (xsp != null) { XSTerm term = xsp.getTerm(); if (term.isElementDecl()) { String elementName = term.asElementDecl().getName(); XSComplexType xscmp = (term.asElementDecl()).getType().asComplexType(); // logger.fine("getElementsRecursively: parent=" + parentElem.getName() + "; elementDecl=" + elementName + "; complexType=" + xscmp); if (xscmp != null) { if (isBaciPropertyType(xscmp)) { if (isCharacteristicComponent(parentElem)) { logger.finer("OK: Baci property " + parentElem.getName() + "#" + elementName); } else { // the bad case: a baci property sits in a parent element that does not derive from CharacteristicComponent String file = parentElem.getLocator().getSystemId(); String typeName = ( xscmp.isGlobal() ? xscmp.getName() : xscmp.getBaseType().getName() ); BaciPropertyLocator propLoc = new BaciPropertyLocator(file, parentElem.getName(), elementName, typeName); badProperties.add(propLoc); } } else { XSContentType xscont = xscmp.getContentType(); XSParticle particle = xscont.asParticle(); checkElementsRecursively(term.asElementDecl(), particle, badProperties); } } } else if (term.isModelGroup()) { XSModelGroup model = term.asModelGroup(); XSParticle[] parr = model.getChildren(); for (XSParticle partemp : parr) { checkElementsRecursively(parentElem, partemp, badProperties); } } } } /** * Extract those types from BACI.xsd that will later be used in the validation. * Must be called at least once before {@link #isBaciPropertyType(XSComplexType)} can be called. */ void extractBaciTypes(XSSchema baciSchema) { if (baciPropertyBaseType == null) { baciPropertyBaseType = baciSchema.getComplexType("Property"); } if (baciCharacteristicComponentType == null) { baciCharacteristicComponentType = baciSchema.getComplexType("CharacteristicComponent"); } } boolean isBaciPropertyType(XSComplexType type) { if (baciPropertyBaseType != null) { return ( type.isDerivedFrom(baciPropertyBaseType) ); } else { throw new IllegalStateException("Must first find the 'Property' complex type (see field #baciPropertyBaseType)!"); } } boolean isCharacteristicComponent(XSElementDecl parentElem) { if (baciCharacteristicComponentType != null) { XSComplexType xscmp = parentElem.getType().asComplexType(); return ( xscmp.isDerivedFrom(baciCharacteristicComponentType) ); } else { throw new IllegalStateException("Must first find the 'Property' complex type (see field #baciCharacteristicComponentType)!"); } } }