diff --git a/tools/pom.xml b/tools/pom.xml index befc1048..e2e84939 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -54,8 +54,9 @@ 2.17.0 3.17.0 1.12.0 + 1.5.5 1.4.9 - 10.0.0 + 2.0.16 @@ -98,6 +99,24 @@ + + + + + org.slf4j + slf4j-api + ${lib.slf4j.api} + + + + org.slf4j + slf4j-simple + ${lib.slf4j.api} + + + + + @@ -123,16 +142,22 @@ compile + + com.networknt + json-schema-validator + ${lib.json.schema.validator} + test + org.junit.jupiter - junit-jupiter-engine - 5.7.0 + junit-jupiter-api + 5.11.4 test + - org.cyclonedx - cyclonedx-core-java - ${lib.cyclonedx.core.java.version} + org.slf4j + slf4j-simple test @@ -142,15 +167,15 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.1 + 3.5.2 - ${basedir}/../schema + ${project.basedir}/../schema - src/test/resources/ + src/test/resources diff --git a/tools/src/test/java/org/cyclonedx/schema/BaseSchemaVerificationTest.java b/tools/src/test/java/org/cyclonedx/schema/BaseSchemaVerificationTest.java index a67565ed..31cfc098 100644 --- a/tools/src/test/java/org/cyclonedx/schema/BaseSchemaVerificationTest.java +++ b/tools/src/test/java/org/cyclonedx/schema/BaseSchemaVerificationTest.java @@ -33,17 +33,14 @@ List getAllResources() throws Exception { return files; } - List getResources(final String resourceDirectory) throws Exception { - final List files = new ArrayList<>(); - String dir = resourceDirectory; - if (!resourceDirectory.endsWith("/")) { - dir += "/"; - } - try (InputStream in = this.getClass().getClassLoader().getResourceAsStream(dir)) { + private List getResources(final String resourceDirectory) throws Exception { + final List resources = new ArrayList<>(); + try (InputStream in = this.getClass().getClassLoader().getResourceAsStream(resourceDirectory)) { if (in != null) { - files.addAll(IOUtils.readLines(in, StandardCharsets.UTF_8)); + IOUtils.readLines(in, StandardCharsets.UTF_8) + .forEach(resource -> resources.add(resourceDirectory + resource)); } } - return files; + return resources; } } diff --git a/tools/src/test/java/org/cyclonedx/schema/JsonSchemaVerificationTest.java b/tools/src/test/java/org/cyclonedx/schema/JsonSchemaVerificationTest.java index 1598c0ae..4680aefa 100644 --- a/tools/src/test/java/org/cyclonedx/schema/JsonSchemaVerificationTest.java +++ b/tools/src/test/java/org/cyclonedx/schema/JsonSchemaVerificationTest.java @@ -13,68 +13,127 @@ */ package org.cyclonedx.schema; -import java.io.File; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.networknt.schema.DefaultJsonMetaSchemaFactory; +import com.networknt.schema.DisallowUnknownKeywordFactory; +import com.networknt.schema.JsonMetaSchema; +import com.networknt.schema.JsonMetaSchemaFactory; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.NonValidationKeyword; +import com.networknt.schema.SchemaId; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.resource.ClasspathSchemaLoader; +import com.networknt.schema.resource.DisallowSchemaLoader; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; - -import org.cyclonedx.parsers.JsonParser; -import org.cyclonedx.Version; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; +class JsonSchemaVerificationTest extends BaseSchemaVerificationTest { + + private static final ObjectMapper MAPPER = new JsonMapper(); + + private static final String JSF_NAMESPACE = "http://cyclonedx.org/schema/jsf-0.82.schema.json"; + private static final String SPDX_NAMESPACE = "http://cyclonedx.org/schema/spdx.schema.json"; + + private static final JsonSchema VERSION_12; + private static final JsonSchema VERSION_13; + private static final JsonSchema VERSION_14; + private static final JsonSchema VERSION_15; + private static final JsonSchema VERSION_16; + + static { + JsonMetaSchemaFactory metaSchemaFactory = new DefaultJsonMetaSchemaFactory() { + @Override + public JsonMetaSchema getMetaSchema( + String iri, JsonSchemaFactory schemaFactory, SchemaValidatorsConfig config) { + return addCustomKeywords(super.getMetaSchema(iri, schemaFactory, config)); + } + }; + JsonSchemaFactory factory = JsonSchemaFactory.builder() + .defaultMetaSchemaIri(SchemaId.V7) + .metaSchema(addCustomKeywords(JsonMetaSchema.getV7())) + .metaSchemaFactory(metaSchemaFactory) + .schemaLoaders(b -> b.add(new ClasspathSchemaLoader()).add(DisallowSchemaLoader.getInstance())) + .schemaMappers(b -> b.mapPrefix(SPDX_NAMESPACE, "classpath:spdx.schema.json") + .mapPrefix(JSF_NAMESPACE, "classpath:jsf-0.82.schema.json")) + .build(); + VERSION_12 = factory.getSchema(SchemaLocation.of("classpath:bom-1.2-strict.schema.json")); + VERSION_13 = factory.getSchema(SchemaLocation.of("classpath:bom-1.3-strict.schema.json")); + VERSION_14 = factory.getSchema(SchemaLocation.of("classpath:bom-1.4.schema.json")); + VERSION_15 = factory.getSchema(SchemaLocation.of("classpath:bom-1.5.schema.json")); + VERSION_16 = factory.getSchema(SchemaLocation.of("classpath:bom-1.6.schema.json")); + } -public class JsonSchemaVerificationTest extends BaseSchemaVerificationTest { + private static JsonMetaSchema addCustomKeywords(JsonMetaSchema metaSchema) { + return JsonMetaSchema.builder(metaSchema) + // Non-standard keywords in the CycloneDX schema files. + .keyword(new NonValidationKeyword("deprecated")) + .keyword(new NonValidationKeyword("meta:enum")) + .unknownKeywordFactory(new DisallowUnknownKeywordFactory()) + .build(); + } @TestFactory Collection dynamicTestsWithCollection() throws Exception { - final List files = getAllResources(); + final List resources = getAllResources(); final List dynamicTests = new ArrayList<>(); - for (final String file: files) { - if (file.endsWith(".json")) { - final Version schemaVersion; - if (file.endsWith("-1.2.json")) { - schemaVersion = Version.VERSION_12; - } else if (file.endsWith("-1.3.json")) { - schemaVersion = Version.VERSION_13; - } else if (file.endsWith("-1.4.json")) { - schemaVersion = Version.VERSION_14; - } else if (file.endsWith("-1.5.json")) { - schemaVersion = Version.VERSION_15; - } else if (file.endsWith("-1.6.json")) { - schemaVersion = Version.VERSION_16; - } else { - schemaVersion = null; - } - if (file.startsWith("valid") && schemaVersion != null) { - dynamicTests.add(DynamicTest.dynamicTest(file, () -> assertTrue( - isValidJson(schemaVersion, "/" + schemaVersion.getVersionString() + "/" + file), file))); - } else if (file.startsWith("invalid") && schemaVersion != null) { - dynamicTests.add(DynamicTest.dynamicTest(file, () -> assertFalse( - isValidJson(schemaVersion, "/" + schemaVersion.getVersionString() + "/" + file), file))); + for (final String resource : resources) { + String resourceName = StringUtils.substringAfterLast(resource, "/"); + if (resourceName.endsWith(".json")) { + JsonSchema schema = getSchema(resourceName); + if (schema != null) { + if (resourceName.startsWith("valid")) { + dynamicTests.add(DynamicTest.dynamicTest( + resource, () -> assertTrue(isValid(schema, resource), resource))); + } else if (resourceName.startsWith("invalid")) { + dynamicTests.add(DynamicTest.dynamicTest( + resource, () -> assertFalse(isValid(schema, resource), resource))); + } } } } return dynamicTests; } - private boolean isValidJson(Version version, String resource) throws Exception { - final File file = new File(this.getClass().getResource(resource).getFile()); - final JsonParser parser = new JsonParser(); - return parser.isValid(file, version); - - // Uncomment to provide more detailed validation errors - /* - try { - final String jsonString = FileUtils.readFileToString(file, StandardCharsets.UTF_8); - parser.getJsonSchema(version, true).validate(new JSONObject(jsonString)); - return true; - } catch (ValidationException e) { - e.printStackTrace(); + private boolean isValid(JsonSchema schema, String resource) { + try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource); + JsonParser parser = MAPPER.createParser(input)) { + JsonNode node = parser.readValueAsTree(); + return schema.validate(node).isEmpty(); + } catch (IOException e) { return false; } - */ + } + + private JsonSchema getSchema(String resourceName) { + if (resourceName.endsWith("-1.2.json")) { + return VERSION_12; + } + if (resourceName.endsWith("-1.3.json")) { + return VERSION_13; + } + if (resourceName.endsWith("-1.4.json")) { + return VERSION_14; + } + if (resourceName.endsWith("-1.5.json")) { + return VERSION_15; + } + if (resourceName.endsWith("-1.6.json")) { + return VERSION_16; + } + return null; } } diff --git a/tools/src/test/java/org/cyclonedx/schema/XmlSchemaVerificationTest.java b/tools/src/test/java/org/cyclonedx/schema/XmlSchemaVerificationTest.java index 2d57dd8a..348d9e5b 100644 --- a/tools/src/test/java/org/cyclonedx/schema/XmlSchemaVerificationTest.java +++ b/tools/src/test/java/org/cyclonedx/schema/XmlSchemaVerificationTest.java @@ -13,57 +13,77 @@ */ package org.cyclonedx.schema; -import java.io.File; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.ArrayList; import java.util.Collection; import java.util.List; - -import org.cyclonedx.parsers.XmlParser; -import org.cyclonedx.Version; +import javax.xml.XMLConstants; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; public class XmlSchemaVerificationTest extends BaseSchemaVerificationTest { - @TestFactory + private static final Schema VERSION_10; + private static final Schema VERSION_11; + private static final Schema VERSION_12; + private static final Schema VERSION_13; + private static final Schema VERSION_14; + private static final Schema VERSION_15; + private static final Schema VERSION_16; + + static { + try { + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file"); + ClassLoader cl = XmlSchemaVerificationTest.class.getClassLoader(); + // Override the `schemaLocation` property in the file + factory.setProperty( + "http://apache.org/xml/properties/schema/external-schemaLocation", + "http://cyclonedx.org/schema/spdx spdx.xsd"); + VERSION_10 = factory.newSchema(cl.getResource("bom-1.0.xsd")); + VERSION_11 = factory.newSchema(cl.getResource("bom-1.1.xsd")); + VERSION_12 = factory.newSchema(cl.getResource("bom-1.2.xsd")); + VERSION_13 = factory.newSchema(cl.getResource("bom-1.3.xsd")); + VERSION_14 = factory.newSchema(cl.getResource("bom-1.4.xsd")); + VERSION_15 = factory.newSchema(cl.getResource("bom-1.5.xsd")); + VERSION_16 = factory.newSchema(cl.getResource("bom-1.6.xsd")); + } catch (SAXException e) { + throw new IllegalStateException(e); + } + } + /** * Generates a collection of dynamic tests based on the available XML files. * * @return Collection a collection of dynamic tests * @throws Exception if an error occurs during the generation of the dynamic tests */ + @TestFactory Collection dynamicTestsWithCollection() throws Exception { - final List files = getAllResources(); + final List resources = getAllResources(); final List dynamicTests = new ArrayList<>(); - for (final String file: files) { - if (file.endsWith(".xml")) { - final Version schemaVersion; - if (file.endsWith("-1.0.xml")) { - schemaVersion = Version.VERSION_10; - } else if (file.endsWith("-1.1.xml")) { - schemaVersion = Version.VERSION_11; - } else if (file.endsWith("-1.2.xml")) { - schemaVersion = Version.VERSION_12; - } else if (file.endsWith("-1.3.xml")) { - schemaVersion = Version.VERSION_13; - } else if (file.endsWith("-1.4.xml")) { - schemaVersion = Version.VERSION_14; - } else if (file.endsWith("-1.5.xml")) { - schemaVersion = Version.VERSION_15; - } else if (file.endsWith("-1.6.xml")) { - schemaVersion = Version.VERSION_16; - } else { - schemaVersion = null; - } - if (file.startsWith("valid") && schemaVersion != null) { - dynamicTests.add(DynamicTest.dynamicTest(file, () -> assertTrue( - isValid(schemaVersion, "/" + schemaVersion.getVersionString() + "/" + file), file))); - } else if (file.startsWith("invalid") && schemaVersion != null) { - dynamicTests.add(DynamicTest.dynamicTest(file, () -> assertFalse( - isValid(schemaVersion, "/" + schemaVersion.getVersionString() + "/" + file), file))); + for (final String resource : resources) { + String resourceName = StringUtils.substringAfterLast(resource, "/"); + if (resourceName.endsWith(".xml")) { + Schema schema = getSchema(resourceName); + if (schema != null) { + if (resourceName.startsWith("valid")) { + dynamicTests.add(DynamicTest.dynamicTest( + resource, () -> assertTrue(isValid(schema, resource), resource))); + } else if (resourceName.startsWith("invalid")) { + dynamicTests.add(DynamicTest.dynamicTest( + resource, () -> assertFalse(isValid(schema, resource), resource))); + } } } } @@ -73,14 +93,59 @@ Collection dynamicTestsWithCollection() throws Exception { /** * Validates the given XML file against the specified CycloneDX schema version. * - * @param version the CycloneDX schema version to validate against - * @param resource the path to the XML file to be validated + * @param schema the CycloneDX schema to validate against + * @param resource the path to the XML file to be validated * @return boolean true if the XML file is valid according to the specified schema version, false otherwise * @throws Exception if an error occurs during the validation process */ - private boolean isValid(Version version, String resource) throws Exception { - final File file = new File(this.getClass().getResource(resource).getFile()); - final XmlParser parser = new XmlParser(); - return parser.isValid(file, version); + private boolean isValid(Schema schema, String resource) throws Exception { + Validator validator = schema.newValidator(); + validator.setErrorHandler(new ErrorHandler() { + @Override + public void warning(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void error(SAXParseException exception) throws SAXException { + throw exception; + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + throw exception; + } + }); + try { + validator.validate(new StreamSource(getClass().getClassLoader().getResourceAsStream(resource))); + } catch (SAXParseException e) { + return false; + } + return true; + } + + private Schema getSchema(String resourceName) { + if (resourceName.endsWith("-1.0.xml")) { + return VERSION_10; + } + if (resourceName.endsWith("-1.1.xml")) { + return VERSION_11; + } + if (resourceName.endsWith("-1.2.xml")) { + return VERSION_12; + } + if (resourceName.endsWith("-1.3.xml")) { + return VERSION_13; + } + if (resourceName.endsWith("-1.4.xml")) { + return VERSION_14; + } + if (resourceName.endsWith("-1.5.xml")) { + return VERSION_15; + } + if (resourceName.endsWith("-1.6.xml")) { + return VERSION_16; + } + return null; } }