Skip to content

Commit 28258d5

Browse files
Adding an XML catalog file and corresponding Java unit tests to validate the XML catalog content match local XSD file content
Signed-off-by: Nicolas-Peiffer <102670102+Nicolas-Peiffer@users.noreply.github.com> Refactor the code, fix indentation Signed-off-by: Nicolas-Peiffer <102670102+Nicolas-Peiffer@users.noreply.github.com> surround the asserEquals with the dynamicTests.add(dynamicTest( Signed-off-by: Nicolas-Peiffer <102670102+Nicolas-Peiffer@users.noreply.github.com>
1 parent 491e813 commit 28258d5

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed

schema/xmlcatalog.xml

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
CycloneDX Software Bill-of-Material (SBoM) Specification
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<!--
18+
This XML catalog provides mappings for CycloneDX schemas.
19+
The catalog maps schema URLs to local XSD files to facilitate schema
20+
validation without needing internet access.
21+
Namespace: urn:oasis:names:tc:entity:xmlns:xml:catalog
22+
-->
23+
<!-- to prevent unintendedn notwork access, we do not set a DTD/XSD in this XML -->
24+
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
25+
26+
<!-- SPDX BOM Schema -->
27+
<uri name="http://cyclonedx.org/schema/spdx" uri="spdx.xsd"/>
28+
29+
<!-- CycloneDX BOM Schemas -->
30+
<uri name="http://cyclonedx.org/schema/bom/1.0" uri="bom-1.0.xsd"/>
31+
<uri name="http://cyclonedx.org/schema/bom/1.1" uri="bom-1.1.xsd"/>
32+
<uri name="http://cyclonedx.org/schema/bom/1.2" uri="bom-1.2.xsd"/>
33+
<uri name="http://cyclonedx.org/schema/bom/1.3" uri="bom-1.3.xsd"/>
34+
<uri name="http://cyclonedx.org/schema/bom/1.4" uri="bom-1.4.xsd"/>
35+
<uri name="http://cyclonedx.org/schema/bom/1.5" uri="bom-1.5.xsd"/>
36+
<uri name="http://cyclonedx.org/schema/bom/1.6" uri="bom-1.6.xsd"/>
37+
38+
<!-- Placeholder for future schemas, where 1.x is the next CycloneDX Spec Version -->
39+
<!-- <uri name="http://cyclonedx.org/schema/bom/1.x" uri="bom-1.x.xsd"/> -->
40+
41+
</catalog>

tools/pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,13 @@
145145
<version>3.2.5</version>
146146
</plugin>
147147
</plugins>
148+
<testResources>
149+
<testResource>
150+
<directory>${basedir}/../schema</directory>
151+
</testResource>
152+
<testResource>
153+
<directory>src/test/resources/</directory>
154+
</testResource>
155+
</testResources>
148156
</build>
149157
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package org.cyclonedx.schema;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.DynamicTest;
5+
import org.junit.jupiter.api.TestFactory;
6+
7+
import org.w3c.dom.Document;
8+
import org.w3c.dom.Node;
9+
import org.w3c.dom.NodeList;
10+
import org.xml.sax.InputSource;
11+
import org.xml.sax.SAXException;
12+
13+
import javax.xml.namespace.NamespaceContext;
14+
import javax.xml.parsers.DocumentBuilder;
15+
import javax.xml.parsers.DocumentBuilderFactory;
16+
import javax.xml.parsers.ParserConfigurationException;
17+
import javax.xml.xpath.XPath;
18+
import javax.xml.xpath.XPathConstants;
19+
import javax.xml.xpath.XPathExpressionException;
20+
import javax.xml.xpath.XPathFactory;
21+
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.util.ArrayList;
25+
import java.util.Iterator;
26+
import java.util.List;
27+
28+
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
29+
30+
public class XmlCatalogVerificationTest {
31+
32+
/**
33+
* Tests the XML catalog by parsing the xmlcatalog.xml file and verifying if the XML namespaces
34+
* in the referenced XSD schema files match the XML namespaces defined in the xmlcatalog.xml file.
35+
*
36+
* @return a list of dynamic tests for each URI in the xmlcatalog.xml file
37+
* @throws IOException if an I/O error occurs while reading the XML catalog file
38+
* @throws ParserConfigurationException if a parser configuration error occurs
39+
* @throws SAXException if a SAX error occurs while parsing the XML catalog file
40+
* @throws XPathExpressionException if an XPath expression error occurs
41+
*/
42+
@TestFactory
43+
public List<DynamicTest> testXmlCatalog() throws IOException, ParserConfigurationException, SAXException, XPathExpressionException {
44+
// Define the path to the XML catalog file. This is relative to "${basedir}/../schema" in the pom.xml.
45+
String xmlCatalogFilename = "xmlcatalog.xml";
46+
47+
// Load the XML catalog file from the classpath
48+
ClassLoader classLoader = getClass().getClassLoader();
49+
InputStream xmlCatalogStream = classLoader.getResourceAsStream(xmlCatalogFilename);
50+
51+
// Ensure the XML catalog file is found
52+
Assertions.assertNotNull(xmlCatalogStream, "XML catalog file not found");
53+
54+
// Parse the xmlcatalog.xml file
55+
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
56+
factory.setNamespaceAware(true); // Make the factory namespace-aware
57+
DocumentBuilder builder = factory.newDocumentBuilder();
58+
Document xmlCatalogDocument = builder.parse(new InputSource(xmlCatalogStream));
59+
60+
// Get the XML catalog elements
61+
NodeList xmlCatalogElements = xmlCatalogDocument.getDocumentElement().getChildNodes();
62+
63+
// List to hold dynamic tests
64+
List<DynamicTest> dynamicTests = new ArrayList<>();
65+
66+
// Iterate through the XML catalog elements
67+
for (int i = 0; i < xmlCatalogElements.getLength(); i++) {
68+
Node xmlCatalogElement = xmlCatalogElements.item(i);
69+
70+
// Check if the element is a <uri> element, continue if it is not
71+
if (!xmlCatalogElement.getNodeName().equals("uri")) {
72+
continue;
73+
}
74+
75+
// Get the URI name and the local filename of the XSD schema
76+
String uriNameXmlCtlg = xmlCatalogElement.getAttributes().getNamedItem("name").getTextContent();
77+
String xsdLocalFilename = xmlCatalogElement.getAttributes().getNamedItem("uri").getTextContent();
78+
79+
// Load the XSD schema local file from the classpath
80+
InputStream xsdSchemaFileStream = classLoader.getResourceAsStream(xsdLocalFilename);
81+
Assertions.assertNotNull(xsdSchemaFileStream, "The following file is missing: " + xsdLocalFilename);
82+
83+
// Parse the XSD schema local file
84+
DocumentBuilderFactory factoryXsd = DocumentBuilderFactory.newInstance();
85+
factoryXsd.setNamespaceAware(true); // Make the factory namespace-aware
86+
DocumentBuilder builderXsd = factoryXsd.newDocumentBuilder();
87+
Document xsdDocument = builderXsd.parse(new InputSource(xsdSchemaFileStream));
88+
89+
// Create an XPath instance to evaluate the targetNamespace field found in the XSD local schema file
90+
XPath xPath = XPathFactory.newInstance().newXPath();
91+
xPath.setNamespaceContext(new NamespaceContext() {
92+
@Override
93+
public String getNamespaceURI(String prefix) {
94+
// Define the namespace URI for the xs prefix
95+
if ("xs".equals(prefix)) {
96+
return "http://www.w3.org/2001/XMLSchema";
97+
}
98+
return null;
99+
}
100+
101+
@Override
102+
public String getPrefix(String namespaceURI) {
103+
// Define the prefix for the namespace URI
104+
if ("http://www.w3.org/2001/XMLSchema".equals(namespaceURI)) {
105+
return "xs";
106+
}
107+
return null;
108+
}
109+
110+
@Override
111+
public Iterator<String> getPrefixes(String namespaceURI) {
112+
return null;
113+
}
114+
});
115+
116+
// Evaluate the targetNamespace attribute from the XSD document
117+
String targetNamespace = (String) xPath.evaluate("/xs:schema/@targetNamespace", xsdDocument, XPathConstants.STRING);
118+
119+
// Create a dynamic test for each URI
120+
dynamicTests.add(dynamicTest("Testing if URI namespace from the XML catalog: " + uriNameXmlCtlg + " matches the URI namespace from the local XSD file: " + targetNamespace, () -> {
121+
// Assert if the targetNamespace from the XSD file matches the uriNameXmlCtlg from the XML catalog
122+
Assertions.assertEquals(uriNameXmlCtlg, targetNamespace, "The namespace " + uriNameXmlCtlg + " does not match the targetNamespace in file " + xsdLocalFilename);
123+
}));
124+
}
125+
126+
// Return the list of dynamic tests
127+
return dynamicTests;
128+
}
129+
}

0 commit comments

Comments
 (0)