diff --git a/examples/org/spdx/examples/ExpandedLicenseExampleV3.java b/examples/org/spdx/examples/ExpandedLicenseExampleV3.java new file mode 100644 index 0000000..030b1b2 --- /dev/null +++ b/examples/org/spdx/examples/ExpandedLicenseExampleV3.java @@ -0,0 +1,189 @@ +/** + * SPDX-FileContributor: Gary O'Neall + * SPDX-FileCopyrightText: Copyright (c) 2025 Source Auditor Inc. + * SPDX-FileType: SOURCE + * SPDX-License-Identifier: Apache-2.0 + * + * Example of serializing a single expanded license + */ + +package org.spdx.examples; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.networknt.schema.Error; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.IModelCopyManager; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.ModelCopyManager; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v3_0_1.SpdxModelClassFactoryV3; +import org.spdx.library.model.v3_0_1.core.CreationInfo; +import org.spdx.library.model.v3_0_1.core.Element; +import org.spdx.library.model.v3_0_1.core.ProfileIdentifierType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.expandedlicensing.ExtendableLicense; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.storage.IModelStore; +import org.spdx.storage.simple.InMemSpdxStore; +import org.spdx.tools.Verify; +import org.spdx.v3jsonldstore.JsonLDStore; + +import java.io.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.spdx.tools.Verify.JSON_SCHEMA_RESOURCE_V3; + +/** + * Simple example serializing a single expanded license + */ +public class ExpandedLicenseExampleV3 { + + static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + + /** + * @param args args[0] is the file path for the output serialized file + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + usage(); + System.exit(1); + } + File outFile = new File(args[0]); + if (outFile.exists()) { + System.out.printf("%s already exists.\n", args[0]); + System.exit(1); + } + if (!outFile.createNewFile()) { + System.out.printf("Unable to create file %s\n", args[0]); + System.exit(1); + } + if (!outFile.canWrite()) { + System.out.printf("Can not write to file %s\n", args[0]); + System.exit(1); + } + SpdxModelFactory.init(); + IModelCopyManager copyManager = new ModelCopyManager(); + try (JsonLDStore modelStore = new JsonLDStore(new InMemSpdxStore())) { + modelStore.setUseExternalListedElements(true); // setting this to false will include all the listed license details in the document + String defaultDocUri = "https://spdx.github.io/spdx-spec/v3.0.1/examples/complex-license-eaa46bdcfa20"; + String prefix = defaultDocUri + "#"; + DefaultModelStore.initialize(modelStore, defaultDocUri, copyManager); + CreationInfo creationInfo = SpdxModelClassFactoryV3.createCreationInfo( + modelStore, prefix + "garyagent", "Gary O'Neall", + copyManager); + SpdxDocument doc = creationInfo.createSpdxDocument(prefix + "document") + .setDataLicense(LicenseInfoFactory.getListedLicenseById("CC0")) + .addNamespaceMap(creationInfo.createNamespaceMap(modelStore.getNextId(IModelStore.IdType.Anonymous)) + .setNamespace(prefix) + .setPrefix("example") + .build()) + .addProfileConformance(ProfileIdentifierType.CORE) + .addProfileConformance(ProfileIdentifierType.SOFTWARE) + .addProfileConformance(ProfileIdentifierType.EXPANDED_LICENSING) + .build(); + doc.setIdPrefix(prefix); + AnyLicenseInfo complexLicense = doc.createConjunctiveLicenseSet(prefix + "complexlicense") + // CustomLicense + .addMember(doc.createCustomLicense(prefix + "LicenseRef-customlicense1") + .setLicenseText("This is the license text for my custom license") + .setName("Gary's Custom License") + .addSeeAlso("https://example.com") + .build()) + // OrLaterOperator + .addMember(doc.createOrLaterOperator(prefix + "complexorlater") + // ListedLicense + .setSubjectLicense(doc.createListedLicense("http://spdx.org/licenses/EPL-1.0") + .setName("Eclipse Public License 1.0") + .setLicenseText("Eclipse Public License - v 1.0\n\nTHE ACCOMPANYING PROGRAM IS PROVIDED" + + " UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION " + + "OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENTS ACCEPTANCE OF THIS AGREEMENT.\n\n1. " + + "DEFINITIONS\n\n\"Contribution\" means:\n a) in the case of the initial Contributor...") + .setIsFsfLibre(true) + .setComment("EPL replaced the CPL on 28 June 2005.") + .addSeeAlso("https://opensource.org/licenses/EPL-1.0") + .build()) + .build()) + // DisjunctiveLicenseSet + .addMember(doc.createDisjunctiveLicenseSet(prefix + "complexdisjunctive") + // WithAdditionOperator + .addMember(doc.createWithAdditionOperator(prefix + "complexwith") + .setSubjectExtendableLicense((ExtendableLicense) LicenseInfoFactory.parseSPDXLicenseString("GPL-2.0-or-later")) + // ListedLicenseException + .setSubjectAddition(doc.createListedLicenseException("http://spdx.org/licenses/Autoconf-exception-2.0") + .setName("Autoconf exception 2.0") + .setComment("Typically used with GPL-2.0-only or GPL-2.0-or-later") + .setAdditionText("As a special exception, the Free Software Foundation gives unlimited " + + "permission to copy, distribute and modify the ...") + .addSeeAlso("http://ftp.gnu.org/gnu/autoconf/autoconf-2.59.tar.gz") + .build()) + .build()) + .addMember(doc.createWithAdditionOperator(prefix + "complexwithcustomaddition") + .setSubjectExtendableLicense((ExtendableLicense) LicenseInfoFactory.parseSPDXLicenseString("Apache-2.0")) + // CustomLicenseAddition + .setSubjectAddition(doc.createCustomLicenseAddition(prefix + "complexcustomaddition") + .setName("My License Addition") + .setAdditionText("Custom addition text - just for me") + .addSeeAlso("https://example.com") + .build()) + .build()) + // ExtendableLicense - Abstract + // IndividualLicensingInfo - used by listed license + // License - Abstract + .addMember(LicenseInfoFactory.parseSPDXLicenseString("MIT")) + .build()) + .build(); + doc.getRootElements().add(complexLicense); + doc.getElements().add(complexLicense); + List warnings = new ArrayList<>(); + Collection docElements = doc.getElements(); + SpdxModelFactory.getSpdxObjects(modelStore, copyManager, null, null, prefix).forEach( + modelObject -> { + if (modelObject instanceof Element) { + Element element = (Element)modelObject; + if (!docElements.contains(element) && !element.equals(doc)) { + warnings.add("Element not in the document elements: " + element.getObjectUri()); + docElements.add(element); + } + } + } + ); + warnings.addAll(complexLicense.verify()); + try (OutputStream outStream = new FileOutputStream(outFile)) { + modelStore.serialize(outStream, doc); + } + SchemaRegistry schemaRegistry = + SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema; + try (InputStream is = Verify.class.getResourceAsStream("/" + JSON_SCHEMA_RESOURCE_V3)) { + schema = schemaRegistry.getSchema(is); + } + JsonNode root; + try (InputStream is = new FileInputStream(outFile)) { + root = JSON_MAPPER.readTree(is); + } + List messages = schema.validate(root); + for (Error msg:messages) { + warnings.add(msg.toString()); + } + if (!warnings.isEmpty()) { + System.out.println("Generated document contains the following warnings:"); + for (String warning:warnings) { + System.out.print("\t"); + System.out.println(warning); + } + } + } + } + + private static void usage() { + System.out.println("Generates an SPDX JSON-LD file containing all of the supported classes."); + System.out.println("Usage: FullSpdxV3Example outputfile"); + } +} diff --git a/examples/org/spdx/examples/FullSpdxV3Example.java b/examples/org/spdx/examples/FullSpdxV3Example.java index 2a42eb6..fd1b269 100644 --- a/examples/org/spdx/examples/FullSpdxV3Example.java +++ b/examples/org/spdx/examples/FullSpdxV3Example.java @@ -1,13 +1,21 @@ -package org.spdx.examples; +/** + * SPDX-FileContributor: Gary O'Neall + * SPDX-FileCopyrightText: Copyright (c) 2025 Source Auditor Inc. + * SPDX-FileType: SOURCE + * SPDX-License-Identifier: Apache-2.0 + * + * Full example of an SPDX document using all classes + */ +package org.spdx.examples; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; +import com.networknt.schema.Error; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; import org.spdx.core.DefaultModelStore; import org.spdx.core.IModelCopyManager; import org.spdx.core.InvalidSPDXAnalysisException; @@ -26,7 +34,6 @@ import org.spdx.library.model.v3_0_1.dataset.DatasetPackage; import org.spdx.library.model.v3_0_1.dataset.DatasetType; import org.spdx.library.model.v3_0_1.expandedlicensing.ExtendableLicense; -import org.spdx.library.model.v3_0_1.extension.Extension; import org.spdx.library.model.v3_0_1.security.*; import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; import org.spdx.library.model.v3_0_1.simplelicensing.SimpleLicensingText; @@ -39,9 +46,7 @@ import java.io.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.*; import static org.spdx.tools.Verify.JSON_SCHEMA_RESOURCE_V3; @@ -61,10 +66,9 @@ public class FullSpdxV3Example { static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); static class ExampleBuilder { - private String prefix = null; - private SpdxDocument doc = null; + private final String prefix; + private final SpdxDocument doc; private Sbom sBom = null; - private Bom aiBom = null; private SpdxPackage pkg = null; public ExampleBuilder(String prefix, SpdxDocument doc) { @@ -88,17 +92,19 @@ private String getNextAnonId() throws InvalidSPDXAnalysisException { } private void addExtensionClasses() throws InvalidSPDXAnalysisException { - // SpdxExtensionExample extension = new SpdxExtensionExample(doc.getModelStore(), prefix + "extension", doc.getCopyManager(), true, prefix); - // This currently causes a schema validation issue and depends on a fix in the v3JsonLD store - // extension.setExtensionProperty("Extension property value"); - // TODO: Add this back in after validation issues are addressed - Extension extension = doc.createCdxPropertiesExtension(prefix + "extension") + //TODO: The following is causing a schema validation error - uncomment when resolved +// ModelRegistry.getModelRegistry().registerExtensionType("Extension.example", +// SpdxExtensionExample.class); +// SpdxExtensionExample extension = new SpdxExtensionExample(doc.getModelStore(), +// prefix + "extension", doc.getCopyManager(), true, prefix); +// extension.setExtensionProperty("Extension property value"); +// doc.getExtensions().add(extension); + doc.getExtensions().add(doc.createCdxPropertiesExtension(getNextAnonId()) .addCdxProperty(doc.createCdxPropertyEntry(getNextAnonId()) - .setCdxPropName("cdxProperty") - .setCdxPropValue("value") + .setCdxPropName("CDXProperty") + .setCdxPropValue("Property Value") .build()) - .build(); - doc.getExtensions().add(extension); + .build()); } private void addBuildClasses() throws InvalidSPDXAnalysisException { @@ -173,7 +179,7 @@ private void addExpandedLicensingClasses() throws InvalidSPDXAnalysisException // ConjunctiveLicenseSet AnyLicenseInfo complexLicense = doc.createConjunctiveLicenseSet(prefix + "complexlicense") // CustomLicense - .addMember(doc.createCustomLicense(prefix + "LicenseRef-customlicense1") + .addMember(doc.createCustomLicense(prefix + "LicenseRef-customlicense3") .setLicenseText("This is the license text for my custom license") .setName("Gary's Custom License") .addSeeAlso("https://example.com") @@ -181,7 +187,7 @@ private void addExpandedLicensingClasses() throws InvalidSPDXAnalysisException // OrLaterOperator .addMember(doc.createOrLaterOperator(prefix + "complexorlater") // ListedLicense - .setSubjectLicense(doc.createListedLicense("https://spdx.org/licenses/EPL-1.0") + .setSubjectLicense(doc.createListedLicense("http://spdx.org/licenses/EPL-1.0") .setName("Eclipse Public License 1.0") .setLicenseText("Eclipse Public License - v 1.0\n\nTHE ACCOMPANYING PROGRAM IS PROVIDED" + " UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION " + @@ -378,20 +384,20 @@ private void addSecurityClasses() throws InvalidSPDXAnalysisException { // ExploitCatalogVulnAssessmentRelationship //TODO: The schema has "locator" for the field while the generated Java code has "securityLocator" //Need to regenerate the library then uncomment the example below -// ExploitCatalogVulnAssessmentRelationship excat = doc.createExploitCatalogVulnAssessmentRelationship(prefix + "exploitcat") -// .setRelationshipType(RelationshipType.HAS_ASSESSMENT_FOR) -// .setFrom(vuln) -// .addTo(log4j) -// .setCatalogType(ExploitCatalogType.KEV) -// .setSecurityLocator("https://www.cisa.gov/known-exploited-vulnerabilities-catalog") -// .setExploited(true) -// .setAssessedElement(log4j) -// .setSuppliedBy(supplierAgent) -// .setPublishedTime(LocalDateTime.of(2023, 9, 18, 0, 0) -// .format(SPDX_DATE_FORMATTER)) -// .build(); -// doc.getElements().add(excat); -// securityBundle.getElements().add(excat); + ExploitCatalogVulnAssessmentRelationship excat = doc.createExploitCatalogVulnAssessmentRelationship(prefix + "exploitcat") + .setRelationshipType(RelationshipType.HAS_ASSESSMENT_FOR) + .setFrom(vuln) + .addTo(log4j) + .setCatalogType(ExploitCatalogType.KEV) + .setSecurityLocator("https://www.cisa.gov/known-exploited-vulnerabilities-catalog") + .setExploited(true) + .setAssessedElement(log4j) + .setSuppliedBy(supplierAgent) + .setPublishedTime(LocalDateTime.of(2023, 9, 18, 0, 0) + .format(SPDX_DATE_FORMATTER)) + .build(); + doc.getElements().add(excat); + securityBundle.getElements().add(excat); // SsvcVulnAssessmentRelationship SsvcVulnAssessmentRelationship ssvs = doc.createSsvcVulnAssessmentRelationship(prefix + "ssvs") @@ -527,7 +533,6 @@ private void addCoreClasses() throws InvalidSPDXAnalysisException { .addLocator("org.spdx:tools-java") .build()) .build()); - } private void addSoftwareClasses() throws InvalidSPDXAnalysisException { @@ -668,7 +673,7 @@ private void addSoftwareClasses() throws InvalidSPDXAnalysisException { } private void addAIandDataClasses() throws InvalidSPDXAnalysisException { - aiBom = doc.createBom(prefix + "aibom") + Bom aiBom = doc.createBom(prefix + "aibom") .setName("AI SBOM") .addProfileConformance(ProfileIdentifierType.CORE) .addProfileConformance(ProfileIdentifierType.SOFTWARE) @@ -758,6 +763,8 @@ private void addAIandDataClasses() throws InvalidSPDXAnalysisException { .addTo(aiPackage) .setCompleteness(RelationshipCompleteness.INCOMPLETE) .build(); + doc.getElements().add(usesData); + aiBom.getElements().add(usesData); } } @@ -809,22 +816,40 @@ public static void main(String[] args) throws Exception { doc.setIdPrefix(prefix); ExampleBuilder builder = new ExampleBuilder(prefix, doc); builder.build(); + List warnings = new ArrayList<>(); + // Add all the elements to the doc to make sure everything gets serialized + Collection docElements = doc.getElements(); + SpdxModelFactory.getSpdxObjects(modelStore, copyManager, null, null, prefix).forEach( + modelObject -> { + if (modelObject instanceof Element) { + Element element = (Element)modelObject; + if (!docElements.contains(element) && !element.equals(doc)) { + warnings.add("Element not in the document elements: " + element.getObjectUri()); + docElements.add(element); + } + } + } + ); - List warnings = doc.verify(); + // Verify using the SPDX Java Library + warnings.addAll(doc.verify()); try (OutputStream outStream = new FileOutputStream(outFile)) { - modelStore.serialize(outStream); + modelStore.serialize(outStream, doc); } - JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012); - JsonSchema schema; + + // Validate using the schema + SchemaRegistry schemaRegistry = + SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12); + Schema schema; try (InputStream is = Verify.class.getResourceAsStream("/" + JSON_SCHEMA_RESOURCE_V3)) { - schema = jsonSchemaFactory.getSchema(is); + schema = schemaRegistry.getSchema(is); } JsonNode root; try (InputStream is = new FileInputStream(outFile)) { root = JSON_MAPPER.readTree(is); } - Set messages = schema.validate(root); - for (ValidationMessage msg:messages) { + List messages = schema.validate(root); + for (Error msg:messages) { warnings.add(msg.toString()); } if (!warnings.isEmpty()) { diff --git a/examples/org/spdx/examples/SpdxExtensionExample.java b/examples/org/spdx/examples/SpdxExtensionExample.java index 135a80e..f75d8c9 100644 --- a/examples/org/spdx/examples/SpdxExtensionExample.java +++ b/examples/org/spdx/examples/SpdxExtensionExample.java @@ -17,6 +17,10 @@ public SpdxExtensionExample(IModelStore modelStore, String objectUri, @Nullable super(modelStore, objectUri, copyManager, create, idPrefix); } + public SpdxExtensionExample(IModelStore modelStore, String objectUri, @Nullable IModelCopyManager copyManager, boolean create, String specVersion, String idPrefix) throws InvalidSPDXAnalysisException { + super(modelStore, objectUri, copyManager, create, idPrefix); + } + public SpdxExtensionExample setExtensionProperty(String value) throws InvalidSPDXAnalysisException { setPropertyValue(EXTENSION_PROPERTY_DESCRIPTOR, value); return this; @@ -25,4 +29,9 @@ public SpdxExtensionExample setExtensionProperty(String value) throws InvalidSPD public Optional getExtensionProperty() throws InvalidSPDXAnalysisException { return getStringPropertyValue(EXTENSION_PROPERTY_DESCRIPTOR); } + + @Override + public String getType() { + return "Extension.example"; + } }