Author: desruisseaux Date: Sun Jan 28 16:43:35 2018 New Revision: 1822469 URL: http://svn.apache.org/viewvc?rev=1822469&view=rev Log: Moved SchemaVerifier in its own package. Keep package-info data in an internal class.
Added: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java - copied, changed from r1822468, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaVerifier.java sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaException.java - copied, changed from r1822468, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaException.java Removed: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaException.java sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaVerifier.java Copied: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java (from r1822468, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaVerifier.java) URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java?p2=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java&p1=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaVerifier.java&r1=1822468&r2=1822469&rev=1822469&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaVerifier.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java [UTF-8] Sun Jan 28 16:43:35 2018 @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sis.metadata.iso; +package org.apache.sis.test.xml; import java.io.IOException; import java.io.InputStream; @@ -32,6 +32,7 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collections; import javax.xml.XMLConstants; +import javax.xml.bind.annotation.XmlNs; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.XmlSchema; import javax.xml.bind.annotation.XmlElement; @@ -47,8 +48,8 @@ import org.apache.sis.internal.jaxb.Lega /** - * Compares the {@link XmlElement} against the ISO 19115 schema. This test requires a connection - * to <a href="http://standards.iso.org/iso/19115/-3/">http://standards.iso.org/iso/19115/-3/</a>. + * Compares JAXB annotations against the ISO 19115 schema. This test requires a connection to + * <a href="http://standards.iso.org/iso/19115/-3/">http://standards.iso.org/iso/19115/-3/</a>. * All classes in a given directory are scanned. * * <div class="section">Limitations</div> @@ -68,7 +69,7 @@ import org.apache.sis.internal.jaxb.Lega * @since 1.0 * @module */ -public final strictfp class SchemaVerifier { +public final strictfp class SchemaCompliance { /** * The root of ISO schemas. May be replaced by {@link #schemaRootDirectory} if a local copy * is available for faster tests. @@ -183,19 +184,25 @@ public final strictfp class SchemaVerifi * If non-null, this is one of the values in the {@link #typeDefinitions} map. * By convention, the {@code null} key is associated to information about the class. */ - private transient Map<String,Info> currentProperties; + private Map<String,Info> currentProperties; /** * A single property type under examination, or {@code null} if none. * If non-null, this is a value ending with the {@value #PROPERTY_TYPE_SUFFIX} suffix. */ - private transient String currentPropertyType; + private String currentPropertyType; /** * Namespace of the type or properties being defined. * This is specified by {@code <xs:schema targetNamespace="(…)">}. */ - private transient String targetNamespace; + private String targetNamespace; + + /** + * The namespaces associated to prefixes, as declared by JAXB {@link XmlNs} annotations. + * Used for verifying that no prefix is defined twice for different namespaces. + */ + private final Map<String,String> allXmlNS; /** * Creates a new verifier for classes under the given directory. The given directory shall be the @@ -207,7 +214,7 @@ public final strictfp class SchemaVerifi * @param schemaRootDirectory if the computer contains a local copy of ISO schemas, path to that directory. * Otherwise {@code null}. This is only for making tests faster. */ - public SchemaVerifier(final Path classRootDirectory, final Path schemaRootDirectory) { + public SchemaCompliance(final Path classRootDirectory, final Path schemaRootDirectory) { this.classRootDirectory = classRootDirectory; this.schemaRootDirectory = schemaRootDirectory; factory = DocumentBuilderFactory.newInstance(); @@ -215,6 +222,7 @@ public final strictfp class SchemaVerifi buffer = new StringBuilder(100); typeDefinitions = new HashMap<>(); schemaLocations = new ArrayDeque<>(); + allXmlNS = new HashMap<>(); } /** @@ -227,8 +235,13 @@ public final strictfp class SchemaVerifi * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file. * @throws ParserConfigurationException if {@link javax.xml.parsers.DocumentBuilder} can not be created. * @throws SAXException if an error occurred while parsing the XSD file. + * @throws SchemaException if a XSD file does not comply with our assumptions, + * or a JAXB annotation failed a compliance check. */ - public void verify(final Path directory) throws IOException, ClassNotFoundException, ParserConfigurationException, SAXException { + public void verify(final Path directory) + throws IOException, ClassNotFoundException, ParserConfigurationException, SAXException, SchemaException + { + PackageVerifier verifier = null; try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) { for (Path path : stream) { final String filename = path.getFileName().toString(); @@ -240,7 +253,11 @@ public final strictfp class SchemaVerifi buffer.setLength(0); buffer.append(path.toString()).setLength(buffer.length() - 6); // Remove ".class" suffix. StringBuilders.replace(buffer, '/', '.'); - verify(Class.forName(buffer.toString())); + final Class<?> c = Class.forName(buffer.toString()); + if (verifier == null) { + verifier = new PackageVerifier(c.getPackage()); + } + verifier.verify(c); } } } @@ -256,7 +273,9 @@ public final strictfp class SchemaVerifi * * @param location URL to the XSD file to load. */ - private void loadSchema(final String location) throws IOException, ParserConfigurationException, SAXException { + private void loadSchema(final String location) + throws IOException, ParserConfigurationException, SAXException, SchemaException + { if (!schemaLocations.contains(location)) { final Document doc; try (final InputStream in = new URL(location).openStream()) { @@ -272,7 +291,9 @@ public final strictfp class SchemaVerifi * for scanning children, until we reach sub-nodes about properties (in which case we continue * with {@link #storePropertyDefinition(Node)}). */ - private void storeClassDefinition(final Node node) throws IOException, ParserConfigurationException, SAXException { + private void storeClassDefinition(final Node node) + throws IOException, ParserConfigurationException, SAXException, SchemaException + { if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) { switch (node.getNodeName()) { case "schema": { @@ -344,7 +365,7 @@ public final strictfp class SchemaVerifi * <xs:element name="(…)" type="(…)_PropertyType" minOccurs="(…)" maxOccurs="(…)"> * } */ - private void storePropertyDefinition(final Node node) throws SAXException { + private void storePropertyDefinition(final Node node) throws SchemaException { if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) { if ("element".equals(node.getNodeName())) { addProperty(getMandatoryAttribute(node, "name").intern(), @@ -366,7 +387,7 @@ public final strictfp class SchemaVerifi * <xs:element ref="(…)"> * } */ - private void verifyPropertyType(final Node node) throws SAXException { + private void verifyPropertyType(final Node node) throws SchemaException { if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) { if ("element".equals(node.getNodeName())) { verifyNamingConvention(schemaLocations.getLast(), @@ -445,7 +466,7 @@ public final strictfp class SchemaVerifi * Returns the attribute of the given name in the given node, * or throws an exception if the attribute is not present. */ - private static String getMandatoryAttribute(final Node node, final String name) throws SAXException { + private static String getMandatoryAttribute(final Node node, final String name) throws SchemaException { final NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { final Node attr = attributes.getNamedItem(name); @@ -460,84 +481,137 @@ public final strictfp class SchemaVerifi } /** - * Verifies the {@link XmlElement} annotation on the given class. - * This method downloads and parses the XSD files (the schemas) as needed. - * - * @param type the class on which to verify annotations. + * Verify JAXB annotations in a single package. + * A new instance of this class must be created for each Java package to be verified. */ - private void verify(final Class<?> type) throws IOException, ParserConfigurationException, SAXException { - /* - * Get information from the @XmlSchema annotation in package-info. - * Opportunistically verify consistency with naming convention. + private final class PackageVerifier { + /** + * The default namespace to use if a class does not define explicitely a namespace. */ - final Package pkg = type.getPackage(); - String namespace = ""; - if (pkg != null) { - final XmlSchema schema = pkg.getAnnotation(XmlSchema.class); - if (schema != null) { - namespace = schema.namespace(); - String location = schema.location(); - if (!XmlSchema.NO_LOCATION.equals(location)) { - if (location.startsWith(SCHEMA_ROOT_DIRECTORY)) { - if (!location.startsWith(schema.namespace())) { - throw new SchemaException("XML schema location inconsistent with namespace in " + pkg.getName() + " package."); + private final String defaultNS; + + /** + * Whether a namespace is actually used of not. + * We use this map for identifying unnecessary prefix declarations. + */ + private final Map<String,Boolean> namespaceIsUsed; + + /** + * Creates a new verifier for the given package. + */ + PackageVerifier(final Package pkg) + throws IOException, ParserConfigurationException, SAXException, SchemaException + { + namespaceIsUsed = new HashMap<>(); + String namespace = ""; + if (pkg != null) { + final XmlSchema schema = pkg.getAnnotation(XmlSchema.class); + if (schema != null) { + namespace = schema.namespace(); + String location = schema.location(); + if (!XmlSchema.NO_LOCATION.equals(location)) { + if (location.startsWith(SCHEMA_ROOT_DIRECTORY)) { + if (!location.startsWith(schema.namespace())) { + throw new SchemaException("XML schema location inconsistent with namespace in " + pkg.getName() + " package."); + } + if (schemaRootDirectory != null) { + location = schemaRootDirectory.resolve(location.substring(SCHEMA_ROOT_DIRECTORY.length())).toUri().toString(); + } + } + loadSchema(location); + } + for (final XmlNs xmlns : schema.xmlns()) { + final String pr = xmlns.prefix(); + final String ns = xmlns.namespaceURI(); + final String cr = allXmlNS.put(pr, ns); + if (cr != null && !cr.equals(ns)) { + throw new SchemaException("Prefix \"" + pr + "\" associated to two different namespaces:\n" + cr + '\n' + ns); } - if (schemaRootDirectory != null) { - location = schemaRootDirectory.resolve(location.substring(SCHEMA_ROOT_DIRECTORY.length())).toUri().toString(); + if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) { + throw new SchemaException("Duplicated namespace in " + pkg + ":\n" + ns); } } - loadSchema(location); } } + defaultNS = namespace; } - /* - * Verify @XmlType and @XmlRootElement on the class. First, we verify naming convention - * (type name should be same as root element name with "_Type" suffix appended). Then, - * we verify that the name exists in the schema, and finally we check its namespace. + + /** + * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention + * (type name should be same as root element name with {@value #TYPE_SUFFIX} suffix appended), ensures that + * the name exists in the schema, and checks the namespace. + * + * @param type the class on which to verify annotations. */ - final XmlType xmlType = type.getDeclaredAnnotation(XmlType.class); - final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class); - final String className; // ISO class name (not the same than Java class name). - if (xmlRoot == null && xmlType == null) { - return; - } else { - final String ns; + final void verify(final Class<?> type) + throws IOException, ParserConfigurationException, SAXException, SchemaException + { + final XmlType xmlType = type.getDeclaredAnnotation(XmlType.class); + final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class); + if (xmlRoot == null && xmlType == null) { + return; + } + /* + * Get the type name and namespace from the @XmlType or @XmlRootElement annotations. + * If both of them are present, verify that they are consistent (same namespace and + * same name with "_Type" suffix in @XmlType). If the type name is not declared, we + * assume that it is the same than the class name (this is what Apache SIS 0.8 does + * in its org.apache.sis.internal.jaxb.code package for CodeList adapters). + */ + String namespace; + final String className; // ISO class name (not the same than Java class name). if (xmlRoot != null) { - ns = xmlRoot.namespace(); + namespace = xmlRoot.namespace(); className = xmlRoot.name(); if (xmlType != null) { - if (!ns.equals(xmlType.namespace())) { + if (!namespace.equals(xmlType.namespace())) { throw new SchemaException("Mismatched namespace in @XmlType and @XmlRootElement of " + type); } verifyNamingConvention(type.getName(), className, xmlType.name(), TYPE_SUFFIX); } } else { - ns = xmlType.namespace(); + namespace = xmlType.namespace(); final String name = xmlType.name(); className = name.equals("##default") ? type.getSimpleName() : trim(name, TYPE_SUFFIX); } - if (!ns.equals("##default")) { - namespace = ns; - } else if (ns.equals(namespace)) { + /* + * Verify that the namespace declared on the class is not redundant with the namespace + * declared in the package. Actually redundant namespaces are not wrong, but we try to + * reduce code size. + */ + if (namespace.equals("##default")) { + namespace = defaultNS; + } else if (namespace.equals(defaultNS)) { throw new SchemaException("Redundant namespace declaration in " + type); } - } - final boolean isDeprecated = DEPRECATED_NAMESPACES.contains(namespace); - if (!isDeprecated && type.isAnnotationPresent(Deprecated.class)) { - throw new SchemaException("Unexpected deprecation status of " + type); - } - final Map<String,Info> properties = typeDefinitions.get(className); - if (properties == null) { - if (!isDeprecated) { - throw new SchemaException("Unknown name declared in @XmlRootElement of " + type); - } - } else { - // By convention, null key is associated to class information. - final String expectedNS = properties.get(null).namespace; - if (!namespace.equals(expectedNS)) { - throw new SchemaException(className + " shall be associated to namespace " + expectedNS); + /* + * Verify that the namespace has a prefix associated to it in the package-info file. + */ + if (namespaceIsUsed.put(namespace, Boolean.TRUE) == null) { + throw new SchemaException("Namespace of " + type + " has no prefix in package-info."); + } + /* + * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement + * is already available or not. However properties in other namespaces should not be deprecated. + * Some validations will be disabled for deprecated properties. + */ + final boolean isDeprecated = DEPRECATED_NAMESPACES.contains(namespace); + if (!isDeprecated && type.isAnnotationPresent(Deprecated.class)) { + throw new SchemaException("Unexpected deprecation status of " + type); + } + final Map<String,Info> properties = typeDefinitions.get(className); + if (properties == null) { + if (!isDeprecated) { + throw new SchemaException("Unknown name declared in @XmlRootElement of " + type); + } + } else { + // By convention, null key is associated to class information. + final String expectedNS = properties.get(null).namespace; + if (!namespace.equals(expectedNS)) { + throw new SchemaException(className + " shall be associated to namespace " + expectedNS); + } + // TODO: scan for properties here. } - // TODO: scan for properties here. } } } Copied: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaException.java (from r1822468, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaException.java) URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaException.java?p2=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaException.java&p1=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaException.java&r1=1822468&r2=1822469&rev=1822469&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/SchemaException.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaException.java [UTF-8] Sun Jan 28 16:43:35 2018 @@ -14,14 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sis.metadata.iso; - -import org.xml.sax.SAXException; +package org.apache.sis.test.xml; /** - * Thrown when a {@link SchemaVerifier} failed to load a XSD file because it does not comply - * with expected OGC/ISO conventions. + * Thrown when a {@link SchemaCompliance} failed to load a XSD file because it does not comply + * with expected OGC/ISO conventions, or when a JAXB annotation failed a compliance check. * * @author Martin Desruisseaux (Geomatys) * @version 1.0 @@ -29,13 +27,13 @@ import org.xml.sax.SAXException; * @module */ @SuppressWarnings("serial") -final class SchemaException extends SAXException { +public final class SchemaException extends Exception { /** * Creates an exception with the specified details message. * * @param message the detail message. */ - public SchemaException(final String message) { + SchemaException(final String message) { super(message); } }