package org.jboss.windup.rules.apps.xml.xml;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.xml.XMLConstants;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DefaultHandler2;
/**
* This implements a SAX handler that maintains a list of validation errors for use by {@link ValidateXmlFilesRuleProvider}.
*
* @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
*/
class ValidateXmlHandler extends DefaultHandler2
{
private static final String XMLSCHEMA_ATTRIBUTE_NAME = "schemaLocation";
private static final String NO_NAMESPACE_XMLSCHEMA_ATTRIBUTE_NAME = "noNamespaceSchemaLocation";
private final List<SAXParseException> parseExceptions = new ArrayList<>();
private final List<String> xsdURLs = new ArrayList<>();
private final EnhancedEntityResolver2 entityResolver;
private boolean firstElementFound = false;
private Locator locator;
private boolean onlineMode;
public ValidateXmlHandler(boolean onlineMode)
{
this.onlineMode = onlineMode;
this.entityResolver = new EnhancedEntityResolver2(onlineMode);
}
/**
* Contains a list of XSD urls found in this document.
*/
public List<String> getXsdURLs()
{
return xsdURLs;
}
/**
* Contains a list of parse failures in the document.
*/
public List<SAXParseException> getParseExceptions()
{
return parseExceptions;
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException
{
return entityResolver.resolveEntity(publicId, systemId);
}
@Override
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException
{
return entityResolver.resolveEntity(name, publicId, baseURI, systemId);
}
@Override
public void setDocumentLocator(Locator locator)
{
this.locator = locator;
}
static boolean isNetworkUrl(String url)
{
return url != null && !url.startsWith("file");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
if (!firstElementFound)
{
firstElementFound = true;
try
{
xsdURLs.addAll(getXSDLocations(attributes));
// validate the xsd urls
for (String xsdUrl : xsdURLs)
{
try
{
if (!onlineMode && isNetworkUrl(xsdUrl))
{
continue;
}
URLConnection urlConnection = new URL(xsdUrl).openConnection();
urlConnection.setConnectTimeout(10000);
urlConnection.setReadTimeout(10000);
// this will throw if invalid
InputStream inputStream = urlConnection.getInputStream();
}
catch (IOException e)
{
parseExceptions.add(new SAXParseException(e.getMessage(), locator, e));
}
}
}
catch (InvalidXSDURLException e)
{
parseExceptions.add(new SAXParseException(e.getMessage(), locator, e));
}
}
super.startElement(uri, localName, qName, attributes);
}
@Override
public void warning(SAXParseException e) throws SAXException
{
super.warning(e);
}
@Override
public void error(SAXParseException e) throws SAXException
{
parseExceptions.add(e);
}
@Override
public void fatalError(SAXParseException e) throws SAXException
{
parseExceptions.add(e);
}
private List<String> getXSDLocations(Attributes attributes) throws InvalidXSDURLException
{
List<String> xsdUrls = new ArrayList<>();
if (attributes == null)
{
return xsdUrls;
}
String xsdSchemaAttr = attributes.getValue(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
XMLSCHEMA_ATTRIBUTE_NAME);
if (StringUtils.isNotBlank(xsdSchemaAttr))
{
String[] splittedXslSchemaAttr = xsdSchemaAttr.split("\\s");
for (int i = 1; i < splittedXslSchemaAttr.length; i += 2)
{
String urlStr = splittedXslSchemaAttr[i];
xsdUrls.add(urlStr);
}
}
String noNamespaceSchema = attributes.getValue(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
NO_NAMESPACE_XMLSCHEMA_ATTRIBUTE_NAME);
if (StringUtils.isNotBlank(noNamespaceSchema))
{
xsdUrls.add(noNamespaceSchema);
}
return xsdUrls;
}
}