/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.tools.ant.taskdefs.optional;

import java.io.File;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.XmlConstants;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;

Validate XML Schema documents. This task validates XML schema documents. It requires an XML parser that handles the relevant SAX, Xerces or JAXP options. To resolve remote references, Ant may need its proxy set up, using the setproxy task. Hands off most of the work to its parent, XMLValidateTask
Since:Ant1.7
/** * Validate XML Schema documents. * This task validates XML schema documents. It requires an XML parser * that handles the relevant SAX, Xerces or JAXP options. * * To resolve remote references, Ant may need its proxy set up, using the * setproxy task. * * Hands off most of the work to its parent, {@link XMLValidateTask} * @since Ant1.7 */
public class SchemaValidate extends XMLValidateTask { // Error strings
SAX1 not supported
/** SAX1 not supported */
public static final String ERROR_SAX_1 = "SAX1 parsers are not supported";
schema features not supported
/** schema features not supported */
public static final String ERROR_NO_XSD_SUPPORT = "Parser does not support Xerces or JAXP schema features";
too many default schemas
/** too many default schemas */
public static final String ERROR_TOO_MANY_DEFAULT_SCHEMAS = "Only one of defaultSchemaFile and defaultSchemaURL allowed";
unable to create parser
/** unable to create parser */
public static final String ERROR_PARSER_CREATION_FAILURE = "Could not create parser";
adding schema
/** adding schema */
public static final String MESSAGE_ADDING_SCHEMA = "Adding schema ";
Duplicate declaration of schema
/** Duplicate declaration of schema */
public static final String ERROR_DUPLICATE_SCHEMA = "Duplicate declaration of schema ";
map of all declared schemas; we catch and complain about redefinitions
/** map of all declared schemas; we catch and complain about redefinitions */
private Map<String, SchemaLocation> schemaLocations = new HashMap<>();
full checking of a schema
/** full checking of a schema */
private boolean fullChecking = true;
flag to disable DTD support. Best left enabled.
/** * flag to disable DTD support. Best left enabled. */
private boolean disableDTD = false;
default URL for nonamespace schemas
/** * default URL for nonamespace schemas */
private SchemaLocation anonymousSchema;
Called by the project to let the task initialize properly. The default implementation is a no-op.
Throws:
  • BuildException – if something goes wrong with the build
/** * Called by the project to let the task initialize properly. The default * implementation is a no-op. * * @throws BuildException if something goes wrong with the build */
@Override public void init() throws BuildException { super.init(); //validating setLenient(false); }
Turn on XSD support in Xerces.
Returns:true on success, false on failure
/** * Turn on XSD support in Xerces. * @return true on success, false on failure */
public boolean enableXercesSchemaValidation() { try { setFeature(XmlConstants.FEATURE_XSD, true); //set the schema source for the doc setNoNamespaceSchemaProperty(XmlConstants.PROPERTY_NO_NAMESPACE_SCHEMA_LOCATION); } catch (BuildException e) { log(e.toString(), Project.MSG_VERBOSE); return false; } return true; }
set nonamespace handling up for xerces or other parsers
Params:
  • property – name of the property to set
/** * set nonamespace handling up for xerces or other parsers * @param property name of the property to set */
private void setNoNamespaceSchemaProperty(String property) { String anonSchema = getNoNamespaceSchemaURL(); if (anonSchema != null) { setProperty(property, anonSchema); } }
Set schema attributes in a JAXP 1.2 engine.
See Also:
Returns:true on success, false on failure
/** * Set schema attributes in a JAXP 1.2 engine. * @see <A href="https://java.sun.com/xml/jaxp/change-requests-11.html"> * JAXP 1.2 Approved CHANGES</A> * @return true on success, false on failure */
public boolean enableJAXP12SchemaValidation() { try { //enable XSD setProperty(XmlConstants.FEATURE_JAXP12_SCHEMA_LANGUAGE, XmlConstants.URI_XSD); //set the schema source for the doc setNoNamespaceSchemaProperty(XmlConstants.FEATURE_JAXP12_SCHEMA_SOURCE); } catch (BuildException e) { log(e.toString(), Project.MSG_VERBOSE); return false; } return true; }
add the schema
Params:
  • location – the schema location.
Throws:
  • BuildException – if there is no namespace, or if there already is a declaration of this schema with a different value
/** * add the schema * @param location the schema location. * @throws BuildException if there is no namespace, or if there already * is a declaration of this schema with a different value */
public void addConfiguredSchema(SchemaLocation location) { log("adding schema " + location, Project.MSG_DEBUG); location.validateNamespace(); SchemaLocation old = schemaLocations.get(location.getNamespace()); if (old != null && !old.equals(location)) { throw new BuildException(ERROR_DUPLICATE_SCHEMA + location); } schemaLocations.put(location.getNamespace(), location); }
enable full schema checking. Slower but better.
Params:
  • fullChecking – a boolean value.
/** * enable full schema checking. Slower but better. * @param fullChecking a <code>boolean</code> value. */
public void setFullChecking(boolean fullChecking) { this.fullChecking = fullChecking; }
create a schema location to hold the anonymous schema
/** * create a schema location to hold the anonymous * schema */
protected void createAnonymousSchema() { if (anonymousSchema == null) { anonymousSchema = new SchemaLocation(); } anonymousSchema.setNamespace("(no namespace)"); }
identify the URL of the default schema
Params:
  • defaultSchemaURL – the URL of the default schema.
/** * identify the URL of the default schema * @param defaultSchemaURL the URL of the default schema. */
public void setNoNamespaceURL(String defaultSchemaURL) { createAnonymousSchema(); this.anonymousSchema.setUrl(defaultSchemaURL); }
identify a file containing the default schema
Params:
  • defaultSchemaFile – the location of the default schema.
/** * identify a file containing the default schema * @param defaultSchemaFile the location of the default schema. */
public void setNoNamespaceFile(File defaultSchemaFile) { createAnonymousSchema(); this.anonymousSchema.setFile(defaultSchemaFile); }
flag to disable DTD support.
Params:
  • disableDTD – a boolean value.
/** * flag to disable DTD support. * @param disableDTD a <code>boolean</code> value. */
public void setDisableDTD(boolean disableDTD) { this.disableDTD = disableDTD; }
init the parser : load the parser class, and set features if necessary It is only after this that the reader is valid
Throws:
  • BuildException – if something went wrong
/** * init the parser : load the parser class, and set features if necessary It * is only after this that the reader is valid * * @throws BuildException if something went wrong */
@Override protected void initValidator() { super.initValidator(); //validate the parser type if (isSax1Parser()) { throw new BuildException(ERROR_SAX_1); } //enable schema setFeature(XmlConstants.FEATURE_NAMESPACES, true); if (!enableXercesSchemaValidation() && !enableJAXP12SchemaValidation()) { //could not use xerces or jaxp calls throw new BuildException(ERROR_NO_XSD_SUPPORT); } //enable schema checking setFeature(XmlConstants.FEATURE_XSD_FULL_VALIDATION, fullChecking); //turn off DTDs if desired setFeatureIfSupported(XmlConstants.FEATURE_DISALLOW_DTD, disableDTD); //schema declarations go in next addSchemaLocations(); }
Create a reader if the use of the class did not specify another one. The reason to not use JAXPUtils.getXMLReader() was to create our own factory with our own options.
Returns:a default XML parser
/** * Create a reader if the use of the class did not specify another one. * The reason to not use {@link org.apache.tools.ant.util.JAXPUtils#getXMLReader()} was to * create our own factory with our own options. * @return a default XML parser */
@Override protected XMLReader createDefaultReader() { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setValidating(true); factory.setNamespaceAware(true); XMLReader reader = null; try { SAXParser saxParser = factory.newSAXParser(); reader = saxParser.getXMLReader(); } catch (ParserConfigurationException | SAXException e) { throw new BuildException(ERROR_PARSER_CREATION_FAILURE, e); } return reader; }
build a string list of all schema locations, then set the relevant property.
/** * build a string list of all schema locations, then set the relevant * property. */
protected void addSchemaLocations() { if (!schemaLocations.isEmpty()) { String joinedValue = schemaLocations.values().stream() .map(SchemaLocation::getURIandLocation) .peek(tuple -> log("Adding schema " + tuple, Project.MSG_VERBOSE)) .collect(Collectors.joining(" ")); setProperty(XmlConstants.PROPERTY_SCHEMA_LOCATION, joinedValue); } }
get the URL of the no namespace schema
Returns:the schema URL
/** * get the URL of the no namespace schema * @return the schema URL */
protected String getNoNamespaceSchemaURL() { return anonymousSchema == null ? null : anonymousSchema.getSchemaLocationURL(); }
set a feature if it is supported, log at verbose level if not
Params:
  • feature – the feature.
  • value – a boolean value.
/** * set a feature if it is supported, log at verbose level if * not * @param feature the feature. * @param value a <code>boolean</code> value. */
protected void setFeatureIfSupported(String feature, boolean value) { try { getXmlReader().setFeature(feature, value); } catch (SAXNotRecognizedException e) { log("Not recognized: " + feature, Project.MSG_VERBOSE); } catch (SAXNotSupportedException e) { log("Not supported: " + feature, Project.MSG_VERBOSE); } }
handler called on successful file validation.
Params:
  • fileProcessed – number of files processed.
/** * handler called on successful file validation. * * @param fileProcessed number of files processed. */
@Override protected void onSuccessfulValidation(int fileProcessed) { log(fileProcessed + MESSAGE_FILES_VALIDATED, Project.MSG_VERBOSE); }
representation of a schema location. This is a URI plus either a file or a url
/** * representation of a schema location. This is a URI plus either a file or * a url */
public static class SchemaLocation { private String namespace; private File file; private String url;
No namespace URI
/** No namespace URI */
public static final String ERROR_NO_URI = "No namespace URI";
Both URL and File were given for schema
/** Both URL and File were given for schema */
public static final String ERROR_TWO_LOCATIONS = "Both URL and File were given for schema ";
File not found
/** File not found */
public static final String ERROR_NO_FILE = "File not found: ";
Cannot make URL
/** Cannot make URL */
public static final String ERROR_NO_URL_REPRESENTATION = "Cannot make a URL of ";
No location provided
/** No location provided */
public static final String ERROR_NO_LOCATION = "No file or URL supplied for the schema ";
Get the namespace.
Returns:the namespace.
/** * Get the namespace. * @return the namespace. */
public String getNamespace() { return namespace; }
set the namespace of this schema. Any URI
Params:
  • namespace – the namespace to use.
/** * set the namespace of this schema. Any URI * @param namespace the namespace to use. */
public void setNamespace(String namespace) { this.namespace = namespace; }
Get the file.
Returns:the file containing the schema.
/** * Get the file. * @return the file containing the schema. */
public File getFile() { return file; }
identify a file that contains this namespace's schema. The file must exist.
Params:
  • file – the file contains the schema.
/** * identify a file that contains this namespace's schema. * The file must exist. * @param file the file contains the schema. */
public void setFile(File file) { this.file = file; }
The URL containing the schema.
Returns:the URL string.
/** * The URL containing the schema. * @return the URL string. */
public String getUrl() { return url; }
identify a URL that hosts the schema.
Params:
  • url – the URL string.
/** * identify a URL that hosts the schema. * @param url the URL string. */
public void setUrl(String url) { this.url = url; }
get the URL of the schema
Throws:
Returns:a URL to the schema
/** * get the URL of the schema * @return a URL to the schema * @throws BuildException if not */
public String getSchemaLocationURL() { boolean hasFile = file != null; boolean hasURL = isSet(url); //error if both are empty, or both are set if (!hasFile && !hasURL) { throw new BuildException(ERROR_NO_LOCATION + namespace); } if (hasFile && hasURL) { throw new BuildException(ERROR_TWO_LOCATIONS + namespace); } String schema = url; if (hasFile) { if (!file.exists()) { throw new BuildException(ERROR_NO_FILE + file); } try { schema = FileUtils.getFileUtils().getFileURL(file).toString(); } catch (MalformedURLException e) { //this is almost implausible, but required handling throw new BuildException(ERROR_NO_URL_REPRESENTATION + file, e); } } return schema; }
validate the fields then create a "uri location" string
Throws:
Returns:string of uri and location
/** * validate the fields then create a "uri location" string * * @return string of uri and location * @throws BuildException if there is an error. */
public String getURIandLocation() throws BuildException { validateNamespace(); return namespace + ' ' + getSchemaLocationURL(); }
assert that a namespace is valid
Throws:
  • BuildException – if not
/** * assert that a namespace is valid * @throws BuildException if not */
public void validateNamespace() { if (!isSet(getNamespace())) { throw new BuildException(ERROR_NO_URI); } }
check that a property is set
Params:
  • property – string to check
Returns:true if it is not null or empty
/** * check that a property is set * @param property string to check * @return true if it is not null or empty */
private boolean isSet(String property) { return property != null && !property.isEmpty(); }
equality test checks namespace, location and filename. All must match,
Params:
  • o – object to compare against
Returns:true iff the objects are considered equal in value
/** * equality test checks namespace, location and filename. All must match, * @param o object to compare against * @return true iff the objects are considered equal in value */
@Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof SchemaLocation)) { return false; } final SchemaLocation schemaLocation = (SchemaLocation) o; return (file == null ? schemaLocation.file == null : file.equals(schemaLocation.file)) && (namespace == null ? schemaLocation.namespace == null : namespace.equals(schemaLocation.namespace)) && (url == null ? schemaLocation.url == null : url.equals(schemaLocation.url)); }
Generate a hashcode depending on the namespace, url and file name.
Returns:the hashcode.
/** * Generate a hashcode depending on the namespace, url and file name. * @return the hashcode. */
@Override public int hashCode() { int result; // CheckStyle:MagicNumber OFF result = (namespace == null ? 0 : namespace.hashCode()); result = 29 * result + (file == null ? 0 : file.hashCode()); result = 29 * result + (url == null ? 0 : url.hashCode()); // CheckStyle:MagicNumber OFF return result; }
Returns a string representation of the object for error messages and the like
Returns:a string representation of the object.
/** * Returns a string representation of the object for error messages * and the like * @return a string representation of the object. */
@Override public String toString() { return (namespace == null ? "(anonymous)" : namespace) + (url == null ? "" : " " + url) + (file == null ? "" : " " + file.getAbsolutePath()); } } }