diff --git a/pom.xml b/pom.xml index 9060c6b..f487df0 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,13 @@ master + + + jitpack.io + https://jitpack.io + + + ossrh @@ -98,17 +105,17 @@ org.spdx java-spdx-library - 1.1.12 + 2.0.0-RC1 org.spdx spdx-rdf-store - 1.1.9 + 2.0.0-RC1 org.spdx spdx-jackson-store - 1.1.9 + 2.0.0-RC1 @@ -147,6 +154,11 @@ 1.1.0 test + + org.spdx + spdx-v3jsonld-store + 1.0.0-RC1 + diff --git a/src/main/java/org/spdx/maven/Annotation.java b/src/main/java/org/spdx/maven/Annotation.java index 6ed1564..0f6281e 100644 --- a/src/main/java/org/spdx/maven/Annotation.java +++ b/src/main/java/org/spdx/maven/Annotation.java @@ -15,11 +15,6 @@ */ package org.spdx.maven; -import org.apache.maven.plugin.MojoExecutionException; -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.enumerations.AnnotationType; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,9 +23,8 @@ * Simple class to hold an SPDX Annotation. * * @author Gary O'Neall - * @see org.spdx.library.model.Annotation - * @see AnnotationType */ +@SuppressWarnings("unused") public class Annotation { private static final Logger LOG = LoggerFactory.getLogger( Annotation.class ); @@ -125,38 +119,8 @@ public void setAnnotationComment( String annotationComment ) this.annotationComment = annotationComment; } - - /** - * @param spdxDoc SPDX document which will contain the annotation - * @return an SPDX model version of the annotation - */ - public org.spdx.library.model.Annotation toSpdxAnnotation( SpdxDocument spdxDoc ) throws MojoExecutionException - { - AnnotationType annotationType = AnnotationType.OTHER; - try - { - annotationType = AnnotationType.valueOf( this.annotationType ); - } - catch ( Exception ex ) - { - throw new MojoExecutionException( "Invalid annotation type "+this.annotationType ); - } - try - { - return spdxDoc.createAnnotation( this.annotator, - annotationType, - annotationDate, - annotationComment ); - } - catch ( InvalidSPDXAnalysisException e ) - { - throw new MojoExecutionException( "Error creating annotation.", e ); - } - } - public void logInfo() { - LOG.debug( - "Annotator: " + this.annotator + ", Date: " + this.annotationDate + ", Type: " + this.annotationType ); + LOG.debug( "Annotator: {}, Date: {}, Type: {}", this.annotator, this.annotationDate, this.annotationType ); } } \ No newline at end of file diff --git a/src/main/java/org/spdx/maven/Checksum.java b/src/main/java/org/spdx/maven/Checksum.java new file mode 100644 index 0000000..17dde3c --- /dev/null +++ b/src/main/java/org/spdx/maven/Checksum.java @@ -0,0 +1,82 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. + */ +package org.spdx.maven; + +import java.util.Objects; + +/** + * Holds the value and algorithm of a checksum used in the SPDX document + * + * @author Gary O'Neall + * + */ +public class Checksum +{ + + private String value; + private String algorithm; + + /** + * @param algorithm checksum algorithm + * @param value checksum result + */ + public Checksum( String algorithm, String value ) + { + this.algorithm = algorithm; + this.value = value; + } + + /** + * @return the value + */ + public String getValue() + { + return value; + } + + /** + * @param value the value to set + */ + public void setValue( String value ) + { + this.value = value; + } + + /** + * @return the algorithm + */ + public String getAlgorithm() + { + return algorithm; + } + + /** + * @param algorithm the algorithm to set + */ + @SuppressWarnings("unused") + public void setAlgorithm( String algorithm ) + { + this.algorithm = algorithm; + } + + @Override + public boolean equals(Object o) + { + if ( !( o instanceof Checksum ) ) + { + return false; + } + Checksum compare = (Checksum)o; + return Objects.equals( compare.getAlgorithm(), this.getAlgorithm() ) && + Objects.equals( compare.getValue(), this.getValue() ); + } + + @Override + public int hashCode() + { + return ( Objects.isNull( algorithm ) ? 11 : algorithm.hashCode() ) ^ ( Objects.isNull( value ) ? 17 : value.hashCode() ); + } + +} diff --git a/src/main/java/org/spdx/maven/CreateSpdxMojo.java b/src/main/java/org/spdx/maven/CreateSpdxMojo.java index afda541..400a1b2 100644 --- a/src/main/java/org/spdx/maven/CreateSpdxMojo.java +++ b/src/main/java/org/spdx/maven/CreateSpdxMojo.java @@ -37,37 +37,28 @@ import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException; import org.apache.maven.shared.dependency.graph.DependencyNode; import org.apache.maven.shared.model.fileset.FileSet; - -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.Checksum; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.enumerations.ChecksumAlgorithm; -import org.spdx.library.model.enumerations.Purpose; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.InvalidLicenseStringException; -import org.spdx.library.model.license.LicenseInfoFactory; -import org.spdx.library.model.license.SpdxNoAssertionLicense; - -import org.spdx.maven.utils.LicenseManagerException; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.core.SpdxCoreConstants.SpdxMajorVersion; +import org.spdx.library.SpdxModelFactory; import org.spdx.maven.utils.LicenseMapperException; import org.spdx.maven.utils.SpdxBuilderException; import org.spdx.maven.utils.SpdxCollectionException; import org.spdx.maven.utils.SpdxDefaultFileInformation; -import org.spdx.maven.utils.SpdxDependencyInformation; -import org.spdx.maven.utils.SpdxDocumentBuilder; -import org.spdx.maven.utils.SpdxFileCollector; +import org.spdx.maven.utils.AbstractDependencyBuilder; +import org.spdx.maven.utils.AbstractDocumentBuilder; +import org.spdx.maven.utils.AbstractFileCollector; +import org.spdx.maven.utils.LicenseManagerException; import org.spdx.maven.utils.SpdxProjectInformation; +import org.spdx.maven.utils.SpdxV2DependencyBuilder; +import org.spdx.maven.utils.SpdxV2DocumentBuilder; +import org.spdx.maven.utils.SpdxV3DependencyBuilder; +import org.spdx.maven.utils.SpdxV3DocumentBuilder; import java.io.File; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; /** * NOTE: Currently this is a prototype plugin for supporting SPDX in a Maven build. @@ -95,6 +86,7 @@ *

* Additional SPDX fields are supplied as configuration parameters to this plugin. */ +@SuppressWarnings({"unused", "DefaultAnnotationParam"}) @Mojo( name = "createSPDX", defaultPhase = LifecyclePhase.VERIFY, requiresOnline = true, @@ -112,6 +104,11 @@ public class CreateSpdxMojo extends AbstractMojo public static final String JSON_OUTPUT_FORMAT = "JSON"; public static final String RDF_OUTPUT_FORMAT = "RDF/XML"; + + static + { + SpdxModelFactory.init(); + } @Component private MavenProject mavenProject; @@ -509,14 +506,13 @@ public void execute() throws MojoExecutionException getLog().info( "Creating SPDX File " + spdxFile.getPath() ); - SpdxDocumentBuilder builder = initSpdxDocumentBuilder( outputFormatEnum ); - SpdxDocument spdxDoc = builder.getSpdxDoc(); + AbstractDocumentBuilder builder = initSpdxDocumentBuilder( outputFormatEnum ); // fill project information try { SpdxProjectInformation projectInformation = getSpdxProjectInfoFromParameters( builder ); - projectInformation.logInfo( spdxDoc ); + projectInformation.logInfo(); builder.fillSpdxDocumentInformation( projectInformation ); } catch ( InvalidSPDXAnalysisException e2 ) @@ -525,8 +521,8 @@ public void execute() throws MojoExecutionException } // collect file-level information - SpdxDefaultFileInformation defaultFileInformation = getDefaultFileInfoFromParameters( spdxDoc ); - HashMap pathSpecificInformation = getPathSpecificInfoFromParameters( defaultFileInformation, spdxDoc ); + SpdxDefaultFileInformation defaultFileInformation = getDefaultFileInfoFromParameters(); + HashMap pathSpecificInformation = getPathSpecificInfoFromParameters( defaultFileInformation ); List sources = toFileSet( mavenProject.getCompileSourceRoots(), mavenProject.getResources() ); sources.addAll( toFileSet( mavenProject.getTestCompileSourceRoots(), null ) ); // TODO: why not test resources given source resources are taken into account? @@ -544,9 +540,7 @@ public void execute() throws MojoExecutionException // add dependencies information try { - SpdxDependencyInformation dependencyInformation = getSpdxDependencyInformation( builder ); - - builder.addDependencyInformation( dependencyInformation ); + buildSpdxDependencyInformation( builder, outputFormatEnum ); } catch ( LicenseMapperException e1 ) { @@ -568,8 +562,8 @@ public void execute() throws MojoExecutionException projectHelper.attachArtifact( mavenProject, artifactType, spdxFile ); // check errors - List spdxErrors = builder.getSpdxDoc().verify(); - if ( spdxErrors != null && spdxErrors.size() > 0 ) + List spdxErrors = builder.verify(); + if ( spdxErrors != null && !spdxErrors.isEmpty() ) { getLog().warn( "The following errors were found in the SPDX file:\n " + String.join( "\n ", spdxErrors ) ); } @@ -599,11 +593,12 @@ private OutputFormat prepareOutput() throw new MojoExecutionException( "Invalid path for SPDX output file. " + "Specify a configuration parameter spdxFile with a valid directory path to resolve." ); } + //noinspection ResultOfMethodCallIgnored outputDir.mkdirs(); return outputFormatEnum; } - private SpdxDocumentBuilder initSpdxDocumentBuilder( OutputFormat outputFormatEnum ) + private AbstractDocumentBuilder initSpdxDocumentBuilder( OutputFormat outputFormatEnum ) throws MojoExecutionException { if ( onlyUseLocalLicenses ) @@ -614,7 +609,7 @@ private SpdxDocumentBuilder initSpdxDocumentBuilder( OutputFormat outputFormatEn defaultLicenseInformationInFile = defaultFileConcludedLicense; } - SpdxDocumentBuilder builder; + AbstractDocumentBuilder builder; try { if ( spdxDocumentNamespace.startsWith( "http://spdx.org/spdxpackages/" )) { @@ -622,8 +617,16 @@ private SpdxDocumentBuilder initSpdxDocumentBuilder( OutputFormat outputFormatEn spdxDocumentNamespace = spdxDocumentNamespace.replace( " ", "%20" ); } URI namespaceUri = new URI( spdxDocumentNamespace ); - builder = new SpdxDocumentBuilder( mavenProject, generatePurls, spdxFile, namespaceUri, - this.matchLicensesOnCrossReferenceUrls, outputFormatEnum ); + if ( SpdxMajorVersion.VERSION_3.equals( outputFormatEnum.getSpecVersion() ) ) { + builder = new SpdxV3DocumentBuilder( mavenProject, generatePurls, spdxFile, namespaceUri, + outputFormatEnum ); + } + else + { + builder = new SpdxV2DocumentBuilder( mavenProject, generatePurls, spdxFile, namespaceUri, + outputFormatEnum ); + } + } catch ( SpdxBuilderException e ) { @@ -653,18 +656,28 @@ private SpdxDocumentBuilder initSpdxDocumentBuilder( OutputFormat outputFormatEn } /** - * Collect dependency information from Maven dependencies + * Collect dependency information from Maven dependencies and adds it to the builder SPDX document * * @param builder SPDX document builder - * @throws LicenseMapperException - * @throws InvalidSPDXAnalysisException + * @throws LicenseMapperException on errors related to mapping Maven licenses to SPDX licenses + * @throws InvalidSPDXAnalysisException on SPDX parsing errors */ - private SpdxDependencyInformation getSpdxDependencyInformation( SpdxDocumentBuilder builder ) + private void buildSpdxDependencyInformation( AbstractDocumentBuilder builder, OutputFormat outputFormatEnum ) throws LicenseMapperException, InvalidSPDXAnalysisException, DependencyGraphBuilderException { - SpdxDependencyInformation retval = new SpdxDependencyInformation( builder.getLicenseManager(), builder.getSpdxDoc(), - createExternalRefs, generatePurls, useArtifactID, - includeTransitiveDependencies ); + AbstractDependencyBuilder dependencyBuilder; + if ( builder instanceof SpdxV3DocumentBuilder ) + { + dependencyBuilder = new SpdxV3DependencyBuilder( (SpdxV3DocumentBuilder)builder, createExternalRefs, + generatePurls, useArtifactID, + includeTransitiveDependencies ); + } + else + { + dependencyBuilder = new SpdxV2DependencyBuilder( (SpdxV2DocumentBuilder)builder, + createExternalRefs, generatePurls, useArtifactID, + includeTransitiveDependencies ); + } if ( session != null ) { @@ -672,10 +685,8 @@ private SpdxDependencyInformation getSpdxDependencyInformation( SpdxDocumentBuil request.setProject( mavenProject ); DependencyNode parentNode = dependencyGraphBuilder.buildDependencyGraph( request, null ); - retval.addMavenDependencies( mavenProjectBuilder, session, mavenProject, parentNode, builder.getProjectPackage() ); + dependencyBuilder.addMavenDependencies( mavenProjectBuilder, session, mavenProject, parentNode, builder.getProjectPackage() ); } - - return retval; } private void logFileSpecificInfo( HashMap fileSpecificInformation ) @@ -694,29 +705,17 @@ private void logFileSpecificInfo( HashMap fi /** * Get the patch specific information * - * @param projectDefault - * @param spdxDoc SPDX document containing any extracted license infos - * @return - * @throws MojoExecutionException + * @param projectDefault default file information if no path specific overrides are present + * @return map path to project specific SPDX parameters */ - private HashMap getPathSpecificInfoFromParameters( SpdxDefaultFileInformation projectDefault, - SpdxDocument spdxDoc ) throws MojoExecutionException - { + private HashMap getPathSpecificInfoFromParameters( SpdxDefaultFileInformation projectDefault ) { HashMap retval = new HashMap<>(); if ( this.pathsWithSpecificSpdxInfo != null ) { for ( PathSpecificSpdxInfo spdxInfo : this.pathsWithSpecificSpdxInfo ) { - SpdxDefaultFileInformation value = null; - try - { - value = spdxInfo.getDefaultFileInformation( projectDefault, spdxDoc ); - } - catch ( InvalidSPDXAnalysisException e ) - { - throw new MojoExecutionException( - "Invalid license string used in the path specific SPDX information for file " + spdxInfo.getPath(), e ); - } + SpdxDefaultFileInformation value; + value = spdxInfo.getDefaultFileInformation( projectDefault ); if ( retval.containsKey( spdxInfo.getPath() ) ) { getLog().warn( "Multiple file path specific SPDX data for " + spdxInfo.getPath() ); @@ -730,7 +729,7 @@ private HashMap getPathSpecificInfoFromParam /** * Primarily for debugging purposes - logs nonStandardLicenses as info * - * @param nonStandardLicenses + * @param nonStandardLicenses non standard licenses to log */ private void logNonStandardLicenses( NonStandardLicense[] nonStandardLicenses ) { @@ -758,7 +757,7 @@ private void logNonStandardLicenses( NonStandardLicense[] nonStandardLicenses ) /** * Primarily for debugging purposes - logs includedDirectories as info * - * @param includedDirectories + * @param includedDirectories included directory fileSet to log */ private void logIncludedDirectories( List includedDirectories ) { @@ -787,46 +786,16 @@ private void logIncludedDirectories( List includedDirectories ) } /** - * @param spdxDoc SPDX Document containing any extracted license infos * @return default file information from the plugin parameters - * @throws MojoExecutionException */ - private SpdxDefaultFileInformation getDefaultFileInfoFromParameters( SpdxDocument spdxDoc ) throws MojoExecutionException - { + private SpdxDefaultFileInformation getDefaultFileInfoFromParameters() { SpdxDefaultFileInformation retval; - try - { - retval = new SpdxDefaultFileInformation(); - } - catch ( InvalidSPDXAnalysisException e1 ) - { - throw new MojoExecutionException( "Error getting default file information", e1 ); - } + retval = new SpdxDefaultFileInformation(); retval.setComment( defaultFileComment ); - AnyLicenseInfo concludedLicense = null; - try - { - concludedLicense = LicenseInfoFactory.parseSPDXLicenseString( defaultFileConcludedLicense.trim(), - spdxDoc.getModelStore(), spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); - } - catch ( InvalidLicenseStringException e ) - { - throw new MojoExecutionException( "Invalid default file concluded license", e ); - } - retval.setConcludedLicense( concludedLicense ); + retval.setConcludedLicense( defaultFileConcludedLicense.trim() ); retval.setContributors( defaultFileContributors ); retval.setCopyright( defaultFileCopyright ); - AnyLicenseInfo declaredLicense = null; - try - { - declaredLicense = LicenseInfoFactory.parseSPDXLicenseString( defaultLicenseInformationInFile.trim(), - spdxDoc.getModelStore(), spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); - } - catch ( InvalidLicenseStringException e ) - { - throw new MojoExecutionException( "Invalid default file declared license", e ); - } - retval.setDeclaredLicense( declaredLicense ); + retval.setDeclaredLicense( defaultLicenseInformationInFile.trim() ); retval.setLicenseComment( defaultFileLicenseComment ); retval.setNotice( defaultFileNotice ); return retval; @@ -845,63 +814,41 @@ private SpdxDefaultFileInformation getDefaultFileInfoFromParameters( SpdxDocumen * " is prepended * * @param builder SPDX document builder - * @return - * @throws MojoExecutionException + * @return SPDX project level information */ - private SpdxProjectInformation getSpdxProjectInfoFromParameters( SpdxDocumentBuilder builder ) throws MojoExecutionException, InvalidSPDXAnalysisException + private SpdxProjectInformation getSpdxProjectInfoFromParameters( AbstractDocumentBuilder builder ) throws InvalidSPDXAnalysisException { - SpdxDocument spdxDoc = builder.getSpdxDoc(); SpdxProjectInformation retval = new SpdxProjectInformation(); if ( this.documentComment != null ) { retval.setDocumentComment( this.documentComment ); } - AnyLicenseInfo declaredLicense = null; + String declaredLicense; if ( this.licenseDeclared == null ) { List mavenLicenses = mavenProject.getLicenses(); try { - declaredLicense = builder.getLicenseManager().mavenLicenseListToSpdxLicense( mavenLicenses ); + declaredLicense = builder.mavenLicenseListToSpdxLicenseExpression( mavenLicenses ); } catch ( LicenseManagerException e ) { getLog().warn( "Unable to map maven licenses to a declared license. Using NOASSERTION" ); - declaredLicense = new SpdxNoAssertionLicense(); + declaredLicense = "NOASSERTION"; } } else { - try - { - declaredLicense = LicenseInfoFactory.parseSPDXLicenseString( this.licenseDeclared.trim(), - spdxDoc.getModelStore(), - spdxDoc.getDocumentUri(), - spdxDoc.getCopyManager()); - } - catch ( InvalidLicenseStringException e ) - { - throw new MojoExecutionException( "Invalid declared license: " + licenseDeclared.trim(), e ); - } + declaredLicense = this.licenseDeclared.trim(); } - AnyLicenseInfo concludedLicense = null; + String concludedLicense; if ( this.licenseConcluded == null ) { concludedLicense = declaredLicense; } else { - try - { - concludedLicense = LicenseInfoFactory.parseSPDXLicenseString( this.licenseConcluded.trim(), - spdxDoc.getModelStore(), - spdxDoc.getDocumentUri(), - spdxDoc.getCopyManager() ); - } - catch ( InvalidLicenseStringException e ) - { - throw new MojoExecutionException( "Invalid concluded license: " + licenseConcluded.trim(), e ); - } + concludedLicense = this.licenseConcluded.trim(); } retval.setConcludedLicense( concludedLicense ); retval.setCreatorComment( this.creatorComment ); @@ -909,7 +856,7 @@ private SpdxProjectInformation getSpdxProjectInfoFromParameters( SpdxDocumentBui { this.creators = new String[0]; } - String[] allCreators = (String[]) Arrays.copyOf( creators, creators.length + 1 ); + String[] allCreators = Arrays.copyOf( creators, creators.length + 1 ); allCreators[allCreators.length - 1] = CREATOR_TOOL_MAVEN_PLUGIN; retval.setCreators( allCreators ); retval.setCopyrightText( this.copyrightText ); @@ -958,8 +905,8 @@ private SpdxProjectInformation getSpdxProjectInfoFromParameters( SpdxDocumentBui try { getLog().debug( "Generating checksum for file "+packageFile.getAbsolutePath() ); - Set algorithms = getChecksumAlgorithms(); - checksums = SpdxFileCollector.generateChecksum( packageFile, algorithms, spdxDoc ); + Set algorithms = getChecksumAlgorithms(); + checksums = AbstractFileCollector.generateChecksum( packageFile, algorithms ); } catch ( SpdxCollectionException | InvalidSPDXAnalysisException e ) { @@ -989,16 +936,16 @@ private SpdxProjectInformation getSpdxProjectInfoFromParameters( SpdxDocumentBui retval.setDocumentAnnotations( this.documentAnnotations ); retval.setPackageAnnotations( this.packageAnnotations ); retval.setExternalRefs( this.externalReferences ); - - final Packaging packaging = Packaging.valueOfPackaging( mavenProject.getPackaging() ); - retval.setPrimaryPurpose(packaging != null ? packaging.getPurpose() : Purpose.LIBRARY); + + final Packaging packaging = Packaging.valueOfPackaging( mavenProject.getPackaging() ); + retval.setPackaging( packaging != null ? packaging : Packaging.JAR ); return retval; } /** * Get the default project name if no project name is specified in the POM * - * @return + * @return the default project name if no project name is specified in the POM */ private String getDefaultProjectName() { @@ -1043,28 +990,17 @@ private static List toFileSet( List roots, List resou } /** - * Map user input algorithms to Checksum.ChecksumAlgorithm values. {@code ChecksumAlgorithm.checksumAlgorithm_sha1} - * is always added to the set because it is mandatory to include the SHA1 checksum. A warning is logged for invalid - * user input. + * Map user input algorithms to Checksum.ChecksumAlgorithm values. {@code SHA1} + * is always added to the set because it is mandatory to include the SHA1 checksum. * @return set of algorithms to calculate checksum with */ - private Set getChecksumAlgorithms() + private Set getChecksumAlgorithms() { - Set algorithms = new HashSet<>(); - algorithms.add( ChecksumAlgorithm.SHA1 ); + Set algorithms = new HashSet<>(); + algorithms.add( "SHA1" ); if ( checksumAlgorithms != null ) { - for ( String checksumAlgorithm : checksumAlgorithms ) - { - try - { - algorithms.add( ChecksumAlgorithm.valueOf( checksumAlgorithm.toUpperCase() ) ); - } - catch (final IllegalArgumentException iae) - { - getLog().warn( "Ignoring unsupported checksum algorithm: " + checksumAlgorithm ); - } - } + Collections.addAll( algorithms, checksumAlgorithms ); } return algorithms; } diff --git a/src/main/java/org/spdx/maven/ExternalReference.java b/src/main/java/org/spdx/maven/ExternalReference.java index e32d412..37e778b 100644 --- a/src/main/java/org/spdx/maven/ExternalReference.java +++ b/src/main/java/org/spdx/maven/ExternalReference.java @@ -15,23 +15,14 @@ */ package org.spdx.maven; -import org.apache.maven.plugin.MojoExecutionException; -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.ExternalRef; -import org.spdx.library.model.ReferenceType; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.enumerations.ReferenceCategory; -import org.spdx.library.referencetype.ListedReferenceTypes; - /** * An External Reference allows a Package to reference an external source of additional information, metadata, * enumerations, asset identifiers, or downloadable content believed to be relevant to the Package. * * @author Gary O'Neall - * @see ExternalRef - * @see ReferenceType - * @see ReferenceCategory + * */ +@SuppressWarnings("unused") public class ExternalReference { private String category; @@ -42,37 +33,35 @@ public class ExternalReference private String comment; - public ExternalRef getExternalRef( SpdxDocument spdxDoc ) throws MojoExecutionException + /** + * @return the category + */ + public String getCategory() + { + return category; + } + + /** + * @return the type + */ + public String getType() + { + return type; + } + + /** + * @return the locator + */ + public String getLocator() + { + return locator; + } + + /** + * @return the comment + */ + public String getComment() { - ReferenceCategory cat = null; - - try { - cat = ReferenceCategory.valueOf( category.replaceAll( "-", "_" ) ); - } - catch ( Exception ex ) - { - throw new MojoExecutionException("External reference category " + category + " is not recognized as a valid, standard category." ); - } - ReferenceType refType = null; - try - { - refType = ListedReferenceTypes.getListedReferenceTypes().getListedReferenceTypeByName( type ); - } - catch ( InvalidSPDXAnalysisException e ) - { - throw new MojoExecutionException( "Error getting listed reference type for " + type, e ); - } - if ( refType == null ) - { - throw new MojoExecutionException( "Listed reference type not found for " + type ); - } - try - { - return spdxDoc.createExternalRef( cat, refType, locator, comment ); - } - catch ( InvalidSPDXAnalysisException e ) - { - throw new MojoExecutionException( "Error creating External Reference", e ); - } + return comment; } } diff --git a/src/main/java/org/spdx/maven/NonStandardLicense.java b/src/main/java/org/spdx/maven/NonStandardLicense.java index 78dd59d..7749cd9 100644 --- a/src/main/java/org/spdx/maven/NonStandardLicense.java +++ b/src/main/java/org/spdx/maven/NonStandardLicense.java @@ -16,9 +16,10 @@ package org.spdx.maven; import java.net.URL; +import java.util.Objects; /** - * Non-standard license (e.g. license which is not in the SPDX standard license list http://spdx.org/licenses) + * Non-standard license (e.g. license which is not in the SPDX standard license list) * * @author Gary O'Neall */ @@ -94,11 +95,7 @@ public String getName() */ public String getComment() { - if ( comment == null ) - { - return ""; - } - return comment; + return Objects.requireNonNullElse( comment, "" ); } /** diff --git a/src/main/java/org/spdx/maven/OutputFormat.java b/src/main/java/org/spdx/maven/OutputFormat.java index 490ff4b..d4c2c57 100644 --- a/src/main/java/org/spdx/maven/OutputFormat.java +++ b/src/main/java/org/spdx/maven/OutputFormat.java @@ -16,6 +16,7 @@ package org.spdx.maven; import java.io.File; +import org.spdx.core.SpdxCoreConstants.SpdxMajorVersion; /** * OutputFormat utility enum @@ -24,18 +25,23 @@ */ public enum OutputFormat { - RDF_XML("RDF/XML", "spdx.rdf.xml", ".rdf.xml"), - JSON("JSON", "spdx.json", ".json"); + + RDF_XML("RDF/XML", "spdx.rdf.xml", ".rdf.xml", SpdxMajorVersion.VERSION_2), + JSON("JSON", "spdx.json", ".json", SpdxMajorVersion.VERSION_2), + JSON_LD("JSON-LD", "spdx.json-ld.json", ".json-ld.json", SpdxMajorVersion.VERSION_3); private final String value; private final String artifactType; private final String fileType; + private final SpdxMajorVersion specVersion; - private OutputFormat(final String value, final String artifactType, final String fileType) + OutputFormat( final String value, final String artifactType, final String fileType, + final SpdxMajorVersion specVersion ) { this.value = value; this.artifactType = artifactType; this.fileType = fileType; + this.specVersion = specVersion; } public static OutputFormat getOutputFormat(final String format, final File file) @@ -45,7 +51,12 @@ public static OutputFormat getOutputFormat(final String format, final File file) { if (file != null) { - return file.getName().toLowerCase().endsWith(".rdf.xml") ? RDF_XML : JSON; + String fileName = file.getName().toLowerCase(); + if (fileName.endsWith(".rdf.xml")) + { + return RDF_XML; + } + return file.getName().toLowerCase().endsWith(".json-ld.json") ? JSON_LD : JSON; } throw new IllegalArgumentException("Could not determine output file"); } @@ -58,6 +69,10 @@ else if (JSON.value.equals(upperCaseFormat)) { return JSON; } + else if (JSON_LD.value.equals(upperCaseFormat)) + { + return JSON_LD; + } throw new IllegalArgumentException("Invalid SPDX output format: " + format); } @@ -70,4 +85,9 @@ public String getFileType() { return fileType; } + + public SpdxMajorVersion getSpecVersion() + { + return specVersion; + } } diff --git a/src/main/java/org/spdx/maven/Packaging.java b/src/main/java/org/spdx/maven/Packaging.java index 933ac12..153b7c3 100644 --- a/src/main/java/org/spdx/maven/Packaging.java +++ b/src/main/java/org/spdx/maven/Packaging.java @@ -15,7 +15,8 @@ */ package org.spdx.maven; -import org.spdx.library.model.enumerations.Purpose; +import org.spdx.library.model.v2.enumerations.Purpose; +import org.spdx.library.model.v3_0_1.software.SoftwarePurpose; /** * Packaging utility enum @@ -24,21 +25,23 @@ */ public enum Packaging { - POM("pom", Purpose.INSTALL), - EJB("ejb", Purpose.LIBRARY), - JAR("jar", Purpose.LIBRARY), - MAVEN_PLUGIN("maven-plugin", Purpose.LIBRARY), - WAR("war", Purpose.APPLICATION), - EAR("ear", Purpose.APPLICATION), - RAR("rar", Purpose.OTHER); + POM("pom", Purpose.INSTALL, SoftwarePurpose.INSTALL), + EJB("ejb", Purpose.LIBRARY, SoftwarePurpose.LIBRARY), + JAR("jar", Purpose.LIBRARY, SoftwarePurpose.LIBRARY), + MAVEN_PLUGIN("maven-plugin", Purpose.LIBRARY, SoftwarePurpose.LIBRARY), + WAR("war", Purpose.APPLICATION, SoftwarePurpose.APPLICATION), + EAR("ear", Purpose.APPLICATION, SoftwarePurpose.APPLICATION), + RAR("rar", Purpose.OTHER, SoftwarePurpose.ARCHIVE); private final String name; - private final Purpose purpose; + private final Purpose v2Purpose; + private final SoftwarePurpose softwarePurpose; - private Packaging(final String name, final Purpose purpose) + Packaging( final String name, final Purpose v2purpose, final SoftwarePurpose v3softwarePurpose ) { this.name = name; - this.purpose = purpose; + this.v2Purpose = v2purpose; + this.softwarePurpose = v3softwarePurpose; } public static Packaging valueOfPackaging(String packagingValue) @@ -61,8 +64,16 @@ public String getName() return name; } - public Purpose getPurpose() + public Purpose getV2Purpose() { - return purpose; + return v2Purpose; + } + + /** + * @return the softwarePurpose + */ + public SoftwarePurpose getSoftwarePurpose() + { + return softwarePurpose; } } diff --git a/src/main/java/org/spdx/maven/PathSpecificSpdxInfo.java b/src/main/java/org/spdx/maven/PathSpecificSpdxInfo.java index f8956ad..1035d61 100644 --- a/src/main/java/org/spdx/maven/PathSpecificSpdxInfo.java +++ b/src/main/java/org/spdx/maven/PathSpecificSpdxInfo.java @@ -16,12 +16,6 @@ package org.spdx.maven; import java.util.List; - -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.LicenseInfoFactory; - import org.spdx.maven.utils.SpdxDefaultFileInformation; /** @@ -30,6 +24,7 @@ * * @author Gary O'Neall */ +@SuppressWarnings("unused") public class PathSpecificSpdxInfo { /** @@ -88,7 +83,7 @@ public class PathSpecificSpdxInfo * or (d) NOASSERTION, if the SPDX file creator has not examined the contents of the actual file or the SPDX file * creator has intentionally provided no information (no meaning should be implied by doing so). For a license set, * when there is a choice between licenses (“disjunctive license”), they should be separated with “or” and enclosed - * in brackets. Similarly when multiple licenses need to be applied (“conjunctive license”), they should be + * in brackets. Similarly, when multiple licenses need to be applied (“conjunctive license”), they should be * separated with “and” and enclosed in parentheses. */ private String licenseInformationInFile; @@ -107,13 +102,10 @@ public PathSpecificSpdxInfo() * Get the default file information to be used with this file path * * @param defaults Default file information to use if the parameter was not specified for this file path - * @param spdxDoc SPDX document containing any extracted license infos that may be needed for concluded or declared - * licenses + * @return default file information to be used with this file path - * @throws InvalidSPDXAnalysisException */ - public SpdxDefaultFileInformation getDefaultFileInformation( SpdxDefaultFileInformation defaults, - SpdxDocument spdxDoc ) throws InvalidSPDXAnalysisException + public SpdxDefaultFileInformation getDefaultFileInformation( SpdxDefaultFileInformation defaults ) { SpdxDefaultFileInformation retval = new SpdxDefaultFileInformation(); if ( this.fileComment != null ) @@ -126,12 +118,7 @@ public SpdxDefaultFileInformation getDefaultFileInformation( SpdxDefaultFileInfo } if ( this.fileConcludedLicense != null ) { - AnyLicenseInfo concludedLicense = null; - concludedLicense = LicenseInfoFactory.parseSPDXLicenseString( fileConcludedLicense.trim(), - spdxDoc.getModelStore(), - spdxDoc.getDocumentUri(), - spdxDoc.getCopyManager() ); - retval.setConcludedLicense( concludedLicense ); + retval.setConcludedLicense( this.fileConcludedLicense ); } else { @@ -171,12 +158,7 @@ public SpdxDefaultFileInformation getDefaultFileInformation( SpdxDefaultFileInfo } if ( this.licenseInformationInFile != null ) { - AnyLicenseInfo declaredLicense = null; - declaredLicense = LicenseInfoFactory.parseSPDXLicenseString( licenseInformationInFile.trim(), - spdxDoc.getModelStore(), - spdxDoc.getDocumentUri(), - spdxDoc.getCopyManager() ); - retval.setDeclaredLicense( declaredLicense ); + retval.setDeclaredLicense( licenseInformationInFile.trim() ); } else { diff --git a/src/main/java/org/spdx/maven/SnippetInfo.java b/src/main/java/org/spdx/maven/SnippetInfo.java index 7d9be16..60c3a31 100644 --- a/src/main/java/org/spdx/maven/SnippetInfo.java +++ b/src/main/java/org/spdx/maven/SnippetInfo.java @@ -15,17 +15,9 @@ */ package org.spdx.maven; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.InvalidLicenseStringException; -import org.spdx.library.model.license.LicenseInfoFactory; - import org.spdx.maven.utils.SpdxBuilderException; import org.slf4j.Logger; @@ -65,26 +57,26 @@ public void logInfo() LOG.debug( "Snippet information follows:" ); if ( this.name != null ) { - LOG.debug( "Name: " + this.name ); + LOG.debug( "Name: {}", this.name ); } - LOG.debug( "Byte range: " + this.byteRange ); + LOG.debug( "Byte range: {}", this.byteRange ); if ( this.comment != null ) { - LOG.debug( "Comment: " + this.comment ); + LOG.debug( "Comment: {}", this.comment ); } - LOG.debug( "Concluded license: " + this.concludedLicense ); + LOG.debug( "Concluded license: {}", this.concludedLicense ); if ( this.copyrightText != null ) { - LOG.debug( "Copyright: " + this.copyrightText ); + LOG.debug( "Copyright: {}", this.copyrightText ); } if ( this.licenseComment != null ) { - LOG.debug( "License comment: " + this.licenseComment ); + LOG.debug( "License comment: {}", this.licenseComment ); } - LOG.debug( "License info in Snippet: " + this.licenseInfoInSnippet ); + LOG.debug( "License info in Snippet: {}", this.licenseInfoInSnippet ); if ( this.lineRange != null ) { - LOG.debug( "Line range: " + this.lineRange ); + LOG.debug( "Line range: {}", this.lineRange ); } } @@ -98,18 +90,9 @@ public String getComment() return this.comment; } - public AnyLicenseInfo getLicenseConcluded( SpdxDocument spdxDoc ) throws InvalidLicenseStringException - { - return LicenseInfoFactory.parseSPDXLicenseString( this.concludedLicense, spdxDoc.getModelStore(), - spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); - } - - public Collection getLicenseInfoInSnippet( SpdxDocument spdxDoc ) throws InvalidLicenseStringException + public String getLicenseConcluded() { - List retval = new ArrayList<>(); - retval.add( LicenseInfoFactory.parseSPDXLicenseString( this.licenseInfoInSnippet, spdxDoc.getModelStore(), - spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() )); - return retval; + return this.concludedLicense; } public String getCopyrightText() diff --git a/src/main/java/org/spdx/maven/utils/AbstractDependencyBuilder.java b/src/main/java/org/spdx/maven/utils/AbstractDependencyBuilder.java new file mode 100644 index 0000000..3ea9153 --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/AbstractDependencyBuilder.java @@ -0,0 +1,195 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. + */ +package org.spdx.maven.utils; + +import java.io.File; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Objects; + +import javax.annotation.Nullable; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; +import org.apache.maven.shared.dependency.graph.DependencyNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spdx.core.CoreModelObject; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.core.SpdxCoreConstants.SpdxMajorVersion; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.maven.OutputFormat; + +/** + * Contains information about package dependencies collected from the Maven dependencies. + *

+ * Subclasses implement dependency information specific to SPDX spec major versions + * + * @author Gary O'Neall + * + */ +public abstract class AbstractDependencyBuilder +{ + + protected static final Logger LOG = LoggerFactory.getLogger( AbstractDependencyBuilder.class ); + protected boolean createExternalRefs; + protected boolean generatePurls; + protected boolean useArtifactID; + protected boolean includeTransitiveDependencies; + DateFormat format = new SimpleDateFormat( SpdxConstantsCompatV2.SPDX_DATE_FORMAT ); + + /** + * @param createExternalRefs if true, create external references for dependencies + * @param generatePurls if true, generate a Package URL and include as an external identifier for the dependencies + * @param useArtifactID if true, use the artifact ID for the name of the dependency package, otherwise use the Maven configured project name + * @param includeTransitiveDependencies If true, include transitive dependencies, otherwise include only direct dependencies + */ + public AbstractDependencyBuilder( boolean createExternalRefs, boolean generatePurls, + boolean useArtifactID, boolean includeTransitiveDependencies ) + { + this.createExternalRefs = createExternalRefs; + this.generatePurls = generatePurls; + this.useArtifactID = useArtifactID; + this.includeTransitiveDependencies = includeTransitiveDependencies; + } + + /** + * Adds information about Maven dependencies to the list of SPDX Dependencies + * + * @param mavenProjectBuilder project builder for the repo containing the POM file + * @param session Maven session for building the project + * @param mavenProject Mave project + * @param node Dependency node which contains all the dependencies + * @param pkg SPDX Package to attach the dependencies to + * @throws InvalidSPDXAnalysisException on errors generating SPDX + * @throws LicenseMapperException on errors mapping licenses or creating custom licenses + */ + public void addMavenDependencies( ProjectBuilder mavenProjectBuilder, MavenSession session, + MavenProject mavenProject, DependencyNode node, + CoreModelObject pkg ) throws LicenseMapperException, InvalidSPDXAnalysisException + { + List children = node.getChildren(); + + logDependencies( children ); + + for ( DependencyNode childNode : children ) + { + addMavenDependency( pkg, childNode, mavenProjectBuilder, session, mavenProject ); + } + } + + abstract void addMavenDependency( CoreModelObject parentPackage, DependencyNode dependencyNode, + ProjectBuilder mavenProjectBuilder, + MavenSession session, MavenProject mavenProject ) + throws LicenseMapperException, InvalidSPDXAnalysisException; + + + /** + * Converts an artifact file to an SPDX file + * + * @param file input file + * @param versionFilter Optional (nullable) version - if present, only return file formats that support the filter version + * @return SPDX file using the SPDX naming conventions if it exists, otherwise return null + */ + protected @Nullable File artifactFileToSpdxFile( @Nullable File file, @Nullable SpdxMajorVersion versionFilter ) + { + if ( Objects.isNull( file ) ) + { + return null; + } + for ( OutputFormat of: OutputFormat.values() ) + { + if ( versionFilter == null || versionFilter.equals( of.getSpecVersion() )) + { + File retval = getFileWithDifferentType( file, of.getFileType() ); + if ( retval.exists() ) { + return retval; + } + } + } + return null; + } + + /** + * Convert a file to a different type (e.g. file.txt -> file.rdf with a type rdf parameter) + * + * @param file Input file + * @param type Type to change to + * @return New file type with only the type changed + */ + private File getFileWithDifferentType( File file, String type ) + { + String filePath = file.getAbsolutePath(); + int indexOfDot = filePath.lastIndexOf( '.' ); + if ( indexOfDot > 0 ) + { + filePath = filePath.substring( 0, indexOfDot + 1 ); + } + filePath = filePath + type; + return new File( filePath ); + } + + private void logDependencies( List dependencies ) + { + if ( !LOG.isDebugEnabled() ) + { + return; + } + LOG.debug( "Dependencies:" ); + if ( dependencies == null ) + { + LOG.debug( "\tNull dependencies" ); + return; + } + if ( dependencies.isEmpty() ) + { + LOG.debug( "\tZero dependencies" ); + return; + } + for ( DependencyNode node : dependencies ) + { + Artifact dependency = node.getArtifact(); + String filePath = dependency.getFile() != null ? dependency.getFile().getAbsolutePath() : "[NONE]"; + String scope = dependency.getScope() != null ? dependency.getScope() : "[NONE]"; + LOG.debug( "ArtifactId: {}, file path: {}, Scope: {}", dependency.getArtifactId(), filePath, scope ); + } + } + + /** + * Make an external document reference ID valid by replacing any invalid characters with dashes + * + * @param externalRefId ID for external reference + * @return valid external ref ID + */ + protected String fixExternalRefId( String externalRefId ) + { + StringBuilder sb = new StringBuilder(); + for ( int i = 0; i < externalRefId.length(); i++ ) + { + if ( validExternalRefIdChar( externalRefId.charAt( i ) ) ) + { + sb.append( externalRefId.charAt( i ) ); + } + else + { + sb.append( "-" ); + } + } + return sb.toString(); + } + + /** + * @param ch character to test + * @return true if the character is valid for use in an External Reference ID + */ + private boolean validExternalRefIdChar( char ch ) + { + return ( ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) || ( ch >= '0' && ch <= '9' ) || ch == '.' || ch == '-' ); + } + +} diff --git a/src/main/java/org/spdx/maven/utils/AbstractDocumentBuilder.java b/src/main/java/org/spdx/maven/utils/AbstractDocumentBuilder.java new file mode 100644 index 0000000..107bb21 --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/AbstractDocumentBuilder.java @@ -0,0 +1,144 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. + */ +package org.spdx.maven.utils; + +import java.io.File; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +import org.apache.maven.model.License; +import org.apache.maven.project.MavenProject; +import org.apache.maven.shared.model.fileset.FileSet; +import org.spdx.core.CoreModelObject; +import org.spdx.library.ModelCopyManager; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.maven.NonStandardLicense; +import org.spdx.maven.OutputFormat; +import org.spdx.storage.ISerializableModelStore; + +/** + * Abstract class to create SPDX documents. + *

+ * Subclasses of this class implement specific SPDX specification versions of the document + * + * @author Gary O'Neall + */ +public abstract class AbstractDocumentBuilder +{ + protected static final String UNSPECIFIED = "UNSPECIFIED"; + public static final String NULL_SHA1 = "cf23df2207d99a74fbe169e3eba035e633b65d94"; + + protected MavenProject project; + protected boolean generatePurls; + protected File spdxFile; + protected OutputFormat outputFormatEnum; + protected ISerializableModelStore modelStore; + protected ModelCopyManager copyManager; + protected DateFormat format = new SimpleDateFormat( SpdxConstantsCompatV2.SPDX_DATE_FORMAT ); + + + /** + * @param project Maven Project + * @param generatePurls If true, generated Package URLs for all package references + * @param spdxFile File to store the SPDX document results + * @param outputFormatEnum File format for the SPDX file + * @throws SpdxBuilderException On errors building the document + */ + public AbstractDocumentBuilder( MavenProject project, boolean generatePurls, File spdxFile, + OutputFormat outputFormatEnum ) throws SpdxBuilderException + { + this.project = project; + this.generatePurls = generatePurls; + this.spdxFile = spdxFile; + this.outputFormatEnum = outputFormatEnum; + copyManager = new ModelCopyManager(); + + // Handle the SPDX file + if ( !spdxFile.exists() ) + { + File parentDir = spdxFile.getParentFile(); + if ( parentDir != null && !parentDir.exists() ) + { + if ( !parentDir.mkdirs() ) + { + throw new SpdxBuilderException( "Unable to create directories for SPDX file" ); + } + } + + try + { + if ( !spdxFile.createNewFile() ) + { + throw new SpdxBuilderException( "Unable to create the SPDX file" ); + } + } + catch ( IOException e ) + { + throw new SpdxBuilderException( "IO error creating the SPDX file", e ); + } + } + if ( !spdxFile.canWrite() ) + { + throw new SpdxBuilderException( "Unable to write to SPDX file - check permissions: " + spdxFile.getPath() ); + } + + } + + /** + * @param projectInformation Information about project extracted from Maven metadata and parameters + * @throws SpdxBuilderException on errors adding document level information + */ + public abstract void fillSpdxDocumentInformation( SpdxProjectInformation projectInformation ) throws SpdxBuilderException; + + /** + * Collect information at the file level, fill in the SPDX document + * + * @param sources Source directories to be included in the document + * @param baseDir project base directory used to construct the relative paths for the SPDX + * files + * @param pathSpecificInformation Map of path to file information used to override the default file information + * @param checksumAlgorithms algorithms to use to generate checksums + * @throws SpdxBuilderException on errors collecting files + */ + public abstract void collectSpdxFileInformation( List sources, String baseDir, + SpdxDefaultFileInformation defaultFileInformation, + HashMap pathSpecificInformation, + Set checksumAlgorithms ) throws SpdxBuilderException; + + /** + * Saves the SPDX document to the file + * @throws SpdxBuilderException On any error saving the file + * + */ + public abstract void saveSpdxDocumentToFile() throws SpdxBuilderException; + + /** + * @param nonStandardLicenses non standard licenses to add + */ + public abstract void addNonStandardLicenses( NonStandardLicense[] nonStandardLicenses ) throws SpdxBuilderException; + + /** + * @return package representing the Mave project + */ + public abstract CoreModelObject getProjectPackage(); + + /** + * @param mavenLicenses list of licenses + * @return license expression representing the list of mavenLicenses + * @throws LicenseManagerException On error converting license + */ + public abstract String mavenLicenseListToSpdxLicenseExpression( List mavenLicenses ) throws LicenseManagerException; + + /** + * Verifies the top level document + * @return list of any errors or warnings + */ + public abstract List verify(); + +} diff --git a/src/main/java/org/spdx/maven/utils/AbstractFileCollector.java b/src/main/java/org/spdx/maven/utils/AbstractFileCollector.java new file mode 100644 index 0000000..f61234d --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/AbstractFileCollector.java @@ -0,0 +1,238 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. + */ +package org.spdx.maven.utils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.Map.Entry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.FileType; +import org.spdx.maven.Checksum; + +/** + * Collects SPDX file information from directories. + *

+ * Concrete subclasses implement specific SPDX spec specific formats + * + * @author Gary O'Neall + */ +public abstract class AbstractFileCollector +{ + protected static final Logger LOG = LoggerFactory.getLogger( AbstractFileCollector.class ); + + // constants for mapping extensions to types. + static final String SPDX_FILE_TYPE_CONSTANTS_PROP_PATH = "resources/SpdxFileTypeConstants.prop"; + + public static final Map EXT_TO_FILE_TYPE = new HashMap<>(); + + static + { + loadFileExtensionConstants(); + } + + public static final Map CHECKSUM_ALGORITHMS = new HashMap<>(); + + static + { + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.SHA1.toString(), "SHA-1" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.SHA224.toString(), "SHA-224" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.SHA256.toString(), "SHA-256" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.SHA384.toString(), "SHA-384" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.SHA3_384.toString(), "SHA-512" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.MD2.toString(), "MD2" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.MD4.toString(), "MD4" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.MD5.toString(), "MD5" ); + CHECKSUM_ALGORITHMS.put( ChecksumAlgorithm.MD6.toString(), "MD6" ); + } + + /** + * Load file type constants from the properties file + */ + private static void loadFileExtensionConstants() + { + Properties prop = new Properties(); + try ( InputStream is = SpdxV2FileCollector.class.getClassLoader().getResourceAsStream( + SPDX_FILE_TYPE_CONSTANTS_PROP_PATH ) ) + { + if ( is == null ) + { + LOG.error( "Unable to load properties file " + SPDX_FILE_TYPE_CONSTANTS_PROP_PATH ); + return; + } + prop.load( is ); + for (Entry entry : prop.entrySet()) { + String fileTypeStr = (String) entry.getKey(); + FileType fileType = FileType.valueOf(fileTypeStr); + String[] extensions = ((String) entry.getValue()).split( "," ); + for (String extension : extensions) { + try { + String trimmedExtension = extension.toUpperCase().trim(); + if (EXT_TO_FILE_TYPE.containsKey(trimmedExtension)) { + LOG.warn( "Duplicate file extension: {}", trimmedExtension ); + } + EXT_TO_FILE_TYPE.put(trimmedExtension, fileType); + } catch (Exception ex) { + LOG.error( "Error adding file extensions to filetype map", ex ); + } + } + } + } + catch ( IOException e ) + { + LOG.warn( + "WARNING: Error reading SpdxFileTypeConstants properties file. All file types will be mapped to Other." ); + } + } + + public String getExtension( File file ) + { + String fileName = file.getName(); + int lastDot = fileName.lastIndexOf( '.' ); + if ( lastDot < 1 ) + { + return ""; + } + else + { + return fileName.substring( lastDot + 1 ); + } + } + + /** + * @param fileTypes list of file types for the file + * @return true if the fileTypes contain a source file type + */ + protected boolean isSourceFile( Collection fileTypes ) + { + for ( FileType ft : fileTypes ) + { + if ( ft == FileType.SOURCE ) + { + return true; + } + } + return false; + } + + /** + * Create the SPDX file name from a system specific path name + * + * @param filePath system specific file path relative to the top of the archive root to the top of the archive + * directory where the file is stored. + * @return valid SPDX file name per the spec + */ + public String convertFilePathToSpdxFileName( String filePath ) + { + String result = filePath.replace( '\\', '/' ); + if ( !result.startsWith( "./" ) ) + { + result = "./" + result; + } + return result; + } + + protected static FileType extensionToFileType( String fileExtension ) + { + return EXT_TO_FILE_TYPE.getOrDefault( fileExtension.trim().toUpperCase(), FileType.OTHER ); + + } + + /** + * Converts an array of bytes to a string compliant with the SPDX sha1 representation + * + * @param digestBytes result of a checksum digest calculation + * @return string representation of the checksum per the SPDX specification + */ + public static String convertChecksumToString( byte[] digestBytes ) + { + StringBuilder sb = new StringBuilder(); + for ( byte digestByte : digestBytes ) + { + String hex = Integer.toHexString( 0xff & digestByte ); + if ( hex.length() < 2 ) + { + sb.append( '0' ); + } + sb.append( hex ); + } + return sb.toString(); + } + + /** + * Generate the Sha1 for a given file. Must have read access to the file. This method is equivalent to calling + * {@code SpdxFileCollector.generateChecksum(file, "SHA-1")}. + * + * @param file file to generate checksum for + * @return SHA1 checksum of the input file + * @throws SpdxCollectionException if the algorithm is unavailable or the file cannot be read + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + public static Checksum generateSha1( File file ) throws SpdxCollectionException, InvalidSPDXAnalysisException + { + Set sha1 = new HashSet<>(); + sha1.add( "SHA-1" ); + return generateChecksum( file, sha1 ).iterator().next(); + } + + /** + * Generate checksums for a given file using each algorithm supplied. Must have read access to the file. + * + * @param file file whose checksum is to be generated + * @param algorithms algorithms to generate the checksums + * @return {@code Set} of checksums for file using each algorithm specified + * @throws SpdxCollectionException if the input algorithm is invalid or unavailable or if the file cannot be read + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + public static Set generateChecksum( File file, Set algorithms ) throws SpdxCollectionException, InvalidSPDXAnalysisException + { + Set checksums = new HashSet<>(); + + byte[] buffer; + try + { + buffer = Files.readAllBytes( file.toPath() ); + } + catch ( IOException e ) + { + throw new SpdxCollectionException( "IO error while calculating checksums.", e ); + } + + for ( String algorithm : algorithms ) + { + String checksumAlgorithm = CHECKSUM_ALGORITHMS.get( algorithm ); + + MessageDigest digest; + try + { + digest = MessageDigest.getInstance( checksumAlgorithm ); + } + catch ( NoSuchAlgorithmException e ) + { + throw new SpdxCollectionException( e ); + } + + digest.update( buffer ); + String checksum = convertChecksumToString( digest.digest() ); + checksums.add( new Checksum( algorithm, checksum ) ); + } + + return checksums; + } + +} diff --git a/src/main/java/org/spdx/maven/utils/LicenseManagerException.java b/src/main/java/org/spdx/maven/utils/LicenseManagerException.java index 16136a6..61957dc 100644 --- a/src/main/java/org/spdx/maven/utils/LicenseManagerException.java +++ b/src/main/java/org/spdx/maven/utils/LicenseManagerException.java @@ -16,7 +16,7 @@ package org.spdx.maven.utils; /** - * Exceptions related to the LicenseManager + * Exceptions related to the SpdxV2LicenseManager * * @author Gary O'Neall */ @@ -29,28 +29,28 @@ public class LicenseManagerException extends Exception private static final long serialVersionUID = 1672757028355331818L; /** - * @param arg0 + * @param msg message */ - public LicenseManagerException( String arg0 ) + public LicenseManagerException( String msg ) { - super( arg0 ); + super( msg ); } /** - * @param arg0 + * @param inner inner exception */ - public LicenseManagerException( Throwable arg0 ) + public LicenseManagerException( Throwable inner ) { - super( arg0 ); + super( inner ); } /** - * @param arg0 - * @param arg1 + * @param msg message + * @param inner inner exception */ - public LicenseManagerException( String arg0, Throwable arg1 ) + public LicenseManagerException( String msg, Throwable inner ) { - super( arg0, arg1 ); + super( msg, inner ); } } diff --git a/src/main/java/org/spdx/maven/utils/LicenseMapperException.java b/src/main/java/org/spdx/maven/utils/LicenseMapperException.java index b6ee99c..2f4ba0e 100644 --- a/src/main/java/org/spdx/maven/utils/LicenseMapperException.java +++ b/src/main/java/org/spdx/maven/utils/LicenseMapperException.java @@ -37,7 +37,7 @@ public LicenseMapperException() } /** - * @param message + * @param message message */ public LicenseMapperException( String message ) { @@ -45,7 +45,7 @@ public LicenseMapperException( String message ) } /** - * @param cause + * @param cause inner exception */ public LicenseMapperException( Throwable cause ) { @@ -53,8 +53,8 @@ public LicenseMapperException( Throwable cause ) } /** - * @param message - * @param cause + * @param message message + * @param cause inner exception */ public LicenseMapperException( String message, Throwable cause ) { diff --git a/src/main/java/org/spdx/maven/utils/MavenToSpdxLicenseMapper.java b/src/main/java/org/spdx/maven/utils/MavenToSpdxLicenseMapper.java index 6847044..dee3991 100644 --- a/src/main/java/org/spdx/maven/utils/MavenToSpdxLicenseMapper.java +++ b/src/main/java/org/spdx/maven/utils/MavenToSpdxLicenseMapper.java @@ -24,17 +24,21 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import org.apache.maven.model.License; - -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.LicenseInfoFactory; -import org.spdx.library.model.license.SpdxListedLicense; -import org.spdx.library.model.license.SpdxNoAssertionLicense; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.license.SpdxListedLicense; +import org.spdx.library.model.v2.license.SpdxNoAssertionLicense; +import org.spdx.library.model.v3_0_1.core.Element; +import org.spdx.library.model.v3_0_1.expandedlicensing.ListedLicense; +import org.spdx.library.model.v3_0_1.expandedlicensing.NoAssertionLicense; +import org.spdx.storage.IModelStore.IdType; import org.spdx.storage.listedlicense.LicenseJsonTOC; import org.slf4j.Logger; @@ -62,7 +66,7 @@ public class MavenToSpdxLicenseMapper private static final String LISTED_LICENSE_JSON_PATH = "resources/licenses.json"; static volatile MavenToSpdxLicenseMapper instance; - private static Object instanceMutex = new Object(); + private static final Object instanceMutex = new Object(); private Map urlStringToSpdxLicenseId; private MavenToSpdxLicenseMapper() throws LicenseMapperException @@ -87,7 +91,13 @@ private MavenToSpdxLicenseMapper() throws LicenseMapperException if ( is == null ) { // use the cached version - is = LicenseManager.class.getClassLoader().getResourceAsStream( LISTED_LICENSE_JSON_PATH ); + is = SpdxV2LicenseManager.class.getClassLoader().getResourceAsStream( LISTED_LICENSE_JSON_PATH ); + } + + if ( is == null ) + { + LOG.error( "Could not load the resource {}", LISTED_LICENSE_JSON_PATH); + throw new LicenseMapperException( "Unable to load the listed licenses file" ); } try (BufferedReader reader = new BufferedReader( new InputStreamReader( is, Charset.defaultCharset() ) )) @@ -130,7 +140,7 @@ public String urlToSpdxId( String url ) * Initialize the urlStringToSpdxLicense map with the SPDX listed licenses * * @param jsonReader Reader for the JSON input file containing the listed licenses - * @throws LicenseMapperException + * @throws LicenseMapperException on errors accessing the listed license or parsing errors */ private void initializeUrlMap( BufferedReader jsonReader ) throws LicenseMapperException { @@ -197,7 +207,7 @@ private void addManualMappings() } /** - * Map a list of Maven licenses to an SPDX license. If no licenses are supplied, SpdxNoAssertion license is + * Map a list of Maven licenses to an SPDX Spec version 2 license. If no licenses are supplied, SpdxNoAssertion license is * returned. if a single license is supplied, and a URL can be found matching a listed license, the listed license * is returned. if a single license is supplied, and a URL can not be found matching a listed license, * SpdxNoAssertion is returned. If multiple licenses are supplied, a conjunctive license is returned containing all @@ -205,10 +215,10 @@ private void addManualMappings() * * @param licenseList list of licenses * @param spdxDoc SPDX document which will hold the licenses - * @return - * @throws InvalidSPDXAnalysisException + * @return SPDX license which matches the list of maven licenses + * @throws InvalidSPDXAnalysisException on SPDX parsing errors */ - public AnyLicenseInfo mavenLicenseListToSpdxLicense( List licenseList, SpdxDocument spdxDoc ) throws InvalidSPDXAnalysisException + public AnyLicenseInfo mavenLicenseListToSpdxV2License( List licenseList, SpdxDocument spdxDoc ) throws InvalidSPDXAnalysisException { if ( licenseList == null ) { @@ -217,13 +227,13 @@ public AnyLicenseInfo mavenLicenseListToSpdxLicense( List licenseList, List spdxLicenses = new ArrayList<>(); for ( License license : licenseList ) { - SpdxListedLicense listedLicense = mavenLicenseToSpdxListedLicense( license ); + SpdxListedLicense listedLicense = mavenLicenseToSpdxV2ListedLicense( license ); if ( listedLicense != null ) { spdxLicenses.add( listedLicense ); } } - if ( spdxLicenses.size() < 1 ) + if (spdxLicenses.isEmpty()) { return new SpdxNoAssertionLicense(); } @@ -233,12 +243,80 @@ else if ( spdxLicenses.size() == 1 ) } else { - AnyLicenseInfo conjunctiveLicense = spdxDoc.createConjunctiveLicenseSet( spdxLicenses ); - return conjunctiveLicense; + return spdxDoc.createConjunctiveLicenseSet( spdxLicenses ); + } + } + + private SpdxListedLicense mavenLicenseToSpdxV2ListedLicense( License license ) + { + if ( license == null ) + { + return null; + } + if ( license.getUrl() == null || license.getUrl().isEmpty() ) + { + return null; + } + String spdxId = this.urlStringToSpdxLicenseId.get( license.getUrl().replaceAll( "https", "http" ) ); + if ( spdxId == null ) + { + return null; + } + try + { + return LicenseInfoFactory.getListedLicenseByIdCompatV2( spdxId ); + } + catch ( InvalidSPDXAnalysisException e ) + { + return null; + } + } + + /** + * Map a list of Maven licenses to an SPDX Spec version 2 license. If no licenses are supplied, SpdxNoAssertion license is + * returned. if a single license is supplied, and a URL can be found matching a listed license, the listed license + * is returned. if a single license is supplied, and a URL can not be found matching a listed license, + * SpdxNoAssertion is returned. If multiple licenses are supplied, a conjunctive license is returned containing all + * mapped SPDX licenses. + * + * @param licenseList list of Maven licenses + * @param spdxDoc SPDX document which will hold the licenses + * @return SPDX version 3 license equivalent to the list of Maven licenses + * @throws InvalidSPDXAnalysisException On SPDX parsing errors + */ + public org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo mavenLicenseListToSpdxV3License( List licenseList, + Element spdxDoc ) throws InvalidSPDXAnalysisException + { + if ( licenseList == null ) + { + return new NoAssertionLicense(); + } + List spdxLicenses = new ArrayList<>(); + for ( License license : licenseList ) + { + ListedLicense listedLicense = mavenLicenseToSpdxV3ListedLicense( license ); + if ( listedLicense != null ) + { + spdxLicenses.add( listedLicense ); + } + } + if (spdxLicenses.isEmpty()) + { + return new NoAssertionLicense(); + } + else if ( spdxLicenses.size() == 1 ) + { + return spdxLicenses.get( 0 ); + } + else + { + return spdxDoc.createConjunctiveLicenseSet( spdxDoc.getModelStore().getNextId( IdType.Anonymous ) ) + .addAllMember( new HashSet<>( spdxLicenses ) ) + .build(); } } - private SpdxListedLicense mavenLicenseToSpdxListedLicense( License license ) + private ListedLicense mavenLicenseToSpdxV3ListedLicense( License license ) { if ( license == null ) { diff --git a/src/main/java/org/spdx/maven/utils/SpdxBuilderException.java b/src/main/java/org/spdx/maven/utils/SpdxBuilderException.java index f5ccf93..bf53eab 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxBuilderException.java +++ b/src/main/java/org/spdx/maven/utils/SpdxBuilderException.java @@ -31,7 +31,7 @@ public class SpdxBuilderException extends MojoExecutionException private static final long serialVersionUID = 1L; /** - * @param message + * @param message message */ public SpdxBuilderException( String message ) { @@ -39,8 +39,8 @@ public SpdxBuilderException( String message ) } /** - * @param message - * @param cause + * @param message message + * @param cause inner exception */ public SpdxBuilderException( String message, Throwable cause ) { diff --git a/src/main/java/org/spdx/maven/utils/SpdxCollectionException.java b/src/main/java/org/spdx/maven/utils/SpdxCollectionException.java index 1d5d8ca..cc78566 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxCollectionException.java +++ b/src/main/java/org/spdx/maven/utils/SpdxCollectionException.java @@ -37,7 +37,7 @@ public SpdxCollectionException() } /** - * @param message + * @param message message */ public SpdxCollectionException( String message ) { @@ -45,7 +45,7 @@ public SpdxCollectionException( String message ) } /** - * @param cause + * @param cause inner exception */ public SpdxCollectionException( Throwable cause ) { @@ -53,8 +53,8 @@ public SpdxCollectionException( Throwable cause ) } /** - * @param message - * @param cause + * @param message message + * @param cause inner exception */ public SpdxCollectionException( String message, Throwable cause ) { diff --git a/src/main/java/org/spdx/maven/utils/SpdxDefaultFileInformation.java b/src/main/java/org/spdx/maven/utils/SpdxDefaultFileInformation.java index d726834..ed32e7a 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxDefaultFileInformation.java +++ b/src/main/java/org/spdx/maven/utils/SpdxDefaultFileInformation.java @@ -18,10 +18,6 @@ import java.util.ArrayList; import java.util.List; -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.SpdxNoAssertionLicense; - import org.spdx.maven.SnippetInfo; import org.slf4j.Logger; @@ -36,27 +32,27 @@ public class SpdxDefaultFileInformation { private static final Logger LOG = LoggerFactory.getLogger( SpdxDefaultFileInformation.class ); - private AnyLicenseInfo declaredLicense; + private String declaredLicense; private String copyright = "NOASSERTION"; private String notice = ""; private String comment = ""; private String[] contributors = new String[0]; - private AnyLicenseInfo concludedLicense; + private String concludedLicense; private String licenseComment = ""; private List snippets = new ArrayList<>(); - public SpdxDefaultFileInformation() throws InvalidSPDXAnalysisException + public SpdxDefaultFileInformation() { - declaredLicense = new SpdxNoAssertionLicense(); - concludedLicense = new SpdxNoAssertionLicense(); + declaredLicense = "NOASSERTION"; + concludedLicense = "NOASSERTION"; } - public AnyLicenseInfo getDeclaredLicense() + public String getDeclaredLicense() { return this.declaredLicense; } - public void setDeclaredLicense( AnyLicenseInfo license ) + public void setDeclaredLicense( String license ) { this.declaredLicense = license; } @@ -101,12 +97,12 @@ public void setContributors( String[] contributors ) this.contributors = contributors; } - public AnyLicenseInfo getConcludedLicense() + public String getConcludedLicense() { return this.concludedLicense; } - public void setConcludedLicense( AnyLicenseInfo license ) + public void setConcludedLicense( String license ) { this.concludedLicense = license; } @@ -143,17 +139,17 @@ public void setSnippets( List snippets ) */ public void logInfo() { - LOG.debug( "Default File Comment: " + getComment() ); - LOG.debug( "Default File Copyright: " + getCopyright() ); - LOG.debug( "Default File License Comment: " + getLicenseComment() ); - LOG.debug( "Default File Notice: " + getNotice() ); - LOG.debug( "Default File Concluded License: " + getConcludedLicense().toString() ); - LOG.debug( "Default File Declared License: " + getDeclaredLicense().toString() ); + LOG.debug( "Default File Comment: {}", getComment() ); + LOG.debug( "Default File Copyright: {}", getCopyright() ); + LOG.debug( "Default File License Comment: {}", getLicenseComment() ); + LOG.debug( "Default File Notice: {}", getNotice() ); + LOG.debug( "Default File Concluded License: {}", getConcludedLicense() ); + LOG.debug( "Default File Declared License: {}", getDeclaredLicense() ); if ( contributors != null ) { for ( String contributor : contributors ) { - LOG.debug( "Default File Contributors: " + contributor ); + LOG.debug( "Default File Contributors: {}", contributor ); } } if ( this.snippets != null ) diff --git a/src/main/java/org/spdx/maven/utils/SpdxDependencyInformation.java b/src/main/java/org/spdx/maven/utils/SpdxDependencyInformation.java deleted file mode 100644 index dd77eea..0000000 --- a/src/main/java/org/spdx/maven/utils/SpdxDependencyInformation.java +++ /dev/null @@ -1,849 +0,0 @@ -/* - * Copyright 2014 Source Auditor Inc. - * - * Licensed 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 - * - * http://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.spdx.maven.utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.security.NoSuchAlgorithmException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import javax.annotation.Nullable; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.repository.ArtifactRepository; -import org.apache.maven.execution.MavenSession; -import org.apache.maven.model.Contributor; -import org.apache.maven.model.License; -import org.apache.maven.model.Model; -import org.apache.maven.model.Resource; -import org.apache.maven.project.DefaultProjectBuildingRequest; -import org.apache.maven.project.MavenProject; -import org.apache.maven.project.ProjectBuilder; -import org.apache.maven.project.ProjectBuildingException; -import org.apache.maven.project.ProjectBuildingRequest; -import org.apache.maven.project.ProjectBuildingResult; -import org.apache.maven.shared.dependency.graph.DependencyNode; -import org.apache.maven.shared.model.fileset.FileSet; -import org.codehaus.plexus.util.xml.pull.XmlPullParserException; -import org.spdx.jacksonstore.MultiFormatStore; -import org.spdx.jacksonstore.MultiFormatStore.Format; -import org.spdx.jacksonstore.MultiFormatStore.Verbose; -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.SpdxConstants; -import org.spdx.library.SpdxInvalidIdException; -import org.spdx.library.model.Checksum; -import org.spdx.library.model.ExternalDocumentRef; -import org.spdx.library.model.ExternalSpdxElement; -import org.spdx.library.model.Relationship; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.SpdxElement; -import org.spdx.library.model.SpdxPackage; -import org.spdx.library.model.enumerations.AnnotationType; -import org.spdx.library.model.enumerations.ChecksumAlgorithm; -import org.spdx.library.model.enumerations.Purpose; -import org.spdx.library.model.enumerations.RelationshipType; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.SpdxNoAssertionLicense; - -import org.spdx.maven.CreateSpdxMojo; -import org.spdx.spdxRdfStore.RdfStore; -import org.spdx.storage.IModelStore.IdType; -import org.spdx.storage.ISerializableModelStore; -import org.spdx.storage.simple.InMemSpdxStore; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Contains information about package dependencies collected from the Maven dependencies. - * - * @author Gary O'Neall - */ -public class SpdxDependencyInformation -{ - private static final Logger LOG = LoggerFactory.getLogger( SpdxDependencyInformation.class ); - - /** - * List of all Relationships added for dependances To a related element - */ - private Map> relationships = new HashMap<>(); - - /** - * Map of namespaces to ExternalDocumentRefs - */ - private Map externalDocuments = new HashMap<>(); - private List documentAnnotations = new ArrayList<>(); - private LicenseManager licenseManager; - private SpdxDocument spdxDoc; - private boolean createExternalRefs = false; - private boolean generatePurls = false; - private boolean useArtifactID = false; - private boolean includeTransitiveDependencies = false; - DateFormat format = new SimpleDateFormat( SpdxConstants.SPDX_DATE_FORMAT ); - - /** - */ - public SpdxDependencyInformation( LicenseManager licenseManager, - SpdxDocument spdxDoc, boolean createExternalRefs, boolean generatePurls, boolean useArtifactID, - boolean includeTransitiveDependencies ) - { - this.licenseManager = licenseManager; - this.spdxDoc = spdxDoc; - this.createExternalRefs = createExternalRefs; - this.generatePurls = generatePurls; - this.useArtifactID = useArtifactID; - this.includeTransitiveDependencies = includeTransitiveDependencies; - } - - /** - * Adds information about Maven dependencies to the list of SPDX Dependencies - * - * @param mavenProjectBuilder project builder for the repo containing the POM file - * @param session Maven session for building the project - * @param mavenProject Maven project - */ - public void addMavenDependencies( ProjectBuilder mavenProjectBuilder, MavenSession session, MavenProject mavenProject, - DependencyNode node, SpdxElement pkg ) throws LicenseMapperException, InvalidSPDXAnalysisException - { - List children = node.getChildren(); - - logDependencies( children ); - - for ( DependencyNode childNode : children ) - { - addMavenDependency( pkg, childNode, mavenProjectBuilder, session, mavenProject ); - } - } - - private void addMavenDependency( SpdxElement parentPackage, DependencyNode dependencyNode, ProjectBuilder mavenProjectBuilder, - MavenSession session, MavenProject mavenProject ) - throws LicenseMapperException, InvalidSPDXAnalysisException - { - Artifact dependency = dependencyNode.getArtifact(); - String scope = dependency.getScope(); - RelationshipType relType = scopeToRelationshipType( scope, dependency.isOptional() ); - if ( relType == RelationshipType.OTHER ) - { - LOG.warn( - "Could not determine the SPDX relationship type for dependency artifact ID " + dependency.getArtifactId() + " scope " + scope ); - } - - SpdxElement dependencyPackage = createSpdxPackage( dependency, mavenProjectBuilder, session, mavenProject, useArtifactID ); - - if ( relType.toString().endsWith( "_OF" ) ) - { - if ( dependencyPackage instanceof SpdxPackage ) - { - this.relationships.computeIfAbsent( parentPackage, key -> new ArrayList<>() ) - .add( spdxDoc.createRelationship( dependencyPackage, relType, - "Relationship created based on Maven POM information" ) ); - LOG.debug( "Added relationship of type " + relType + " for " + dependencyPackage.getName() ); - } - else - { - this.relationships.computeIfAbsent( dependencyPackage, key -> new ArrayList<>() ) - .add( spdxDoc.createRelationship( parentPackage, RelationshipType.OTHER, - "This relationship is the inverse of " + relType + " to an external document reference." ) ); - LOG.debug( "Could not create proper to relationships for external element " + dependencyPackage.getId() ); - } - } - else - { - this.relationships.computeIfAbsent( parentPackage, key -> new ArrayList<>() ) - .add( spdxDoc.createRelationship( dependencyPackage, relType, - "Relationship based on Maven POM file dependency information" ) ); - } - - if ( includeTransitiveDependencies ) { - addMavenDependencies( mavenProjectBuilder, session, mavenProject, dependencyNode, dependencyPackage ); - } - } - - private void logDependencies( List dependencies ) - { - if ( !LOG.isDebugEnabled() ) - { - return; - } - LOG.debug( "Dependencies:" ); - if ( dependencies == null ) - { - LOG.debug( "\tNull dependencies" ); - return; - } - if ( dependencies.isEmpty() ) - { - LOG.debug( "\tZero dependencies" ); - return; - } - for ( DependencyNode node : dependencies ) - { - Artifact dependency = node.getArtifact(); - String filePath = dependency.getFile() != null ? dependency.getFile().getAbsolutePath() : "[NONE]"; - String scope = dependency.getScope() != null ? dependency.getScope() : "[NONE]"; - LOG.debug( - "ArtifactId: " + dependency.getArtifactId() + ", file path: " + filePath + ", Scope: " + scope ); - } - } - - /** - * Translate the scope to the SPDX relationship type - * - * @param scope Maven Dependency Scope (see https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope) - * @param optional True if this is an optional dependency - * @return - */ - private RelationshipType scopeToRelationshipType( String scope, boolean optional ) - { - if ( scope == null ) - { - return RelationshipType.OTHER; - } - else if ( optional ) - { - return RelationshipType.OPTIONAL_COMPONENT_OF; - } - else if ( scope.equals( "compile" ) || scope.equals( "runtime" ) ) - { - return RelationshipType.DYNAMIC_LINK; - } - else if ( scope.equals( "test" ) ) - { - return RelationshipType.TEST_DEPENDENCY_OF; - } - else - { - return RelationshipType.OTHER; - } - } - - /** - * Create an SPDX Document using the mavenProjectBuilder to resolve properties - * including inherited properties - * @param artifact Maven dependency artifact - * @param mavenProjectBuilder project builder for the repo containing the POM file - * @param session Maven session for building the project - * @param mavenProject Maven project - * @param useArtifactID If true, use ${project.groupId}:${artifactId} as the SPDX package name, otherwise, ${project.name} will be used - * @return SPDX Package build from the MavenProject metadata - * @throws LicenseMapperException - * @throws InvalidSPDXAnalysisException - */ - private SpdxElement createSpdxPackage( Artifact artifact, - ProjectBuilder mavenProjectBuilder, MavenSession session, - MavenProject mavenProject, boolean useArtifactID ) throws LicenseMapperException, InvalidSPDXAnalysisException - { - LOG.debug( "Creating SPDX package for artifact " + artifact.getArtifactId() ); - if ( artifact.getFile() == null ) - { - LOG.debug( "Artifact file is null" ); - } - else - { - LOG.debug( "Artifact file name = " + artifact.getFile().getName() ); - } - File spdxFile = null; - if ( artifact.getFile() != null ) - { - spdxFile = artifactFileToSpdxFile( artifact.getFile() ); - } - if ( spdxFile != null && spdxFile.exists() ) - { - LOG.debug( - "Dependency " + artifact.getArtifactId() + "Looking for SPDX file " + spdxFile.getAbsolutePath() ); - try - { - LOG.debug( - "Dependency " + artifact.getArtifactId() + "Dependency information collected from SPDX file " + spdxFile.getAbsolutePath() ); - - SpdxDocument externalSpdxDoc = spdxDocumentFromFile( spdxFile.getPath() ); - if ( createExternalRefs ) - { - return createExternalSpdxPackageReference( externalSpdxDoc, spdxFile, artifact.getGroupId(), - artifact.getArtifactId(), artifact.getVersion() ); - } - else - { - return copyPackageInfoFromExternalDoc( externalSpdxDoc, artifact.getGroupId(), - artifact.getArtifactId(), artifact.getVersion() ); - } - } - catch ( IOException e ) - { - LOG.warn( - "IO error reading SPDX document for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() + ". Using POM file information for creating SPDX package data." ); - } - catch ( SpdxInvalidIdException e ) - { - LOG.warn( - "Invalid SPDX ID exception reading SPDX document for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() + ". Using POM file information for creating SPDX package data." ); - } - catch ( InvalidSPDXAnalysisException e ) - { - LOG.warn( - "Invalid SPDX analysis exception reading SPDX document for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() + ". Using POM file information for creating SPDX package data." ); - } - catch ( SpdxCollectionException e ) - { - LOG.warn( - "Unable to create file checksum for external SPDX document for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() + ". Using POM file information for creating SPDX package data." ); - } - catch ( Exception e ) - { - LOG.warn( - "Unknown error processing SPDX document for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() + ". Using POM file information for creating SPDX package data." ); - } - } - try - { - ProjectBuildingRequest request = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() ); - request.setRemoteRepositories( mavenProject.getRemoteArtifactRepositories() ); - for ( ArtifactRepository ar : request.getRemoteRepositories() ) { - LOG.debug( "request Remote repository ID: " + ar.getId() ); - } - for ( ArtifactRepository ar : mavenProject.getRemoteArtifactRepositories() ) { - LOG.debug( "Project Remote repository ID: " + ar.getId() ); - } - ProjectBuildingResult build = mavenProjectBuilder.build( artifact, request ); - MavenProject depProject = build.getProject(); - LOG.debug( - "Dependency " + artifact.getArtifactId() + "Collecting information from project metadata for " + depProject.getArtifactId() ); - return createSpdxPackage( depProject, useArtifactID ); - } - catch ( SpdxCollectionException e ) - { - LOG.error( - "SPDX File Collection Error creating SPDX package for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() ); - } - catch ( NoSuchAlgorithmException e ) - { - LOG.error( - "Verification Code Error creating SPDX package for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() ); - } - catch ( ProjectBuildingException e ) - { - LOG.error( - "Maven Project Build Error creating SPDX package for dependency artifact ID " + artifact.getArtifactId() + ":" + e.getMessage() ); - } - LOG.warn( - "Error creating SPDX package for dependency artifact ID " + artifact.getArtifactId() + ". A minimal SPDX package will be created." ); - // Create a minimal SPDX package from dependency - // Name will be the artifact ID - LOG.debug( - "Dependency " + artifact.getArtifactId() + "Using only artifact information to create dependent package" ); - SpdxPackage pkg = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId, spdxDoc.getDocumentUri() ), - artifact.getArtifactId(), new SpdxNoAssertionLicense(), "NOASSERTION", - new SpdxNoAssertionLicense() ) - .setComment( "This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file." ) - .setVersionInfo( artifact.getBaseVersion() ) - .setFilesAnalyzed( false ) - .setDownloadLocation( "NOASSERTION" ) - .setExternalRefs( SpdxExternalRefBuilder.getDefaultExternalRefs( spdxDoc, generatePurls, mavenProject ) ) - .build(); - return pkg; - } - - /** - * Copies the closest matching described package in the externalSpdxDoc to the returned element - * @param externalSpdxDoc - * @param groupId Group ID of the artifact - * @param artifactId Artifact ID to search for - * @param version Version of the artifact - * @return SPDX Package with values copied from the externalSpdxDoc - * @throws InvalidSPDXAnalysisException - */ - private SpdxPackage copyPackageInfoFromExternalDoc( SpdxDocument externalSpdxDoc, String groupId, - String artifactId, String version ) throws InvalidSPDXAnalysisException - { - SpdxPackage source = findMatchingDescribedPackage( externalSpdxDoc, artifactId ); - Optional downloadLocation = source.getDownloadLocation(); - Optional name = source.getName(); - SpdxPackage dest = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId, spdxDoc.getDocumentUri()), - name.isPresent() ? name.get() : "NONE", source.getLicenseConcluded(), source.getCopyrightText(), - source.getLicenseDeclared() ) - .setFilesAnalyzed( false ) - .setAnnotations( source.getAnnotations() ) - .setChecksums( source.getChecksums() ) - .setDownloadLocation( downloadLocation.isPresent() ? downloadLocation.get() : "NOASSERTION" ) - .setExternalRefs( source.getExternalRefs() ) - .build(); - // We don't want to copy any of the properties which have other elements since it - // may duplicate artifacts already included in the document - so we can't use copyFrom - - Optional builtDate = source.getBuiltDate(); - if ( builtDate.isPresent() ) - { - dest.setBuiltDate( builtDate.get() ); - } - Optional comment = source.getComment(); - if ( comment.isPresent() ) - { - dest.setComment( comment.get() ); - } - Optional desc = source.getDescription(); - if ( desc.isPresent() ) - { - dest.setDescription( desc.get() ); - } - Optional homePage = source.getHomepage(); - if ( homePage.isPresent() ) - { - dest.setHomepage( homePage.get() ); - } - Optional licenseComments = source.getLicenseComments(); - if ( licenseComments.isPresent() ) - { - dest.setLicenseComments( licenseComments.get() ); - } - Optional originator = source.getOriginator(); - if ( originator.isPresent() ) - { - dest.setOriginator( originator.get() ); - } - Optional pkgFileName = source.getPackageFileName(); - if ( pkgFileName.isPresent() ) - { - dest.setPackageFileName( pkgFileName.get() ); - } - Optional primaryPurpose = source.getPrimaryPurpose(); - if ( primaryPurpose.isPresent() ) - { - dest.setPrimaryPurpose( primaryPurpose.get() ); - } - Optional releaseDate = source.getReleaseDate(); - if ( releaseDate.isPresent() ) - { - dest.setReleaseDate( releaseDate.get() ); - } - Optional sourceInfo = source.getSourceInfo(); - if ( sourceInfo.isPresent() ) - { - dest.setSourceInfo( sourceInfo.get() ); - } - Optional summary = source.getSummary(); - if ( summary.isPresent() ) - { - dest.setSummary( summary.get() ); - } - Optional supplier = source.getSupplier(); - if ( supplier.isPresent() ) { - dest.setSupplier( supplier.get() ); - } - Optional validUntil = source.getValidUntilDate(); - if ( validUntil.isPresent() ) - { - dest.setValidUntilDate( validUntil.get() ); - } - Optional versionInfo = source.getVersionInfo(); - if ( versionInfo.isPresent() ) - { - dest.setVersionInfo( versionInfo.get() ); - } - return dest; - } - - /** - * Searched the described packages for the SPDX document for the closest matching package to the artifactId - * @param externalSpdxDoc Doc containing the package - * @param artifactId Maven artifact ID - * @return the closest matching package described by the doc - * @throws InvalidSPDXAnalysisException - */ - private SpdxPackage findMatchingDescribedPackage( SpdxDocument externalSpdxDoc, String artifactId ) throws InvalidSPDXAnalysisException - { - SpdxElement itemDescribed = null; - // Find an item described with matching artifact ID - for ( SpdxElement item : externalSpdxDoc.getDocumentDescribes() ) - { - Optional name = item.getName(); - if ( item instanceof SpdxPackage && name.isPresent() && item.getName().get().equals( artifactId ) ) - { - itemDescribed = item; - break; - } - } - if ( itemDescribed == null ) { - // Find the first package - LOG.warn( "Could not find matching artifact ID in SPDX file for "+artifactId+". Using the first package found in SPDX file." ); - for ( SpdxElement item : externalSpdxDoc.getDocumentDescribes() ) - { - if ( item instanceof SpdxPackage ) - { - itemDescribed = item; - break; - } - } - } - if ( itemDescribed == null ) { - throw new InvalidSPDXAnalysisException( "SPDX document does not contain any described items." ); - } - return (SpdxPackage)itemDescribed; - } - - /** - * Creates an SPDX document from a file - * @param path Path to the SPDX file - * @return - * @throws IOException - * @throws FileNotFoundException - * @throws InvalidSPDXAnalysisException - */ - private SpdxDocument spdxDocumentFromFile( String path ) throws FileNotFoundException, IOException, InvalidSPDXAnalysisException - { - ISerializableModelStore modelStore; - if ( path.toLowerCase().endsWith( "json" ) ) - { - modelStore = new MultiFormatStore(new InMemSpdxStore(), Format.JSON_PRETTY, Verbose.COMPACT); - } - else - { - modelStore = new RdfStore(); - } - try ( InputStream inputStream = new FileInputStream( path ) ) - { - String documentUri = modelStore.deSerialize( inputStream, false ); - return new SpdxDocument(modelStore, documentUri, spdxDoc.getCopyManager(), false); - } - finally - { - if ( modelStore != null ) { - try - { - modelStore.close(); - } - catch ( Exception e ) - { - LOG.error( "Error closing SPDX model store", e ); - } - } - } - } - - /** - * Create and return an external document reference for an existing package in an SPDX document - * - * @param externalSpdxDoc SPDX Document containing the package to be referenced. - * @param spdxFile SPDX file containing the SPDX document - * @param groupId Group ID for the external artifact - * @param artifactId Artifact ID for the external artifact - * @param version version for the external artifact - * @return created SPDX element - * @throws SpdxCollectionException - * @throws InvalidSPDXAnalysisException - */ - private SpdxElement createExternalSpdxPackageReference( SpdxDocument externalSpdxDoc, - File spdxFile, - String groupId, - String artifactId, - @Nullable String version ) throws SpdxCollectionException, InvalidSPDXAnalysisException - { - String externalDocNamespace = externalSpdxDoc.getDocumentUri(); - ExternalDocumentRef externalRef = this.externalDocuments.get( externalDocNamespace ); - StringBuilder sb = new StringBuilder( groupId ).append( artifactId ); - if ( Objects.nonNull( version )) { - sb.append( version ); - } - String fullArtifactId = sb.toString(); - if ( externalRef == null ) - { - String externalRefDocId = SpdxConstants.EXTERNAL_DOC_REF_PRENUM + fixExternalRefId( fullArtifactId ); - LOG.debug( "Creating external document ref " + externalDocNamespace ); - String sha1 = SpdxFileCollector.generateSha1( spdxFile, spdxDoc ); - Checksum cksum = externalSpdxDoc.createChecksum( ChecksumAlgorithm.SHA1, sha1 ); - externalRef = spdxDoc.createExternalDocumentRef( externalRefDocId, externalSpdxDoc.getDocumentUri(), cksum ); - spdxDoc.getExternalDocumentRefs().add( externalRef ); - org.spdx.library.model.Annotation docRefAddedAnnotation = spdxDoc.createAnnotation( "Tool: spdx-maven-plugin", - AnnotationType.OTHER, - format.format( new Date() ), - "External document ref '"+externalRefDocId+"' created for artifact "+fullArtifactId ); - spdxDoc.getAnnotations().add( docRefAddedAnnotation ); - this.documentAnnotations.add( docRefAddedAnnotation ); - this.externalDocuments.put( externalDocNamespace, externalRef ); - LOG.debug( "Created external document ref " + externalRefDocId ); - } - SpdxPackage pkg = findMatchingDescribedPackage( externalSpdxDoc, artifactId ); - return new ExternalSpdxElement( spdxDoc.getModelStore(), spdxDoc.getDocumentUri(), - externalRef.getId() + ":" + pkg.getId(), spdxDoc.getCopyManager(), true ); - } - - /** - * Make an external document reference ID valid by replacing any invalid characters with dashes - * - * @param externalRefId - * @return - */ - private String fixExternalRefId( String externalRefId ) - { - StringBuilder sb = new StringBuilder(); - for ( int i = 0; i < externalRefId.length(); i++ ) - { - if ( validExternalRefIdChar( externalRefId.charAt( i ) ) ) - { - sb.append( externalRefId.charAt( i ) ); - } - else - { - sb.append( "-" ); - } - } - return sb.toString(); - } - - - /** - * @param ch character to test - * @return true if the character is valid for use in an External Reference ID - */ - private boolean validExternalRefIdChar( char ch ) - { - return ( ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) || ( ch >= '0' && ch <= '9' ) || ch == '.' || ch == '-' ); - } - - /** - * Create an SPDX package from the information in a Maven Project - * - * @param project Maven project - * @param useArtifactID If true, use ${project.groupId}:${artifactId} as the SPDX package name, otherwise, ${project.name} will be used - * @return SPDX Package generated from the metadata in the Maven Project - * @throws XmlPullParserException - * @throws IOException - * @throws SpdxCollectionException - * @throws NoSuchAlgorithmException - * @throws LicenseMapperException - * @throws InvalidSPDXAnalysisException - */ - private SpdxPackage createSpdxPackage( MavenProject project, boolean useArtifactID ) throws SpdxCollectionException, NoSuchAlgorithmException, LicenseMapperException, InvalidSPDXAnalysisException - { - SpdxDefaultFileInformation fileInfo = new SpdxDefaultFileInformation(); - - // initialize the SPDX information from the project - String packageName = project.getName(); - if ( packageName == null || packageName.isEmpty() || useArtifactID ) - { - packageName = project.getGroupId() + ":" + project.getArtifactId(); - } - List contributors = project.getContributors(); - ArrayList fileContributorList = new ArrayList<>(); - if ( contributors != null ) - { - for ( Contributor contributor : contributors ) - { - fileContributorList.add( contributor.getName() ); - } - } - String copyright = "UNSPECIFIED"; - String notice = "UNSPECIFIED"; - String downloadLocation = "NOASSERTION"; - AnyLicenseInfo declaredLicense = mavenLicensesToSpdxLicense( project.getLicenses() ); - fileInfo.setComment( "" ); - fileInfo.setConcludedLicense( new SpdxNoAssertionLicense() ); - fileInfo.setContributors( fileContributorList.toArray( new String[0] ) ); - fileInfo.setCopyright( copyright ); - fileInfo.setDeclaredLicense( declaredLicense ); - fileInfo.setLicenseComment( "" ); - fileInfo.setNotice( notice ); - - SpdxPackage retval = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId, spdxDoc.getDocumentUri() ), - packageName, new SpdxNoAssertionLicense(), copyright, declaredLicense ) - .setDownloadLocation( downloadLocation ) - .setFilesAnalyzed( false ) - .setExternalRefs( SpdxExternalRefBuilder.getDefaultExternalRefs( spdxDoc, generatePurls, project ) ) - .build(); - if ( project.getVersion() != null ) - { - retval.setVersionInfo( project.getVersion() ); - } - if ( project.getDescription() != null ) - { - retval.setDescription( project.getDescription() ); - retval.setSummary( project.getDescription() ); - } - if ( project.getOrganization() != null ) - { - retval.setOriginator( SpdxConstants.CREATOR_PREFIX_ORGANIZATION + project.getOrganization().getName() ); - } - if ( project.getUrl() != null ) - { - try { - retval.setHomepage( project.getUrl() ); - } catch ( InvalidSPDXAnalysisException e ) { - LOG.warn( "Invalid homepage for dependency " + project.getArtifactId() + ": " + project.getUrl() ); - } - } - return retval; - } - - /** - * Convert a list of Maven licenses to an SPDX License - * - * @param mavenLicenses List of maven licenses to map - * @return - * @throws LicenseMapperException - * @throws InvalidSPDXAnalysisException - * @throws LicenseManagerException - */ - private AnyLicenseInfo mavenLicensesToSpdxLicense( List mavenLicenses ) throws LicenseMapperException, InvalidSPDXAnalysisException - { - try - { - // The call below will map non-standard licenses as well as standard licenses - // but will throw an exception if no mapping is found - we'll try this first - // and if there is an error, try just the standard license mapper which will - // return an UNSPECIFIED license type if there is no mapping - return this.licenseManager.mavenLicenseListToSpdxLicense( mavenLicenses ); - } - catch ( LicenseManagerException ex ) - { - return MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxLicense( mavenLicenses, spdxDoc ); - } - - } - - /** - * Get filsets of files included in the project from the Maven model - * - * @param model Maven model - * @return Source file set and resource filesets - */ - @SuppressWarnings( "unused" ) - private FileSet[] getIncludedDirectoriesFromModel( Model model ) - { - //TODO: This can be refactored to common code from the CreateSpdxMojo - ArrayList result = new ArrayList<>(); - String sourcePath = model.getBuild().getSourceDirectory(); - if ( sourcePath != null && !sourcePath.isEmpty() ) - { - FileSet srcFileSet = new FileSet(); - File sourceDir = new File( sourcePath ); - srcFileSet.setDirectory( sourceDir.getAbsolutePath() ); - srcFileSet.addInclude( CreateSpdxMojo.INCLUDE_ALL ); - result.add( srcFileSet ); - } - - List resourceList = model.getBuild().getResources(); - if ( resourceList != null ) - { - for ( Resource resource : resourceList ) - { - FileSet resourceFileSet = new FileSet(); - File resourceDir = new File( resource.getDirectory() ); - resourceFileSet.setDirectory( resourceDir.getAbsolutePath() ); - resourceFileSet.setExcludes( resource.getExcludes() ); - resourceFileSet.setIncludes( resource.getIncludes() ); - result.add( resourceFileSet ); - } - } - return result.toArray( new FileSet[0] ); - } - - /** - * Converts an artifact file to an SPDX file - * - * @param file input file - * @return SPDX file using the SPDX naming conventions - */ - private File artifactFileToSpdxFile( File file ) - { - File retval = getFileWithDifferentType( file, "spdx.rdf.xml" ); - if ( retval == null || !retval.exists() ) - { - retval = getFileWithDifferentType( file, "spdx.json" ); - } - if ( retval == null || !retval.exists() ) - { - retval = getFileWithDifferentType( file, "spdx" ); - } - return retval; - } - - /** - * Convert a file to a different type (e.g. file.txt -> file.rdf with a type rdf parameter) - * - * @param file Input file - * @param type Type to change to - * @return New file type with only the type changed - */ - private File getFileWithDifferentType( File file, String type ) - { - String filePath = file.getAbsolutePath(); - int indexOfDot = filePath.lastIndexOf( '.' ); - if ( indexOfDot > 0 ) - { - filePath = filePath.substring( 0, indexOfDot + 1 ); - } - filePath = filePath + type; - File retval = new File( filePath ); - return retval; - } - - /** - * @return All external document references used by any dependency toRelationships - */ - public Collection getDocumentExternalReferences() - { - return this.externalDocuments.values(); - } - - /** - * @return the relationships - */ - public Map> getRelationships() - { - return relationships; - } - - /** - * @return the externalDocuments - */ - public Map getExternalDocuments() - { - return externalDocuments; - } - - /** - * @return the documentAnnotations - */ - public List getDocumentAnnotations() - { - return documentAnnotations; - } - - /** - * @return the spdxDoc - */ - public SpdxDocument getSpdxDoc() - { - return spdxDoc; - } - - /** - * @return the createExternalRefs - */ - public boolean isCreateExternalRefs() - { - return createExternalRefs; - } -} diff --git a/src/main/java/org/spdx/maven/utils/SpdxExternalIdBuilder.java b/src/main/java/org/spdx/maven/utils/SpdxExternalIdBuilder.java new file mode 100644 index 0000000..de5d68c --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/SpdxExternalIdBuilder.java @@ -0,0 +1,43 @@ +package org.spdx.maven.utils; + +import java.util.Collection; +import java.util.List; +import org.apache.maven.project.MavenProject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.model.v3_0_1.core.ExternalIdentifier; +import org.spdx.library.model.v3_0_1.core.ExternalIdentifierType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.storage.IModelStore.IdType; + +public class SpdxExternalIdBuilder +{ + + private static final Logger LOG = LoggerFactory.getLogger( SpdxExternalIdBuilder.class ); + + public static Collection getDefaultExternalIdentifiers( SpdxDocument spdxDoc, + boolean generatePurls, MavenProject project ) + { + ExternalIdentifier generatedPurlExternalIdentifier = + generatePurls ? generatePurlExternalIdentifier( spdxDoc, project ) : null; + return generatedPurlExternalIdentifier == null ? List.of() : List.of( generatedPurlExternalIdentifier ); + } + + private static ExternalIdentifier generatePurlExternalIdentifier( SpdxDocument spdxDoc, MavenProject project ) + { + try + { + return spdxDoc.createExternalIdentifier( spdxDoc.getModelStore().getNextId( IdType.Anonymous ) ) + .setExternalIdentifierType( ExternalIdentifierType.PACKAGE_URL ) + .setIdentifier( SpdxExternalRefBuilder.generatePurl( project ) ) + .build(); + } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.warn( "Invalid reference type \"purl\" for generated purl external ref"); + return null; + } + } + +} diff --git a/src/main/java/org/spdx/maven/utils/SpdxExternalRefBuilder.java b/src/main/java/org/spdx/maven/utils/SpdxExternalRefBuilder.java index d93dd57..7b1b9b2 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxExternalRefBuilder.java +++ b/src/main/java/org/spdx/maven/utils/SpdxExternalRefBuilder.java @@ -4,31 +4,34 @@ import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; -import org.apache.maven.artifact.Artifact; import org.apache.maven.project.MavenProject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.ExternalRef; -import org.spdx.library.model.ReferenceType; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.enumerations.ReferenceCategory; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.model.v2.ExternalRef; +import org.spdx.library.model.v2.ReferenceType; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.enumerations.ReferenceCategory; public class SpdxExternalRefBuilder { private static final Logger LOG = LoggerFactory.getLogger( SpdxExternalRefBuilder.class ); - public static Collection getDefaultExternalRefs( SpdxDocument spdxDoc, boolean generatePurls, MavenProject project ) { - ExternalRef generatedPurlExternalRef = generatePurls ? generatePurlExternalRef( spdxDoc, project ) : null; + public static Collection getDefaultExternalRefs( SpdxDocument spdxDoc, + boolean generatePurls, MavenProject project ) { + ExternalRef generatedPurlExternalRef = + generatePurls ? generatePurlV2ExternalRef( spdxDoc, project ) : null; return generatedPurlExternalRef == null ? List.of() : List.of( generatedPurlExternalRef ); } - private static ExternalRef generatePurlExternalRef( SpdxDocument spdxDoc, MavenProject project ) + private static ExternalRef generatePurlV2ExternalRef( SpdxDocument spdxDoc, + MavenProject project ) { try { - return spdxDoc.createExternalRef( ReferenceCategory.PACKAGE_MANAGER, new ReferenceType("http://spdx.org/rdf/references/purl"), + return spdxDoc.createExternalRef( ReferenceCategory.PACKAGE_MANAGER, + new ReferenceType( "http://spdx.org/rdf/references/purl" ), generatePurl( project ), null ); } catch ( InvalidSPDXAnalysisException e ) @@ -38,7 +41,7 @@ private static ExternalRef generatePurlExternalRef( SpdxDocument spdxDoc, MavenP } } - private static String generatePurl( MavenProject project ) + public static String generatePurl( MavenProject project ) { return "pkg:maven/" + project.getGroupId() + "/" + URLEncoder.encode( project.getArtifactId(), StandardCharsets.UTF_8 ) diff --git a/src/main/java/org/spdx/maven/utils/SpdxProjectInformation.java b/src/main/java/org/spdx/maven/utils/SpdxProjectInformation.java index 41bc574..69d0d89 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxProjectInformation.java +++ b/src/main/java/org/spdx/maven/utils/SpdxProjectInformation.java @@ -15,24 +15,12 @@ */ package org.spdx.maven.utils; -import java.net.URI; -import java.net.URISyntaxException; import java.util.Set; -import org.apache.maven.plugin.MojoExecutionException; - -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.Checksum; -import org.spdx.library.model.ExternalRef; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.enumerations.Purpose; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.SpdxNoAssertionLicense; -import org.spdx.library.referencetype.ListedReferenceTypes; - import org.spdx.maven.Annotation; +import org.spdx.maven.Checksum; import org.spdx.maven.ExternalReference; - +import org.spdx.maven.Packaging; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,8 +35,8 @@ public class SpdxProjectInformation private String[] creators = new String[0]; private String creatorComment = ""; - private AnyLicenseInfo concludedLicense; - private AnyLicenseInfo declaredLicense; + private String concludedLicense; + private String declaredLicense; private String description; private String downloadUrl; private String homePage; @@ -66,28 +54,30 @@ public class SpdxProjectInformation private Annotation[] documentAnnotations; private ExternalReference[] externalRefs; private Set checksums; - private Purpose primaryPurpose; + private Packaging packaging; + + /** - * @return the primaryPurpose + * @return the packaging */ - public Purpose getPrimaryPurpose() + public Packaging getPackaging() { - return primaryPurpose; + return packaging; } /** - * @param primaryPurpose the primaryPurpose to set + * @param packaging the packaging to set */ - public void setPrimaryPurpose( Purpose primaryPurpose ) + public void setPackaging( Packaging packaging ) { - this.primaryPurpose = primaryPurpose; + this.packaging = packaging; } - public SpdxProjectInformation () throws InvalidSPDXAnalysisException + public SpdxProjectInformation () { - this.concludedLicense = new SpdxNoAssertionLicense(); - this.declaredLicense = new SpdxNoAssertionLicense(); + this.concludedLicense = "NOASSERTION"; + this.declaredLicense = "NOASSERTION"; } /** @@ -125,7 +115,7 @@ public void setChecksums( Set checksums ) /** * @return the concludedLicense */ - public AnyLicenseInfo getConcludedLicense() + public String getConcludedLicense() { return concludedLicense; } @@ -133,7 +123,7 @@ public AnyLicenseInfo getConcludedLicense() /** * @param concludedLicense the concludedLicense to set */ - public void setConcludedLicense( AnyLicenseInfo concludedLicense ) + public void setConcludedLicense( String concludedLicense ) { this.concludedLicense = concludedLicense; } @@ -141,7 +131,7 @@ public void setConcludedLicense( AnyLicenseInfo concludedLicense ) /** * @return the declaredLicense */ - public AnyLicenseInfo getDeclaredLicense() + public String getDeclaredLicense() { return declaredLicense; } @@ -149,7 +139,7 @@ public AnyLicenseInfo getDeclaredLicense() /** * @param declaredLicense the declaredLicense to set */ - public void setDeclaredLicense( AnyLicenseInfo declaredLicense ) + public void setDeclaredLicense( String declaredLicense ) { this.declaredLicense = declaredLicense; } @@ -343,26 +333,26 @@ public void setName( String name ) /** * Log information on all fields - typically used for debugging */ - public void logInfo( SpdxDocument spdxDoc ) + public void logInfo() { if ( !LOG.isDebugEnabled() ) { return; } - LOG.debug( "SPDX Project Name: " + this.getName() ); - LOG.debug( "SPDX Document comment: " + this.getDocumentComment() ); - LOG.debug( "SPDX Creator comment: " + this.getCreatorComment() ); - LOG.debug( "SPDX Description: " + this.getDescription() ); - LOG.debug( "SPDX License comment: " + this.getLicenseComment() ); - LOG.debug( "SPDX Originator: " + this.getOriginator() ); - LOG.debug( "SPDX PackageArchiveFileName: " + this.getPackageArchiveFileName() ); - LOG.debug( "SPDX Short description: " + this.getShortDescription() ); - LOG.debug( "SPDX Supplier: " + this.getSupplier() ); - LOG.debug( "SPDX Source Info: " + this.getSourceInfo() ); - LOG.debug( "SPDX Version info: " + this.getVersionInfo() ); - LOG.debug( "SPDX Concluded license: " + this.getConcludedLicense().toString() ); - LOG.debug( "SPDX Declared license: " + this.getDeclaredLicense().toString() ); - LOG.debug( "SPDX Download URL: " + this.getDownloadUrl() ); - LOG.debug( "SPDX Home page: " + this.getHomePage() ); + LOG.debug( "SPDX Project Name: {}", this.getName() ); + LOG.debug( "SPDX Document comment: {}", this.getDocumentComment() ); + LOG.debug( "SPDX Creator comment: {}", this.getCreatorComment() ); + LOG.debug( "SPDX Description: {}", this.getDescription() ); + LOG.debug( "SPDX License comment: {}", this.getLicenseComment() ); + LOG.debug( "SPDX Originator: {}", this.getOriginator() ); + LOG.debug( "SPDX PackageArchiveFileName: {}", this.getPackageArchiveFileName() ); + LOG.debug( "SPDX Short description: {}", this.getShortDescription() ); + LOG.debug( "SPDX Supplier: {}", this.getSupplier() ); + LOG.debug( "SPDX Source Info: {}", this.getSourceInfo() ); + LOG.debug( "SPDX Version info: {}", this.getVersionInfo() ); + LOG.debug( "SPDX Concluded license: {}", this.getConcludedLicense() ); + LOG.debug( "SPDX Declared license: {}", this.getDeclaredLicense() ); + LOG.debug( "SPDX Download URL: {}", this.getDownloadUrl() ); + LOG.debug( "SPDX Home page: {}", this.getHomePage() ); if ( this.documentAnnotations != null && this.documentAnnotations.length > 0 ) { LOG.debug( "Document annotations: " ); @@ -383,66 +373,22 @@ public void logInfo( SpdxDocument spdxDoc ) { for ( String creator : creators ) { - LOG.debug( "SPDX Creator: " + creator ); + LOG.debug( "SPDX Creator: {}", creator ); } } if ( this.externalRefs != null ) { for ( ExternalReference externalReference : externalRefs ) { - ExternalRef externalRef; - try - { - externalRef = externalReference.getExternalRef( spdxDoc ); - StringBuilder externalRefString = new StringBuilder(); - try - { - externalRefString.append( externalRef.getReferenceCategory().toString() ); - } - catch ( InvalidSPDXAnalysisException e1 ) - { - externalRefString.append( "Invalid Reference Category" ); - } - externalRefString.append( ' ' ); - try - { - externalRefString.append( ListedReferenceTypes.getListedReferenceTypes().getListedReferenceName( - new URI( externalRef.getReferenceType().getIndividualURI() ) ) ); - } - catch ( InvalidSPDXAnalysisException | URISyntaxException e ) - { - externalRefString.append( "Invalid Reference Type" ); - } - externalRefString.append( ' ' ); - try - { - externalRefString.append( externalRef.getReferenceLocator() ); - } - catch ( InvalidSPDXAnalysisException e ) - { - externalRefString.append( "Invalid Reference Locator" ); - } - LOG.debug( "External Ref: " + externalRefString.toString() ); - } - catch ( MojoExecutionException e1 ) - { - LOG.error( "Invalid external reference", e1 ); - } - + LOG.debug( "External Ref: {} {} {}", externalReference.getCategory(), + externalReference.getType(), externalReference.getLocator() ); } } - if ( checksums != null && checksums.size() > 0 ) + if ( checksums != null && !checksums.isEmpty()) { for ( Checksum checksum : checksums ) { - try - { - String algorithm = SpdxFileCollector.checksumAlgorithms.get( checksum.getAlgorithm() ); - LOG.debug( "SPDX " + algorithm + ": " + checksum.getValue() ); - } catch ( InvalidSPDXAnalysisException e ) - { - LOG.debug( "Invalid SPDX checksum" ); - } + LOG.debug( "SPDX {}: {}", checksum.getAlgorithm(), checksum.getValue() ); } } } diff --git a/src/main/java/org/spdx/maven/utils/SpdxSourceFileParser.java b/src/main/java/org/spdx/maven/utils/SpdxSourceFileParser.java index 00290c7..1c376af 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxSourceFileParser.java +++ b/src/main/java/org/spdx/maven/utils/SpdxSourceFileParser.java @@ -23,9 +23,6 @@ import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.InvalidLicenseStringException; -import org.spdx.library.model.license.LicenseInfoFactory; /** * Helper class with static methods to parse SPDX source files @@ -44,7 +41,7 @@ public class SpdxSourceFileParser * @param file Text file to parse * @return list of all license expressions found following SPDX-License-Identifier: */ - public static List parseFileForSpdxLicenses( File file ) throws SpdxSourceParserException + public static List parseFileForSpdxLicenses( File file ) throws SpdxSourceParserException { try { @@ -60,9 +57,9 @@ public static List parseFileForSpdxLicenses( File file ) throws } } - public static List parseTextForSpdxLicenses( String text ) throws SpdxSourceParserException + public static List parseTextForSpdxLicenses( String text ) throws SpdxSourceParserException { - List retval = new ArrayList<>(); + List retval = new ArrayList<>(); Matcher match = SPDX_LICENSE_PATTERN.matcher( text ); int pos = 0; while ( pos < text.length() && match.find( pos ) ) @@ -70,7 +67,7 @@ public static List parseTextForSpdxLicenses( String text ) throw String matchingLine = match.group( 1 ).trim(); if ( matchingLine.startsWith( "(" ) ) { - // This could be a multi-line expression, so we need to parse until we get to the last ) + // This could be a multi-line expression, so we need to parse until we get to the last ")" int parenCount = 1; StringBuilder sb = new StringBuilder( "(" ); pos = match.start( 1 ) + 1; @@ -105,14 +102,7 @@ else if ( ch == ')' ) { pos = match.end() + 1; } - try - { - retval.add( LicenseInfoFactory.parseSPDXLicenseString( matchingLine ) ); - } - catch ( InvalidLicenseStringException e ) - { - throw new SpdxSourceParserException( "Invalid SPDX license string '" + matchingLine + "'." ); - } + retval.add( matchingLine ); } return retval; } diff --git a/src/main/java/org/spdx/maven/utils/SpdxSourceParserException.java b/src/main/java/org/spdx/maven/utils/SpdxSourceParserException.java index 4a3e599..19bb83f 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxSourceParserException.java +++ b/src/main/java/org/spdx/maven/utils/SpdxSourceParserException.java @@ -18,39 +18,28 @@ public class SpdxSourceParserException extends Exception private static final long serialVersionUID = 1L; /** - * @param arg0 + * @param msg message */ - public SpdxSourceParserException( String arg0 ) + public SpdxSourceParserException( String msg ) { - super( arg0 ); + super( msg ); } /** - * @param arg0 + * @param cause inner exception */ - public SpdxSourceParserException( Throwable arg0 ) + public SpdxSourceParserException( Throwable cause ) { - super( arg0 ); + super( cause ); } /** - * @param arg0 - * @param arg1 + * @param msg message + * @param cause inner exception */ - public SpdxSourceParserException( String arg0, Throwable arg1 ) + public SpdxSourceParserException( String msg, Throwable cause ) { - super( arg0, arg1 ); - } - - /** - * @param arg0 - * @param arg1 - * @param arg2 - * @param arg3 - */ - public SpdxSourceParserException( String arg0, Throwable arg1, boolean arg2, boolean arg3 ) - { - super( arg0, arg1, arg2, arg3 ); + super( msg, cause ); } } diff --git a/src/main/java/org/spdx/maven/utils/SpdxV2DependencyBuilder.java b/src/main/java/org/spdx/maven/utils/SpdxV2DependencyBuilder.java new file mode 100644 index 0000000..76c1956 --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/SpdxV2DependencyBuilder.java @@ -0,0 +1,612 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. + */ +package org.spdx.maven.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import javax.annotation.Nullable; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Contributor; +import org.apache.maven.model.License; +import org.apache.maven.project.DefaultProjectBuildingRequest; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.project.ProjectBuildingResult; +import org.apache.maven.shared.dependency.graph.DependencyNode; +import org.spdx.core.CoreModelObject; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.core.SpdxCoreConstants.SpdxMajorVersion; +import org.spdx.core.SpdxInvalidIdException; +import org.spdx.jacksonstore.MultiFormatStore; +import org.spdx.jacksonstore.MultiFormatStore.Format; +import org.spdx.jacksonstore.MultiFormatStore.Verbose; +import org.spdx.library.model.v2.Checksum; +import org.spdx.library.model.v2.ExternalDocumentRef; +import org.spdx.library.model.v2.ExternalSpdxElement; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.SpdxElement; +import org.spdx.library.model.v2.SpdxPackage; +import org.spdx.library.model.v2.enumerations.AnnotationType; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.Purpose; +import org.spdx.library.model.v2.enumerations.RelationshipType; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.license.SpdxNoAssertionLicense; +import org.spdx.maven.OutputFormat; +import org.spdx.spdxRdfStore.RdfStore; +import org.spdx.storage.ISerializableModelStore; +import org.spdx.storage.IModelStore.IdType; +import org.spdx.storage.simple.InMemSpdxStore; + +/** + * Builds dependencies for a parent package based on Maven dependency information + * @author Gary O'Neall + * + */ +public class SpdxV2DependencyBuilder + extends AbstractDependencyBuilder +{ + + /** + * Creates an SPDX document from a file + * @param path Path to the SPDX file + * @return an SPDX Spec version 2 document + * @throws IOException on IO Error + * @throws FileNotFoundException if the file does not exist + * @throws InvalidSPDXAnalysisException on invalid SPDX file + */ + protected static SpdxDocument spdxDocumentFromFile( String path ) throws FileNotFoundException, IOException, InvalidSPDXAnalysisException + { + ISerializableModelStore modelStore; + OutputFormat of = OutputFormat.getOutputFormat( null, new File( path ) ); + + if (!SpdxMajorVersion.VERSION_2.equals( of.getSpecVersion() )) { + throw new InvalidSPDXAnalysisException( String.format( "Unsupported file type for SPDX Version 2 SPDX documents: %s", of.getSpecVersion().toString() )); + } + if ( of.getArtifactType().equals( "spdx.json" ) ) + { + modelStore = new MultiFormatStore(new InMemSpdxStore(), Format.JSON_PRETTY, Verbose.COMPACT); + } + else + { + modelStore = new RdfStore(); + } + try ( InputStream inputStream = new FileInputStream( path ) ) + { + return (SpdxDocument)modelStore.deSerialize( inputStream, false ); + } + finally + { + try { + modelStore.close(); + } catch (Exception e) { + LOG.error( "Error closing SPDX model store", e ); + } + } + } + + /** + * Searched the described packages for the SPDX document for the closest matching package to the artifactId + * @param externalSpdxDoc Doc containing the package + * @param artifactId Maven artifact ID + * @return the closest matching package described by the doc + * @throws InvalidSPDXAnalysisException on SPDX errors + */ + protected static SpdxPackage findMatchingDescribedPackage( SpdxDocument externalSpdxDoc, String artifactId ) throws InvalidSPDXAnalysisException + { + SpdxElement itemDescribed = null; + // Find an item described with matching artifact ID + for ( SpdxElement item : externalSpdxDoc.getDocumentDescribes() ) + { + Optional name = item.getName(); + if ( item instanceof SpdxPackage && name.isPresent() && item.getName().orElse( "" ).equals( artifactId ) ) + { + itemDescribed = item; + break; + } + } + if ( itemDescribed == null ) { + // Find the first package + LOG.warn( "Could not find matching artifact ID in SPDX file for {}. Using the first package found in SPDX file.", artifactId ); + for ( SpdxElement item : externalSpdxDoc.getDocumentDescribes() ) + { + if ( item instanceof SpdxPackage ) + { + itemDescribed = item; + break; + } + } + } + if ( itemDescribed == null ) { + throw new InvalidSPDXAnalysisException( "SPDX document does not contain any described items." ); + } + return (SpdxPackage)itemDescribed; + } + + /** + * Map of namespaces to ExternalDocumentRefs + */ + private final Map externalDocuments = new HashMap<>(); + private final SpdxDocument spdxDoc; + private final SpdxV2LicenseManager licenseManager; + + /** + * @param builder The document builder + * @param createExternalRefs if true, create external references for dependencies + * @param generatePurls if true, generate a Package URL and include as an external identifier for the dependencies + * @param useArtifactID if true, use the artifact ID for the name of the dependency package, otherwise use the Maven configured project name + * @param includeTransitiveDependencies If true, include transitive dependencies, otherwise include only direct dependencies + */ + public SpdxV2DependencyBuilder( SpdxV2DocumentBuilder builder, boolean createExternalRefs, + boolean generatePurls, boolean useArtifactID, + boolean includeTransitiveDependencies ) + { + super( createExternalRefs, generatePurls, useArtifactID, includeTransitiveDependencies ); + this.spdxDoc = builder.getSpdxDoc(); + this.licenseManager = builder.getLicenseManager(); + } + + @Override + protected void addMavenDependency( CoreModelObject parentPackage, DependencyNode dependencyNode, + ProjectBuilder mavenProjectBuilder, + MavenSession session, MavenProject mavenProject ) + throws LicenseMapperException, InvalidSPDXAnalysisException + { + if ( !(parentPackage instanceof SpdxPackage) ) + { + LOG.error( "Invalid type for parent package. Expected 'SpdxPackage', found {}", parentPackage.getClass().getName() ); + return; + } + Artifact dependency = dependencyNode.getArtifact(); + String scope = dependency.getScope(); + RelationshipType relType = scopeToRelationshipType( scope, dependency.isOptional() ); + if ( relType == RelationshipType.OTHER ) + { + LOG.warn( "Could not determine the SPDX relationship type for dependency artifact ID {} scope {}", dependency.getArtifactId(), scope ); + } + + SpdxElement dependencyPackage = createSpdxPackage( dependency, mavenProjectBuilder, session, + mavenProject, useArtifactID ); + + if ( relType.toString().endsWith( "_OF" ) ) + { + if ( dependencyPackage instanceof SpdxPackage ) + { + ((SpdxPackage)parentPackage).addRelationship( spdxDoc.createRelationship( dependencyPackage, relType, + "Relationship created based on Maven POM information" ) ); + LOG.debug( "Added relationship of type {} for {}", relType, dependencyPackage.getName() ); + } + else + { + ((SpdxPackage)parentPackage).addRelationship(spdxDoc.createRelationship( (SpdxPackage)parentPackage, RelationshipType.OTHER, + "This relationship is the inverse of " + relType + " to an external document reference." ) ); + LOG.debug( "Could not create proper to relationships for external element {}", + dependencyPackage.getId() ); + } + } + else + { + ((SpdxPackage)parentPackage).addRelationship( spdxDoc.createRelationship( dependencyPackage, relType, + "Relationship based on Maven POM file dependency information" ) ); + } + + if ( includeTransitiveDependencies ) { + addMavenDependencies( mavenProjectBuilder, session, mavenProject, dependencyNode, dependencyPackage ); + } + } + + /** + * Translate the scope to the SPDX relationship type + * + * @param scope Maven Dependency Scope (see Maven dependency scope documentation) + * @param optional True if this is an optional dependency + * @return SPDX Relationship type based on the scope + */ + private RelationshipType scopeToRelationshipType( String scope, boolean optional ) + { + if ( scope == null ) + { + return RelationshipType.OTHER; + } + else if ( optional ) + { + return RelationshipType.OPTIONAL_COMPONENT_OF; + } + else if ( scope.equals( "compile" ) || scope.equals( "runtime" ) ) + { + return RelationshipType.DYNAMIC_LINK; + } + else if ( scope.equals( "test" ) ) + { + return RelationshipType.TEST_DEPENDENCY_OF; + } + else + { + return RelationshipType.OTHER; + } + } + + /** + * Create an SPDX package from the information in a Maven Project + * + * @param project Maven project + * @param useArtifactID If true, use ${project.groupId}:${artifactId} as the SPDX package name, otherwise, ${project.name} will be used + * @return SPDX Package generated from the metadata in the Maven Project + * @throws SpdxCollectionException On errors with SPDX collections + * @throws NoSuchAlgorithmException if no checksum algorithm was found + * @throws LicenseMapperException on errors mapping or creating SPDX custom licenses + * @throws InvalidSPDXAnalysisException on any other general SPDX errors + */ + private SpdxPackage createSpdxPackage( MavenProject project, boolean useArtifactID ) throws SpdxCollectionException, NoSuchAlgorithmException, LicenseMapperException, InvalidSPDXAnalysisException + { + SpdxDefaultFileInformation fileInfo = new SpdxDefaultFileInformation(); + + // initialize the SPDX information from the project + String packageName = project.getName(); + if ( packageName == null || packageName.isEmpty() || useArtifactID ) + { + packageName = project.getGroupId() + ":" + project.getArtifactId(); + } + List contributors = project.getContributors(); + ArrayList fileContributorList = new ArrayList<>(); + if ( contributors != null ) + { + for ( Contributor contributor : contributors ) + { + fileContributorList.add( contributor.getName() ); + } + } + String copyright = "UNSPECIFIED"; + String notice = "UNSPECIFIED"; + String downloadLocation = "NOASSERTION"; + AnyLicenseInfo declaredLicense = mavenLicensesToSpdxLicense( project.getLicenses() ); + fileInfo.setComment( "" ); + fileInfo.setConcludedLicense( "NOASSERTION" ); + fileInfo.setContributors( fileContributorList.toArray( new String[0] ) ); + fileInfo.setCopyright( copyright ); + fileInfo.setDeclaredLicense( declaredLicense.toString() ); + fileInfo.setLicenseComment( "" ); + fileInfo.setNotice( notice ); + + SpdxPackage retval = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId ), + packageName, new SpdxNoAssertionLicense(), copyright, declaredLicense ) + .setDownloadLocation( downloadLocation ) + .setFilesAnalyzed( false ) + .setExternalRefs( SpdxExternalRefBuilder.getDefaultExternalRefs( spdxDoc, generatePurls, project ) ) + .build(); + if ( project.getVersion() != null ) + { + retval.setVersionInfo( project.getVersion() ); + } + if ( project.getDescription() != null ) + { + retval.setDescription( project.getDescription() ); + retval.setSummary( project.getDescription() ); + } + if ( project.getOrganization() != null ) + { + retval.setOriginator( SpdxConstantsCompatV2.CREATOR_PREFIX_ORGANIZATION + project.getOrganization().getName() ); + } + if ( project.getUrl() != null ) + { + try { + retval.setHomepage( project.getUrl() ); + } catch ( InvalidSPDXAnalysisException e ) { + LOG.warn( "Invalid homepage for dependency {}: {}", project.getArtifactId(), project.getUrl() ); + } + } + return retval; + } + + /** + * Create an SPDX Document using the mavenProjectBuilder to resolve properties + * including inherited properties + * @param artifact Maven dependency artifact + * @param mavenProjectBuilder project builder for the repo containing the POM file + * @param session Maven session for building the project + * @param mavenProject Maven project + * @param useArtifactID If true, use ${project.groupId}:${artifactId} as the SPDX package name, otherwise, ${project.name} will be used + * @return SPDX Package build from the MavenProject metadata + * @throws InvalidSPDXAnalysisException on errors generating SPDX + * @throws LicenseMapperException on errors mapping licenses or creating custom licenses + */ + private SpdxElement createSpdxPackage( Artifact artifact, + ProjectBuilder mavenProjectBuilder, MavenSession session, + MavenProject mavenProject, boolean useArtifactID ) throws LicenseMapperException, InvalidSPDXAnalysisException + { + LOG.debug( "Creating SPDX package for artifact {}", artifact.getArtifactId() ); + if ( artifact.getFile() == null ) + { + LOG.debug( "Artifact file is null" ); + } + else + { + LOG.debug( "Artifact file name = {}", artifact.getFile().getName() ); + } + File spdxFile = null; + if ( artifact.getFile() != null ) + { + spdxFile = artifactFileToSpdxFile( artifact.getFile(), SpdxMajorVersion.VERSION_2 ); + } + if ( spdxFile != null && spdxFile.exists() ) + { + LOG.debug( "Dependency {}Looking for SPDX file {}", artifact.getArtifactId(), + spdxFile.getAbsolutePath() ); + try + { + LOG.debug( "Dependency {}Dependency information collected from SPDX file {}", + artifact.getArtifactId(), spdxFile.getAbsolutePath() ); + + SpdxDocument externalSpdxDoc = spdxDocumentFromFile( spdxFile.getPath() ); + if ( createExternalRefs ) + { + return createExternalSpdxPackageReference( externalSpdxDoc, spdxFile, artifact.getGroupId(), + artifact.getArtifactId(), artifact.getVersion() ); + } + else + { + return copyPackageInfoFromExternalDoc( externalSpdxDoc, artifact.getArtifactId() ); + } + } + catch ( IOException e ) + { + LOG.warn( "IO error reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", + artifact.getArtifactId(), e.getMessage() ); + } + catch ( SpdxInvalidIdException e ) + { + LOG.warn( "Invalid SPDX ID exception reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", + artifact.getArtifactId(), e.getMessage() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.warn( "Invalid SPDX analysis exception reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", + artifact.getArtifactId(), e.getMessage() ); + } + catch ( SpdxCollectionException e ) + { + LOG.warn( "Unable to create file checksum for external SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", + artifact.getArtifactId(), e.getMessage() ); + } + catch ( Exception e ) + { + LOG.warn( "Unknown error processing SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", + artifact.getArtifactId(), e.getMessage() ); + } + } + try + { + ProjectBuildingRequest request = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() ); + request.setRemoteRepositories( mavenProject.getRemoteArtifactRepositories() ); + for ( ArtifactRepository ar : request.getRemoteRepositories() ) { + LOG.debug( "request Remote repository ID: {}", ar.getId() ); + } + for ( ArtifactRepository ar : mavenProject.getRemoteArtifactRepositories() ) { + LOG.debug( "Project Remote repository ID: {}", ar.getId() ); + } + ProjectBuildingResult build = mavenProjectBuilder.build( artifact, request ); + MavenProject depProject = build.getProject(); + LOG.debug( "Dependency {}Collecting information from project metadata for {}", artifact.getArtifactId(), + depProject.getArtifactId() ); + return createSpdxPackage( depProject, useArtifactID ); + } + catch ( SpdxCollectionException e ) + { + LOG.error( "SPDX File Collection Error creating SPDX package for dependency artifact ID {}:{}", + artifact.getArtifactId(), e.getMessage() ); + } + catch ( NoSuchAlgorithmException e ) + { + LOG.error( "Verification Code Error creating SPDX package for dependency artifact ID {}:{}", + artifact.getArtifactId(), e.getMessage() ); + } + catch ( ProjectBuildingException e ) + { + LOG.error( "Maven Project Build Error creating SPDX package for dependency artifact ID {}:{}", + artifact.getArtifactId(), e.getMessage() ); + } + LOG.warn( "Error creating SPDX package for dependency artifact ID {}. A minimal SPDX package will be created.", + artifact.getArtifactId() ); + // Create a minimal SPDX package from dependency + // Name will be the artifact ID + LOG.debug( "Dependency {}Using only artifact information to create dependent package", + artifact.getArtifactId() ); + return spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId ), + artifact.getArtifactId(), new SpdxNoAssertionLicense(), "NOASSERTION", + new SpdxNoAssertionLicense() ) + .setComment( "This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file." ) + .setVersionInfo( artifact.getBaseVersion() ) + .setFilesAnalyzed( false ) + .setDownloadLocation( "NOASSERTION" ) + .setExternalRefs( SpdxExternalRefBuilder.getDefaultExternalRefs( spdxDoc, generatePurls, mavenProject ) ) + .build(); + } + + /** + * Create and return an external document reference for an existing package in an SPDX document + * + * @param externalSpdxDoc SPDX Document containing the package to be referenced. + * @param spdxFile SPDX file containing the SPDX document + * @param groupId Group ID for the external artifact + * @param artifactId Artifact ID for the external artifact + * @param version version for the external artifact + * @return created SPDX element + * @throws SpdxCollectionException on incompatible types for collections + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + private SpdxElement createExternalSpdxPackageReference( SpdxDocument externalSpdxDoc, + File spdxFile, + String groupId, + String artifactId, + @Nullable String version ) throws SpdxCollectionException, InvalidSPDXAnalysisException + { + String externalDocNamespace = externalSpdxDoc.getDocumentUri(); + ExternalDocumentRef externalRef = this.externalDocuments.get( externalDocNamespace ); + StringBuilder sb = new StringBuilder( groupId ).append( artifactId ); + if ( Objects.nonNull( version )) { + sb.append( version ); + } + String fullArtifactId = sb.toString(); + if ( externalRef == null ) + { + String externalRefDocId = SpdxConstantsCompatV2.EXTERNAL_DOC_REF_PRENUM + fixExternalRefId( fullArtifactId ); + LOG.debug( "Creating external document ref {}", externalDocNamespace ); + org.spdx.maven.Checksum mavenChecksum = AbstractFileCollector.generateSha1( spdxFile ); + Checksum cksum = spdxDoc.createChecksum( ChecksumAlgorithm.valueOf( mavenChecksum.getAlgorithm() ), + mavenChecksum.getValue() ); + externalRef = spdxDoc.createExternalDocumentRef( externalRefDocId, externalSpdxDoc.getDocumentUri(), cksum ); + spdxDoc.getExternalDocumentRefs().add( externalRef ); + org.spdx.library.model.v2.Annotation docRefAddedAnnotation = spdxDoc.createAnnotation( "Tool: spdx-maven-plugin", + AnnotationType.OTHER, + format.format( new Date() ), + "External document ref '"+externalRefDocId+"' created for artifact "+fullArtifactId ); + spdxDoc.getAnnotations().add( docRefAddedAnnotation ); + this.externalDocuments.put( externalDocNamespace, externalRef ); + LOG.debug( "Created external document ref {}", externalRefDocId ); + } + SpdxPackage pkg = findMatchingDescribedPackage( externalSpdxDoc, artifactId ); + return new ExternalSpdxElement( spdxDoc.getModelStore(), spdxDoc.getDocumentUri(), + externalRef.getId() + ":" + pkg.getId(), spdxDoc.getCopyManager(), true ); + } + + /** + * Copies the closest matching described package in the externalSpdxDoc to the returned element + * @param externalSpdxDoc SPDX document to copy from + * @param artifactId Artifact ID to search for + * @return SPDX Package with values copied from the externalSpdxDoc + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + private SpdxPackage copyPackageInfoFromExternalDoc( SpdxDocument externalSpdxDoc, String artifactId ) throws InvalidSPDXAnalysisException + { + SpdxPackage source = findMatchingDescribedPackage( externalSpdxDoc, artifactId ); + Optional downloadLocation = source.getDownloadLocation(); + Optional name = source.getName(); + SpdxPackage dest = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId ), + name.orElse( "NONE" ), source.getLicenseConcluded(), source.getCopyrightText(), + source.getLicenseDeclared() ) + .setFilesAnalyzed( false ) + .setAnnotations( source.getAnnotations() ) + .setChecksums( source.getChecksums() ) + .setDownloadLocation( downloadLocation.orElse( "NOASSERTION" ) ) + .setExternalRefs( source.getExternalRefs() ) + .build(); + // We don't want to copy any of the properties which have other elements since it + // may duplicate artifacts already included in the document - so we can't use copyFrom + + Optional builtDate = source.getBuiltDate(); + if ( builtDate.isPresent() ) + { + dest.setBuiltDate( builtDate.get() ); + } + Optional comment = source.getComment(); + if ( comment.isPresent() ) + { + dest.setComment( comment.get() ); + } + Optional desc = source.getDescription(); + if ( desc.isPresent() ) + { + dest.setDescription( desc.get() ); + } + Optional homePage = source.getHomepage(); + if ( homePage.isPresent() ) + { + dest.setHomepage( homePage.get() ); + } + Optional licenseComments = source.getLicenseComments(); + if ( licenseComments.isPresent() ) + { + dest.setLicenseComments( licenseComments.get() ); + } + Optional originator = source.getOriginator(); + if ( originator.isPresent() ) + { + dest.setOriginator( originator.get() ); + } + Optional pkgFileName = source.getPackageFileName(); + if ( pkgFileName.isPresent() ) + { + dest.setPackageFileName( pkgFileName.get() ); + } + Optional primaryPurpose = source.getPrimaryPurpose(); + if ( primaryPurpose.isPresent() ) + { + dest.setPrimaryPurpose( primaryPurpose.get() ); + } + Optional releaseDate = source.getReleaseDate(); + if ( releaseDate.isPresent() ) + { + dest.setReleaseDate( releaseDate.get() ); + } + Optional sourceInfo = source.getSourceInfo(); + if ( sourceInfo.isPresent() ) + { + dest.setSourceInfo( sourceInfo.get() ); + } + Optional summary = source.getSummary(); + if ( summary.isPresent() ) + { + dest.setSummary( summary.get() ); + } + Optional supplier = source.getSupplier(); + if ( supplier.isPresent() ) { + dest.setSupplier( supplier.get() ); + } + Optional validUntil = source.getValidUntilDate(); + if ( validUntil.isPresent() ) + { + dest.setValidUntilDate( validUntil.get() ); + } + Optional versionInfo = source.getVersionInfo(); + if ( versionInfo.isPresent() ) + { + dest.setVersionInfo( versionInfo.get() ); + } + return dest; + } + + /** + * Convert a list of Maven licenses to an SPDX License + * + * @param mavenLicenses List of maven licenses to map + * @return SPDX license equivalent to the list of Maven licenses + * @throws LicenseMapperException on errors accessing either the SPDX listed licenses or local extracted licenses + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + private AnyLicenseInfo mavenLicensesToSpdxLicense( List mavenLicenses ) throws LicenseMapperException, InvalidSPDXAnalysisException + { + try + { + // The call below will map non-standard licenses as well as standard licenses + // but will throw an exception if no mapping is found - we'll try this first + // and if there is an error, try just the standard license mapper which will + // return an UNSPECIFIED license type if there is no mapping + return this.licenseManager.mavenLicenseListToSpdxLicense( mavenLicenses ); + } + catch ( LicenseManagerException ex ) + { + return MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV2License( mavenLicenses, spdxDoc ); + } + + } +} diff --git a/src/main/java/org/spdx/maven/utils/SpdxDocumentBuilder.java b/src/main/java/org/spdx/maven/utils/SpdxV2DocumentBuilder.java similarity index 61% rename from src/main/java/org/spdx/maven/utils/SpdxDocumentBuilder.java rename to src/main/java/org/spdx/maven/utils/SpdxV2DocumentBuilder.java index 81b652b..0facfd8 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxDocumentBuilder.java +++ b/src/main/java/org/spdx/maven/utils/SpdxV2DocumentBuilder.java @@ -1,17 +1,6 @@ -/* - * Copyright 2014 Source Auditor Inc. - * - * Licensed 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 - * - * http://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. +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. */ package org.spdx.maven.utils; @@ -21,134 +10,91 @@ import java.io.IOException; import java.net.URI; import java.security.NoSuchAlgorithmException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; +import org.apache.maven.model.License; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.model.fileset.FileSet; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spdx.core.CoreModelObject; +import org.spdx.core.InvalidSPDXAnalysisException; import org.spdx.jacksonstore.MultiFormatStore; import org.spdx.jacksonstore.MultiFormatStore.Format; -import org.spdx.library.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.ListedLicenses; import org.spdx.library.ModelCopyManager; -import org.spdx.library.SpdxConstants; -import org.spdx.library.SpdxVerificationHelper; -import org.spdx.library.model.Annotation; -import org.spdx.library.model.Relationship; -import org.spdx.library.model.SpdxCreatorInformation; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.SpdxElement; -import org.spdx.library.model.SpdxModelFactory; -import org.spdx.library.model.SpdxPackage; -import org.spdx.library.model.SpdxPackageVerificationCode; -import org.spdx.library.model.enumerations.ChecksumAlgorithm; -import org.spdx.library.model.enumerations.RelationshipType; -import org.spdx.library.model.license.LicenseInfoFactory; -import org.spdx.library.model.license.ListedLicenses; -import org.spdx.library.model.license.SpdxListedLicense; +import org.spdx.library.model.v2.Annotation; +import org.spdx.library.model.v2.ExternalRef; +import org.spdx.library.model.v2.ReferenceType; +import org.spdx.library.model.v2.Relationship; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.library.model.v2.SpdxCreatorInformation; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.SpdxModelFactoryCompatV2; +import org.spdx.library.model.v2.SpdxPackage; +import org.spdx.library.model.v2.SpdxPackageVerificationCode; +import org.spdx.library.model.v2.SpdxVerificationHelper; +import org.spdx.library.model.v2.enumerations.ReferenceCategory; +import org.spdx.library.model.v2.enumerations.RelationshipType; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.license.SpdxListedLicense; +import org.spdx.library.referencetype.ListedReferenceTypes; +import org.spdx.library.model.v2.enumerations.AnnotationType; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.Purpose; +import org.spdx.maven.Checksum; import org.spdx.maven.ExternalReference; import org.spdx.maven.NonStandardLicense; import org.spdx.maven.OutputFormat; +import org.spdx.maven.Packaging; import org.spdx.spdxRdfStore.RdfStore; import org.spdx.storage.IModelStore.IdType; -import org.spdx.storage.ISerializableModelStore; import org.spdx.storage.simple.InMemSpdxStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** - * Builds SPDX documents for a given set of source files. This is the primary class to use when creating SPDX documents - * based on project files. - * + * Builder for SPDX Spec version 2 SPDX Documents + * * @author Gary O'Neall + * */ -public class SpdxDocumentBuilder +public class SpdxV2DocumentBuilder + extends AbstractDocumentBuilder { - private static final Logger LOG = LoggerFactory.getLogger( SpdxDocumentBuilder.class ); - - private static final String UNSPECIFIED = "UNSPECIFIED"; - - public static final String NULL_SHA1 = "cf23df2207d99a74fbe169e3eba035e633b65d94"; - - //TODO: Use a previous SPDX to document file specific information and update - //TODO: Map the SPDX document to the Maven build artifacts - DateFormat format = new SimpleDateFormat( SpdxConstants.SPDX_DATE_FORMAT ); - - private MavenProject project; - private boolean generatePurls; - private SpdxDocument spdxDoc; - private SpdxPackage projectPackage; - private LicenseManager licenseManager; - private File spdxFile; - - private ISerializableModelStore modelStore; - - private ModelCopyManager copyManager; + private static final Logger LOG = LoggerFactory.getLogger( SpdxV2DocumentBuilder.class ); + protected SpdxDocument spdxDoc; + protected SpdxV2LicenseManager licenseManager; + protected SpdxPackage projectPackage; + /** - * @param spdxFile File to store the SPDX document results - * @param spdxDocumentNamespace URI for SPDX document - must be unique - * @param useStdLicenseSourceUrls if true, map any SPDX standard license source URL to license ID. Note: - * significant performance degradation - * @param outputFormat File format for the SPDX file - * @throws SpdxBuilderException - * @throws LicenseMapperException + * @param mavenProject Maven project + * @param generatePurls If true, generated Package URLs for all package references + * @param spdxFile File to store the SPDX document results + * @param spdxDocumentNamespace SPDX Document namespace - must be unique + * @param outputFormatEnum output format to use for storing the SPDX file */ - public SpdxDocumentBuilder( MavenProject project, boolean generatePurls, File spdxFile, URI spdxDocumentNamespace, - boolean useStdLicenseSourceUrls, OutputFormat outputFormat ) throws SpdxBuilderException, LicenseMapperException + public SpdxV2DocumentBuilder( MavenProject mavenProject, boolean generatePurls, File spdxFile, URI spdxDocumentNamespace, + OutputFormat outputFormatEnum ) throws SpdxBuilderException, LicenseMapperException { - this.project = project; - this.generatePurls = generatePurls; - this.spdxFile = spdxFile; - + super( mavenProject, generatePurls, spdxFile, outputFormatEnum ); if ( spdxDocumentNamespace == null ) { - throw new SpdxBuilderException( "Missing spdxDocumentNamespace" ); - } - - // Handle the SPDX file - if ( !spdxFile.exists() ) - { - File parentDir = spdxFile.getParentFile(); - if ( parentDir != null && !parentDir.exists() ) - { - if ( !parentDir.mkdirs() ) - { - throw new SpdxBuilderException( "Unable to create directories for SPDX file" ); - } - } - - try - { - if ( !spdxFile.createNewFile() ) - { - throw new SpdxBuilderException( "Unable to create the SPDX file" ); - } - } - catch ( IOException e ) - { - throw new SpdxBuilderException( "IO error creating the SPDX file", e ); - } - } - if ( !spdxFile.canWrite() ) - { - throw new SpdxBuilderException( "Unable to write to SPDX file - check permissions: " + spdxFile.getPath() ); + throw new SpdxBuilderException( "Missing namespaceUri" ); } - + // create the SPDX document try { - modelStore = outputFormat == OutputFormat.RDF_XML ? new RdfStore() : new MultiFormatStore( new InMemSpdxStore(), Format.JSON_PRETTY ); + modelStore = outputFormatEnum == OutputFormat.RDF_XML ? new RdfStore( spdxDocumentNamespace.toString() ) : new MultiFormatStore( new InMemSpdxStore(), Format.JSON_PRETTY ); copyManager = new ModelCopyManager(); - spdxDoc = SpdxModelFactory.createSpdxDocument( modelStore, spdxDocumentNamespace.toString(), copyManager ); + spdxDoc = SpdxModelFactoryCompatV2.createSpdxDocumentV2( modelStore, spdxDocumentNamespace.toString(), copyManager ); } catch ( InvalidSPDXAnalysisException e ) { @@ -156,96 +102,18 @@ public SpdxDocumentBuilder( MavenProject project, boolean generatePurls, File sp } // process the licenses - licenseManager = new LicenseManager( spdxDoc, useStdLicenseSourceUrls ); + licenseManager = new SpdxV2LicenseManager( spdxDoc); } /** - * Add non-standard licenses to the SPDX document. - * - * @param nonStandardLicenses - * @throws SpdxBuilderException + * @return the SPDX Document */ - public void addNonStandardLicenses( NonStandardLicense[] nonStandardLicenses ) throws SpdxBuilderException - { - if ( nonStandardLicenses != null ) - { - for ( NonStandardLicense nonStandardLicense : nonStandardLicenses ) - { - try - { - // the following will add the non-standard license to the document container - licenseManager.addExtractedLicense( nonStandardLicense ); - } - catch ( LicenseManagerException e ) - { - throw new SpdxBuilderException( "Error adding non standard license", e ); - } - } - } - } - public SpdxDocument getSpdxDoc() { return this.spdxDoc; } - public void saveSpdxDocumentToFile() throws SpdxBuilderException - { - try ( FileOutputStream spdxOut = new FileOutputStream( spdxFile ) ) - { - modelStore.serialize( spdxDoc.getDocumentUri(), spdxOut ); - } - catch ( FileNotFoundException e ) - { - throw new SpdxBuilderException( "Error saving SPDX data to file", e ); - } - catch ( InvalidSPDXAnalysisException e ) - { - throw new SpdxBuilderException( "Error collecting SPDX file data", e ); - } - catch ( IOException e ) - { - throw new SpdxBuilderException( "I/O Error saving SPDX data to file", e ); - } - } - - /** - * Add dependency information to the SPDX file - * - * @param dependencyInformation dependency information collected from the project POM file - * @throws SpdxBuilderException - */ - public void addDependencyInformation( SpdxDependencyInformation dependencyInformation ) throws SpdxBuilderException - { - Map> packageRelationships = dependencyInformation.getRelationships(); - if ( packageRelationships != null ) - { - for ( Map.Entry> entry : packageRelationships.entrySet() ) - { - SpdxElement parentElement = entry.getKey(); - List relationships = entry.getValue(); - - for ( Relationship relationship : relationships ) - { - try - { - parentElement.addRelationship( relationship ); - } - catch ( InvalidSPDXAnalysisException e ) - { - throw new SpdxBuilderException("Unable to set package dependencies", e); - } - } - } - } - } - - /** - * Fill in the document level information for SPDX - * - * @param projectInformation project information to be used - * @throws SpdxBuilderException - */ + @Override public void fillSpdxDocumentInformation( SpdxProjectInformation projectInformation ) throws SpdxBuilderException { try @@ -258,15 +126,13 @@ public void fillSpdxDocumentInformation( SpdxProjectInformation projectInformati // creator fillCreatorInfo( projectInformation ); // data license - SpdxListedLicense dataLicense = LicenseInfoFactory.getListedLicenseById( SpdxConstants.SPDX_DATA_LICENSE_ID ); + SpdxListedLicense dataLicense = LicenseInfoFactory.getListedLicenseByIdCompatV2( SpdxConstantsCompatV2.SPDX_DATA_LICENSE_ID ); spdxDoc.setDataLicense( dataLicense ); // annotations if ( projectInformation.getDocumentAnnotations() != null && projectInformation.getDocumentAnnotations().length > 0 ) { spdxDoc.setAnnotations( toSpdxAnnotations( projectInformation.getDocumentAnnotations() ) ); } - //TODO: Implement document annotations - //TODO: Add document level relationships spdxDoc.setName( projectInformation.getName() ); // Same as package name // Package level information projectPackage = createSpdxPackage( projectInformation ); @@ -278,22 +144,67 @@ public void fillSpdxDocumentInformation( SpdxProjectInformation projectInformati throw new SpdxBuilderException( "Error adding package information to SPDX document", e ); } } - + private Collection toSpdxAnnotations( org.spdx.maven.Annotation[] annotations ) throws MojoExecutionException { List retval = new ArrayList<>(); for ( org.spdx.maven.Annotation annotation: annotations ) { - retval.add( annotation.toSpdxAnnotation( spdxDoc ) ); + + @SuppressWarnings("UnusedAssignment") AnnotationType annotationType = AnnotationType.OTHER; + try + { + annotationType = AnnotationType.valueOf( annotation.getAnnotationType() ); + } + catch ( Exception ex ) + { + throw new MojoExecutionException( "Invalid annotation type "+annotation.getAnnotationType() ); + } + try + { + retval.add( spdxDoc.createAnnotation( annotation.getAnnotator(), + annotationType, + annotation.getAnnotationDate(), + annotation.getAnnotationComment() ) ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new MojoExecutionException( "Error creating annotation.", e ); + } } return retval; } - + + /** + * Fill in the creator information to the SPDX document + * + * @param projectInformation project level information including the creators + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + private void fillCreatorInfo( SpdxProjectInformation projectInformation ) throws InvalidSPDXAnalysisException + { + ArrayList creators = new ArrayList<>(); + String[] parameterCreators = projectInformation.getCreators(); + for ( String parameterCreator : parameterCreators ) + { + String verify = SpdxVerificationHelper.verifyCreator( parameterCreator ); + if ( verify == null ) + { + creators.add( parameterCreator ); + } + else + { + LOG.warn( "Invalid creator string ( {} ), {} will be skipped.", verify, parameterCreator ); + } + } + SpdxCreatorInformation spdxCreator = spdxDoc.createCreationInfo( creators, format.format( new Date() ) ); + spdxCreator.setComment( projectInformation.getCreatorComment() ); + spdxCreator.setLicenseListVersion( ListedLicenses.getListedLicenses().getLicenseListVersion() ); + spdxDoc.setCreationInfo( spdxCreator ); + } + private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation ) throws SpdxBuilderException { - //TODO: Add annotations - //TODO: Add relationships - //TODO: Add comment String copyrightText = projectInformation.getCopyrightText(); if ( copyrightText == null ) { @@ -307,7 +218,7 @@ private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation } else { - LOG.warn( "Invalid download location in POM file: " + projectInformation.getDownloadUrl() ); + LOG.warn( "Invalid download location in POM file: {}", projectInformation.getDownloadUrl() ); } if ( downloadUrl == null ) { @@ -316,7 +227,7 @@ private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation SpdxPackageVerificationCode nullPackageVerificationCode; try { - nullPackageVerificationCode = spdxDoc.createPackageVerificationCode( NULL_SHA1, new ArrayList() ); + nullPackageVerificationCode = spdxDoc.createPackageVerificationCode( NULL_SHA1, new ArrayList<>() ); } catch ( InvalidSPDXAnalysisException e ) { @@ -325,12 +236,20 @@ private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation SpdxPackage pkg; try { - pkg = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId, spdxDoc.getDocumentUri() ), - projectInformation.getName(), projectInformation.getConcludedLicense(), - copyrightText, projectInformation.getDeclaredLicense() ) + final AnyLicenseInfo concludedLicense = LicenseInfoFactory + .parseSPDXLicenseStringCompatV2( projectInformation.getConcludedLicense(), spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); + final AnyLicenseInfo declaredLicense = LicenseInfoFactory + .parseSPDXLicenseStringCompatV2( projectInformation.getDeclaredLicense(), spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); + final Packaging packaging = Packaging.valueOfPackaging( project.getPackaging() ); + final Purpose primaryPurpose = packaging != null ? packaging.getV2Purpose() : Purpose.LIBRARY; + pkg = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId ), + projectInformation.getName(), concludedLicense, + copyrightText, declaredLicense ) .setDownloadLocation( downloadUrl ) .setPackageVerificationCode( nullPackageVerificationCode ) - .setPrimaryPurpose( projectInformation.getPrimaryPurpose() ) + .setPrimaryPurpose( primaryPurpose ) .setExternalRefs( SpdxExternalRefBuilder.getDefaultExternalRefs( spdxDoc, generatePurls, project ) ) .build(); } @@ -376,7 +295,7 @@ private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation } catch( InvalidSPDXAnalysisException ex ) { - LOG.warn( "Invalid URL in project POM file: "+projectInformation.getHomePage() ); + LOG.warn( "Invalid URL in project POM file: {}", projectInformation.getHomePage() ); } } @@ -421,7 +340,18 @@ private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation { try { - pkg.getChecksums().addAll( projectInformation.getChecksums() ); + for ( Checksum checksum : projectInformation.getChecksums() ) + { + try + { + final ChecksumAlgorithm algorithm = ChecksumAlgorithm.valueOf( checksum.getAlgorithm() ); + pkg.getChecksums().add( spdxDoc.createChecksum( algorithm, checksum.getValue() )); + } + catch ( IllegalArgumentException | NullPointerException e1 ) + { + LOG.error( "Invalid checksum algorithm {}", checksum.getAlgorithm() ); + } + } } catch ( InvalidSPDXAnalysisException e ) { @@ -431,13 +361,13 @@ private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation } // external references ExternalReference[] externalRefs = projectInformation.getExternalRefs(); - if ( externalRefs != null && externalRefs.length > 0 ) + if (externalRefs != null) { for ( ExternalReference externalRef : externalRefs ) { try { - pkg.getExternalRefs().add( externalRef.getExternalRef( spdxDoc ) ); + pkg.getExternalRefs().add( convertExternalRef( externalRef ) ); } catch ( MojoExecutionException | InvalidSPDXAnalysisException e ) { @@ -449,55 +379,17 @@ private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation return pkg; } - /** - * Fill in the creator information to the SPDX document - * - * @param projectInformation project level information including the creators - * @throws InvalidSPDXAnalysisException - */ - private void fillCreatorInfo( SpdxProjectInformation projectInformation ) throws InvalidSPDXAnalysisException - { - ArrayList creators = new ArrayList<>(); - String[] parameterCreators = projectInformation.getCreators(); - for ( String parameterCreator : parameterCreators ) - { - String verify = SpdxVerificationHelper.verifyCreator( parameterCreator ); - if ( verify == null ) - { - creators.add( parameterCreator ); - } - else - { - LOG.warn( - "Invalid creator string ( " + verify + " ), " + parameterCreator + " will be skipped." ); - } - } - SpdxCreatorInformation spdxCreator = spdxDoc.createCreationInfo( creators, format.format( new Date() ) ); - spdxCreator.setComment( projectInformation.getCreatorComment() ); - spdxCreator.setLicenseListVersion( ListedLicenses.getListedLicenses().getLicenseListVersion() ); - spdxDoc.setCreationInfo( spdxCreator ); - } - - /** - * Collect information at the file level, fill in the SPDX document - * - * @param sources Source directories to be included in the document - * @param baseDir project base directory used to construct the relative paths for the SPDX - * files - * @param pathSpecificInformation Map of path to file information used to override the default file information - * @param algorithms algorithms to use to generate checksums - * @throws SpdxBuilderException - */ + @Override public void collectSpdxFileInformation( List sources, String baseDir, SpdxDefaultFileInformation defaultFileInformation, - Map pathSpecificInformation, - Set algorithms ) throws SpdxBuilderException + HashMap pathSpecificInformation, + Set checksumAlgorithms ) throws SpdxBuilderException { - SpdxFileCollector fileCollector = new SpdxFileCollector(); + SpdxV2FileCollector fileCollector = new SpdxV2FileCollector(); try { fileCollector.collectFiles( sources, baseDir, defaultFileInformation, - pathSpecificInformation, projectPackage, RelationshipType.GENERATES, spdxDoc, algorithms ); + pathSpecificInformation, projectPackage, RelationshipType.GENERATES, spdxDoc, checksumAlgorithms ); projectPackage.getFiles().addAll( fileCollector.getFiles() ); projectPackage.getLicenseInfoFromFiles().addAll( fileCollector.getLicenseInfoFromFiles() ); } @@ -520,14 +412,105 @@ public void collectSpdxFileInformation( List sources, String baseDir, } } - public LicenseManager getLicenseManager() + @Override + public void saveSpdxDocumentToFile() throws SpdxBuilderException { - return this.licenseManager; + try ( FileOutputStream spdxOut = new FileOutputStream( spdxFile ) ) + { + modelStore.serialize( spdxOut ); + } + catch ( FileNotFoundException e ) + { + throw new SpdxBuilderException( "Error saving SPDX data to file", e ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxBuilderException( "Error collecting SPDX file data", e ); + } + catch ( IOException e ) + { + throw new SpdxBuilderException( "I/O Error saving SPDX data to file", e ); + } } - public SpdxPackage getProjectPackage() + @Override + public void addNonStandardLicenses( NonStandardLicense[] nonStandardLicenses ) throws SpdxBuilderException + { + if ( nonStandardLicenses != null ) + { + for ( NonStandardLicense nonStandardLicense : nonStandardLicenses ) + { + try + { + // the following will add the non-standard license to the document container + licenseManager.addExtractedLicense( nonStandardLicense ); + } + catch ( LicenseManagerException e ) + { + throw new SpdxBuilderException( "Error adding non standard license", e ); + } + } + } + } + + public ExternalRef convertExternalRef( ExternalReference externalReference ) throws MojoExecutionException + { + ReferenceCategory cat; + + try { + cat = ReferenceCategory.valueOf( externalReference.getCategory().replaceAll( "-", "_" ) ); + } + catch ( Exception ex ) + { + throw new MojoExecutionException( "External reference category " + externalReference.getCategory() + " is not recognized as a valid, standard category." ); + } + ReferenceType refType; + try + { + refType = ListedReferenceTypes.getListedReferenceTypes().getListedReferenceTypeByName( externalReference.getType() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new MojoExecutionException( "Error getting listed reference type for " + externalReference.getType(), e ); + } + if ( refType == null ) + { + throw new MojoExecutionException( "Listed reference type not found for " + externalReference.getType() ); + } + try + { + return spdxDoc.createExternalRef( cat, refType, externalReference.getLocator(), externalReference.getComment() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new MojoExecutionException( "Error creating External Reference", e ); + } + } + + @Override + public CoreModelObject getProjectPackage() { return projectPackage; } + @Override + public String mavenLicenseListToSpdxLicenseExpression( List mavenLicenses ) throws LicenseManagerException + { + return licenseManager.mavenLicenseListToSpdxLicense( mavenLicenses ).toString(); + } + + @Override + public List verify() + { + return spdxDoc.verify(); + } + + /** + * @return the license manager + */ + public SpdxV2LicenseManager getLicenseManager() + { + return this.licenseManager; + } + } diff --git a/src/main/java/org/spdx/maven/utils/SpdxFileCollector.java b/src/main/java/org/spdx/maven/utils/SpdxV2FileCollector.java similarity index 56% rename from src/main/java/org/spdx/maven/utils/SpdxFileCollector.java rename to src/main/java/org/spdx/maven/utils/SpdxV2FileCollector.java index 038eeb4..017a02e 100644 --- a/src/main/java/org/spdx/maven/utils/SpdxFileCollector.java +++ b/src/main/java/org/spdx/maven/utils/SpdxV2FileCollector.java @@ -16,75 +16,44 @@ package org.spdx.maven.utils; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; -import java.util.Map.Entry; import org.apache.maven.shared.model.fileset.FileSet; import org.apache.maven.shared.model.fileset.util.FileSetManager; - -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.Checksum; -import org.spdx.library.model.Relationship; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.SpdxFile; -import org.spdx.library.model.SpdxPackage; -import org.spdx.library.model.SpdxPackageVerificationCode; -import org.spdx.library.model.SpdxSnippet; -import org.spdx.library.model.enumerations.ChecksumAlgorithm; -import org.spdx.library.model.enumerations.FileType; -import org.spdx.library.model.enumerations.RelationshipType; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.InvalidLicenseStringException; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.model.v2.Relationship; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.SpdxFile; +import org.spdx.library.model.v2.SpdxPackage; +import org.spdx.library.model.v2.SpdxPackageVerificationCode; +import org.spdx.library.model.v2.SpdxSnippet; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.FileType; +import org.spdx.library.model.v2.enumerations.RelationshipType; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.license.InvalidLicenseStringException; +import org.spdx.maven.Checksum; import org.spdx.maven.SnippetInfo; import org.spdx.storage.IModelStore.IdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; /** - * Collects SPDX file information from directories. + * Collects SPDX file information from directories in SPDX Spec version 2 format *

* The method collectFilesInDirectory(FileSet[] filesets) will scan and create SPDX File information for * all files in the filesets. * * @author Gary O'Neall */ -public class SpdxFileCollector +public class SpdxV2FileCollector extends AbstractFileCollector { - private static final Logger LOG = LoggerFactory.getLogger( SpdxFileCollector.class ); - - // constants for mapping extensions to types. - static final String SPDX_FILE_TYPE_CONSTANTS_PROP_PATH = "resources/SpdxFileTypeConstants.prop"; - - static final Map EXT_TO_FILE_TYPE = new HashMap<>(); - - static - { - loadFileExtensionConstants(); - } - - static final Map checksumAlgorithms = new HashMap<>(); - - static - { - checksumAlgorithms.put( ChecksumAlgorithm.SHA1, "SHA-1" ); - checksumAlgorithms.put( ChecksumAlgorithm.SHA224, "SHA-224" ); - checksumAlgorithms.put( ChecksumAlgorithm.SHA256, "SHA-256" ); - checksumAlgorithms.put( ChecksumAlgorithm.SHA384, "SHA-384" ); - checksumAlgorithms.put( ChecksumAlgorithm.SHA3_384, "SHA-512" ); - checksumAlgorithms.put( ChecksumAlgorithm.MD2, "MD2" ); - checksumAlgorithms.put( ChecksumAlgorithm.MD4, "MD4" ); - checksumAlgorithms.put( ChecksumAlgorithm.MD5, "MD5" ); - checksumAlgorithms.put( ChecksumAlgorithm.MD6, "MD6" ); - } - + private static final String DEFAULT_SHA1_VALUE = "0000000000000000000000000000000000000000"; Set licensesFromFiles = new HashSet<>(); /** * Map of fileName, SPDXFile for all files in the SPDX document @@ -97,54 +66,8 @@ public class SpdxFileCollector /** * SpdxFileCollector collects SPDX file information for files */ - public SpdxFileCollector() - { - } - - /** - * Load file type constants from the properties file - */ - private static void loadFileExtensionConstants() + public SpdxV2FileCollector() { - Properties prop = new Properties(); - try ( InputStream is = SpdxFileCollector.class.getClassLoader().getResourceAsStream( - SPDX_FILE_TYPE_CONSTANTS_PROP_PATH ) ) - { - if ( is == null ) - { - LOG.error( "Unable to load properties file " + SPDX_FILE_TYPE_CONSTANTS_PROP_PATH ); - return; - } - prop.load( is ); - Iterator> iter = prop.entrySet().iterator(); - while ( iter.hasNext() ) - { - Entry entry = iter.next(); - String fileTypeStr = (String)entry.getKey(); - FileType fileType = FileType.valueOf( fileTypeStr ); - String[] extensions = ((String)entry.getValue()).split( "," ); - for ( String extension:extensions ) - { - try - { - String trimmedExtension = extension.toUpperCase().trim(); - if ( EXT_TO_FILE_TYPE.containsKey( trimmedExtension ) ) - { - LOG.warn( "Duplicate file extension: "+trimmedExtension ); - } - EXT_TO_FILE_TYPE.put( trimmedExtension, fileType ); - } - catch ( Exception ex ) { - LOG.error( "Error adding file extensions to filetype map", ex ); - } - } - } - } - catch ( IOException e ) - { - LOG.warn( - "WARNING: Error reading SpdxFileTypeConstants properties file. All file types will be mapped to Other." ); - } } /** @@ -158,13 +81,14 @@ private static void loadFileExtensionConstants() * @param projectPackage Package to which the files belong * @param spdxDoc SPDX document which contains the extracted license infos that may be needed for license parsing * - * @throws SpdxCollectionException + * @throws SpdxCollectionException on incompatible types in an SPDX collection */ - public void collectFiles( List fileSets, String baseDir, - SpdxDefaultFileInformation defaultFileInformation, - Map pathSpecificInformation, - SpdxPackage projectPackage, RelationshipType relationshipType, - SpdxDocument spdxDoc, Set algorithms ) throws SpdxCollectionException + @SuppressWarnings("DuplicateExpressions") + public void collectFiles(List fileSets, String baseDir, + SpdxDefaultFileInformation defaultFileInformation, + Map pathSpecificInformation, + SpdxPackage projectPackage, RelationshipType relationshipType, + SpdxDocument spdxDoc, Set algorithms ) throws SpdxCollectionException { for ( FileSet fileSet : fileSets ) { @@ -198,13 +122,13 @@ public void collectFiles( List fileSets, String baseDir, /** * Find the most appropriate file information based on the lowest level match (closed to file) * - * @param filePath - * @param pathSpecificInformation - * @return + * @param filePath file path for possible file path specific information + * @param pathSpecificInformation information to be applied to the file path + * @return default SPDX parameters for a given file path or null if package level defaults are to be used */ - private SpdxDefaultFileInformation findDefaultFileInformation( String filePath, Map pathSpecificInformation ) + private @Nullable SpdxDefaultFileInformation findDefaultFileInformation(String filePath, Map pathSpecificInformation ) { - LOG.debug( "Checking for file path " + filePath ); + LOG.debug( "Checking for file path {}", filePath ); SpdxDefaultFileInformation retval = pathSpecificInformation.get( filePath ); if ( retval != null ) { @@ -213,7 +137,7 @@ private SpdxDefaultFileInformation findDefaultFileInformation( String filePath, } // see if any of the parent directories contain default information which should be used String parentPath = filePath; - int parentPathIndex = 0; + int parentPathIndex; do { parentPathIndex = parentPath.lastIndexOf( "/" ); @@ -225,7 +149,8 @@ private SpdxDefaultFileInformation findDefaultFileInformation( String filePath, } while ( retval == null && parentPathIndex > 0 ); if ( retval != null ) { - LOG.debug( "Found directory containing file path for path specific information. File path: " + parentPath ); + LOG.debug( "Found directory containing file path for path specific information. File path: {}", + parentPath ); } return retval; } @@ -233,15 +158,17 @@ private SpdxDefaultFileInformation findDefaultFileInformation( String filePath, /** * Collect SPDX information for a specific file * - * @param file + * @param file File to collect SPDX information about * @param outputFileName Path to the output file name relative to the root of the output archive file * @param relationshipType Type of relationship to the project package * @param projectPackage Package to which the files belong * @param spdxDoc SPDX Document which will contain the files * @param algorithms algorithms to use to generate checksums - * @throws SpdxCollectionException + * @throws SpdxCollectionException on incompatible type errors in an SPDX collection */ - private void collectFile( File file, String outputFileName, SpdxDefaultFileInformation fileInfo, RelationshipType relationshipType, SpdxPackage projectPackage, SpdxDocument spdxDoc, Set algorithms ) throws SpdxCollectionException + private void collectFile( File file, String outputFileName, SpdxDefaultFileInformation fileInfo, + RelationshipType relationshipType, SpdxPackage projectPackage, + SpdxDocument spdxDoc, Set algorithms ) throws SpdxCollectionException { if ( spdxFiles.containsKey( file.getPath() ) ) { @@ -300,19 +227,26 @@ private void collectFile( File file, String outputFileName, SpdxDefaultFileInfor /** * Create an SpdxSnippet from the snippet information provided - * @param snippet - * @param spdxFile - * @param spdxDoc - * @return - * @throws SpdxBuilderException - * @throws InvalidSPDXAnalysisException + * @param snippet snippet to collect SPDX information about + * @param spdxFile SPDX file containing the snippet + * @param spdxDoc SPDX document containing the SPDX file + * @return SPDX Snippet based on the information collected + * @throws SpdxBuilderException on errors building the snippet + * @throws InvalidSPDXAnalysisException on SPDX parsing errors */ private SpdxSnippet convertToSpdxSnippet( SnippetInfo snippet, SpdxFile spdxFile, SpdxDocument spdxDoc ) throws SpdxBuilderException, InvalidSPDXAnalysisException { //TODO: Add annotations to snippet - return spdxDoc.createSpdxSnippet( spdxDoc.getModelStore().getNextId( IdType.SpdxId, spdxDoc.getDocumentUri() ), - snippet.getName(), snippet.getLicenseConcluded( spdxDoc ), - snippet.getLicenseInfoInSnippet( spdxDoc ), + AnyLicenseInfo concludedLicense = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( snippet.getLicenseConcluded(), spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); + List licenseInfoInSnippet = new ArrayList<>(); + licenseInfoInSnippet.add( LicenseInfoFactory.parseSPDXLicenseStringCompatV2( snippet.getLicenseInfoInSnippet(), + spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), + spdxDoc.getCopyManager() ) ); + return spdxDoc.createSpdxSnippet( spdxDoc.getModelStore().getNextId( IdType.SpdxId ), + snippet.getName(), concludedLicense, + licenseInfoInSnippet, snippet.getCopyrightText(), spdxFile, snippet.getByteRangeStart(), snippet.getByteRangeEnd() ) .setComment( snippet.getComment() ) @@ -322,17 +256,17 @@ private SpdxSnippet convertToSpdxSnippet( SnippetInfo snippet, SpdxFile spdxFile } /** - * @param file + * @param file File to be to convert to SPDX file metadata * @param outputFileName Path to the output file name relative to the root of the output archive file * @param defaultFileInformation Information on default SPDX field data for the files * @param algorithms algorithms to use to generate checksums * @param spdxDoc SPDX document which will contain the SPDX file - * @return - * @throws SpdxCollectionException + * @return SPDX file based on file and default file information + * @throws SpdxCollectionException on incompatible class types in an SPDX collection */ private SpdxFile convertToSpdxFile( File file, String outputFileName, SpdxDefaultFileInformation defaultFileInformation, - Set algorithms, + Set algorithms, SpdxDocument spdxDoc ) throws SpdxCollectionException { String relativePath = convertFilePathToSpdxFileName( outputFileName ); @@ -341,18 +275,18 @@ private SpdxFile convertToSpdxFile( File file, String outputFileName, Set checksums; try { - checksums = generateChecksum( file, algorithms, spdxDoc ); + checksums = generateChecksum( file, algorithms ); } catch ( SpdxCollectionException | InvalidSPDXAnalysisException e1 ) { throw new SpdxCollectionException( "Unable to generate checksum for file "+file.getName() ); } - AnyLicenseInfo concludedLicense = null; + AnyLicenseInfo concludedLicense; AnyLicenseInfo license = null; String licenseComment = defaultFileInformation.getLicenseComment(); if ( isSourceFile( fileTypes ) && file.length() < SpdxSourceFileParser.MAXIMUM_SOURCE_FILE_LENGTH ) { - List fileSpdxLicenses = null; + List fileSpdxLicenses = null; try { fileSpdxLicenses = SpdxSourceFileParser.parseFileForSpdxLicenses( file ); @@ -361,40 +295,67 @@ private SpdxFile convertToSpdxFile( File file, String outputFileName, { LOG.error( "Error parsing for SPDX license ID's", ex ); } - if ( fileSpdxLicenses != null && fileSpdxLicenses.size() > 0 ) + if ( fileSpdxLicenses != null && !fileSpdxLicenses.isEmpty()) { // The file has declared licenses of the form SPDX-License-Identifier: licenseId - if ( fileSpdxLicenses.size() == 1 ) - { - license = fileSpdxLicenses.get( 0 ); - } - else + try { - try + if ( fileSpdxLicenses.size() == 1 ) { - license = spdxDoc.createConjunctiveLicenseSet( fileSpdxLicenses ); + license = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( fileSpdxLicenses.get( 0 ), + spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), + spdxDoc.getCopyManager() ); } - catch ( InvalidSPDXAnalysisException e ) + else { - throw new SpdxCollectionException( "Error creating SPDX file - unable to create a license set", e ); + Set licenseSet = new HashSet<>(); + for ( String licenseExpression : fileSpdxLicenses ) + { + licenseSet.add( LicenseInfoFactory.parseSPDXLicenseStringCompatV2( licenseExpression, + spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), + spdxDoc.getCopyManager() ) ); + } + license = spdxDoc.createConjunctiveLicenseSet( licenseSet ); } } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.error( "Invalid license expressions found in source file {}", file.getName(), e ); + } if ( licenseComment == null ) { licenseComment = ""; } - else if ( licenseComment.length() > 0 ) + else if ( !licenseComment.isEmpty() ) { licenseComment = licenseComment.concat( "; " ); } licenseComment = licenseComment.concat( "This file contains SPDX-License-Identifiers for " ); - licenseComment = licenseComment.concat( license.toString() ); + if ( license != null ) + { + licenseComment = licenseComment.concat( license.toString() ); + } } } if ( license == null ) { - license = defaultFileInformation.getDeclaredLicense(); - concludedLicense = defaultFileInformation.getConcludedLicense(); + try + { + license = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( defaultFileInformation.getDeclaredLicense(), + spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), + spdxDoc.getCopyManager() ); + concludedLicense = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( defaultFileInformation.getConcludedLicense(), + spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), + spdxDoc.getCopyManager() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxCollectionException( "Error creating SPDX file - unable create default file license", e ); + } } else { @@ -412,29 +373,34 @@ else if ( licenseComment.length() > 0 ) contributors = new ArrayList<>(); } - SpdxFile retval = null; + SpdxFile retval; //TODO: Add annotation try { List seenLicenses = new ArrayList<>(); seenLicenses.add( license ); - Checksum sha1 = null; + String sha1Value = null; for ( Checksum checksum:checksums ) { - if (ChecksumAlgorithm.SHA1.equals( checksum.getAlgorithm() )) { - sha1 = checksum; + if (ChecksumAlgorithm.SHA1.toString().equals( checksum.getAlgorithm() )) { + sha1Value = checksum.getValue(); break; } } - retval = spdxDoc.createSpdxFile( spdxDoc.getModelStore().getNextId( IdType.SpdxId, spdxDoc.getDocumentUri() ), + if ( sha1Value == null ) + { + LOG.error( "No SHA1 checksum was found for file {}", file.getName() ); + sha1Value = DEFAULT_SHA1_VALUE; + } + retval = spdxDoc.createSpdxFile( spdxDoc.getModelStore().getNextId( IdType.SpdxId ), relativePath, concludedLicense, seenLicenses, - copyright, sha1 ) + copyright, + spdxDoc.createChecksum( ChecksumAlgorithm.SHA1, sha1Value ) ) .setComment( comment ) .setLicenseComments( licenseComment ) .setFileTypes( fileTypes ) .setFileContributors( contributors ) .build(); - retval.setNoticeText( notice ); } @@ -446,62 +412,7 @@ else if ( licenseComment.length() > 0 ) return retval; } - /** - * @param fileTypes - * @return true if the fileTypes contain a source file type - */ - protected boolean isSourceFile( Collection fileTypes ) - { - for ( FileType ft : fileTypes ) - { - if ( ft == FileType.SOURCE ) - { - return true; - } - } - return false; - } - - /** - * Create the SPDX file name from a system specific path name - * - * @param filePath system specific file path relative to the top of the archive root to the top of the archive - * directory where the file is stored. - * @return - */ - public String convertFilePathToSpdxFileName( String filePath ) - { - String result = filePath.replace( '\\', '/' ); - if ( !result.startsWith( "./" ) ) - { - result = "./" + result; - } - return result; - } - - public String getExtension( File file ) - { - String fileName = file.getName(); - int lastDot = fileName.lastIndexOf( '.' ); - if ( lastDot < 1 ) - { - return ""; - } - else - { - return fileName.substring( lastDot + 1 ); - } - } - - protected static FileType extensionToFileType( String fileExtension ) - { - FileType retval = EXT_TO_FILE_TYPE.get( fileExtension.trim().toUpperCase() ); - if ( retval == null ) - { - retval = FileType.OTHER; - } - return retval; - } + /** * @return SPDX Files which have been acquired through the collectFilesInDirectory method @@ -530,11 +441,11 @@ public Collection getLicenseInfoFromFiles() /** * Create a verification code from all SPDX files collected * - * @param spdxFilePath Complete file path for the SPDX file - this will be excluded from the verification code - * @param spdxDoc SPDX document which will contain the package verification code. - * @return - * @throws NoSuchAlgorithmException - * @throws InvalidSPDXAnalysisException + * @param spdxFilePath Complete file path for the SPDX file - this will be excluded from the verification code + * @param spdxDoc SPDX document which will contain the package verification code. + * @return package verification code + * @throws NoSuchAlgorithmException on error generating checksum + * @throws InvalidSPDXAnalysisException on SPDX parsing errors */ public SpdxPackageVerificationCode getVerificationCode( String spdxFilePath, SpdxDocument spdxDoc ) throws NoSuchAlgorithmException, InvalidSPDXAnalysisException { @@ -543,10 +454,7 @@ public SpdxPackageVerificationCode getVerificationCode( String spdxFilePath, Spd if ( spdxFilePath != null && spdxFiles.containsKey( spdxFilePath ) ) { Optional excludedFileName = spdxFiles.get( spdxFilePath ).getName(); - if ( excludedFileName.isPresent() ) - { - excludedFileNamesFromVerificationCode.add( excludedFileName.get() ); - } + excludedFileName.ifPresent(excludedFileNamesFromVerificationCode::add); } SpdxPackageVerificationCode verificationCode; verificationCode = calculatePackageVerificationCode( spdxFiles.values(), @@ -560,9 +468,9 @@ public SpdxPackageVerificationCode getVerificationCode( String spdxFilePath, Spd * @param spdxFiles Files used to calculate the verification code * @param excludedFileNamesFromVerificationCode List of file names to exclude * @param spdxDoc SPDX document which will contain the Package Verification Code - * @return - * @throws NoSuchAlgorithmException - * @throws InvalidSPDXAnalysisException + * @return Generated SPDX Package Verification Code + * @throws NoSuchAlgorithmException in the unlikely event the encryption algorithm could not be found + * @throws InvalidSPDXAnalysisException on SPDX parsing errors */ private SpdxPackageVerificationCode calculatePackageVerificationCode( Collection spdxFiles, List excludedFileNamesFromVerificationCode, @@ -572,7 +480,7 @@ private SpdxPackageVerificationCode calculatePackageVerificationCode( Collection for ( SpdxFile file : spdxFiles ) { Optional filename = file.getName(); - if ( filename.isPresent() && includeInVerificationCode( file.getName().get(), excludedFileNamesFromVerificationCode ) ) + if ( filename.isPresent() && includeInVerificationCode( filename.get(), excludedFileNamesFromVerificationCode ) ) { fileChecksums.add( file.getSha1() ); } @@ -599,90 +507,4 @@ private boolean includeInVerificationCode( String name, List excludedFil } return true; } - - /** - * Converts an array of bytes to a string compliant with the SPDX sha1 representation - * - * @param digestBytes - * @return - */ - public static String convertChecksumToString( byte[] digestBytes ) - { - StringBuilder sb = new StringBuilder(); - for ( byte digestByte : digestBytes ) - { - String hex = Integer.toHexString( 0xff & digestByte ); - if ( hex.length() < 2 ) - { - sb.append( '0' ); - } - sb.append( hex ); - } - return sb.toString(); - } - - /** - * Generate the Sha1 for a given file. Must have read access to the file. This method is equivalent to calling - * {@code SpdxFileCollector.generateChecksum(file, "SHA-1")}. - * - * @param file file to generate checksum for - * @param spdxDoc SPDX document which will contain the checksum - * @return SHA1 checksum of the input file - * @throws SpdxCollectionException if the algorithm is unavailable or the file cannot be read - * @throws InvalidSPDXAnalysisException - */ - public static String generateSha1( File file, SpdxDocument spdxDoc ) throws SpdxCollectionException, InvalidSPDXAnalysisException - { - Set sha1 = new HashSet<>(); - sha1.add( ChecksumAlgorithm.SHA1 ); - Checksum sha1Checksum = generateChecksum( file, sha1, spdxDoc ).iterator().next(); - return sha1Checksum.getValue(); - } - - /** - * Generate checksums for a given file using each algorithm supplied. Must have read access to the file. - * - * @param file file whose checksum is to be generated - * @param algorithms algorithms to generate the checksums - * @param spdxDoc SPDX document which will contain the checksum - * @return {@code Set} of checksums for file using each algorithm specified - * @throws SpdxCollectionException if the input algorithm is invalid or unavailable or if the file cannot be read - * @throws InvalidSPDXAnalysisException - */ - public static Set generateChecksum( File file, Set algorithms, - SpdxDocument spdxDoc ) throws SpdxCollectionException, InvalidSPDXAnalysisException - { - Set checksums = new HashSet<>(); - - byte[] buffer; - try - { - buffer = Files.readAllBytes( file.toPath() ); - } - catch ( IOException e ) - { - throw new SpdxCollectionException( "IO error while calculating checksums.", e ); - } - - for ( ChecksumAlgorithm algorithm : algorithms ) - { - String checksumAlgorithm = checksumAlgorithms.get( algorithm ); - - MessageDigest digest; - try - { - digest = MessageDigest.getInstance( checksumAlgorithm ); - } - catch ( NoSuchAlgorithmException e ) - { - throw new SpdxCollectionException( e ); - } - - digest.update( buffer ); - String checksum = convertChecksumToString( digest.digest() ); - checksums.add( spdxDoc.createChecksum( algorithm, checksum ) ); - } - - return checksums; - } } diff --git a/src/main/java/org/spdx/maven/utils/LicenseManager.java b/src/main/java/org/spdx/maven/utils/SpdxV2LicenseManager.java similarity index 78% rename from src/main/java/org/spdx/maven/utils/LicenseManager.java rename to src/main/java/org/spdx/maven/utils/SpdxV2LicenseManager.java index 36953f5..06a5cf0 100644 --- a/src/main/java/org/spdx/maven/utils/LicenseManager.java +++ b/src/main/java/org/spdx/maven/utils/SpdxV2LicenseManager.java @@ -21,36 +21,35 @@ import java.util.Map; import org.apache.maven.model.License; - -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.ExtractedLicenseInfo; -import org.spdx.library.model.license.InvalidLicenseStringException; -import org.spdx.library.model.license.LicenseInfoFactory; -import org.spdx.library.model.license.SpdxListedLicense; -import org.spdx.library.model.license.SpdxNoAssertionLicense; - +import org.spdx.core.DefaultStoreNotInitialized; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.license.ExtractedLicenseInfo; +import org.spdx.library.model.v2.license.InvalidLicenseStringException; +import org.spdx.library.model.v2.license.SpdxListedLicense; +import org.spdx.library.model.v2.license.SpdxNoAssertionLicense; import org.spdx.maven.NonStandardLicense; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Manages the SPDX licenses for the Spdx plugin. Keeps track of any extracted licenses (added as configuration + * Manages the SPDX Spec version 2licenses for the Spdx plugin. Keeps track of any extracted licenses (added as configuration * parameters to the plugin). Maps Maven licenses to SPDX licenses. Creates a Maven license from an SPDX license. * * @author Gary O'Neall */ -public class LicenseManager +public class SpdxV2LicenseManager { - private static final Logger LOG = LoggerFactory.getLogger( LicenseManager.class ); + private static final Logger LOG = LoggerFactory.getLogger( SpdxV2LicenseManager.class ); /** * SPDX document containing the license information collected. All extracted licenses are added to the SPDX * document */ - SpdxDocument spdxDoc = null; + SpdxDocument spdxDoc; /** * Maps URLs to SPDX license ID's. The SPDX licenses could be an SPDX listed license or an extracted license. @@ -67,11 +66,9 @@ public class LicenseManager * mapping uses the license URL to uniquely identify the licenses. * * @param spdxDoc SPDX document to add any extracted licenses - * @param useStdLicenseSourceUrls if true, map any SPDX listed license source URL to license ID. Note: significant - * performance degradation - * @throws LicenseMapperException + * @throws LicenseMapperException on errors accessing SPDX listed or local licenses */ - public LicenseManager( SpdxDocument spdxDoc, boolean useStdLicenseSourceUrls ) throws LicenseMapperException + public SpdxV2LicenseManager( SpdxDocument spdxDoc ) throws LicenseMapperException { this.spdxDoc = spdxDoc; initializeUrlMap(); @@ -80,7 +77,7 @@ public LicenseManager( SpdxDocument spdxDoc, boolean useStdLicenseSourceUrls ) t /** * Initialize the URL map from the SPDX listed licenses * - * @throws LicenseMapperException + * @throws LicenseMapperException on errors accessing SPDX listed or local licenses */ private void initializeUrlMap() throws LicenseMapperException { @@ -91,8 +88,8 @@ private void initializeUrlMap() throws LicenseMapperException * Add a non-listed license to the SPDX document. Once added, the non-listed license can be referenced by the * license ID * - * @param license - * @throws LicenseManagerException + * @param license extracted license to add + * @throws LicenseManagerException on errors accessing SPDX listed or local licenses */ public void addExtractedLicense( NonStandardLicense license ) throws LicenseManagerException { @@ -130,11 +127,10 @@ public void addExtractedLicense( NonStandardLicense license ) throws LicenseMana if ( this.urlStringToSpdxLicenseId.containsKey( url ) ) { String oldLicenseId = urlStringToSpdxLicenseId.get( url ); - LOG.warn( - "Duplicate URL for SPDX extracted license. Replacing " + oldLicenseId + " with " - + license.getLicenseId() + " for " + url ); + LOG.warn( "Duplicate URL for SPDX extracted license. Replacing {} with {} for {}", + oldLicenseId, license.getLicenseId(), url ); } - LOG.debug( "Adding URL mapping for non-standard license " + spdxLicense.getLicenseId() ); + LOG.debug( "Adding URL mapping for non-standard license {}", spdxLicense.getLicenseId() ); this.urlStringToSpdxLicenseId.put( url, spdxLicense.getLicenseId() ); } } @@ -147,8 +143,11 @@ public void addExtractedLicense( NonStandardLicense license ) throws LicenseMana * returned. if a single license is supplied, the mapped SPDX license is returned. If multiple licenses are * supplied, a conjunctive license is returned containing all mapped SPDX licenses. * - * @return - * @throws LicenseManagerException + * @return If no licenses are supplied, SpdxNoAssertion license is + * returned. if a single license is supplied, the mapped SPDX license is returned. + * If multiple licenses are supplied, a conjunctive license is returned containing + * all mapped SPDX licenses. + * @throws LicenseManagerException on errors accessing SPDX listed or local licenses */ public AnyLicenseInfo mavenLicenseListToSpdxLicense( List licenseList ) throws LicenseManagerException { @@ -162,7 +161,7 @@ public AnyLicenseInfo mavenLicenseListToSpdxLicense( List licenseList ) { spdxLicenses.add( mavenLicenseToSpdxLicense( license ) ); } - if ( spdxLicenses.size() < 1 ) + if (spdxLicenses.isEmpty()) { return new SpdxNoAssertionLicense(); } @@ -194,7 +193,7 @@ public AnyLicenseInfo mavenLicenseToSpdxLicense( License mavenLicense ) throws L throw new LicenseManagerException( "Can not map maven license " + mavenLicense.getName() + " No URL exists to provide a mapping" ); } - String licenseId = this.urlStringToSpdxLicenseId.get( mavenLicense.getUrl().replaceAll("https:", "http:") ); + String licenseId = this.urlStringToSpdxLicenseId.get( mavenLicense.getUrl().replaceAll( "https:", "http:" ) ); if ( licenseId == null ) { throw new LicenseManagerException( @@ -205,15 +204,19 @@ public AnyLicenseInfo mavenLicenseToSpdxLicense( License mavenLicense ) throws L { try { - retval = LicenseInfoFactory.parseSPDXLicenseString( licenseId, spdxDoc.getModelStore(), - spdxDoc.getDocumentUri(), - spdxDoc.getCopyManager() ); + retval = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( licenseId, spdxDoc.getModelStore(), + spdxDoc.getDocumentUri(), + spdxDoc.getCopyManager() ); } catch ( InvalidLicenseStringException e ) { throw new LicenseManagerException( "Can not map maven license " + mavenLicense.getName() + " Invalid listed or extracted license id matching the URL " + mavenLicense.getUrl() ); } + catch ( DefaultStoreNotInitialized e ) + { + throw new LicenseManagerException( "Default model store not initialized" ); + } } return retval; } @@ -221,9 +224,9 @@ public AnyLicenseInfo mavenLicenseToSpdxLicense( License mavenLicense ) throws L /** * Create a Maven license from the SPDX license * - * @param spdxLicense - * @return - * @throws LicenseManagerException + * @param spdxLicense SPDX license to convert + * @return a Maven license from the SPDX license + * @throws LicenseManagerException on errors accessing SPDX listed or local licenses */ public License spdxLicenseToMavenLicense( AnyLicenseInfo spdxLicense ) throws LicenseManagerException { @@ -267,9 +270,9 @@ private License spdxStdLicenseToMavenLicense( SpdxListedLicense spdxLicense ) th } if ( spdxLicense.getSeeAlso().size() > 1 ) { - LOG.warn( - "SPDX license " + spdxLicense.getLicenseId() - + " contains multiple URLs. Only the first URL will be preserved in the Maven license created." ); + //noinspection LoggingSimilarMessage + LOG.warn( "SPDX license {} contains multiple URLs. Only the first URL will be preserved in the Maven license created.", + spdxLicense.getLicenseId() ); } return retval; } catch ( InvalidSPDXAnalysisException e ) @@ -303,9 +306,9 @@ private License spdxNonStdLicenseToMavenLicense( ExtractedLicenseInfo spdxLicens } if ( spdxLicense.getSeeAlso().size() > 1 ) { - LOG.warn( - "SPDX license " + spdxLicense.getLicenseId() - + " contains multiple URLs. Only the first URL will be preserved in the Maven license created." ); + //noinspection LoggingSimilarMessage + LOG.warn( "SPDX license {} contains multiple URLs. Only the first URL will be preserved in the Maven license created.", + spdxLicense.getLicenseId() ); } return retval; } diff --git a/src/main/java/org/spdx/maven/utils/SpdxV3DependencyBuilder.java b/src/main/java/org/spdx/maven/utils/SpdxV3DependencyBuilder.java new file mode 100644 index 0000000..788f641 --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/SpdxV3DependencyBuilder.java @@ -0,0 +1,940 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. + */ +package org.spdx.maven.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Contributor; +import org.apache.maven.model.License; +import org.apache.maven.project.DefaultProjectBuildingRequest; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.project.ProjectBuildingResult; +import org.apache.maven.shared.dependency.graph.DependencyNode; +import org.spdx.core.CoreModelObject; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.core.SpdxInvalidIdException; +import org.spdx.core.SpdxCoreConstants.SpdxMajorVersion; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.conversion.Spdx2to3Converter; +import org.spdx.library.model.v2.Checksum; +import org.spdx.library.model.v2.SpdxPackageVerificationCode; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.Purpose; +import org.spdx.library.model.v3_0_1.SpdxConstantsV3; +import org.spdx.library.model.v3_0_1.core.Agent; +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.ExternalElement; +import org.spdx.library.model.v3_0_1.core.ExternalMap; +import org.spdx.library.model.v3_0_1.core.Hash; +import org.spdx.library.model.v3_0_1.core.HashAlgorithm; +import org.spdx.library.model.v3_0_1.core.LifecycleScopeType; +import org.spdx.library.model.v3_0_1.core.Relationship; +import org.spdx.library.model.v3_0_1.core.RelationshipCompleteness; +import org.spdx.library.model.v3_0_1.core.RelationshipType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.expandedlicensing.NoAssertionLicense; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.library.model.v3_0_1.simplelicensing.LicenseExpression; +import org.spdx.library.model.v3_0_1.software.Sbom; +import org.spdx.library.model.v3_0_1.software.SoftwarePurpose; +import org.spdx.library.model.v3_0_1.software.SpdxFile; +import org.spdx.library.model.v3_0_1.software.SpdxPackage; +import org.spdx.maven.OutputFormat; +import org.spdx.storage.ISerializableModelStore; +import org.spdx.storage.IModelStore.IdType; +import org.spdx.storage.simple.InMemSpdxStore; +import org.spdx.v3jsonldstore.JsonLDStore; + +/** + * Adds dependency information into the spdxDoc + * @author Gary O'Neall + * + */ +@SuppressWarnings("LoggingSimilarMessage") +public class SpdxV3DependencyBuilder + extends AbstractDependencyBuilder +{ + private final SpdxDocument spdxDoc; + private final SpdxV3LicenseManager licenseManager; + + /** + * @param builder The document builder + * @param createExternalRefs if true, create external references for dependencies + * @param generatePurls if true, generate a Package URL and include as an external identifier for the dependencies + * @param useArtifactID if true, use the artifact ID for the name of the dependency package, otherwise use the Maven configured project name + * @param includeTransitiveDependencies If true, include transitive dependencies, otherwise include only direct dependencies + */ + public SpdxV3DependencyBuilder( SpdxV3DocumentBuilder builder, boolean createExternalRefs, + boolean generatePurls, boolean useArtifactID, + boolean includeTransitiveDependencies ) + { + super( createExternalRefs, generatePurls, useArtifactID, includeTransitiveDependencies ); + this.spdxDoc = builder.getSpdxDoc(); + this.licenseManager = builder.getLicenseManager(); + } + + @Override + protected void addMavenDependency( CoreModelObject parentPackage, DependencyNode dependencyNode, + ProjectBuilder mavenProjectBuilder, + MavenSession session, MavenProject mavenProject ) + throws LicenseMapperException, InvalidSPDXAnalysisException + { + if ( !(parentPackage instanceof SpdxPackage) ) + { + LOG.error( "Invalid type for parent package. Expected 'SpdxPackage', found {}", + parentPackage.getClass().getName() ); + return; + } + Artifact dependency = dependencyNode.getArtifact(); + String scope = dependency.getScope(); + RelationshipType relType = scopeToRelationshipType( scope, dependency.isOptional() ); + if ( relType == RelationshipType.OTHER ) + { + LOG.warn( "Could not determine the SPDX relationship type for dependency artifact ID {} scope {}", dependency.getArtifactId(), scope ); + } + + Element dependencyPackage = createSpdxPackage( dependency, mavenProjectBuilder, session, + mavenProject, useArtifactID ); + + spdxDoc.createLifecycleScopedRelationship(spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setRelationshipType( relType ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setFrom( (SpdxPackage)parentPackage ) + .addTo( dependencyPackage ) + .setScope( scopeToLifecycleScope( scope ) ) + .setComment( "Relationship created based on Maven POM information" ) + .build(); + LOG.debug( "Added relationship of type {} for {}", relType, dependencyPackage.getName() ); + + if ( includeTransitiveDependencies ) { + addMavenDependencies( mavenProjectBuilder, session, mavenProject, dependencyNode, dependencyPackage ); + } + } + + /** + * Translate the scope to the SPDX relationship type + * + * @param scope Maven Dependency Scope (see Maven dependency scope documentation) + * @param optional True if this is an optional dependency + * @return SPDX Relationship type based on the scope + */ + private RelationshipType scopeToRelationshipType( String scope, boolean optional ) + { + if ( scope == null ) + { + return RelationshipType.OTHER; + } + else if ( optional ) + { + return RelationshipType.HAS_OPTIONAL_COMPONENT; + } + else if ( scope.equals( "compile" ) || scope.equals( "runtime" ) ) + { + return RelationshipType.HAS_DYNAMIC_LINK; + } + else if ( scope.equals( "test" ) ) + { + return RelationshipType.DEPENDS_ON; + } + else + { + return RelationshipType.OTHER; + } + } + + private LifecycleScopeType scopeToLifecycleScope( String scope ) { + if ( scope == null ) + { + return LifecycleScopeType.OTHER; + } + else if ( scope.equals( "compile" ) || scope.equals( "runtime" ) ) + { + return LifecycleScopeType.RUNTIME; + } + else if ( scope.equals( "test" ) ) + { + return LifecycleScopeType.TEST; + } + else + { + return LifecycleScopeType.OTHER; + } + } + + /** + * Create an SPDX package from the information in a Maven Project + * + * @param project Maven project + * @param useArtifactID If true, use ${project.groupId}:${artifactId} as the SPDX package name, otherwise, ${project.name} will be used + * @return SPDX Package generated from the metadata in the Maven Project + * @throws SpdxCollectionException On errors with SPDX collections + * @throws NoSuchAlgorithmException if no checksum algorithm was found + * @throws LicenseMapperException on errors mapping or creating SPDX custom licenses + * @throws InvalidSPDXAnalysisException on any other general SPDX errors + */ + private SpdxPackage createSpdxPackage( MavenProject project, boolean useArtifactID ) throws SpdxCollectionException, NoSuchAlgorithmException, LicenseMapperException, InvalidSPDXAnalysisException + { + SpdxDefaultFileInformation fileInfo = new SpdxDefaultFileInformation(); + + // initialize the SPDX information from the project + String packageName = project.getName(); + if ( packageName == null || packageName.isEmpty() || useArtifactID ) + { + packageName = project.getGroupId() + ":" + project.getArtifactId(); + } + List contributors = project.getContributors(); + ArrayList fileContributorList = new ArrayList<>(); + if ( contributors != null ) + { + for ( Contributor contributor : contributors ) + { + fileContributorList.add( contributor.getName() ); + } + } + String copyright = "UNSPECIFIED"; + String notice = "UNSPECIFIED"; + String downloadLocation = "NOASSERTION"; + AnyLicenseInfo declaredLicense = mavenLicensesToSpdxLicense( project.getLicenses() ); + fileInfo.setComment( "" ); + fileInfo.setConcludedLicense( "NOASSERTION" ); + fileInfo.setContributors( fileContributorList.toArray( new String[0] ) ); + fileInfo.setCopyright( copyright ); + fileInfo.setDeclaredLicense( declaredLicense.toString() ); + fileInfo.setLicenseComment( "" ); + fileInfo.setNotice( notice ); + + SpdxPackage retval = spdxDoc.createSpdxPackage( spdxDoc.getIdPrefix() + + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( packageName ) + .setCopyrightText( copyright ) + .setDownloadLocation( downloadLocation ) + .addAllExternalIdentifier( SpdxExternalIdBuilder.getDefaultExternalIdentifiers( spdxDoc, generatePurls, project ) ) + .build(); + if ( generatePurls ) + { + retval.setPackageUrl( SpdxExternalRefBuilder.generatePurl( project ) ); + } + spdxDoc.createRelationship( spdxDoc.getIdPrefix() + + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( retval ) + .addTo( declaredLicense ) + .setRelationshipType( RelationshipType.HAS_DECLARED_LICENSE ) + .build(); + spdxDoc.createRelationship( spdxDoc.getIdPrefix() + + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( retval ) + .addTo( new NoAssertionLicense() ) + .setRelationshipType( RelationshipType.HAS_CONCLUDED_LICENSE ) + .build(); + + if ( project.getVersion() != null ) + { + retval.setPackageVersion( project.getVersion() ); + } + if ( project.getDescription() != null ) + { + retval.setDescription( project.getDescription() ); + retval.setSummary( project.getDescription() ); + } + if ( project.getOrganization() != null ) + { + retval.getOriginatedBys().add( spdxDoc.createOrganization( spdxDoc.getIdPrefix() + + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( project.getOrganization().getName() ) + .build() ); + } + if ( project.getUrl() != null ) + { + try { + retval.setHomePage( project.getUrl() ); + } catch ( InvalidSPDXAnalysisException e ) { + LOG.warn( "Invalid homepage for dependency {}: {}", project.getArtifactId(), project.getUrl() ); + } + } + return retval; + } + + /** + * Create an SPDX Document using the mavenProjectBuilder to resolve properties + * including inherited properties + * @param artifact Maven dependency artifact + * @param mavenProjectBuilder project builder for the repo containing the POM file + * @param session Maven session for building the project + * @param mavenProject Maven project + * @param useArtifactID If true, use ${project.groupId}:${artifactId} as the SPDX package name, otherwise, ${project.name} will be used + * @return SPDX Package build from the MavenProject metadata + * @throws InvalidSPDXAnalysisException on errors generating SPDX + * @throws LicenseMapperException on errors mapping licenses or creating custom licenses + */ + private Element createSpdxPackage( Artifact artifact, + ProjectBuilder mavenProjectBuilder, MavenSession session, + MavenProject mavenProject, boolean useArtifactID ) throws LicenseMapperException, InvalidSPDXAnalysisException + { + LOG.debug( "Creating SPDX package for artifact {}", artifact.getArtifactId() ); + if ( artifact.getFile() == null ) + { + LOG.debug( "Artifact file is null" ); + } + else + { + LOG.debug( "Artifact file name = {}", artifact.getFile().getName() ); + } + File spdxFile = null; + if ( artifact.getFile() != null ) + { + spdxFile = artifactFileToSpdxFile( artifact.getFile(), SpdxMajorVersion.VERSION_3 ); + } + Element retval = null; + if ( spdxFile != null && spdxFile.exists() ) + { + LOG.debug( "Dependency {}Looking for SPDX file {}", artifact.getArtifactId(), spdxFile.getAbsolutePath() ); + try + { + LOG.debug( "Dependency {}Dependency information collected from SPDX spec version 3 file {}", artifact.getArtifactId(), spdxFile.getAbsolutePath() ); + + SpdxDocument externalSpdxDoc = spdxDocumentFromFile( spdxFile.getPath() ); + if ( createExternalRefs ) + { + retval = createExternalSpdxPackage( externalSpdxDoc, spdxFile, artifact.getGroupId(), + artifact.getArtifactId(), artifact.getVersion() ); + } + else + { + retval = copyPackageInfoFromExternalDoc( externalSpdxDoc, artifact.getArtifactId() ); + } + } + catch ( IOException e ) + { + LOG.warn( "IO error reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + catch ( SpdxInvalidIdException e ) + { + LOG.warn( "Invalid SPDX ID exception reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.warn( "Invalid SPDX analysis exception reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + catch ( SpdxCollectionException e ) + { + LOG.warn( "Unable to create file checksum for external SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + catch ( Exception e ) + { + LOG.warn( "Unknown error processing SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + } + if ( retval != null ) + { + return retval; + } + // Check for an SPDX spec version 2 file + spdxFile = artifactFileToSpdxFile( artifact.getFile(), SpdxMajorVersion.VERSION_2 ); + if ( spdxFile != null && spdxFile.exists() ) + { + LOG.debug( "Dependency {}Looking for SPDX spec version 2 file {}", artifact.getArtifactId(), spdxFile.getAbsolutePath() ); + try + { + LOG.debug( "Dependency {}Dependency information collected from SPDX spec version 2 file {}", artifact.getArtifactId(), spdxFile.getAbsolutePath() ); + + retval = copyPackageInfoFromV2File( spdxFile.getPath(), artifact.getArtifactId() ); + } + catch ( IOException e ) + { + LOG.warn( "IO error reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + catch ( SpdxInvalidIdException e ) + { + LOG.warn( "Invalid SPDX ID exception reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.warn( "Invalid SPDX analysis exception reading SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + catch ( Exception e ) + { + LOG.warn( "Unknown error processing SPDX document for dependency artifact ID {}:{}. Using POM file information for creating SPDX package data.", artifact.getArtifactId(), e.getMessage() ); + } + } + if ( retval != null ) + { + return retval; + } + try + { + ProjectBuildingRequest request = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() ); + request.setRemoteRepositories( mavenProject.getRemoteArtifactRepositories() ); + for ( ArtifactRepository ar : request.getRemoteRepositories() ) { + LOG.debug( "request Remote repository ID: {}", ar.getId() ); + } + for ( ArtifactRepository ar : mavenProject.getRemoteArtifactRepositories() ) { + LOG.debug( "Project Remote repository ID: {}", ar.getId() ); + } + ProjectBuildingResult build = mavenProjectBuilder.build( artifact, request ); + MavenProject depProject = build.getProject(); + LOG.debug( "Dependency {}Collecting information from project metadata for {}", artifact.getArtifactId(), depProject.getArtifactId() ); + retval = createSpdxPackage( depProject, useArtifactID ); + } + catch ( SpdxCollectionException e ) + { + LOG.error( "SPDX File Collection Error creating SPDX package for dependency artifact ID {}:{}", artifact.getArtifactId(), e.getMessage() ); + } + catch ( NoSuchAlgorithmException e ) + { + LOG.error( "Verification Code Error creating SPDX package for dependency artifact ID {}:{}", artifact.getArtifactId(), e.getMessage() ); + } + catch ( ProjectBuildingException e ) + { + LOG.error( "Maven Project Build Error creating SPDX package for dependency artifact ID {}:{}", artifact.getArtifactId(), e.getMessage() ); + } + if ( retval != null ) + { + return retval; + } + LOG.warn( "Error creating SPDX package for dependency artifact ID {}. A minimal SPDX package will be created.", artifact.getArtifactId() ); + // Create a minimal SPDX package from dependency + // Name will be the artifact ID + LOG.debug( "Dependency {}Using only artifact information to create dependent package", artifact.getArtifactId() ); + SpdxPackage pkg = spdxDoc.createSpdxPackage( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( artifact.getArtifactId() ) + .setComment( "This package was created for a Maven dependency. No SPDX or license information could be found in the Maven POM file." ) + .setPackageVersion( artifact.getBaseVersion() ) + .addAllExternalIdentifier( SpdxExternalIdBuilder + .getDefaultExternalIdentifiers( spdxDoc, generatePurls, + mavenProject ) ) + .build(); + spdxDoc.createRelationship( spdxDoc.getIdPrefix() + + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( pkg ) + .addTo( new NoAssertionLicense() ) + .setRelationshipType( RelationshipType.HAS_DECLARED_LICENSE ) + .build(); + spdxDoc.createRelationship( spdxDoc.getIdPrefix() + + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( pkg ) + .addTo( new NoAssertionLicense() ) + .setRelationshipType( RelationshipType.HAS_CONCLUDED_LICENSE ) + .build(); + return pkg; + } + + /** + * Creates a copy from an SPDX version 2 file + * @param path Path to the SPDX spec version 2 file + * @param artifactId Maven artifact ID for the file + * @return SPDX V3 compliant element for the SPDX package represented by the arttifactId in the SPDX file + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + * @throws IOException on errors reading from the SPDX V2 document file + * @throws FileNotFoundException on the SPDX V2 document file not being found + */ + private Element copyPackageInfoFromV2File( String path, String artifactId ) throws FileNotFoundException, IOException, InvalidSPDXAnalysisException + { + org.spdx.library.model.v2.SpdxDocument v2Doc = SpdxV2DependencyBuilder.spdxDocumentFromFile( path ); + org.spdx.library.model.v2.SpdxPackage source = SpdxV2DependencyBuilder.findMatchingDescribedPackage( v2Doc, artifactId ); + + Optional downloadLocation = source.getDownloadLocation(); + Optional name = source.getName(); + + SpdxPackage dest = spdxDoc.createSpdxPackage( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName(name.orElse( "NONE" )) + .setCopyrightText( source.getCopyrightText() != null ? source.getCopyrightText() : "NOASSERTION" ) + .setDownloadLocation(downloadLocation.orElse( "NOASSERTION" )) + .build(); + + Optional pvc = source.getPackageVerificationCode(); + if ( pvc.isPresent() ) + { + dest.getVerifiedUsings().add( dest.createPackageVerificationCode( dest.getModelStore().getNextId( IdType.Anonymous ) ) + .setAlgorithm( HashAlgorithm.SHA1 ) + .setHashValue( pvc.get().getValue() ) + .addAllPackageVerificationCodeExcludedFile( pvc.get().getExcludedFileNames() ) + .build()); + } + + for ( org.spdx.library.model.v2.ExternalRef fromRef : source.getExternalRefs() ) + { + Spdx2to3Converter.addExternalRefToArtifact( fromRef, dest, dest.getModelStore() ); + } + for ( org.spdx.library.model.v2.Annotation fromAnnotation : source.getAnnotations() ) + { + CreationInfo creationInfo = new CreationInfo.CreationInfoBuilder( dest.getModelStore(), + dest.getModelStore().getNextId(IdType.Anonymous), + null ) + .setCreated( fromAnnotation.getAnnotationDate() ) + .setSpecVersion( SpdxConstantsV3.MODEL_SPEC_VERSION ) + .build(); + creationInfo.getCreatedBys().add( Spdx2to3Converter.stringToAgent( fromAnnotation.getAnnotator(), creationInfo ) ); + dest.createAnnotation( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setAnnotationType( Spdx2to3Converter.ANNOTATION_TYPE_MAP.get( fromAnnotation.getAnnotationType() ) ) + .setStatement( fromAnnotation.getComment() ) + .setSubject( dest ) + .setCreationInfo( creationInfo ) + .build(); + } + org.spdx.library.model.v2.license.AnyLicenseInfo v2Declared = source.getLicenseDeclared(); + LicenseExpression declaredLicense = dest.createLicenseExpression( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setLicenseExpression( v2Declared == null ? "NOASSERTION" : v2Declared.toString() ) + .build(); + Optional licenseListVersion = v2Doc.getCreationInfo() == null ? Optional.empty() : + v2Doc.getCreationInfo().getLicenseListVersion(); + if ( licenseListVersion.isPresent() ) + { + declaredLicense.setLicenseListVersion( licenseListVersion.get() ); + } + dest.createRelationship( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setRelationshipType( RelationshipType.HAS_DECLARED_LICENSE ) + .setFrom( dest ) + .addTo( declaredLicense ) + .build(); + + LicenseExpression concludedLicense = dest.createLicenseExpression( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setLicenseExpression( source.getLicenseConcluded().toString() ) + .build(); + if ( licenseListVersion.isPresent() ) + { + concludedLicense.setLicenseListVersion( licenseListVersion.get() ); + } + dest.createRelationship( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setRelationshipType( RelationshipType.HAS_CONCLUDED_LICENSE ) + .setFrom( dest ) + .addTo( concludedLicense ) + .build(); + Optional builtDate = source.getBuiltDate(); + + if ( builtDate.isPresent() ) + { + dest.setBuiltTime( builtDate.get() ); + } + Optional comment = source.getComment(); + Optional licenseComments = source.getLicenseComments(); + if ( comment.isPresent() ) + { + if ( licenseComments.isPresent() ) + { + dest.setComment( comment.get() + "; License Comments: " + licenseComments.get() ); + } + else + { + dest.setComment( comment.get() ); + } + + } + else if ( licenseComments.isPresent() ) + { + dest.setComment( "License Comments: " + licenseComments.get() ); + } + Optional desc = source.getDescription(); + if ( desc.isPresent() ) + { + dest.setDescription( desc.get() ); + } + Optional homePage = source.getHomepage(); + if ( homePage.isPresent() ) + { + dest.setHomePage( homePage.get() ); + } + Optional originator = source.getOriginator(); + if ( originator.isPresent() ) + { + // we know the creationInfo is not null since it is copied from the SPDX package when initially created + //noinspection DataFlowIssue + dest.getOriginatedBys().add( Spdx2to3Converter.stringToAgent( originator.get(), + dest.getCreationInfo() ) ); + } + Optional pkgFileName = source.getPackageFileName(); + if ( pkgFileName.isPresent() ) + { + SpdxFile packageFile = dest.createSpdxFile( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( pkgFileName.get() ) + .build(); + for ( Checksum fromChecksum : source.getChecksums() ) + { + packageFile.getVerifiedUsings().add( dest.createHash( dest.getModelStore().getNextId( IdType.Anonymous ) ) + .setAlgorithm( Spdx2to3Converter.HASH_ALGORITH_MAP.get( fromChecksum.getAlgorithm() ) ) + .setHashValue( fromChecksum.getValue() ) + .build() ); + } + dest.createRelationship( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( dest ) + .addTo( packageFile ) + .setRelationshipType( RelationshipType.HAS_DISTRIBUTION_ARTIFACT ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .build(); + } + Optional primaryPurpose = source.getPrimaryPurpose(); + if ( primaryPurpose.isPresent() ) + { + dest.setPrimaryPurpose( Spdx2to3Converter.PURPOSE_MAP.get( primaryPurpose.get() ) ); + } + Optional releaseDate = source.getReleaseDate(); + if ( releaseDate.isPresent() ) + { + dest.setReleaseTime( releaseDate.get() ); + } + Optional sourceInfo = source.getSourceInfo(); + if ( sourceInfo.isPresent() ) + { + dest.setSourceInfo( sourceInfo.get() ); + } + Optional summary = source.getSummary(); + if ( summary.isPresent() ) + { + dest.setSummary( summary.get() ); + } + Optional supplier = source.getSupplier(); + if ( supplier.isPresent() ) { + // we know the creationInfo is not null since it is copied from the SPDX package when initially created + //noinspection DataFlowIssue + dest.setSuppliedBy( Spdx2to3Converter.stringToAgent( supplier.get(), dest.getCreationInfo() ) ); + } + Optional validUntil = source.getValidUntilDate(); + if ( validUntil.isPresent() ) + { + dest.setValidUntilTime( validUntil.get() ); + } + Optional versionInfo = source.getVersionInfo(); + if ( versionInfo.isPresent() ) + { + dest.setPackageVersion( versionInfo.get() ); + } + return dest; + } + + /** + * Create and return an external element for the root document or root of an SBOM + * + * @param externalSpdxDoc SPDX Document containing the package to be referenced. + * @param spdxFile SPDX file containing the SPDX document + * @param groupId Group ID for the external artifact + * @param artifactId Artifact ID for the external artifact + * @param version version for the external artifact + * @return package described in the externalSpdxDoc, otherwise null if no package found + * @throws InvalidSPDXAnalysisException on errors creating the external element + * @throws SpdxCollectionException on errors creating the SHA1 has for the file + */ + private @Nullable ExternalElement createExternalSpdxPackage( SpdxDocument externalSpdxDoc, + File spdxFile, + String groupId, + String artifactId, + @Nullable String version ) throws InvalidSPDXAnalysisException, SpdxCollectionException + { + SpdxPackage describedPackage = null; + for ( Element root : externalSpdxDoc.getRootElements() ) + { + if ( root instanceof SpdxPackage ) + { + describedPackage = (SpdxPackage)root; + break; + } + else if ( root instanceof Sbom ) + { + for ( Element sbomRoot : ((Sbom)root).getRootElements() ) + { + if ( sbomRoot instanceof SpdxPackage ) + { + describedPackage = (SpdxPackage)sbomRoot; + break; + } + } + if ( describedPackage != null ) + { + break; + } + } + } + if ( describedPackage == null ) + { + // not found + return null; + } + + ExternalElement retval = new ExternalElement(spdxDoc.getModelStore(), describedPackage.getObjectUri(), + spdxDoc.getCopyManager(), true, describedPackage.getIdPrefix()); + for ( ExternalMap ext : spdxDoc.getSpdxImports() ) + { + if ( describedPackage.getObjectUri().equals( ext.getExternalSpdxId() ) ) + { + return retval; // No need to create the external map + } + } + org.spdx.maven.Checksum checksum = AbstractFileCollector.generateSha1( spdxFile ); + final HashAlgorithm algorithm = Spdx2to3Converter.HASH_ALGORITH_MAP.get( ChecksumAlgorithm.valueOf( checksum.getAlgorithm() ) ); + Hash hash = spdxDoc.createHash( spdxDoc.getModelStore().getNextId( IdType.Anonymous ) ) + .setAlgorithm( algorithm ) + .setHashValue( checksum.getValue() ) + .build(); + + StringBuilder sb = new StringBuilder( groupId ).append( artifactId ); + if ( Objects.nonNull( version )) { + sb.append( version ); + } + String fullArtifactId = sb.toString(); + SpdxFile fileArtifact = spdxDoc.createSpdxFile( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( spdxFile.getName() ) + .setDescription( String.format( "SPDX File for %s", fullArtifactId ) ) + .addVerifiedUsing( hash ) + .build(); + spdxDoc.getSpdxImports().add( spdxDoc.createExternalMap( spdxDoc.getModelStore().getNextId( IdType.Anonymous ) ) + .addVerifiedUsing( hash ) + .setExternalSpdxId( describedPackage.getObjectUri() ) + .setDefiningArtifact( fileArtifact ) + .build() ); + return retval; + } + + + /** + * Creates an SPDX document from a file + * @param path Path to the SPDX file + * @return an SPDX Spec version 2 document + * @throws IOException on IO Error + * @throws FileNotFoundException if the file does not exist + * @throws InvalidSPDXAnalysisException on invalid SPDX file + */ + private SpdxDocument spdxDocumentFromFile( String path ) throws FileNotFoundException, IOException, InvalidSPDXAnalysisException + { + ISerializableModelStore modelStore; + OutputFormat of = OutputFormat.getOutputFormat( null, new File( path ) ); + if (!SpdxMajorVersion.VERSION_3.equals( of.getSpecVersion() )) { + throw new InvalidSPDXAnalysisException( String.format( "Unsupported file type for SPDX Version 2 SPDX documents: %s", of.getSpecVersion().toString() )); + } + modelStore = new JsonLDStore( new InMemSpdxStore() ); + + try ( InputStream inputStream = new FileInputStream( path ) ) + { + CoreModelObject root = modelStore.deSerialize( inputStream, false ); + if ( root != null ) + { + root.setCopyManager( spdxDoc.getCopyManager() ); + return (SpdxDocument)root; + } + else + { + throw new InvalidSPDXAnalysisException( String.format( "Could not find an SPDX document for SPDX file name %s", + path ) ); + } + } + finally + { + try { + modelStore.close(); + } catch (Exception e) { + LOG.error( "Error closing SPDX model store", e ); + } + } + } + + /** + * Copies the closest matching described package in the externalSpdxDoc to the returned element + * @param externalSpdxDoc SPDX document containing the described package + * @param artifactId Artifact ID to search for + * @return SPDX Package with values copied from the externalSpdxDoc + * @throws InvalidSPDXAnalysisException on errors copying from the external document + */ + private SpdxPackage copyPackageInfoFromExternalDoc( SpdxDocument externalSpdxDoc, String artifactId ) throws InvalidSPDXAnalysisException + { + SpdxPackage source = findMatchingDescribedPackage( externalSpdxDoc, artifactId ); + Optional downloadLocation = source.getDownloadLocation(); + Optional name = source.getName(); + SpdxPackage dest = spdxDoc.createSpdxPackage( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( name.orElse( "NONE" ) ) + .setCopyrightText( source.getCopyrightText().orElse( "NOASSERTION" ) ) + .addAllVerifiedUsing( source.getVerifiedUsings() ) + .setDownloadLocation( downloadLocation.orElse( "NOASSERTION" ) ) + .addAllExternalIdentifier( source.getExternalIdentifiers() ) + .addAllExternalRef( source.getExternalRefs() ) + .addAllOriginatedBy( source.getOriginatedBys() ) + .build(); + @SuppressWarnings( "unchecked" ) + List sourceRelationships = + (List) SpdxModelFactory.getSpdxObjects( externalSpdxDoc.getModelStore(), externalSpdxDoc.getCopyManager(), + SpdxConstantsV3.CORE_RELATIONSHIP, null, null ) + .filter( spdxObj -> { + try + { + return source.equals( ((Relationship)spdxObj).getFrom() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.error( "Error copying relationships from SPDX file for artifact {}", artifactId, e ); + return false; + } + } ) + .collect( Collectors.toList() ); + for ( Relationship rel : sourceRelationships ) + { + dest.createRelationship( dest.getIdPrefix() + dest.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( dest ) + .setCompleteness( rel.getCompleteness().orElse( RelationshipCompleteness.NO_ASSERTION ) ) + .setRelationshipType( rel.getRelationshipType() ) + .addAllTo( rel.getTos() ) // NOTE: The dest my have the same CopyManager as the relationships for this to copy correctly + .build(); + } + // We don't want to copy any of the properties which have other elements since it + // may duplicate artifacts already included in the document - so we can't use copyFrom + + Optional builtTime = source.getBuiltTime(); + if ( builtTime.isPresent() ) + { + dest.setBuiltTime( builtTime.get() ); + } + Optional comment = source.getComment(); + if ( comment.isPresent() ) + { + dest.setComment( comment.get() ); + } + Optional desc = source.getDescription(); + if ( desc.isPresent() ) + { + dest.setDescription( desc.get() ); + } + Optional homePage = source.getHomePage(); + if ( homePage.isPresent() ) + { + dest.setHomePage( homePage.get() ); + } + Optional primaryPurpose = source.getPrimaryPurpose(); + if ( primaryPurpose.isPresent() ) + { + dest.setPrimaryPurpose( primaryPurpose.get() ); + } + Optional releaseTime = source.getReleaseTime(); + if ( releaseTime.isPresent() ) + { + dest.setReleaseTime( releaseTime.get() ); + } + Optional sourceInfo = source.getSourceInfo(); + if ( sourceInfo.isPresent() ) + { + dest.setSourceInfo( sourceInfo.get() ); + } + Optional summary = source.getSummary(); + if ( summary.isPresent() ) + { + dest.setSummary( summary.get() ); + } + Optional supplier = source.getSuppliedBy(); + if ( supplier.isPresent() ) { + dest.setSuppliedBy( supplier.get() ); + } + Optional validUntil = source.getValidUntilTime(); + if ( validUntil.isPresent() ) + { + dest.setValidUntilTime( validUntil.get() ); + } + Optional versionInfo = source.getPackageVersion(); + if ( versionInfo.isPresent() ) + { + dest.setPackageVersion( versionInfo.get() ); + } + return dest; + } + + /** + * Searched the SPDX document for the closest matching package to the artifactId + * @param externalSpdxDoc Doc containing the package + * @param artifactId Maven artifact ID + * @return the closest matching package described by the doc + * @throws InvalidSPDXAnalysisException on SPDX errors + */ + private SpdxPackage findMatchingDescribedPackage( SpdxDocument externalSpdxDoc, String artifactId ) throws InvalidSPDXAnalysisException + { + Sbom firstFoundSbom = null; + SpdxPackage firstFoundPackage = null; + for ( Element root : externalSpdxDoc.getRootElements() ) + { + if ( root instanceof SpdxPackage ) + { + if ( root.getName().isPresent() && root.getName().get().equals( artifactId ) ) + { + return (SpdxPackage)root; + } else if ( firstFoundPackage == null ) + { + firstFoundPackage =(SpdxPackage)root; + } + } + else if ( root instanceof Sbom ) + { + for ( Element sRoot : ((Sbom)root).getRootElements() ) + { + if ( sRoot instanceof SpdxPackage ) + { + if ( sRoot.getName().isPresent() && sRoot.getName().get().equals( artifactId ) ) + { + return (SpdxPackage)sRoot; + } + } + } + if ( firstFoundSbom == null ) + { + firstFoundSbom = (Sbom)root; + } + } + } + + // If we got here, we didn't find the package in the SPDX document root or the SBOMs at the root of the SPDX document + if ( firstFoundPackage != null ) + { + LOG.warn( "Could not find matching artifact ID in SPDX file for {}. Using the first package found in SPDX file.", artifactId ); + return firstFoundPackage; + } + if ( firstFoundSbom != null ) + { + for ( Element sRoot : firstFoundSbom.getRootElements() ) + { + if ( sRoot instanceof SpdxPackage ) + { + LOG.warn( "Could not find matching artifact ID in SPDX file for {}. Using the first package found in Sbom.", artifactId ); + return (SpdxPackage)sRoot; + } + } + } + throw new InvalidSPDXAnalysisException( "SPDX document does not contain any described items." ); + } + + /** + * Convert a list of Maven licenses to an SPDX License + * + * @param mavenLicenses List of maven licenses to map + * @return SPDX license represented by the maven license + * @throws LicenseMapperException thrown if no SPDX listed or extracted license exists with the same URL + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + private AnyLicenseInfo mavenLicensesToSpdxLicense( List mavenLicenses ) throws LicenseMapperException, InvalidSPDXAnalysisException + { + try + { + // The call below will map non-standard licenses as well as standard licenses + // but will throw an exception if no mapping is found - we'll try this first + // and if there is an error, try just the standard license mapper which will + // return an UNSPECIFIED license type if there is no mapping + return this.licenseManager.mavenLicenseListToSpdxLicense( mavenLicenses ); + } + catch ( LicenseManagerException ex ) + { + return MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV3License( mavenLicenses, spdxDoc ); + } + + } +} diff --git a/src/main/java/org/spdx/maven/utils/SpdxV3DocumentBuilder.java b/src/main/java/org/spdx/maven/utils/SpdxV3DocumentBuilder.java new file mode 100644 index 0000000..41e038d --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/SpdxV3DocumentBuilder.java @@ -0,0 +1,571 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Source Auditor Inc. + */ +package org.spdx.maven.utils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.apache.maven.model.License; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.project.MavenProject; +import org.apache.maven.shared.model.fileset.FileSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spdx.core.CoreModelObject; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.conversion.Spdx2to3Converter; +import org.spdx.library.model.v2.ReferenceType; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.ReferenceCategory; +import org.spdx.library.model.v3_0_1.SpdxConstantsV3; +import org.spdx.library.model.v3_0_1.core.AnnotationType; +import org.spdx.library.model.v3_0_1.core.CreationInfo; +import org.spdx.library.model.v3_0_1.core.DictionaryEntry; +import org.spdx.library.model.v3_0_1.core.Element; +import org.spdx.library.model.v3_0_1.core.HashAlgorithm; +import org.spdx.library.model.v3_0_1.core.Relationship; +import org.spdx.library.model.v3_0_1.core.RelationshipCompleteness; +import org.spdx.library.model.v3_0_1.core.RelationshipType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.core.Tool; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.library.model.v3_0_1.software.Sbom; +import org.spdx.library.model.v3_0_1.software.SoftwareArtifact; +import org.spdx.library.model.v3_0_1.software.SoftwarePurpose; +import org.spdx.library.model.v3_0_1.software.SpdxFile; +import org.spdx.library.model.v3_0_1.software.SpdxPackage; +import org.spdx.library.referencetype.ListedReferenceTypes; +import org.spdx.maven.Checksum; +import org.spdx.maven.ExternalReference; +import org.spdx.maven.NonStandardLicense; +import org.spdx.maven.OutputFormat; +import org.spdx.maven.Packaging; +import org.spdx.storage.IModelStore.IdType; +import org.spdx.storage.simple.InMemSpdxStore; +import org.spdx.v3jsonldstore.JsonLDStore; + +import static java.util.Objects.requireNonNull; + +/** + * Builder for SPDX Spec version 3 SBOMs + * + * @author Gary O'Neall + * + */ +public class SpdxV3DocumentBuilder + extends AbstractDocumentBuilder +{ + private static final Logger LOG = LoggerFactory.getLogger( SpdxV3DocumentBuilder.class ); + + protected CreationInfo creationInfo; + protected Sbom sbom; + protected SpdxDocument spdxDoc; + protected SpdxPackage projectPackage; + protected SpdxV3LicenseManager licenseManager; + /** + * Holds a mapping of IDs to URIs for any custom licenses defined outside the spdxDoc + */ + protected List customIdToUri = new ArrayList<>(); + + /** + * @param mavenProject Maven project + * @param generatePurls If true, generated Package URLs for all package references + * @param spdxFile File to store the SPDX document results + * @param namespaceUri Namespace prefix for generated SPDX URIs document - must be unique + * @param outputFormatEnum format for the SPDX document + */ + public SpdxV3DocumentBuilder( MavenProject mavenProject, boolean generatePurls, File spdxFile, URI namespaceUri, + OutputFormat outputFormatEnum ) throws SpdxBuilderException, LicenseMapperException + { + super( mavenProject, generatePurls, spdxFile, outputFormatEnum ); + if ( namespaceUri == null ) + { + throw new SpdxBuilderException( "Missing namespaceUri" ); + } + + if ( !OutputFormat.JSON_LD.equals( outputFormatEnum )) { + throw new SpdxBuilderException( String.format( "Unsupported output format for SPDX spec version 3: %s", + outputFormatEnum.toString() )); + } + // create the SPDX document + try + { + modelStore = new JsonLDStore( new InMemSpdxStore() ); + creationInfo = new CreationInfo( modelStore, modelStore.getNextId( IdType.Anonymous ), copyManager, true, namespaceUri.toString() ); + sbom = creationInfo.createSbom( namespaceUri + "sbom" ).build(); + spdxDoc = sbom.createSpdxDocument( namespaceUri + "/Document" ) + .addRootElement( sbom ) + .build(); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxBuilderException( "Error creating SPDX SBOM", e ); + } + + // process the licenses + licenseManager = new SpdxV3LicenseManager( spdxDoc ); + // TODO: if we want to support external custom licenses, we will need to add dictionary entries + // to the customIdToUri + } + + /** + * @return the SPDX Document + */ + public SpdxDocument getSpdxDoc() + { + return this.spdxDoc; + } + + @Override + public void fillSpdxDocumentInformation( SpdxProjectInformation projectInformation ) throws SpdxBuilderException + { + try + { + // document comment + if ( projectInformation.getDocumentComment() != null && !projectInformation.getDocumentComment().isEmpty() ) + { + spdxDoc.setComment( projectInformation.getDocumentComment() ); + } + // creator + fillCreatorInfo( projectInformation ); + // data license + // TODO: Add a data license option to the project configuration options + // spdxDoc.setDataLicense( dataLicense ); + // annotations + if ( projectInformation.getDocumentAnnotations() != null && projectInformation.getDocumentAnnotations().length > 0 ) + { + addSpdxAnnotations( projectInformation.getDocumentAnnotations(), spdxDoc ); + } + spdxDoc.setName( projectInformation.getName() ); // Same as package name + // Package level information + projectPackage = createSpdxPackage( projectInformation ); + sbom.getRootElements().add( projectPackage ); + } + catch ( InvalidSPDXAnalysisException | MojoExecutionException e ) + { + throw new SpdxBuilderException( "Error adding package information to SPDX document", e ); + } + } + + /** + * Adds annotation to the element + * @param annotations Annotations to add + * @param element Elements to add the annotations to + * @throws MojoExecutionException On any SPDX parsing errors (typically due to invalid annotation data) + */ + private void addSpdxAnnotations( org.spdx.maven.Annotation[] annotations, Element element ) throws MojoExecutionException + { + for ( org.spdx.maven.Annotation annotation: annotations ) + { + + AnnotationType annotationType; + try + { + annotationType = Spdx2to3Converter.ANNOTATION_TYPE_MAP.get( + org.spdx.library.model.v2.enumerations.AnnotationType + .valueOf( annotation.getAnnotationType() ) ); + } + catch ( Exception ex ) + { + throw new MojoExecutionException( "Invalid annotation type "+annotation.getAnnotationType() ); + } + try + { + CreationInfo creationInfo = new CreationInfo.CreationInfoBuilder( spdxDoc.getModelStore(), + spdxDoc.getModelStore().getNextId(IdType.Anonymous), + spdxDoc.getCopyManager() ) + .setCreated( annotation.getAnnotationDate() ) + .setSpecVersion( SpdxConstantsV3.MODEL_SPEC_VERSION ) + .build(); + creationInfo.setIdPrefix( element.getIdPrefix() ); + creationInfo.getCreatedBys().add( Spdx2to3Converter.stringToAgent( annotation.getAnnotator(), creationInfo ) ); + element.createAnnotation( element.getIdPrefix() + element.getModelStore().getNextId( IdType.SpdxId ) ) + .setAnnotationType( annotationType ) + .setStatement( annotation.getAnnotationComment() ) + .setSubject( element ) + .setCreationInfo( creationInfo ) + .build(); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new MojoExecutionException( "Error creating annotation.", e ); + } + } + } + + /** + * Fill in the creator information to the SPDX document + * + * @param projectInformation project level information including the creators + * @throws InvalidSPDXAnalysisException on SPDX parsing errors + */ + private void fillCreatorInfo( SpdxProjectInformation projectInformation ) throws InvalidSPDXAnalysisException + { + CreationInfo creationInfo = spdxDoc.getCreationInfo(); + requireNonNull( creationInfo, "CreationInfo for the SPDX document must not be null" ); + String[] parameterCreators = projectInformation.getCreators(); + for ( String parameterCreator : parameterCreators ) + { + try + { + if ( parameterCreator.startsWith( "Tool:" )) + { + Tool tool = spdxDoc.createTool( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( parameterCreator.substring( "Tool:".length() ).trim() ) + .build(); + creationInfo.getCreatedUsings().add( tool ); + } + else + { + creationInfo.getCreatedBys().add( Spdx2to3Converter.stringToAgent( parameterCreator, creationInfo ) ); + } + } + catch (InvalidSPDXAnalysisException e) + { + LOG.warn( "Invalid creator string, {} will be skipped.", parameterCreator ); + } + + } + String comment = projectInformation.getCreatorComment(); + if ( Objects.nonNull( comment ) && !comment.isBlank() ) + { + creationInfo.setComment( comment ); + } + } + + private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation ) throws SpdxBuilderException + { + String copyrightText = projectInformation.getCopyrightText(); + if ( copyrightText == null ) + { + copyrightText = UNSPECIFIED; + } + String downloadUrl = projectInformation.getDownloadUrl(); + + if ( downloadUrl == null || downloadUrl.isBlank() ) + { + downloadUrl = UNSPECIFIED; + } + SpdxPackage pkg; + try + { + + final AnyLicenseInfo concludedLicense = LicenseInfoFactory + .parseSPDXLicenseString( projectInformation.getConcludedLicense(), spdxDoc.getModelStore(), + spdxDoc.getIdPrefix(), spdxDoc.getCopyManager(), customIdToUri ); + final AnyLicenseInfo declaredLicense = LicenseInfoFactory + .parseSPDXLicenseString( projectInformation.getDeclaredLicense(), spdxDoc.getModelStore(), + spdxDoc.getIdPrefix(), spdxDoc.getCopyManager(), customIdToUri ); + final Packaging packaging = Packaging.valueOfPackaging( project.getPackaging() ); + final SoftwarePurpose primaryPurpose = packaging != null ? packaging.getSoftwarePurpose() : SoftwarePurpose.LIBRARY; + pkg = spdxDoc.createSpdxPackage( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( projectInformation.getName() ) + .setDownloadLocation( downloadUrl ) + .setPrimaryPurpose( primaryPurpose ) + .setCopyrightText( copyrightText ) + .addAllExternalIdentifier( SpdxExternalIdBuilder.getDefaultExternalIdentifiers( spdxDoc, generatePurls, project ) ) + .build(); + + pkg.createRelationship( pkg.getIdPrefix() + pkg.getModelStore().getNextId( IdType.SpdxId ) ) + .setRelationshipType( RelationshipType.HAS_DECLARED_LICENSE ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setFrom( pkg ) + .addTo( declaredLicense ) + .build(); + + pkg.createRelationship( pkg.getIdPrefix() + pkg.getModelStore().getNextId( IdType.SpdxId ) ) + .setRelationshipType( RelationshipType.HAS_CONCLUDED_LICENSE ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setFrom( pkg ) + .addTo( concludedLicense ) + .build(); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxBuilderException( "Error creating initial package", e ); + } + // Annotations + if ( projectInformation.getPackageAnnotations() != null && projectInformation.getPackageAnnotations().length > 0 ) + { + try + { + addSpdxAnnotations( projectInformation.getPackageAnnotations(), pkg ); + } + catch ( MojoExecutionException e ) + { + throw new SpdxBuilderException( "Error adding package annotations to SPDX document", e ); + } + } + try + { + // description + if ( projectInformation.getDescription() != null ) + { + pkg.setDescription( projectInformation.getDescription() ); + } + // download url + if ( projectInformation.getDownloadUrl() != null ) + { + pkg.setDownloadLocation( projectInformation.getDownloadUrl() ); + } + // archive file name + if ( projectInformation.getPackageArchiveFileName() != null ) + { + SpdxFile packageFile = pkg.createSpdxFile( pkg.getIdPrefix() + pkg.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( projectInformation.getPackageArchiveFileName() ) + .build(); + + if ( projectInformation.getChecksums() != null ) + { + try + { + for ( Checksum checksum : projectInformation.getChecksums() ) + { + final HashAlgorithm algorithm = Spdx2to3Converter.HASH_ALGORITH_MAP.get( ChecksumAlgorithm.valueOf( checksum.getAlgorithm() ) ); + if ( Objects.isNull( algorithm )) + { + LOG.error( "Invalid checksum algorithm {}", checksum.getAlgorithm() ); + } + else + { + packageFile.getVerifiedUsings().add( packageFile.createHash( packageFile.getModelStore().getNextId( IdType.Anonymous ) ) + .setAlgorithm( algorithm ) + .setHashValue( checksum.getValue() ) + .build() ); + } + } + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxBuilderException( + "Error adding package information to SPDX document - Invalid checksum provided", e ); + } + } + + pkg.createRelationship( pkg.getIdPrefix() + pkg.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( pkg ) + .addTo( packageFile ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setRelationshipType( RelationshipType.HAS_DISTRIBUTION_ARTIFACT ) + .build(); + } + // home page + if ( projectInformation.getHomePage() != null ) + { + try + { + pkg.setHomePage( projectInformation.getHomePage() ); + } + catch( InvalidSPDXAnalysisException ex ) + { + LOG.warn( "Invalid URL in project POM file: {}", projectInformation.getHomePage() ); + } + + } + // source information + if ( projectInformation.getSourceInfo() != null ) + { + pkg.setSourceInfo( projectInformation.getSourceInfo() ); + } + // license comment + if ( projectInformation.getLicenseComment() != null ) + { + pkg.setComment( "License: " + projectInformation.getLicenseComment() ); + } + // originator + if ( projectInformation.getOriginator() != null ) + { + // creationInfo can not be null due to the builder implementation in the SPDX core package + //noinspection DataFlowIssue + pkg.getOriginatedBys().add( Spdx2to3Converter.stringToAgent( projectInformation.getOriginator(), pkg.getCreationInfo() ) ); + } + // short description + if ( projectInformation.getShortDescription() != null ) + { + pkg.setSummary( projectInformation.getShortDescription() ); + } + // supplier + if ( projectInformation.getSupplier() != null ) + { + // creationInfo can not be null due to the builder implementation in the SPDX core package + //noinspection DataFlowIssue + pkg.setSuppliedBy( Spdx2to3Converter.stringToAgent( projectInformation.getSupplier(), pkg.getCreationInfo() ) ); + } + // version info + if ( projectInformation.getVersionInfo() != null ) + { + pkg.setPackageVersion( projectInformation.getVersionInfo() ); + } + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxBuilderException( "Error adding package properties", e ); + } + // external references + ExternalReference[] externalRefs = projectInformation.getExternalRefs(); + try + { + addExternalRefs( externalRefs, pkg ); + } + catch ( MojoExecutionException e ) + { + throw new SpdxBuilderException( "Error adding external references to package", e ); + } + + return pkg; + } + + /** + * @param externalRefs ExternalRefs to add + * @param artifact Spdx Artifact to add the ExternalRefs to + * @throws MojoExecutionException On invalid externalRef data + */ + private void addExternalRefs( ExternalReference[] externalRefs, SoftwareArtifact artifact ) throws MojoExecutionException + { + if ( Objects.isNull( externalRefs )) + { + return; + } + for ( ExternalReference externalRef : externalRefs ) + { + ReferenceCategory cat; + + try { + cat = ReferenceCategory.valueOf( externalRef.getCategory().replaceAll( "-", "_" ) ); + } + catch ( Exception ex ) + { + throw new MojoExecutionException( "External reference category " + externalRef.getCategory() + + " is not recognized as a valid, standard category." ); + } + ReferenceType refType; + try + { + refType = ListedReferenceTypes.getListedReferenceTypes().getListedReferenceTypeByName( externalRef.getType() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new MojoExecutionException( "Error getting listed reference type for " + externalRef.getType(), e ); + } + if ( refType == null ) + { + throw new MojoExecutionException( "Listed reference type not found for " + externalRef.getType() ); + } + try + { + Spdx2to3Converter.addExternalRefToArtifact( cat, refType, externalRef.getLocator(), externalRef.getComment(), + artifact, artifact.getModelStore() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new MojoExecutionException( "Error adding external ref to artifact", e ); + } + } + } + + @Override + public void collectSpdxFileInformation( List sources, String baseDir, + SpdxDefaultFileInformation defaultFileInformation, + HashMap pathSpecificInformation, + Set checksumAlgorithms ) throws SpdxBuilderException + { + SpdxV3FileCollector fileCollector = new SpdxV3FileCollector( customIdToUri ); + try + { + fileCollector.collectFiles( sources, baseDir, defaultFileInformation, + pathSpecificInformation, projectPackage, RelationshipType.GENERATES, spdxDoc, checksumAlgorithms ); + Relationship pkgRelationship = projectPackage.createRelationship( projectPackage.getIdPrefix() + projectPackage.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( projectPackage ) + .setRelationshipType( RelationshipType.CONTAINS ) + .build(); + for ( SpdxFile file : fileCollector.getFiles() ) + { + pkgRelationship.getTos().add( file ); + } + } + catch ( SpdxCollectionException|InvalidSPDXAnalysisException e ) + { + throw new SpdxBuilderException( "Error collecting SPDX file information", e ); + } + // TODO: Set a GITOID identifier + } + + @Override + public void saveSpdxDocumentToFile() throws SpdxBuilderException + { + try ( FileOutputStream spdxOut = new FileOutputStream( spdxFile ) ) + { + modelStore.serialize( spdxOut ); + } + catch ( FileNotFoundException e ) + { + throw new SpdxBuilderException( "Error saving SPDX data to file", e ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxBuilderException( "Error collecting SPDX file data", e ); + } + catch ( IOException e ) + { + throw new SpdxBuilderException( "I/O Error saving SPDX data to file", e ); + } + } + + @Override + public void addNonStandardLicenses( NonStandardLicense[] nonStandardLicenses ) throws SpdxBuilderException + { + if ( nonStandardLicenses != null ) + { + for ( NonStandardLicense nonStandardLicense : nonStandardLicenses ) + { + try + { + // the following will add the non-standard license to the document container + licenseManager.addExtractedLicense( nonStandardLicense ); + } + catch ( LicenseManagerException e ) + { + throw new SpdxBuilderException( "Error adding non standard license", e ); + } + } + } + } + + @Override + public CoreModelObject getProjectPackage() + { + return projectPackage; + } + + @Override + public String mavenLicenseListToSpdxLicenseExpression( List mavenLicenses ) throws LicenseManagerException + { + return licenseManager.mavenLicenseListToSpdxLicense( mavenLicenses ).toString(); + } + + /** + * @return the licenseManager + */ + public SpdxV3LicenseManager getLicenseManager() + { + return licenseManager; + } + + @Override + public List verify() + { + return spdxDoc.verify(); + } + +} diff --git a/src/main/java/org/spdx/maven/utils/SpdxV3FileCollector.java b/src/main/java/org/spdx/maven/utils/SpdxV3FileCollector.java new file mode 100644 index 0000000..7a73f2e --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/SpdxV3FileCollector.java @@ -0,0 +1,511 @@ +/* + * Copyright 2014 Source Auditor Inc. + * + * Licensed 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 + * + * http://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.spdx.maven.utils; + +import java.io.File; +import java.util.*; +import java.util.Map.Entry; + +import org.apache.maven.shared.model.fileset.FileSet; +import org.apache.maven.shared.model.fileset.util.FileSetManager; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.conversion.Spdx2to3Converter; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.FileType; +import org.spdx.library.model.v3_0_1.core.Agent; +import org.spdx.library.model.v3_0_1.core.DictionaryEntry; +import org.spdx.library.model.v3_0_1.core.HashAlgorithm; +import org.spdx.library.model.v3_0_1.core.IntegrityMethod; +import org.spdx.library.model.v3_0_1.core.PositiveIntegerRange; +import org.spdx.library.model.v3_0_1.core.RelationshipCompleteness; +import org.spdx.library.model.v3_0_1.core.RelationshipType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.library.model.v3_0_1.software.Snippet; +import org.spdx.library.model.v3_0_1.software.SoftwarePurpose; +import org.spdx.library.model.v3_0_1.software.SpdxFile; +import org.spdx.library.model.v3_0_1.software.SpdxPackage; +import org.spdx.maven.Checksum; +import org.spdx.maven.SnippetInfo; +import org.spdx.storage.IModelStore.IdType; + + +/** + * Collects SPDX file information from directories in SPDX Spec version 3 format + *

+ * The method collectFilesInDirectory(FileSet[] filesets) will scan and create SPDX File information for + * all files in the filesets. + * + * @author Gary O'Neall + */ +public class SpdxV3FileCollector extends AbstractFileCollector +{ + static final Map EXT_TO_MEDIA_TYPE = new HashMap<>(); + static final Map EXT_TO_PURPOSE = new HashMap<>(); + + static + { + for ( Entry entry : SpdxV2FileCollector.EXT_TO_FILE_TYPE.entrySet() ) + { + switch ( entry.getValue() ) + { + case SOURCE: EXT_TO_MEDIA_TYPE.put( entry.getKey(), "text/plain" ); + EXT_TO_PURPOSE.put( entry.getKey(), SoftwarePurpose.SOURCE ); + break; + case BINARY: EXT_TO_MEDIA_TYPE.put( entry.getKey(), "application/octet-stream" ); + EXT_TO_PURPOSE.put( entry.getKey(), SoftwarePurpose.LIBRARY ); + break; + case ARCHIVE: EXT_TO_PURPOSE.put( entry.getKey(), SoftwarePurpose.ARCHIVE ); break; + case APPLICATION: EXT_TO_PURPOSE.put( entry.getKey(), SoftwarePurpose.APPLICATION ); break; + case AUDIO: EXT_TO_MEDIA_TYPE.put( entry.getKey(), "audio/*" ); break; + case IMAGE: EXT_TO_MEDIA_TYPE.put( entry.getKey(), "image/*" ); break; + case TEXT: EXT_TO_MEDIA_TYPE.put( entry.getKey(), "text/plain" ); break; + case VIDEO: EXT_TO_MEDIA_TYPE.put( entry.getKey(), "video/*" ); break; + case DOCUMENTATION: EXT_TO_PURPOSE.put( entry.getKey(), SoftwarePurpose.DOCUMENTATION ); break; + case SPDX: EXT_TO_MEDIA_TYPE.put( entry.getKey(), "application/spdx" ); + EXT_TO_PURPOSE.put( entry.getKey(), SoftwarePurpose.BOM ); break; + case OTHER: EXT_TO_PURPOSE.put( entry.getKey(), SoftwarePurpose.OTHER ); break; + } + } + } + + /** + * Map of fileName, SPDXFile for all files in the SPDX document + */ + Map spdxFiles = new HashMap<>(); + List spdxSnippets = new ArrayList<>(); + + FileSetManager fileSetManager = new FileSetManager(); + + private final List customIdToUri; + + /** + * SpdxFileCollector collects SPDX file information for files + * @param customIdToUri Holds a mapping of IDs to URIs for any custom licenses defined outside the spdxDoc + */ + public SpdxV3FileCollector( List customIdToUri) + { + this.customIdToUri = customIdToUri; + } + + + /** + * Collect file information in the directory (including subdirectories). + * + * @param fileSets FileSets containing the description of the directory to be scanned + * @param baseDir project base directory used to construct the relative paths for the SPDX files + * @param defaultFileInformation Information on default SPDX field data for the files + * @param pathSpecificInformation Map of path to file information used to override the default file information + * @param relationshipType Type of relationship to the project package + * @param projectPackage Package to which the files belong + * @param spdxDoc SPDX document which contains the extracted license infos that may be needed for license parsing + * + * @throws SpdxCollectionException on incompatible types in an SPDX collection + */ + public void collectFiles( List fileSets, String baseDir, + SpdxDefaultFileInformation defaultFileInformation, + Map pathSpecificInformation, + SpdxPackage projectPackage, RelationshipType relationshipType, + SpdxDocument spdxDoc, Set algorithms ) throws SpdxCollectionException + { + for ( FileSet fileSet : fileSets ) + { + String[] includedFiles = fileSetManager.getIncludedFiles( fileSet ); + for ( String includedFile : includedFiles ) + { + String filePath = fileSet.getDirectory() + File.separator + includedFile; + File file = new File( filePath ); + String relativeFilePath = file.getAbsolutePath().substring( baseDir.length() + 1 ).replace( '\\', '/' ); + SpdxDefaultFileInformation fileInfo = findDefaultFileInformation( relativeFilePath, + pathSpecificInformation ); + if ( fileInfo == null ) + { + fileInfo = defaultFileInformation; + } + + String outputFileName; + if ( fileSet.getOutputDirectory() != null ) + { + outputFileName = fileSet.getOutputDirectory() + File.separator + includedFile; + } + else + { + outputFileName = file.getAbsolutePath().substring( baseDir.length() + 1 ); + } + collectFile( file, outputFileName, fileInfo, relationshipType, projectPackage, spdxDoc, algorithms ); + } + } + } + + /** + * Find the most appropriate file information based on the lowest level match (closed to file) + * + * @param filePath file path for possible file path specific information + * @param pathSpecificInformation information to be applied to the file path + * @return default SPDX parameters for a given file path or null if package level defaults are to be used + */ + private SpdxDefaultFileInformation findDefaultFileInformation( String filePath, Map pathSpecificInformation ) + { + LOG.debug( "Checking for file path {}", filePath ); + SpdxDefaultFileInformation retval = pathSpecificInformation.get( filePath ); + if ( retval != null ) + { + LOG.debug( "Found filepath" ); + return retval; + } + // see if any of the parent directories contain default information which should be used + String parentPath = filePath; + int parentPathIndex; + do + { + parentPathIndex = parentPath.lastIndexOf( "/" ); + if ( parentPathIndex > 0 ) + { + parentPath = parentPath.substring( 0, parentPathIndex ); + retval = pathSpecificInformation.get( parentPath ); + } + } while ( retval == null && parentPathIndex > 0 ); + if ( retval != null ) + { + LOG.debug( "Found directory containing file path for path specific information. File path: {}", parentPath ); + } + return retval; + } + + /** + * Collect SPDX information for a specific file + * + * @param file File to collect SPDX information for + * @param outputFileName Path to the output file name relative to the root of the output archive file + * @param relationshipType Type of relationship to the project package + * @param projectPackage Package to which the files belong + * @param spdxDoc SPDX Document which will contain the files + * @param algorithms algorithms to use to generate checksums + * @throws SpdxCollectionException on incompatible types in an SPDX collection + */ + private void collectFile( File file, String outputFileName, SpdxDefaultFileInformation fileInfo, + RelationshipType relationshipType, SpdxPackage projectPackage, + SpdxDocument spdxDoc, Set algorithms ) throws SpdxCollectionException + { + if ( spdxFiles.containsKey( file.getPath() ) ) + { + return; // already added from a previous scan + } + SpdxFile spdxFile = convertToSpdxFile( file, outputFileName, fileInfo, algorithms, spdxDoc ); + try + { + spdxDoc.createRelationship( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setFrom( spdxFile ) + .addTo( projectPackage ) + .setRelationshipType( relationshipType ) + .build(); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxCollectionException( "Error creating SPDX file relationship", e ); + } + if ( fileInfo.getSnippets() != null ) + { + for ( SnippetInfo snippet : fileInfo.getSnippets() ) + { + Snippet spdxSnippet; + try + { + spdxSnippet = convertToSpdxSnippet( snippet, spdxFile ); + } + catch ( SpdxBuilderException e ) + { + throw new SpdxCollectionException( "Error creating SPDX snippet information.", e ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxCollectionException( + "Error processing SPDX snippet information.", + e ); + } + spdxSnippets.add( spdxSnippet ); + } + } + spdxFiles.put( file.getPath(), spdxFile ); + } + + /** + * Create an SpdxSnippet from the snippet information provided + * @param snippet Information on the Snippet + * @param spdxFile File containing the snippet + * @return SPDX Snippet + * @throws SpdxBuilderException on Maven metadata related errors + * @throws InvalidSPDXAnalysisException on SPDX analysis errors + */ + private Snippet convertToSpdxSnippet( SnippetInfo snippet, SpdxFile spdxFile ) throws SpdxBuilderException, InvalidSPDXAnalysisException + { + //TODO: Add annotations to snippet + + PositiveIntegerRange byteRange = spdxFile.createPositiveIntegerRange( spdxFile.getModelStore().getNextId( IdType.Anonymous ) ) + .setBeginIntegerRange( snippet.getByteRangeStart() ) + .setEndIntegerRange( snippet.getByteRangeEnd() ) + .build(); + PositiveIntegerRange lineRange = spdxFile.createPositiveIntegerRange( spdxFile.getModelStore().getNextId( IdType.Anonymous ) ) + .setBeginIntegerRange( snippet.getLineRangeStart() ) + .setEndIntegerRange( snippet.getLineRangeEnd() ) + .build(); + + Snippet retval = spdxFile.createSnippet( spdxFile.getIdPrefix() + spdxFile.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( snippet.getName() ) + .setCopyrightText( snippet.getCopyrightText() ) + .setSnippetFromFile( spdxFile ) + .setByteRange( byteRange ) + .setLineRange( lineRange ) + .build(); + + String comment = snippet.getComment(); + if ( Objects.isNull( comment ) ) + { + comment = ""; + } + String licenseComment = snippet.getLicensComment(); + if ( Objects.nonNull( licenseComment ) && !licenseComment.isBlank() ) + { + comment = comment + "; License: " + licenseComment; + } + retval.setComment( comment ); + final AnyLicenseInfo concludedLicense = LicenseInfoFactory + .parseSPDXLicenseString( snippet.getConcludedLicense(), spdxFile.getModelStore(), + spdxFile.getIdPrefix(), spdxFile.getCopyManager(), customIdToUri ); + retval.createRelationship( retval.getIdPrefix() + retval.getModelStore().getNextId( IdType.SpdxId ) ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setFrom( retval ) + .addTo( concludedLicense ) + .setRelationshipType( RelationshipType.HAS_CONCLUDED_LICENSE ) + .build(); + + final AnyLicenseInfo declaredLicense = LicenseInfoFactory + .parseSPDXLicenseString( snippet.getLicenseInfoInSnippet(), spdxFile.getModelStore(), + spdxFile.getIdPrefix(), spdxFile.getCopyManager(), customIdToUri ); + retval.createRelationship( retval.getIdPrefix() + retval.getModelStore().getNextId( IdType.SpdxId ) ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setFrom( retval ) + .addTo( declaredLicense ) + .setRelationshipType( RelationshipType.HAS_DECLARED_LICENSE ) + .build(); + return retval; + } + + /** + * @param file File to convert to an SPDX file from + * @param outputFileName Path to the output file name relative to the root of the output archive file + * @param defaultFileInformation Information on default SPDX field data for the files + * @param algorithms algorithms to use to generate checksums + * @param spdxDoc SPDX document which will contain the SPDX file + * @return SPDX file based on file and default file information + * @throws SpdxCollectionException on incompatible class types in an SPDX collection + */ + private SpdxFile convertToSpdxFile( File file, String outputFileName, + SpdxDefaultFileInformation defaultFileInformation, + Set algorithms, + SpdxDocument spdxDoc ) throws SpdxCollectionException + { + String relativePath = convertFilePathToSpdxFileName( outputFileName ); + String extension = getExtension( file ).trim().toUpperCase(); + SoftwarePurpose purpose = EXT_TO_PURPOSE.getOrDefault( extension, SoftwarePurpose.OTHER ); + Collection hashes = new ArrayList<>(); + try + { + Set checksums = generateChecksum( file, algorithms ); + for ( Checksum checksum : checksums ) + { + final HashAlgorithm algorithm = Spdx2to3Converter.HASH_ALGORITH_MAP.get( ChecksumAlgorithm.valueOf( checksum.getAlgorithm() ) ); + if ( Objects.isNull( algorithm ) ) + { + throw new SpdxCollectionException( "Invalid checksum algorithm for file "+file.getName() ); + } + hashes.add( spdxDoc.createHash( spdxDoc.getModelStore().getNextId( IdType.Anonymous ) ) + .setAlgorithm( algorithm ) + .setHashValue( checksum.getValue() ) + .build() ); + } + + } + catch ( SpdxCollectionException | InvalidSPDXAnalysisException e1 ) + { + throw new SpdxCollectionException( "Unable to generate checksum for file "+file.getName() ); + } + AnyLicenseInfo concludedLicense; + AnyLicenseInfo license = null; + String licenseComment = defaultFileInformation.getLicenseComment(); + if ( SoftwarePurpose.SOURCE.equals( purpose ) && file.length() < SpdxSourceFileParser.MAXIMUM_SOURCE_FILE_LENGTH ) + { + List fileSpdxLicenses = null; + try + { + fileSpdxLicenses = SpdxSourceFileParser.parseFileForSpdxLicenses( file ); + } + catch ( SpdxSourceParserException ex ) + { + LOG.error( "Error parsing for SPDX license ID's", ex ); + } + if ( fileSpdxLicenses != null && !fileSpdxLicenses.isEmpty() ) + { + // The file has declared licenses of the form SPDX-License-Identifier: licenseId + try + { + if ( fileSpdxLicenses.size() == 1 ) + { + license = LicenseInfoFactory.parseSPDXLicenseString( fileSpdxLicenses.get( 0 ), + spdxDoc.getModelStore(), spdxDoc.getIdPrefix(), spdxDoc.getCopyManager(), customIdToUri ); + } + else + { + Set licenseSet = new HashSet<>(); + for ( String licenseExpression : fileSpdxLicenses ) + { + licenseSet.add( LicenseInfoFactory.parseSPDXLicenseString( licenseExpression, + spdxDoc.getModelStore(), spdxDoc.getIdPrefix(), spdxDoc.getCopyManager(), customIdToUri ) ); + } + license = spdxDoc.createConjunctiveLicenseSet( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .addAllMember( licenseSet ) + .build(); + } + } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.error( "Invalid license expressions found in source file {}", file.getName(), e ); + } + if ( licenseComment == null ) + { + licenseComment = ""; + } + else if ( !licenseComment.isEmpty() ) + { + licenseComment = licenseComment.concat( "; " ); + } + licenseComment = licenseComment.concat( "This file contains SPDX-License-Identifiers for " ); + if ( license != null ) + { + licenseComment = licenseComment.concat( license.toString() ); + } + } + } + if ( license == null ) + { + try + { + license = LicenseInfoFactory.parseSPDXLicenseString( defaultFileInformation.getDeclaredLicense(), + spdxDoc.getModelStore(), spdxDoc.getIdPrefix(), spdxDoc.getCopyManager(), customIdToUri ); + concludedLicense = LicenseInfoFactory.parseSPDXLicenseString( defaultFileInformation.getConcludedLicense(), + spdxDoc.getModelStore(), spdxDoc.getIdPrefix(), spdxDoc.getCopyManager(), customIdToUri ); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxCollectionException( "Error creating SPDX file - unable create default file license", e ); + } + } + else + { + concludedLicense = license; + } + + String copyright = defaultFileInformation.getCopyright(); + String notice = defaultFileInformation.getNotice(); + String comment = defaultFileInformation.getComment(); + if ( Objects.isNull( comment ) ) + { + comment = ""; + } + if ( Objects.nonNull( licenseComment ) && !licenseComment.isBlank() ) + { + comment = comment + " ;License: " + licenseComment; + } + String[] defaultContributors = defaultFileInformation.getContributors(); + List contributors = new ArrayList<>(); + if ( defaultContributors != null ) { + for ( String contributor : defaultFileInformation.getContributors() ) + { + if ( Objects.nonNull( contributor ) && !contributor.isBlank() ) + { + try + { + contributors.add( spdxDoc.createPerson( spdxDoc.getModelStore().getNextId( IdType.Anonymous ) ) + .setName( contributor ) + .setDescription( "Contributor" ) + .build() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + LOG.warn( "Error creating contributor {} for file {}. Skipping.", contributor, file ); + } + } + } + } else { + contributors = new ArrayList<>(); + } + + SpdxFile retval; + //TODO: Add annotation + try + { + retval = spdxDoc.createSpdxFile( spdxDoc.getIdPrefix() + spdxDoc.getModelStore().getNextId( IdType.SpdxId ) ) + .setName( relativePath ) + .setCopyrightText( copyright ) + .setComment( comment ) + .setPrimaryPurpose( purpose ) + .addAllVerifiedUsing( hashes ) + .addAttributionText( notice ) + .addAllOriginatedBy( contributors ) + .build(); + String mediaType = EXT_TO_MEDIA_TYPE.get( extension ); + if ( Objects.nonNull( mediaType ) ) + { + retval.setContentType( mediaType ); + } + retval.createRelationship( retval.getIdPrefix() + retval.getModelStore().getNextId( IdType.SpdxId ) ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setFrom( retval ) + .addTo( concludedLicense ) + .setRelationshipType( RelationshipType.HAS_CONCLUDED_LICENSE ) + .build(); + retval.createRelationship( retval.getIdPrefix() + retval.getModelStore().getNextId( IdType.SpdxId ) ) + .setCompleteness( RelationshipCompleteness.COMPLETE ) + .setFrom( retval ) + .addTo( license ) + .setRelationshipType( RelationshipType.HAS_DECLARED_LICENSE ) + .build(); + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new SpdxCollectionException( "Error creating SPDX file", e ); + } + + return retval; + } + + /** + * @return SPDX Files which have been acquired through the collectFilesInDirectory method + */ + public Collection getFiles() + { + return spdxFiles.values(); + } + + /** + * @return SPDX Snippets collected through the collectFilesInDirectory method + */ + public List getSnippets() + { + return this.spdxSnippets; + } +} diff --git a/src/main/java/org/spdx/maven/utils/SpdxV3LicenseManager.java b/src/main/java/org/spdx/maven/utils/SpdxV3LicenseManager.java new file mode 100644 index 0000000..1356bb8 --- /dev/null +++ b/src/main/java/org/spdx/maven/utils/SpdxV3LicenseManager.java @@ -0,0 +1,315 @@ +/* + * Copyright 2014 Source Auditor Inc. + * + * Licensed 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 + * + * http://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.spdx.maven.utils; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.model.License; +import org.spdx.core.DefaultStoreNotInitialized; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.model.v2.license.InvalidLicenseStringException; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.expandedlicensing.CustomLicense; +import org.spdx.library.model.v3_0_1.expandedlicensing.ListedLicense; +import org.spdx.library.model.v3_0_1.expandedlicensing.NoAssertionLicense; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.maven.NonStandardLicense; +import org.spdx.storage.IModelStore.IdType; +import org.spdx.storage.listedlicense.SpdxListedLicenseModelStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages the SPDX Spec Version 3 licenses for the Spdx plugin. Keeps track of any extracted licenses (added as configuration + * parameters to the plugin). Maps Maven licenses to SPDX licenses. Creates a Maven license from an SPDX license. + * + * @author Gary O'Neall + */ +public class SpdxV3LicenseManager +{ + private static final Logger LOG = LoggerFactory.getLogger( SpdxV3LicenseManager.class ); + + /** + * SPDX document containing the license information collected. All extracted licenses are added to the SPDX + * document + */ + SpdxDocument spdxDoc; + + /** + * Maps URLs to SPDX license ID's. The SPDX licenses could be an SPDX listed license or an extracted license. + */ + Map urlStringToSpdxLicenseId = new HashMap<>(); + + /** + * Map of extracted license ID's to the SPDX license + */ + Map extractedLicenses = new HashMap<>(); + + /** + * License manager will track any extracted SPDX licenses and map between SPDX licenses and Maven licenses. The + * mapping uses the license URL to uniquely identify the licenses. + * + * @param spdxDoc SPDX document to add any extracted licenses + * @throws LicenseMapperException on errors accessing SPDX listed or local licenses + */ + public SpdxV3LicenseManager( SpdxDocument spdxDoc ) throws LicenseMapperException + { + this.spdxDoc = spdxDoc; + initializeUrlMap(); + } + + /** + * Initialize the URL map from the SPDX listed licenses + * + * @throws LicenseMapperException on errors accessing SPDX listed or local licenses + */ + private void initializeUrlMap() throws LicenseMapperException + { + this.urlStringToSpdxLicenseId.putAll( MavenToSpdxLicenseMapper.getInstance().getMap() ); + } + + /** + * Add a non-listed license to the SPDX document. Once added, the non-listed license can be referenced by the + * license ID + * + * @param license license to add to extracted license map + * @throws LicenseManagerException on errors accessing SPDX listed or local licenses + */ + public void addExtractedLicense( NonStandardLicense license ) throws LicenseManagerException + { + CustomLicense spdxLicense; + try + { + spdxLicense = spdxDoc.createCustomLicense( spdxDoc.getIdPrefix() + license.getLicenseId() ) + .setLicenseText( license.getExtractedText() ) + .setName( license.getName() ) + .setComment( license.getComment() ) + .build(); + for (String crossRef:license.getCrossReference()) { + spdxLicense.getSeeAlsos().add( crossRef ); + } + spdxDoc.getElements().add( spdxLicense ); + } + catch ( InvalidSPDXAnalysisException e ) + { + String licenseId = license.getLicenseId(); + if ( licenseId == null ) + { + licenseId = "[NullLicenseId]"; + } + throw new LicenseManagerException( "Unable to add non listed license " + licenseId, e ); + } + // add to URL mapping + String[] urls = license.getCrossReference(); + if ( urls != null ) + { + for ( String url : urls ) + { + if ( this.urlStringToSpdxLicenseId.containsKey( url ) ) + { + String oldLicenseId = urlStringToSpdxLicenseId.get( url ); + LOG.warn( "Duplicate URL for SPDX extracted license. Replacing {} with {} for {}", oldLicenseId, license.getLicenseId(), url ); + } + LOG.debug( "Adding URL mapping for non-standard license {}", license.getLicenseId() ); + this.urlStringToSpdxLicenseId.put( url, license.getLicenseId() ); + } + } + // add to extracted license cache + extractedLicenses.put( license.getLicenseId(), spdxLicense ); + } + + /** + * Map a list of Maven licenses to an SPDX license. If no licenses are supplied, SpdxNoAssertion license is + * returned. if a single license is supplied, the mapped SPDX license is returned. If multiple licenses are + * supplied, a conjunctive license is returned containing all mapped SPDX licenses. + * + * @return If no licenses are supplied, SpdxNoAssertion license is + * returned. if a single license is supplied, the mapped SPDX license is returned. + * If multiple licenses are supplied, a conjunctive license is returned containing + * all mapped SPDX licenses. + * @throws LicenseManagerException on errors accessing SPDX listed or local licenses + */ + public AnyLicenseInfo mavenLicenseListToSpdxLicense( List licenseList ) throws LicenseManagerException + { + try { + if ( licenseList == null || licenseList.isEmpty() ) + { + return new NoAssertionLicense(); + } + else if ( licenseList.size() == 1 ) + { + return mavenLicenseToSpdxLicense( licenseList.get( 0 ) ); + } + else + { + Set spdxLicenses = new HashSet<>(); + for ( License license : licenseList ) + { + spdxLicenses.add( mavenLicenseToSpdxLicense( license ) ); + } + return spdxDoc.createConjunctiveLicenseSet( spdxDoc.getModelStore().getNextId( IdType.Anonymous ) ) + .addAllMember( spdxLicenses ) + .build(); + } + } catch ( InvalidSPDXAnalysisException e ) + { + throw new LicenseManagerException( "Error converting Maven license to SPDX license", e ); + } + } + + /** + * Map a Maven license to an SPDX license based on the URL + * + * @param mavenLicense license to map to a listed SPDX license + * @return SPDX license + * @throws LicenseManagerException thrown if no SPDX listed or extracted license exists with the same URL + */ + public AnyLicenseInfo mavenLicenseToSpdxLicense( License mavenLicense ) throws LicenseManagerException + { + if ( mavenLicense.getUrl() == null ) + { + throw new LicenseManagerException( + "Can not map maven license " + mavenLicense.getName() + " No URL exists to provide a mapping" ); + } + String licenseId = this.urlStringToSpdxLicenseId.get( mavenLicense.getUrl().replaceAll("https:", "http:") ); + if ( licenseId == null ) + { + throw new LicenseManagerException( + "Can not map maven license " + mavenLicense.getName() + " No listed or extracted license matches the URL " + mavenLicense.getUrl() ); + } + AnyLicenseInfo retval = extractedLicenses.get( licenseId ); + if ( retval == null ) + { + try + { + retval = LicenseInfoFactory.parseSPDXLicenseString( licenseId, spdxDoc.getModelStore(), + spdxDoc.getIdPrefix(), + spdxDoc.getCopyManager(), null ); + } + catch ( InvalidLicenseStringException e ) + { + throw new LicenseManagerException( + "Can not map maven license " + mavenLicense.getName() + " Invalid listed or extracted license id matching the URL " + mavenLicense.getUrl() ); + } + catch ( DefaultStoreNotInitialized e ) + { + throw new LicenseManagerException( "Default model store not initialized" ); + } + } + return retval; + } + + /** + * Create a Maven license from the SPDX license + * + * @param spdxLicense source SPDX license to convert + * @return a Maven license from the SPDX license + * @throws LicenseManagerException thrown if no SPDX listed or extracted license exists with the same UR + */ + public License spdxLicenseToMavenLicense( AnyLicenseInfo spdxLicense ) throws LicenseManagerException + { + if ( spdxLicense instanceof CustomLicense ) + { + return spdxNonStdLicenseToMavenLicense( (CustomLicense) spdxLicense ); + } + else if ( spdxLicense instanceof ListedLicense ) + { + return spdxStdLicenseToMavenLicense( (ListedLicense) spdxLicense ); + } + else + { + throw new LicenseManagerException( + "Can not create a Maven license from this SPDX license type. " + "Must be an ExtractedLicenseInfo or an SpdxListedLicense " ); + } + } + + private License spdxStdLicenseToMavenLicense( ListedLicense spdxLicense ) throws LicenseManagerException + { + try + { + License retval = new License(); + // name + if ( spdxLicense.getName().isPresent() && !spdxLicense.getName().get().isEmpty() ) + { + retval.setName( spdxLicense.getName().get() ); + } + else + { + retval.setName( SpdxListedLicenseModelStore.objectUriToLicenseOrExceptionId( spdxLicense.getObjectUri() ) ); + } + // comment + if ( spdxLicense.getComment().isPresent() && !spdxLicense.getComment().get().isEmpty() ) + { + retval.setComments( spdxLicense.getComment().get() ); + } + // url + for (String url:spdxLicense.getSeeAlsos()) { + retval.setUrl( url ); + } + if ( spdxLicense.getSeeAlsos().size() > 1 ) + { + LOG.warn( "SPDX license {} contains multiple URLs. Only the first URL will be preserved in the Maven license created.", SpdxListedLicenseModelStore.objectUriToLicenseOrExceptionId( spdxLicense.getObjectUri() ) ); + } + return retval; + } catch ( InvalidSPDXAnalysisException e ) + { + throw new LicenseManagerException( "Error converting SPDX Listed License to Maven license", e ); + } + } + + private License spdxNonStdLicenseToMavenLicense( CustomLicense spdxLicense ) throws LicenseManagerException + { + try + { + License retval = new License(); + // license ID + int prefixLen = spdxLicense.getIdPrefix() == null ? 0 : spdxLicense.getIdPrefix().length(); + String licenseId = spdxLicense.getObjectUri().substring( prefixLen ); + // name + if ( spdxLicense.getName().isPresent() && !spdxLicense.getName().get().isEmpty() ) + { + retval.setName( spdxLicense.getName().get() ); + } + else + { + retval.setName( licenseId ); + } + // comment + if ( spdxLicense.getComment().isPresent() && !spdxLicense.getComment().get().isEmpty() ) + { + retval.setComments( spdxLicense.getComment().get() ); + } + // url + for (String url:spdxLicense.getSeeAlsos()) { + retval.setUrl( url ); + } + if ( spdxLicense.getSeeAlsos().size() > 1 ) + { + LOG.warn( "SPDX license {} contains multiple URLs. Only the first URL will be preserved in the Maven license created.", licenseId ); + } + return retval; + } + catch ( InvalidSPDXAnalysisException e ) + { + throw new LicenseManagerException( "Error converting SPDX non-standard license to Maven license", e ); + } + } +} diff --git a/src/test/java/org/spdx/maven/TestSpdxMojo.java b/src/test/java/org/spdx/maven/TestSpdxV2Mojo.java similarity index 89% rename from src/test/java/org/spdx/maven/TestSpdxMojo.java rename to src/test/java/org/spdx/maven/TestSpdxV2Mojo.java index d97db6d..89a950c 100644 --- a/src/test/java/org/spdx/maven/TestSpdxMojo.java +++ b/src/test/java/org/spdx/maven/TestSpdxV2Mojo.java @@ -15,28 +15,30 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; +import org.spdx.core.DefaultModelStore; import org.spdx.jacksonstore.MultiFormatStore; import org.spdx.jacksonstore.MultiFormatStore.Format; +import org.spdx.library.LicenseInfoFactory; import org.spdx.library.ModelCopyManager; -import org.spdx.library.model.ExternalRef; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.SpdxElement; -import org.spdx.library.model.SpdxFile; -import org.spdx.library.model.SpdxModelFactory; -import org.spdx.library.model.SpdxPackage; -import org.spdx.library.model.SpdxSnippet; -import org.spdx.library.model.enumerations.AnnotationType; -import org.spdx.library.model.enumerations.ReferenceCategory; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.ExtractedLicenseInfo; -import org.spdx.library.model.license.LicenseInfoFactory; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v2.ExternalRef; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.SpdxElement; +import org.spdx.library.model.v2.SpdxFile; +import org.spdx.library.model.v2.SpdxPackage; +import org.spdx.library.model.v2.SpdxSnippet; +import org.spdx.library.model.v2.enumerations.AnnotationType; +import org.spdx.library.model.v2.enumerations.ReferenceCategory; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.license.ExtractedLicenseInfo; import org.spdx.library.referencetype.ListedReferenceTypes; -import org.spdx.maven.utils.TestSpdxFileCollector; +import org.spdx.maven.utils.TestSpdxV2FileCollector; import org.spdx.spdxRdfStore.RdfStore; import org.spdx.storage.ISerializableModelStore; import org.spdx.storage.simple.InMemSpdxStore; -public class TestSpdxMojo extends AbstractMojoTestCase +public class TestSpdxV2Mojo extends AbstractMojoTestCase { private static final String UNIT_TEST_RESOURCE_DIR = "target/test-classes/unit/spdx-maven-plugin-test"; @@ -55,6 +57,8 @@ public static void tearDownAfterClass() throws Exception protected void setUp() throws Exception { super.setUp(); + SpdxModelFactory.init(); + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); } @After @@ -82,11 +86,9 @@ public void testExecute() throws Exception ISerializableModelStore modelStore = new RdfStore(); ModelCopyManager copyManager = new ModelCopyManager(); SpdxDocument result; - String documentUri; try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) { - documentUri = modelStore.deSerialize( is, false ); - result = new SpdxDocument( modelStore, documentUri, copyManager, false ); + result = (SpdxDocument)modelStore.deSerialize( is, false ); } List warnings = result.verify(); assertEquals( 0, warnings.size() ); @@ -140,9 +142,9 @@ else if ( seeAlso.equals( "http://www.test.url/testLicense2-alt.html" ) ) assertEquals( "Document Comment", result.getComment().get() ); // documentAnnotations assertEquals( 2, result.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation1 = null; - org.spdx.library.model.Annotation annotation2 = null; - for ( org.spdx.library.model.Annotation annotation : result.getAnnotations() ) + org.spdx.library.model.v2.Annotation annotation1 = null; + org.spdx.library.model.v2.Annotation annotation2 = null; + for ( org.spdx.library.model.v2.Annotation annotation : result.getAnnotations() ) { if ( annotation.getComment().equals( "Annotation1" ) ) { @@ -188,16 +190,16 @@ else if ( creator.equals( "Person: Creator2" ) ) SpdxPackage pkg = (SpdxPackage) described; // packageAnnotations assertEquals( 1, pkg.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.Annotation [pkg.getAnnotations().size()] )[0]; + org.spdx.library.model.v2.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.v2.Annotation [pkg.getAnnotations().size()] )[0]; assertEquals( "PackageAnnotation", annotation.getComment() ); assertEquals( "2015-01-29T18:30:22Z", annotation.getAnnotationDate() ); assertEquals( "Person:Test Package Person", annotation.getAnnotator() ); assertEquals( AnnotationType.REVIEW, annotation.getAnnotationType() ); //licenseDeclared - AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseById( "BSD-2-Clause" ); + AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-2-Clause" ); assertEquals( licenseDeclared, pkg.getLicenseDeclared() ); //licenseConcluded - AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseById( "BSD-3-Clause" ); + AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-3-Clause" ); assertEquals( licenseConcluded, pkg.getLicenseConcluded() ); //licenseComments assertEquals( "License comments", pkg.getLicenseComments().get() ); @@ -277,7 +279,8 @@ else if ( externalRefs[0].getReferenceCategory().equals( } assertEquals( 0, filePaths.size() ); List snippets = new ArrayList<>(); - SpdxModelFactory.getElements( modelStore, documentUri, copyManager, SpdxSnippet.class ).forEach( (snippet) -> { + SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsCompatV2.CLASS_SPDX_SNIPPET, + null, result.getIdPrefix() ).forEach( (snippet) -> { snippets.add( (SpdxSnippet)snippet ); }); assertEquals( 2, snippets.size() ); @@ -287,10 +290,10 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet License Comment", snippets.get( 0 ).getLicenseComments().get() ); assertEquals( "SnippetName", snippets.get( 0 ).getName().get() ); assertEquals( "1231:3442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); assertEquals( "BSD-2-Clause", snippets.get( 0 ).getLicenseConcluded().toString() ); assertEquals( "BSD-2-Clause-FreeBSD", snippets.get( 0 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 0 ).getLicenseInfoFromFiles().size()] )[0].toString() ); - assertEquals( "44:55", TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); + assertEquals( "44:55", TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 0 ).getSnippetFromFile().getId() ); assertEquals( "Snippet Comment2", snippets.get( 1 ).getComment().get() ); @@ -298,11 +301,11 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet2 License Comment", snippets.get( 1 ).getLicenseComments().get() ); assertEquals( "SnippetName2", snippets.get( 1 ).getName().get() ); assertEquals( "31231:33442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); assertEquals( "MITNFA", snippets.get( 1 ).getLicenseConcluded().toString() ); assertEquals( "LicenseRef-testLicense", snippets.get( 1 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 1 ).getLicenseInfoFromFiles().size()] )[0].toString() ); assertEquals( "444:554", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 1 ).getSnippetFromFile().getId() ); //TODO Test dependencies } @@ -326,11 +329,9 @@ public void testExecuteUseArtfactId() throws Exception ISerializableModelStore modelStore = new MultiFormatStore( new InMemSpdxStore(), Format.JSON ); ModelCopyManager copyManager = new ModelCopyManager(); SpdxDocument result; - String documentUri; try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) { - documentUri = modelStore.deSerialize( is, false ); - result = new SpdxDocument( modelStore, documentUri, copyManager, false ); + result = (SpdxDocument)modelStore.deSerialize( is, false ); } List warnings = result.verify(); assertEquals( 0, warnings.size() ); @@ -384,9 +385,9 @@ else if ( seeAlso.equals( "http://www.test.url/testLicense2-alt.html" ) ) assertEquals( "Document Comment", result.getComment().get() ); // documentAnnotations assertEquals( 2, result.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation1 = null; - org.spdx.library.model.Annotation annotation2 = null; - for ( org.spdx.library.model.Annotation annotation : result.getAnnotations() ) + org.spdx.library.model.v2.Annotation annotation1 = null; + org.spdx.library.model.v2.Annotation annotation2 = null; + for ( org.spdx.library.model.v2.Annotation annotation : result.getAnnotations() ) { if ( annotation.getComment().equals( "Annotation1" ) ) { @@ -434,16 +435,16 @@ else if ( creator.equals( "Person: Creator2" ) ) assertEquals( "org.spdx:spdx maven plugin test", pkg.getName().get() ); // packageAnnotations assertEquals( 1, pkg.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.Annotation [pkg.getAnnotations().size()] )[0]; + org.spdx.library.model.v2.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.v2.Annotation [pkg.getAnnotations().size()] )[0]; assertEquals( "PackageAnnotation", annotation.getComment() ); assertEquals( "2015-01-29T18:30:22Z", annotation.getAnnotationDate() ); assertEquals( "Person:Test Package Person", annotation.getAnnotator() ); assertEquals( AnnotationType.REVIEW, annotation.getAnnotationType() ); //licenseDeclared - AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseById( "BSD-2-Clause" ); + AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-2-Clause" ); assertEquals( licenseDeclared, pkg.getLicenseDeclared() ); //licenseConcluded - AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseById( "BSD-3-Clause" ); + AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-3-Clause" ); assertEquals( licenseConcluded, pkg.getLicenseConcluded() ); //licenseComments assertEquals( "License comments", pkg.getLicenseComments().get() ); @@ -523,7 +524,8 @@ else if ( externalRefs[0].getReferenceCategory().equals( } assertEquals( 0, filePaths.size() ); List snippets = new ArrayList<>(); - SpdxModelFactory.getElements( modelStore, documentUri, copyManager, SpdxSnippet.class ).forEach( (snippet) -> { + SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsCompatV2.CLASS_SPDX_SNIPPET, + null, result.getIdPrefix() ).forEach( (snippet) -> { snippets.add( (SpdxSnippet)snippet ); }); assertEquals( 2, snippets.size() ); @@ -533,10 +535,10 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet License Comment", snippets.get( 0 ).getLicenseComments().get() ); assertEquals( "SnippetName", snippets.get( 0 ).getName().get() ); assertEquals( "1231:3442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); assertEquals( "BSD-2-Clause", snippets.get( 0 ).getLicenseConcluded().toString() ); assertEquals( "BSD-2-Clause-FreeBSD", snippets.get( 0 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 0 ).getLicenseInfoFromFiles().size()] )[0].toString() ); - assertEquals( "44:55", TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); + assertEquals( "44:55", TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 0 ).getSnippetFromFile().getId() ); assertEquals( "Snippet Comment2", snippets.get( 1 ).getComment().get() ); @@ -544,11 +546,11 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet2 License Comment", snippets.get( 1 ).getLicenseComments().get() ); assertEquals( "SnippetName2", snippets.get( 1 ).getName().get() ); assertEquals( "31231:33442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); assertEquals( "MITNFA", snippets.get( 1 ).getLicenseConcluded().toString() ); assertEquals( "LicenseRef-testLicense", snippets.get( 1 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 1 ).getLicenseInfoFromFiles().size()] )[0].toString() ); assertEquals( "444:554", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 1 ).getSnippetFromFile().getId() ); //TODO Test dependencies } @@ -572,11 +574,9 @@ public void testExecuteJson() throws Exception ISerializableModelStore modelStore = new MultiFormatStore( new InMemSpdxStore(), Format.JSON ); ModelCopyManager copyManager = new ModelCopyManager(); SpdxDocument result; - String documentUri; try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) { - documentUri = modelStore.deSerialize( is, false ); - result = new SpdxDocument( modelStore, documentUri, copyManager, false ); + result = (SpdxDocument)modelStore.deSerialize( is, false ); } List warnings = result.verify(); assertEquals( 0, warnings.size() ); @@ -630,9 +630,9 @@ else if ( seeAlso.equals( "http://www.test.url/testLicense2-alt.html" ) ) assertEquals( "Document Comment", result.getComment().get() ); // documentAnnotations assertEquals( 2, result.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation1 = null; - org.spdx.library.model.Annotation annotation2 = null; - for ( org.spdx.library.model.Annotation annotation : result.getAnnotations() ) + org.spdx.library.model.v2.Annotation annotation1 = null; + org.spdx.library.model.v2.Annotation annotation2 = null; + for ( org.spdx.library.model.v2.Annotation annotation : result.getAnnotations() ) { if ( annotation.getComment().equals( "Annotation1" ) ) { @@ -680,16 +680,16 @@ else if ( creator.equals( "Person: Creator2" ) ) assertEquals( "Test SPDX Plugin", pkg.getName().get() ); // packageAnnotations assertEquals( 1, pkg.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.Annotation [pkg.getAnnotations().size()] )[0]; + org.spdx.library.model.v2.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.v2.Annotation [pkg.getAnnotations().size()] )[0]; assertEquals( "PackageAnnotation", annotation.getComment() ); assertEquals( "2015-01-29T18:30:22Z", annotation.getAnnotationDate() ); assertEquals( "Person:Test Package Person", annotation.getAnnotator() ); assertEquals( AnnotationType.REVIEW, annotation.getAnnotationType() ); //licenseDeclared - AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseById( "BSD-2-Clause" ); + AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-2-Clause" ); assertEquals( licenseDeclared, pkg.getLicenseDeclared() ); //licenseConcluded - AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseById( "BSD-3-Clause" ); + AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-3-Clause" ); assertEquals( licenseConcluded, pkg.getLicenseConcluded() ); //licenseComments assertEquals( "License comments", pkg.getLicenseComments().get() ); @@ -769,7 +769,8 @@ else if ( externalRefs[0].getReferenceCategory().equals( } assertEquals( 0, filePaths.size() ); List snippets = new ArrayList<>(); - SpdxModelFactory.getElements( modelStore, documentUri, copyManager, SpdxSnippet.class ).forEach( (snippet) -> { + SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsCompatV2.CLASS_SPDX_SNIPPET, + null, result.getIdPrefix() ).forEach( (snippet) -> { snippets.add( (SpdxSnippet)snippet ); }); assertEquals( 2, snippets.size() ); @@ -779,10 +780,10 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet License Comment", snippets.get( 0 ).getLicenseComments().get() ); assertEquals( "SnippetName", snippets.get( 0 ).getName().get() ); assertEquals( "1231:3442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); assertEquals( "BSD-2-Clause", snippets.get( 0 ).getLicenseConcluded().toString() ); assertEquals( "BSD-2-Clause-FreeBSD", snippets.get( 0 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 0 ).getLicenseInfoFromFiles().size()] )[0].toString() ); - assertEquals( "44:55", TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); + assertEquals( "44:55", TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 0 ).getSnippetFromFile().getId() ); assertEquals( "Snippet Comment2", snippets.get( 1 ).getComment().get() ); @@ -790,11 +791,11 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet2 License Comment", snippets.get( 1 ).getLicenseComments().get() ); assertEquals( "SnippetName2", snippets.get( 1 ).getName().get() ); assertEquals( "31231:33442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); assertEquals( "MITNFA", snippets.get( 1 ).getLicenseConcluded().toString() ); assertEquals( "LicenseRef-testLicense", snippets.get( 1 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 1 ).getLicenseInfoFromFiles().size()] )[0].toString() ); assertEquals( "444:554", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 1 ).getSnippetFromFile().getId() ); //TODO Test dependencies } @@ -852,20 +853,18 @@ public void testExecuteUriNotUrl() throws Exception File artifactFile = getTestFile( "target/test-classes/unit/spdx-maven-plugin-test/spdx maven plugin test.spdx.rdf.xml" ); assertTrue( artifactFile.exists() ); - ISerializableModelStore modelStore = new RdfStore(); - ModelCopyManager copyManager = new ModelCopyManager(); - SpdxDocument result; - String documentUri; - try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) - { - documentUri = modelStore.deSerialize( is, false ); - result = new SpdxDocument( modelStore, documentUri, copyManager, false ); + try ( ISerializableModelStore modelStore = new RdfStore() ) { + SpdxDocument result; + try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) + { + result = (SpdxDocument)modelStore.deSerialize( is, false ); + } + List warnings = result.verify(); + assertEquals( 0, warnings.size() ); + // Test configuration parameters found in the test resources pom.xml file + // Document namespace + assertEquals( "spdx://sbom.foobar.dev/2.3/test-package-1.1.0", result.getDocumentUri() ); } - List warnings = result.verify(); - assertEquals( 0, warnings.size() ); - // Test configuration parameters found in the test resources pom.xml file - // Document namespace - assertEquals( "spdx://sbom.foobar.dev/2.3/test-package-1.1.0", result.getDocumentUri() ); } @Test @@ -888,11 +887,9 @@ public void testExecuteNoContributors() throws Exception ISerializableModelStore modelStore = new RdfStore(); ModelCopyManager copyManager = new ModelCopyManager(); SpdxDocument result; - String documentUri; try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) { - documentUri = modelStore.deSerialize( is, false ); - result = new SpdxDocument( modelStore, documentUri, copyManager, false ); + result = (SpdxDocument)modelStore.deSerialize( is, false ); } List warnings = result.verify(); assertEquals( 0, warnings.size() ); @@ -946,9 +943,9 @@ else if ( seeAlso.equals( "http://www.test.url/testLicense2-alt.html" ) ) assertEquals( "Document Comment", result.getComment().get() ); // documentAnnotations assertEquals( 2, result.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation1 = null; - org.spdx.library.model.Annotation annotation2 = null; - for ( org.spdx.library.model.Annotation annotation : result.getAnnotations() ) + org.spdx.library.model.v2.Annotation annotation1 = null; + org.spdx.library.model.v2.Annotation annotation2 = null; + for ( org.spdx.library.model.v2.Annotation annotation : result.getAnnotations() ) { if ( annotation.getComment().equals( "Annotation1" ) ) { @@ -994,16 +991,16 @@ else if ( creator.equals( "Person: Creator2" ) ) SpdxPackage pkg = (SpdxPackage) described; // packageAnnotations assertEquals( 1, pkg.getAnnotations().size() ); - org.spdx.library.model.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.Annotation [pkg.getAnnotations().size()] )[0]; + org.spdx.library.model.v2.Annotation annotation = pkg.getAnnotations().toArray( new org.spdx.library.model.v2.Annotation [pkg.getAnnotations().size()] )[0]; assertEquals( "PackageAnnotation", annotation.getComment() ); assertEquals( "2015-01-29T18:30:22Z", annotation.getAnnotationDate() ); assertEquals( "Person:Test Package Person", annotation.getAnnotator() ); assertEquals( AnnotationType.REVIEW, annotation.getAnnotationType() ); //licenseDeclared - AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseById( "BSD-2-Clause" ); + AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-2-Clause" ); assertEquals( licenseDeclared, pkg.getLicenseDeclared() ); //licenseConcluded - AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseById( "BSD-3-Clause" ); + AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseByIdCompatV2( "BSD-3-Clause" ); assertEquals( licenseConcluded, pkg.getLicenseConcluded() ); //licenseComments assertEquals( "License comments", pkg.getLicenseComments().get() ); @@ -1081,7 +1078,8 @@ else if ( externalRefs[0].getReferenceCategory().equals( } assertEquals( 0, filePaths.size() ); List snippets = new ArrayList<>(); - SpdxModelFactory.getElements( modelStore, documentUri, copyManager, SpdxSnippet.class ).forEach( (snippet) -> { + SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsCompatV2.CLASS_SPDX_SNIPPET, + null, result.getIdPrefix() ).forEach( (snippet) -> { snippets.add( (SpdxSnippet)snippet ); }); assertEquals( 2, snippets.size() ); @@ -1091,10 +1089,10 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet License Comment", snippets.get( 0 ).getLicenseComments().get() ); assertEquals( "SnippetName", snippets.get( 0 ).getName().get() ); assertEquals( "1231:3442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getByteRange() ) ); assertEquals( "BSD-2-Clause", snippets.get( 0 ).getLicenseConcluded().toString() ); assertEquals( "BSD-2-Clause-FreeBSD", snippets.get( 0 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 0 ).getLicenseInfoFromFiles().size()] )[0].toString() ); - assertEquals( "44:55", TestSpdxFileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); + assertEquals( "44:55", TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 0 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 0 ).getSnippetFromFile().getId() ); assertEquals( "Snippet Comment2", snippets.get( 1 ).getComment().get() ); @@ -1102,11 +1100,11 @@ else if ( externalRefs[0].getReferenceCategory().equals( assertEquals( "Snippet2 License Comment", snippets.get( 1 ).getLicenseComments().get() ); assertEquals( "SnippetName2", snippets.get( 1 ).getName().get() ); assertEquals( "31231:33442", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getByteRange() ) ); assertEquals( "MITNFA", snippets.get( 1 ).getLicenseConcluded().toString() ); assertEquals( "LicenseRef-testLicense", snippets.get( 1 ).getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[snippets.get( 1 ).getLicenseInfoFromFiles().size()] )[0].toString() ); assertEquals( "444:554", - TestSpdxFileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); + TestSpdxV2FileCollector.startEndPointerToString( snippets.get( 1 ).getLineRange().get() ) ); assertEquals( fileWithSnippet, snippets.get( 1 ).getSnippetFromFile().getId() ); //TODO Test dependencies } @@ -1130,11 +1128,9 @@ public void testExecuteUseGeneratePurls() throws Exception ISerializableModelStore modelStore = new MultiFormatStore( new InMemSpdxStore(), Format.JSON ); ModelCopyManager copyManager = new ModelCopyManager(); SpdxDocument result; - String documentUri; try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) { - documentUri = modelStore.deSerialize( is, false ); - result = new SpdxDocument( modelStore, documentUri, copyManager, false ); + result = (SpdxDocument)modelStore.deSerialize( is, false ); } List warnings = result.verify(); assertEquals( 0, warnings.size() ); @@ -1143,8 +1139,9 @@ public void testExecuteUseGeneratePurls() throws Exception assertEquals( "http://spdx.org/documents/spdx%20toolsv2.0%20rc1", result.getDocumentUri() ); // purls List packages = new ArrayList<>(); - SpdxModelFactory.getElements( modelStore, documentUri, copyManager, SpdxPackage.class ).forEach( (pkg) -> { - packages.add( (SpdxPackage)pkg ); + SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsCompatV2.CLASS_SPDX_PACKAGE, + null, result.getIdPrefix() ).forEach( (pkg) -> { + packages.add( (SpdxPackage)pkg ); }); for ( SpdxPackage pkg : packages ) { Collection externalRefs = pkg.getExternalRefs(); diff --git a/src/test/java/org/spdx/maven/TestSpdxV3Mojo.java b/src/test/java/org/spdx/maven/TestSpdxV3Mojo.java new file mode 100644 index 0000000..674b0fa --- /dev/null +++ b/src/test/java/org/spdx/maven/TestSpdxV3Mojo.java @@ -0,0 +1,474 @@ +package org.spdx.maven; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.spdx.core.DefaultModelStore; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.ModelCopyManager; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v3_0_1.SpdxConstantsV3; +import org.spdx.library.model.v3_0_1.core.Agent; +import org.spdx.library.model.v3_0_1.core.AnnotationType; +import org.spdx.library.model.v3_0_1.core.Element; +import org.spdx.library.model.v3_0_1.core.ExternalIdentifier; +import org.spdx.library.model.v3_0_1.core.ExternalIdentifierType; +import org.spdx.library.model.v3_0_1.core.ExternalRef; +import org.spdx.library.model.v3_0_1.core.ExternalRefType; +import org.spdx.library.model.v3_0_1.core.Organization; +import org.spdx.library.model.v3_0_1.core.Person; +import org.spdx.library.model.v3_0_1.core.Relationship; +import org.spdx.library.model.v3_0_1.core.RelationshipType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.expandedlicensing.CustomLicense; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.library.model.v3_0_1.software.Sbom; +import org.spdx.library.model.v3_0_1.software.Snippet; +import org.spdx.library.model.v3_0_1.software.SpdxFile; +import org.spdx.library.model.v3_0_1.software.SpdxPackage; +import org.spdx.maven.utils.TestSpdxV3FileCollector; +import org.spdx.storage.ISerializableModelStore; +import org.spdx.storage.simple.InMemSpdxStore; +import org.spdx.v3jsonldstore.JsonLDStore; + +public class TestSpdxV3Mojo extends AbstractMojoTestCase +{ + + private static final String UNIT_TEST_RESOURCE_DIR = "target/test-classes/unit/spdx-maven-plugin-test"; + private static final String SPDX_FILE_NAME = UNIT_TEST_RESOURCE_DIR + "/test.json-ld.json"; + + @AfterClass + public static void tearDownAfterClass() throws Exception + { + } + + @Before + protected void setUp() throws Exception + { + super.setUp(); + SpdxModelFactory.init(); + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); + } + + @After + protected void tearDown() throws Exception + { + super.tearDown(); + } + + @SuppressWarnings( "unchecked" ) + @Test + public void testExecute() throws Exception + { + File testPom = new File( getBasedir(), UNIT_TEST_RESOURCE_DIR + "/pom-v3.xml" ); + // CreateSpdxMojo mojo = (CreateSpdxMojo) configureMojo( myMojo, "spdx-maven-plugin", testPom ); + // if the below does not work due to a lookup error, run mvn test goal + CreateSpdxMojo mojo = (CreateSpdxMojo) lookupMojo( "createSPDX", testPom ); + assertNotNull( mojo ); + mojo.execute(); + // Test SPDX filename parameter + File spdxFile = new File( getBasedir(), SPDX_FILE_NAME ); + assertTrue( spdxFile.exists() ); + // Test output artifact file is created + File artifactFile = getTestFile( + "target/test-classes/unit/spdx-maven-plugin-test/spdx maven plugin test.spdx.json-ld.json" ); + assertTrue( artifactFile.exists() ); + ISerializableModelStore modelStore = new JsonLDStore( new InMemSpdxStore() ); + ModelCopyManager copyManager = new ModelCopyManager(); + SpdxDocument result; + try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) + { + result = (SpdxDocument)modelStore.deSerialize( is, false ); + } + List warnings = result.verify(); + assertEquals( 0, warnings.size() ); + // Test configuration parameters found in the test resources pom.xml file + // Non-standard licenses + List customLicenses = (List)SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsV3.EXPANDED_LICENSING_CUSTOM_LICENSE, + null, result.getIdPrefix() ).collect( Collectors.toList() ); + assertEquals( 2, customLicenses.size() ); + CustomLicense testLicense1 = null; + CustomLicense testLicense2 = null; + for ( CustomLicense li : customLicenses ) + { + if ( li.getObjectUri().endsWith( "LicenseRef-testLicense" ) ) + { + testLicense1 = li; + } + else if ( li.getObjectUri().endsWith( "LicenseRef-testLicense2" ) ) + { + testLicense2 = li; + } + } + assertTrue( testLicense1 != null ); + assertEquals( "Test License", testLicense1.getName().get() ); + assertEquals( "Test license text", testLicense1.getLicenseText() ); + assertEquals( 1, testLicense1.getSeeAlsos().size() ); + assertEquals( "http://www.test.url/testLicense.html", testLicense1.getSeeAlsos().toArray( new String[testLicense1.getSeeAlsos().size()] )[0] ); + assertEquals( "Test license comment", testLicense1.getComment().get() ); + + assertTrue( testLicense2 != null ); + assertEquals( "Second Test License", testLicense2.getName().get() ); + assertEquals( "Second est license text", testLicense2.getLicenseText() ); + assertEquals( 2, testLicense2.getSeeAlsos().size() ); + boolean foundSeeAlso1 = false; + boolean foundSeeAlso2 = false; + for ( String seeAlso : testLicense2.getSeeAlsos() ) + { + if ( seeAlso.equals( "http://www.test.url/testLicense2.html" ) ) + { + foundSeeAlso1 = true; + } + else if ( seeAlso.equals( "http://www.test.url/testLicense2-alt.html" ) ) + { + foundSeeAlso2 = true; + } + } + assertTrue( foundSeeAlso1 ); + assertTrue( foundSeeAlso2 ); + assertEquals( "Second Test license comment", testLicense2.getComment().get() ); + // documentComment + assertEquals( "Document Comment", result.getComment().get() ); + // documentAnnotations + List allAnnotations = (List)SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsV3.CORE_ANNOTATION, + null, result.getIdPrefix() ).collect( Collectors.toList() ); + int numDocAnnotations = 0; + org.spdx.library.model.v3_0_1.core.Annotation annotation1 = null; + org.spdx.library.model.v3_0_1.core.Annotation annotation2 = null; + for ( org.spdx.library.model.v3_0_1.core.Annotation annotation : allAnnotations ) + { + if ( annotation.getSubject().equals( result ) ) + { + numDocAnnotations++; + if ( annotation.getStatement().get().equals( "Annotation1" ) ) + { + annotation1 = annotation; + } + else if ( annotation.getStatement().get().equals( "Annotation2" ) ) + { + annotation2 = annotation; + } + } + } + assertEquals( 2, numDocAnnotations ); + + assertTrue( annotation1 != null ); + assertEquals( "2010-01-29T18:30:22Z", annotation1.getCreationInfo().getCreated() ); + Collection annotators = annotation1.getCreationInfo().getCreatedBys(); + assertEquals( 1, annotators.size() ); + assertEquals( "Test Person", annotators.iterator().next().getName().get() ); + assertEquals( AnnotationType.REVIEW, annotation1.getAnnotationType() ); + + assertTrue( annotation2 != null ); + annotators = annotation2.getCreationInfo().getCreatedBys(); + assertEquals( 1, annotators.size() ); + assertEquals( "2012-11-29T18:30:22Z", annotation2.getCreationInfo().getCreated() ); + assertEquals( "Test Organization", annotators.iterator().next().getName().get() ); + assertEquals( AnnotationType.OTHER, annotation2.getAnnotationType() ); + //creatorComment + assertEquals( "Creator comment", result.getCreationInfo().getComment().get() ); + // creators + assertEquals( 2, result.getCreationInfo().getCreatedBys().size() ); + boolean foundCreator1 = false; + boolean foundCreator2 = false; + for ( Agent creator : result.getCreationInfo().getCreatedBys() ) + { + if ( creator instanceof Person && creator.getName().get().equals( "Creator1" ) ) + { + foundCreator1 = true; + } + else if ( creator instanceof Person && creator.getName().get().equals( "Creator2" ) ) + { + foundCreator2 = true; + } + } + assertTrue( foundCreator1 ); + assertTrue( foundCreator2 ); + assertEquals( 1, result.getCreationInfo().getCreatedUsings().size() ); + assertEquals( "spdx-maven-plugin", result.getCreationInfo().getCreatedUsings().iterator().next().getName().get() ); + // package parameters + assertEquals( 1, result.getRootElements().size() ); + Element sbom = result.getRootElements().toArray( new Element[result.getRootElements().size()] )[0]; + assertTrue( sbom instanceof Sbom ); + assertEquals(1, ((Sbom)sbom).getRootElements().size() ); + Element described = ((Sbom)sbom).getRootElements().iterator().next(); + assertTrue( described instanceof SpdxPackage ); + SpdxPackage pkg = (SpdxPackage) described; + // packageAnnotations + int numPkgAnnotations = 0; + org.spdx.library.model.v3_0_1.core.Annotation pkgAnnotation = null; + + for ( org.spdx.library.model.v3_0_1.core.Annotation annotation : allAnnotations ) + { + if ( annotation.getSubject().equals( pkg ) ) + { + numPkgAnnotations++; + pkgAnnotation = annotation; + } + } + assertEquals( 1, numPkgAnnotations ); + assertEquals( "PackageAnnotation", pkgAnnotation.getStatement().get() ); + assertEquals( "2015-01-29T18:30:22Z", pkgAnnotation.getCreationInfo().getCreated() ); + annotators = pkgAnnotation.getCreationInfo().getCreatedBys(); + assertEquals( 1, annotators.size() ); + Agent annotator = annotators.iterator().next(); + assertTrue( annotator instanceof Person ); + assertEquals( "Test Package Person", annotator.getName().get() ); + assertEquals( AnnotationType.REVIEW, pkgAnnotation.getAnnotationType() ); + List allRelationships = (List)SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsV3.CORE_RELATIONSHIP, + null, result.getIdPrefix() ).collect( Collectors.toList() ); + AnyLicenseInfo pkgLicenseDeclared = null; + AnyLicenseInfo pkgLicenseConcluded = null; + + for ( Relationship relationship : allRelationships ) + { + if ( relationship.getFrom().equals( pkg ) ) + { + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_DECLARED_LICENSE )) + { + assertTrue( pkgLicenseDeclared == null ); + assertEquals( 1, relationship.getTos().size() ); + pkgLicenseDeclared = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_CONCLUDED_LICENSE )) + { + assertTrue( pkgLicenseConcluded == null ); + assertEquals( 1, relationship.getTos().size() ); + pkgLicenseConcluded = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + } + } + //licenseDeclared + AnyLicenseInfo licenseDeclared = LicenseInfoFactory.getListedLicenseById( "BSD-2-Clause" ); + assertEquals( licenseDeclared, pkgLicenseDeclared ); + //licenseConcluded + AnyLicenseInfo licenseConcluded = LicenseInfoFactory.getListedLicenseById( "BSD-3-Clause" ); + assertEquals( licenseConcluded, pkgLicenseConcluded ); + //licenseComments + assertTrue( pkg.getComment().get().endsWith( "License comments" ) ); + //originator + assertEquals( 1, pkg.getOriginatedBys().size() ); + Agent originator = pkg.getOriginatedBys().iterator().next(); + assertTrue( originator instanceof Organization ); + assertEquals( "Originating org.", originator.getName().get() ); + // sourceInfo + assertEquals( "Source info", pkg.getSourceInfo().get() ); + //copyrightText + assertEquals( "Copyright Text for Package", pkg.getCopyrightText().get() ); + //external Refs + ExternalRef[] externalRefs = pkg.getExternalRefs().toArray( new ExternalRef[pkg.getExternalRefs().size()] ); + assertEquals( 1, externalRefs.length ); + assertEquals( ExternalRefType.MAVEN_CENTRAL, externalRefs[0].getExternalRefType().get() ); + assertEquals( "extref comment2", externalRefs[0].getComment().get() ); + assertEquals( 1, externalRefs[0].getLocators().size() ); + assertEquals( "org.apache.tomcat:tomcat:9.0.0.M4", externalRefs[0].getLocators().iterator().next() ); + ExternalIdentifier[] externalIds = pkg.getExternalIdentifiers().toArray( new ExternalIdentifier[pkg.getExternalIdentifiers().size()] ); + assertEquals( 1, externalIds.length ); + assertEquals( ExternalIdentifierType.CPE22, externalIds[0].getExternalIdentifierType() ); + assertEquals( "extref comment1", externalIds[0].getComment().get() ); + assertEquals( "example-locator-CPE22Type", externalIds[0].getIdentifier() ); + + // Test all files are included + List filePaths = new ArrayList<>(); + File sourceDir = new File( getBasedir(), UNIT_TEST_RESOURCE_DIR + "/src/main" ); + addFilePaths( getBasedir() + UNIT_TEST_RESOURCE_DIR, sourceDir, filePaths ); + File testDir = new File( getBasedir(), UNIT_TEST_RESOURCE_DIR + "/src/test" ); + addFilePaths( getBasedir() + UNIT_TEST_RESOURCE_DIR, testDir, filePaths ); + @SuppressWarnings( "unused" ) + File resourceDir = new File( getBasedir(), UNIT_TEST_RESOURCE_DIR + "/src/resources" ); + //TODO: Add resource to project stub and uncomment the line below + // addFilePaths( getBasedir() + UNIT_TEST_RESOURCE_DIR, resourceDir, filePaths ); + List pkgFiles = new ArrayList<>(); + for ( Relationship relationship : allRelationships ) + { + if ( relationship.getFrom().equals( pkg ) ) + { + if ( relationship.getRelationshipType().equals( RelationshipType.CONTAINS )) + { + for ( Element to : relationship.getTos() ) + { + if ( to instanceof SpdxFile ) + { + pkgFiles.add( (SpdxFile)to ); + } + } + } + } + } + assertEquals( filePaths.size(), pkgFiles.size() ); + String fileWithSnippet = null; + for ( SpdxFile sFile : pkgFiles ) + { + AnyLicenseInfo fileLicenseConcluded = null; + AnyLicenseInfo fileLicenseDeclared = null; + for ( Relationship relationship : allRelationships ) + { + if ( relationship.getFrom().equals( sFile ) ) + { + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_DECLARED_LICENSE )) + { + assertTrue( fileLicenseDeclared == null ); + assertEquals( 1, relationship.getTos().size() ); + fileLicenseDeclared = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_CONCLUDED_LICENSE )) + { + assertTrue( fileLicenseConcluded == null ); + assertEquals( 1, relationship.getTos().size() ); + fileLicenseConcluded = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + } + } + if ( sFile.getName().get().equals( "./src/main/java/CommonCode.java" ) ) + { + fileWithSnippet = sFile.getObjectUri(); + assertTrue( sFile.getComment().get().startsWith( "Comment for CommonCode" ) ); + assertEquals( "Common Code Copyright", sFile.getCopyrightText().get() ); + assertTrue( sFile.getComment().get().endsWith( "License Comment for Common Code" ) ); + assertEquals( 1, sFile.getAttributionTexts().size() ); + assertEquals( "Notice for Commmon Code", sFile.getAttributionTexts().iterator().next() ); + assertEquals( "EPL-1.0", fileLicenseConcluded.toString() ); + assertEquals( 1, sFile.getOriginatedBys().size() ); + Agent fileOriginator = sFile.getOriginatedBys().iterator().next(); + assertTrue( fileOriginator instanceof Person ); + assertEquals( "Contributor to CommonCode", fileOriginator.getName().get() ); + assertEquals( "ISC", fileLicenseDeclared.toString() ); + } + else + { + assertTrue( sFile.getComment().get().startsWith( "Default file comment" ) ); + assertEquals( "Copyright (c) 2012, 2013, 2014 Source Auditor Inc.", sFile.getCopyrightText().get() ); + assertTrue( sFile.getComment().get().endsWith( "Default file license comment" ) ); + assertEquals( 1, sFile.getAttributionTexts().size() ); + assertEquals( "Default file notice", sFile.getAttributionTexts().iterator().next() ); + assertEquals( "Apache-2.0", fileLicenseConcluded.toString() ); + assertEquals( 2, sFile.getOriginatedBys().size() ); + assertEquals( "Apache-1.1", fileLicenseDeclared.toString() ); + } + filePaths.remove( sFile.getName().get() ); + } + assertEquals( 0, filePaths.size() ); + List snippets = (List)SpdxModelFactory.getSpdxObjects( modelStore, copyManager, SpdxConstantsV3.SOFTWARE_SNIPPET, + null, result.getIdPrefix() ).collect( Collectors.toList() ); + assertEquals( 2, snippets.size() ); + Snippet snippet1; + Snippet snippet2; + if ( snippets.get( 0 ).getComment().get().startsWith( "Snippet Comment2" ) ) + { + snippet1 = snippets.get( 1 ); + snippet2 = snippets.get( 0 ); + } + else + { + snippet1 = snippets.get( 0 ); + snippet2 = snippets.get( 1 ); + } + AnyLicenseInfo snippet1LicenseConcluded = null; + AnyLicenseInfo snippet1LicenseDeclared = null; + AnyLicenseInfo snippet2LicenseConcluded = null; + AnyLicenseInfo snippet2LicenseDeclared = null; + for ( Relationship relationship : allRelationships ) + { + if ( relationship.getFrom().equals( snippet1 ) ) + { + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_DECLARED_LICENSE )) + { + assertTrue( snippet1LicenseDeclared == null ); + assertEquals( 1, relationship.getTos().size() ); + snippet1LicenseDeclared = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_CONCLUDED_LICENSE )) + { + assertTrue( snippet1LicenseConcluded == null ); + assertEquals( 1, relationship.getTos().size() ); + snippet1LicenseConcluded = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + } else if ( relationship.getFrom().equals( snippet2 ) ) + { + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_DECLARED_LICENSE )) + { + assertTrue( snippet2LicenseDeclared == null ); + assertEquals( 1, relationship.getTos().size() ); + snippet2LicenseDeclared = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + if ( relationship.getRelationshipType().equals( RelationshipType.HAS_CONCLUDED_LICENSE )) + { + assertTrue( snippet2LicenseConcluded == null ); + assertEquals( 1, relationship.getTos().size() ); + snippet2LicenseConcluded = (AnyLicenseInfo)relationship.getTos().iterator().next(); + } + } + } + assertTrue( snippet1.getComment().get().startsWith( "Snippet Comment" ) ); + assertEquals( "Snippet Copyright Text", snippet1.getCopyrightText().get() ); + assertTrue( snippet1.getComment().get().endsWith( "Snippet License Comment" ) ); + assertEquals( "SnippetName", snippet1.getName().get() ); + assertEquals( "1231:3442", TestSpdxV3FileCollector.positiveIntegerRangeToString( snippet1.getByteRange().get() ) ); + assertEquals( "BSD-2-Clause", snippet1LicenseConcluded.toString() ); + assertEquals( "BSD-2-Clause-FreeBSD", snippet1LicenseDeclared.toString() ); + assertEquals( "44:55", TestSpdxV3FileCollector.positiveIntegerRangeToString( snippet1.getLineRange().get() ) ); + assertEquals( fileWithSnippet, snippet1.getSnippetFromFile().getObjectUri() ); + + assertTrue( snippet2.getComment().get().startsWith( "Snippet Comment2" ) ); + assertEquals( "Snippet2 Copyright Text", snippet2.getCopyrightText().get() ); + assertTrue( snippet2.getComment().get().endsWith( "Snippet2 License Comment" ) ); + assertEquals( "SnippetName2", snippet2.getName().get() ); + assertEquals( "31231:33442", + TestSpdxV3FileCollector.positiveIntegerRangeToString( snippet2.getByteRange().get() ) ); + assertEquals( "MITNFA", snippet2LicenseConcluded.toString() ); + assertEquals( "LicenseRef-testLicense", snippet2LicenseDeclared.toString() ); + assertEquals( "444:554", + TestSpdxV3FileCollector.positiveIntegerRangeToString( snippet2.getLineRange().get() ) ); + assertEquals( fileWithSnippet, snippet2.getSnippetFromFile().getObjectUri() ); + //TODO Test dependencies + } + + /** + * Add relative file paths to the filePaths list + * + * @param prefix Absolute path of the directory to which the filepaths are relative + * @param dir Directory of files to add + * @param filePaths return list of file paths to which paths are added + */ + private void addFilePaths( String prefix, File dir, List filePaths ) + { + if ( !dir.exists() ) + { + return; + } + if ( !dir.isDirectory() ) + { + filePaths.add( "./" + dir.getAbsolutePath().substring( prefix.length() + 2 ).replace( '\\', '/' ) ); + return; + } + File[] files = dir.listFiles(); + for ( File file : files ) + { + if ( file.isDirectory() ) + { + addFilePaths( prefix, file, filePaths ); + } + else + { + filePaths.add( "./" + file.getAbsolutePath().substring( prefix.length() + 2 ).replace( '\\', '/' ) ); + } + } + } + + @Test + public void testmatchLicensesOnCrossReferenceUrls() + { + //TODO Implement testcase - parameter matLicensesOnCrossReferenceUrls=false + } +} diff --git a/src/test/java/org/spdx/maven/TestWithSessionSpdxMojo.java b/src/test/java/org/spdx/maven/TestWithSessionSpdxV2Mojo.java similarity index 87% rename from src/test/java/org/spdx/maven/TestWithSessionSpdxMojo.java rename to src/test/java/org/spdx/maven/TestWithSessionSpdxV2Mojo.java index 0ee240b..23d3d65 100644 --- a/src/test/java/org/spdx/maven/TestWithSessionSpdxMojo.java +++ b/src/test/java/org/spdx/maven/TestWithSessionSpdxV2Mojo.java @@ -28,24 +28,42 @@ import org.eclipse.aether.impl.DefaultServiceLocator; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.InvalidSPDXAnalysisException; import org.spdx.jacksonstore.MultiFormatStore; import org.spdx.jacksonstore.MultiFormatStore.Format; -import org.spdx.library.InvalidSPDXAnalysisException; import org.spdx.library.ModelCopyManager; -import org.spdx.library.model.Relationship; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.SpdxModelFactory; -import org.spdx.library.model.SpdxPackage; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v2.Relationship; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.SpdxPackage; import org.spdx.spdxRdfStore.RdfStore; import org.spdx.storage.ISerializableModelStore; import org.spdx.storage.simple.InMemSpdxStore; -public class TestWithSessionSpdxMojo extends AbstractMojoTestCase +public class TestWithSessionSpdxV2Mojo extends AbstractMojoTestCase { private static final String UNIT_TEST_RESOURCE_DIR = "target/test-classes/unit/spdx-maven-plugin-test"; + + @Before + protected void setUp() throws Exception + { + super.setUp(); + SpdxModelFactory.init(); + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); + } + + @After + protected void tearDown() throws Exception + { + super.tearDown(); + } @Test public void testDependencies() throws Exception @@ -55,7 +73,7 @@ public void testDependencies() throws Exception Set packages = new HashSet<>(); Set relationships = new HashSet<>(); - SpdxModelFactory.getElements( result.getModelStore(), result.getDocumentUri(), result.getCopyManager(), SpdxPackage.class ) + SpdxModelFactory.getSpdxObjects( result.getModelStore(), result.getCopyManager(), SpdxConstantsCompatV2.CLASS_SPDX_PACKAGE, null, result.getIdPrefix() ) .forEach( ( element ) -> { SpdxPackage pkg = (SpdxPackage) element; try @@ -91,11 +109,9 @@ private SpdxDocument runMojoWithPom( File pom ) throws Exception assertTrue( artifactFile.exists() ); String outputFormat = (String) getVariableValueFromObject( mojo, "outputFormat" ); ISerializableModelStore modelStore = buildModelStore( outputFormat ); - ModelCopyManager copyManager = new ModelCopyManager(); try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) { - String documentUri = modelStore.deSerialize( is, false ); - return new SpdxDocument( modelStore, documentUri, copyManager, false ); + return (SpdxDocument)modelStore.deSerialize( is, false ); } } diff --git a/src/test/java/org/spdx/maven/TestWithSessionSpdxV3Mojo.java b/src/test/java/org/spdx/maven/TestWithSessionSpdxV3Mojo.java new file mode 100644 index 0000000..f44572a --- /dev/null +++ b/src/test/java/org/spdx/maven/TestWithSessionSpdxV3Mojo.java @@ -0,0 +1,209 @@ +package org.spdx.maven; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; +import org.apache.maven.artifact.repository.MavenArtifactRepository; +import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout; +import org.apache.maven.execution.DefaultMavenExecutionRequest; +import org.apache.maven.execution.DefaultMavenExecutionResult; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenExecutionResult; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; +import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.repository.internal.MavenRepositorySystemUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.impl.DefaultServiceLocator; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.LocalRepositoryManager; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.jacksonstore.MultiFormatStore; +import org.spdx.jacksonstore.MultiFormatStore.Format; +import org.spdx.library.ModelCopyManager; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v3_0_1.SpdxConstantsV3; +import org.spdx.library.model.v3_0_1.core.Element; +import org.spdx.library.model.v3_0_1.core.Relationship; +import org.spdx.library.model.v3_0_1.core.RelationshipType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.software.SpdxPackage; +import org.spdx.spdxRdfStore.RdfStore; +import org.spdx.storage.ISerializableModelStore; +import org.spdx.storage.simple.InMemSpdxStore; +import org.spdx.v3jsonldstore.JsonLDStore; + +public class TestWithSessionSpdxV3Mojo extends AbstractMojoTestCase +{ + + private static final String UNIT_TEST_RESOURCE_DIR = "target/test-classes/unit/spdx-maven-plugin-test"; + + @Before + protected void setUp() throws Exception + { + super.setUp(); + SpdxModelFactory.init(); + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); + } + + @After + protected void tearDown() throws Exception + { + super.tearDown(); + } + + @Test + public void testDependencies() throws Exception + { + File pom = new File( getBasedir(), UNIT_TEST_RESOURCE_DIR + "/json-pom-dependencies-v3.xml" ); + SpdxDocument result = runMojoWithPom( pom ); + + Set packages = new HashSet<>(); + Set relationships = new HashSet<>(); + SpdxModelFactory.getSpdxObjects( result.getModelStore(), result.getCopyManager(), SpdxConstantsV3.CORE_LIFECYCLE_SCOPED_RELATIONSHIP, + null, result.getIdPrefix() ) + .forEach( ( element ) -> { + try + { + Relationship rel = (Relationship)element; + if ( rel.getFrom() instanceof SpdxPackage ) + { + SpdxPackage pkg = (SpdxPackage)rel.getFrom(); + packages.add( pkg.getName().get() ); + for ( Element to : rel.getTos() ) + { + if ( to instanceof SpdxPackage ) + { + Optional pkgName = pkg.getName(); + Optional toName = to.getName(); + relationships.add( pkgName.get() + "->" + toName.get() ); + } + } + } + } + catch( InvalidSPDXAnalysisException e ) + { + throw new RuntimeException( e ); + } + }); + + assertTrue( packages.contains( "org.spdx:spdx-maven-plugin-test" ) ); + assertTrue( packages.contains( "junit" ) ); + assertTrue( relationships.contains( "org.spdx:spdx-maven-plugin-test->junit" ) ); + assertTrue( relationships.contains( "junit->hamcrest-core" ) || relationships.contains( "junit->org.hamcrest:hamcrest-core" ) ); + } + + // -- Configure mojo loader + + private SpdxDocument runMojoWithPom( File pom ) throws Exception + { + CreateSpdxMojo mojo = (CreateSpdxMojo) lookupConfiguredMojo( readMavenProject( pom ), "createSPDX" ); + mojo.execute(); + + File artifactFile = (File) getVariableValueFromObject( mojo, "spdxFile" ); + assertTrue( artifactFile.exists() ); + String outputFormat = (String) getVariableValueFromObject( mojo, "outputFormat" ); + ISerializableModelStore modelStore = buildModelStore( outputFormat ); + try ( InputStream is = new FileInputStream( artifactFile.getAbsolutePath() ) ) + { + return (SpdxDocument)modelStore.deSerialize( is, false ); + } + } + + private ISerializableModelStore buildModelStore( String outputFormat ) + { + switch ( outputFormat ) + { + case "JSON": + return new MultiFormatStore( new InMemSpdxStore(), Format.JSON ); + case "RDF/XML": + return new RdfStore(); + case "JSON-LD": + return new JsonLDStore( new InMemSpdxStore() ); + default: + throw new IllegalArgumentException( "Unknown output format " + outputFormat ); + } + } + + @Override + protected MavenSession newMavenSession( MavenProject project ) + { + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + MavenExecutionResult result = new DefaultMavenExecutionResult(); + + MavenSession session = new MavenSession( getContainer(), createRepositorySystemSession(), request, result ); + session.setCurrentProject( project ); + session.setProjects( List.of( project ) ); + session.getRequest().setLocalRepository(createLocalArtifactRepository()); + return session; + } + + private RepositorySystemSession createRepositorySystemSession() { + DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); + RepositorySystem repositorySystem = locator.getService( RepositorySystem.class ); + + LocalRepository localRepo = null; + try + { + localRepo = new LocalRepository( Files.createTempDirectory("tmpDirPrefix").toFile() ); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); + LocalRepositoryManager lrm = repositorySystem.newLocalRepositoryManager( session, localRepo ); + session.setLocalRepositoryManager( lrm ); + + return session; + } + + private ArtifactRepository createLocalArtifactRepository() { + try { + return new MavenArtifactRepository( + "local", + Files.createTempDirectory( "tmpDirPrefix" ).toString(), + new DefaultRepositoryLayout(), + new ArtifactRepositoryPolicy( true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE ), + new ArtifactRepositoryPolicy( true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE ) + ); + } + catch ( IOException e ) + { + throw new RuntimeException(e); + } + } + + private MavenProject readMavenProject( File pom ) + throws Exception + { + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + request.setBaseDirectory( new File( getBasedir() ) ); + ProjectBuildingRequest configuration = request.getProjectBuildingRequest(); + configuration.setResolveDependencies( true ); + configuration.setLocalRepository( createLocalArtifactRepository() ); + configuration.setRepositorySession( createRepositorySystemSession() ); + MavenProject project = lookup( ProjectBuilder.class ).build( pom, configuration ).getProject(); + Assert.assertNotNull( project ); + return project; + } + +} diff --git a/src/test/java/org/spdx/maven/utils/TestMavenToSpdxLicenseMapper.java b/src/test/java/org/spdx/maven/utils/TestMavenToSpdxLicenseMapper.java index 58952ba..29a3814 100644 --- a/src/test/java/org/spdx/maven/utils/TestMavenToSpdxLicenseMapper.java +++ b/src/test/java/org/spdx/maven/utils/TestMavenToSpdxLicenseMapper.java @@ -13,12 +13,13 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import org.spdx.library.InvalidSPDXAnalysisException; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; import org.spdx.library.ModelCopyManager; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.LicenseInfoFactory; -import org.spdx.library.model.license.SpdxNoAssertionLicense; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v3_0_1.core.Element; import org.spdx.storage.simple.InMemSpdxStore; public class TestMavenToSpdxLicenseMapper @@ -31,6 +32,7 @@ public class TestMavenToSpdxLicenseMapper private static final String MIT_SPDX_ID = "MIT"; SpdxDocument spdxDoc = null; + Element spdxV3Doc = null; Log log = null; @@ -40,6 +42,7 @@ public class TestMavenToSpdxLicenseMapper @BeforeClass public static void setUpBeforeClass() throws Exception { + SpdxModelFactory.init(); } /** @@ -56,7 +59,10 @@ public static void tearDownAfterClass() throws Exception @Before public void setUp() throws Exception { + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); spdxDoc = new SpdxDocument( new InMemSpdxStore(), TEST_SPDX_DOCUMENT_URL, new ModelCopyManager(), true ); + spdxV3Doc = new org.spdx.library.model.v3_0_1.software.SpdxPackage( new InMemSpdxStore(), TEST_SPDX_DOCUMENT_URL + "/v3doc", + new ModelCopyManager(), true, TEST_SPDX_DOCUMENT_URL + "/"); } /** @@ -83,41 +89,75 @@ public void testGetMap() throws LicenseMapperException } @Test - public void testMavenLicenseListToSpdxLicenseNone() throws LicenseMapperException, InvalidSPDXAnalysisException + public void testMavenLicenseListToSpdxLicenseNoneV2() throws LicenseMapperException, InvalidSPDXAnalysisException { List licenseList = new ArrayList<>(); - AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxLicense( + org.spdx.library.model.v2.license.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV2License( licenseList.subList( 0, 0 ), spdxDoc ); - assertEquals( new SpdxNoAssertionLicense(), result ); + assertEquals( new org.spdx.library.model.v2.license.SpdxNoAssertionLicense(), result ); + } + + @Test + public void testMavenLicenseListToSpdxLicenseNoneV3() throws LicenseMapperException, InvalidSPDXAnalysisException + { + List licenseList = new ArrayList<>(); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV3License( + licenseList.subList( 0, 0 ), spdxV3Doc ); + assertEquals( new org.spdx.library.model.v3_0_1.expandedlicensing.NoAssertionLicense(), result ); } @Test - public void testMavenLicenseListToSpdxLicenseUnknown() throws LicenseMapperException, InvalidSPDXAnalysisException + public void testMavenLicenseListToSpdxLicenseUnknownV2() throws LicenseMapperException, InvalidSPDXAnalysisException { List licenseList = new ArrayList<>(); License license = new License(); license.setUrl( "http://not.a.known.url" ); licenseList.add( license ); - AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxLicense( + org.spdx.library.model.v2.license.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV2License( licenseList, spdxDoc ); - assertEquals( new SpdxNoAssertionLicense(), result ); + assertEquals( new org.spdx.library.model.v2.license.SpdxNoAssertionLicense(), result ); + } + + @Test + public void testMavenLicenseListToSpdxLicenseUnknownV3() throws LicenseMapperException, InvalidSPDXAnalysisException + { + List licenseList = new ArrayList<>(); + License license = new License(); + license.setUrl( "http://not.a.known.url" ); + licenseList.add( license ); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV3License( + licenseList, spdxV3Doc ); + assertEquals( new org.spdx.library.model.v3_0_1.expandedlicensing.NoAssertionLicense(), result ); } @Test - public void testMavenLicenseListToSpdxLicenseSingle() throws LicenseMapperException, InvalidSPDXAnalysisException + public void testMavenLicenseListToSpdxLicenseSingleV2() throws LicenseMapperException, InvalidSPDXAnalysisException { List licenseList = new ArrayList<>(); License license = new License(); license.setUrl( APACHE2_URL ); licenseList.add( license ); - AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxLicense( + org.spdx.library.model.v2.license.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV2License( licenseList, spdxDoc ); - AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseString( APACHE_SPDX_ID ); + org.spdx.library.model.v2.license.AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( APACHE_SPDX_ID ); + assertEquals( expected, result ); + } + + @Test + public void testMavenLicenseListToSpdxLicenseSingleV3() throws LicenseMapperException, InvalidSPDXAnalysisException + { + List licenseList = new ArrayList<>(); + License license = new License(); + license.setUrl( APACHE2_URL ); + licenseList.add( license ); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV3License( + licenseList, spdxV3Doc ); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseString( APACHE_SPDX_ID ); assertEquals( expected, result ); } @Test - public void testMavenLicenseListToSpdxLicenseConjunctive() throws LicenseMapperException, InvalidSPDXAnalysisException + public void testMavenLicenseListToSpdxLicenseConjunctiveV2() throws LicenseMapperException, InvalidSPDXAnalysisException { List licenseList = new ArrayList<>(); License license = new License(); @@ -126,14 +166,30 @@ public void testMavenLicenseListToSpdxLicenseConjunctive() throws LicenseMapperE License licenseM = new License(); licenseM.setUrl( MIT_URL ); licenseList.add( licenseM ); - AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxLicense( + org.spdx.library.model.v2.license.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV2License( licenseList, spdxDoc ); - AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseString( APACHE_SPDX_ID + " AND " + MIT_SPDX_ID ); + org.spdx.library.model.v2.license.AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( APACHE_SPDX_ID + " AND " + MIT_SPDX_ID ); + assertEquals( expected, result ); + } + + @Test + public void testMavenLicenseListToSpdxLicenseConjunctiveV3() throws LicenseMapperException, InvalidSPDXAnalysisException + { + List licenseList = new ArrayList<>(); + License license = new License(); + license.setUrl( APACHE2_URL ); + licenseList.add( license ); + License licenseM = new License(); + licenseM.setUrl( MIT_URL ); + licenseList.add( licenseM ); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV3License( + licenseList, spdxV3Doc ); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseString( APACHE_SPDX_ID + " AND " + MIT_SPDX_ID ); assertEquals( expected, result ); } @Test - public void testMavenLicenseListToSpdxLicenseConunctiveUnknown() throws LicenseMapperException, InvalidSPDXAnalysisException + public void testMavenLicenseListToSpdxLicenseConunctiveUnknownV2() throws LicenseMapperException, InvalidSPDXAnalysisException { List licenseList = new ArrayList<>(); License license = new License(); @@ -142,9 +198,25 @@ public void testMavenLicenseListToSpdxLicenseConunctiveUnknown() throws LicenseM License licenseM = new License(); licenseM.setUrl( "http://unknown.url" ); licenseList.add( licenseM ); - AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxLicense( + org.spdx.library.model.v2.license.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV2License( licenseList, spdxDoc ); - AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseString( APACHE_SPDX_ID ); + org.spdx.library.model.v2.license.AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( APACHE_SPDX_ID ); + assertEquals( expected, result ); + } + + @Test + public void testMavenLicenseListToSpdxLicenseConunctiveUnknownV3() throws LicenseMapperException, InvalidSPDXAnalysisException + { + List licenseList = new ArrayList<>(); + License license = new License(); + license.setUrl( APACHE2_URL ); + licenseList.add( license ); + License licenseM = new License(); + licenseM.setUrl( "http://unknown.url" ); + licenseList.add( licenseM ); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo result = MavenToSpdxLicenseMapper.getInstance().mavenLicenseListToSpdxV3License( + licenseList, spdxV3Doc ); + org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo expected = LicenseInfoFactory.parseSPDXLicenseString( APACHE_SPDX_ID ); assertEquals( expected, result ); } } diff --git a/src/test/java/org/spdx/maven/utils/TestSpdxSourceFileParser.java b/src/test/java/org/spdx/maven/utils/TestSpdxSourceFileParser.java index 0947596..5e25e48 100644 --- a/src/test/java/org/spdx/maven/utils/TestSpdxSourceFileParser.java +++ b/src/test/java/org/spdx/maven/utils/TestSpdxSourceFileParser.java @@ -8,12 +8,19 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.spdx.library.InvalidSPDXAnalysisException; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.ConjunctiveLicenseSet; -import org.spdx.library.model.license.DisjunctiveLicenseSet; -import org.spdx.library.model.license.ExtractedLicenseInfo; -import org.spdx.library.model.license.SpdxListedLicense; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.DefaultStoreNotInitialized; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.ModelCopyManager; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v2.license.InvalidLicenseStringException; +import org.spdx.library.model.v3_0_1.expandedlicensing.ConjunctiveLicenseSet; +import org.spdx.library.model.v3_0_1.expandedlicensing.CustomLicense; +import org.spdx.library.model.v3_0_1.expandedlicensing.DisjunctiveLicenseSet; +import org.spdx.library.model.v3_0_1.expandedlicensing.ListedLicense; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.storage.simple.InMemSpdxStore; public class TestSpdxSourceFileParser { @@ -29,12 +36,13 @@ public class TestSpdxSourceFileParser private static final String MULTIPLE_SIMPLE_IDS = "Now is the time\n" + "SPDX-License-Identifier:" + APACHE_LICENSE_ID + "\nFor all good men" + " SPDX-License-Identifier: " + MIT_LICENSE_ID + "\nto come to the aid of their country."; private static final String MULTIPLE_COMPLEX_IDS = COMPLEX + "\nNow is the time\n" + "SPDX-License-Identifier:" + APACHE_LICENSE_ID + "\nFor all good men" + "\n SPDX-License-Identifier: " + MIT_LICENSE_ID + "\nto come to the aid of their country.\n" + COMPLEX_MULTI + "\n" + CONJUNCTIVE + "\n\n\nSPDX-License-Identifier:" + LICENSE_REF_ID; private static final String MISSMATCHED_PARENS = " SPDX-License-Identifier: (((" + MIT_LICENSE_ID + " OR \n" + APACHE_LICENSE_ID + ") OR \n" + APACHE_LICENSE_ID + ")"; - private static final String INVALID_EXPRESSION = " SPDX-License-Identifier: " + APACHE_LICENSE_ID + " NOTVALID " + MIT_LICENSE_ID + " AND " + LICENSE_REF_ID; private static final String TEST_CLASS_FILE_NAME = "target/test-classes/unit/ClassWithManySpdxIDs.java"; @Before public void setUp() throws Exception { + SpdxModelFactory.init(); + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); } @After @@ -56,57 +64,62 @@ private String getBaseDir() public void testParseFileForSpdxLicenses() throws SpdxSourceParserException, InvalidSPDXAnalysisException { File javaFile = new File( getBaseDir(), TEST_CLASS_FILE_NAME ); - List result = SpdxSourceFileParser.parseFileForSpdxLicenses( javaFile ); + List result = SpdxSourceFileParser.parseFileForSpdxLicenses( javaFile ); assertEquals( 3, result.size() ); - assertEquals( APACHE_LICENSE_ID, ( (SpdxListedLicense) result.get( 0 ) ).getLicenseId() ); - assertEquals( MIT_LICENSE_ID, ( (SpdxListedLicense) result.get( 1 ) ).getLicenseId() ); - assertTrue( result.get( 2 ) instanceof DisjunctiveLicenseSet ); - assertEquals( 2, ( (DisjunctiveLicenseSet) result.get( 2 ) ).getMembers().size() ); + assertEquals( APACHE_LICENSE_ID, ( (ListedLicense) parseLic( result.get( 0 ) ) ).toString() ); + assertEquals( MIT_LICENSE_ID, ( (ListedLicense) parseLic( result.get( 1 ) ) ).toString() ); + assertTrue( parseLic( result.get( 2 ) ) instanceof DisjunctiveLicenseSet ); + assertEquals( 2, ( (DisjunctiveLicenseSet) parseLic( result.get( 2 ) ) ).getMembers().size() ); + } + + private AnyLicenseInfo parseLic( String expression ) throws InvalidLicenseStringException, DefaultStoreNotInitialized + { + return LicenseInfoFactory.parseSPDXLicenseString( expression ); } @Test public void testParseTextForSpdxLicenses() throws SpdxSourceParserException, InvalidSPDXAnalysisException { // Empty String - List result = SpdxSourceFileParser.parseTextForSpdxLicenses( EMPTY ); + List result = SpdxSourceFileParser.parseTextForSpdxLicenses( EMPTY ); assertEquals( 0, result.size() ); // Simple single license ID result = SpdxSourceFileParser.parseTextForSpdxLicenses( SIMPLE ); assertEquals( 1, result.size() ); - assertEquals( APACHE_LICENSE_ID, ( (SpdxListedLicense) result.get( 0 ) ).getLicenseId() ); + assertEquals( APACHE_LICENSE_ID, ( (ListedLicense) parseLic( result.get( 0 ) ) ).toString() ); // LicenseRef conjunctive result = SpdxSourceFileParser.parseTextForSpdxLicenses( CONJUNCTIVE ); assertEquals( 1, result.size() ); - assertTrue( result.get( 0 ) instanceof ConjunctiveLicenseSet ); - assertEquals( 3, ( (ConjunctiveLicenseSet) result.get( 0 ) ).getMembers().size() ); + assertTrue( parseLic( result.get( 0 ) ) instanceof ConjunctiveLicenseSet ); + assertEquals( 3, ( (ConjunctiveLicenseSet) parseLic( result.get( 0 ) ) ).getMembers().size() ); // Single Line complex result = SpdxSourceFileParser.parseTextForSpdxLicenses( COMPLEX ); assertEquals( 1, result.size() ); - assertTrue( result.get( 0 ) instanceof DisjunctiveLicenseSet ); - assertEquals( 2, ( (DisjunctiveLicenseSet) result.get( 0 ) ).getMembers().size() ); + assertTrue( parseLic( result.get( 0 ) ) instanceof DisjunctiveLicenseSet ); + assertEquals( 2, ( (DisjunctiveLicenseSet) parseLic( result.get( 0 ) ) ).getMembers().size() ); // Multi Line complex result = SpdxSourceFileParser.parseTextForSpdxLicenses( COMPLEX_MULTI ); assertEquals( 1, result.size() ); - assertTrue( result.get( 0 ) instanceof DisjunctiveLicenseSet ); - assertEquals( 2, ( (DisjunctiveLicenseSet) result.get( 0 ) ).getMembers().size() ); + assertTrue( parseLic( result.get( 0 ) ) instanceof DisjunctiveLicenseSet ); + assertEquals( 2, ( (DisjunctiveLicenseSet) parseLic( result.get( 0 ) ) ).getMembers().size() ); // Multiple SPDX ID's simple result = SpdxSourceFileParser.parseTextForSpdxLicenses( MULTIPLE_SIMPLE_IDS ); assertEquals( 2, result.size() ); - assertEquals( APACHE_LICENSE_ID, ( (SpdxListedLicense) result.get( 0 ) ).getLicenseId() ); - assertEquals( MIT_LICENSE_ID, ( (SpdxListedLicense) result.get( 1 ) ).getLicenseId() ); + assertEquals( APACHE_LICENSE_ID, ( (ListedLicense) parseLic( result.get( 0 ) ) ).toString() ); + assertEquals( MIT_LICENSE_ID, ( (ListedLicense) parseLic( result.get( 1 ) ) ).toString() ); // Multiple SPDX ID's complex result = SpdxSourceFileParser.parseTextForSpdxLicenses( MULTIPLE_COMPLEX_IDS ); assertEquals( 6, result.size() ); - assertTrue( result.get( 0 ) instanceof DisjunctiveLicenseSet ); - assertEquals( 2, ( (DisjunctiveLicenseSet) result.get( 0 ) ).getMembers().size() ); - assertEquals( APACHE_LICENSE_ID, ( (SpdxListedLicense) result.get( 1 ) ).getLicenseId() ); - assertEquals( MIT_LICENSE_ID, ( (SpdxListedLicense) result.get( 2 ) ).getLicenseId() ); - assertTrue( result.get( 3 ) instanceof DisjunctiveLicenseSet ); - assertEquals( 2, ( (DisjunctiveLicenseSet) result.get( 3 ) ).getMembers().size() ); - assertTrue( result.get( 4 ) instanceof ConjunctiveLicenseSet ); - assertEquals( 3, ( (ConjunctiveLicenseSet) result.get( 4 ) ).getMembers().size() ); - assertTrue( result.get( 5 ) instanceof ExtractedLicenseInfo ); - assertEquals( LICENSE_REF_ID, ( (ExtractedLicenseInfo) result.get( 5 ) ).getLicenseId() ); + assertTrue( parseLic( result.get( 0 ) ) instanceof DisjunctiveLicenseSet ); + assertEquals( 2, ( (DisjunctiveLicenseSet) parseLic( result.get( 0 ) ) ).getMembers().size() ); + assertEquals( APACHE_LICENSE_ID, ( (ListedLicense) parseLic( result.get( 1 ) ) ).toString() ); + assertEquals( MIT_LICENSE_ID, ( (ListedLicense) parseLic( result.get( 2 ) ) ).toString() ); + assertTrue( parseLic( result.get( 3 ) ) instanceof DisjunctiveLicenseSet ); + assertEquals( 2, ( (DisjunctiveLicenseSet) parseLic( result.get( 3 ) ) ).getMembers().size() ); + assertTrue( parseLic( result.get( 4 ) ) instanceof ConjunctiveLicenseSet ); + assertEquals( 3, ( (ConjunctiveLicenseSet) parseLic( result.get( 4 ) ) ).getMembers().size() ); + assertTrue( parseLic( result.get( 5 ) ) instanceof CustomLicense ); + assertEquals( LICENSE_REF_ID, ( (CustomLicense) parseLic( result.get( 5 ) ) ).toString() ); // Miss matched parens (should error) try { @@ -117,16 +130,6 @@ public void testParseTextForSpdxLicenses() throws SpdxSourceParserException, Inv { //IGNORE - this is success } - // Invalid expression (should error) - try - { - result = SpdxSourceFileParser.parseTextForSpdxLicenses( INVALID_EXPRESSION ); - fail( "Invalid expression did not fail like it should" ); - } - catch ( SpdxSourceParserException ex ) - { - //IGNORE - this is success - } } } diff --git a/src/test/java/org/spdx/maven/utils/TestSpdxFileCollector.java b/src/test/java/org/spdx/maven/utils/TestSpdxV2FileCollector.java similarity index 85% rename from src/test/java/org/spdx/maven/utils/TestSpdxFileCollector.java rename to src/test/java/org/spdx/maven/utils/TestSpdxV2FileCollector.java index 992a13d..fc276fe 100644 --- a/src/test/java/org/spdx/maven/utils/TestSpdxFileCollector.java +++ b/src/test/java/org/spdx/maven/utils/TestSpdxV2FileCollector.java @@ -24,37 +24,39 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; - -import org.spdx.library.InvalidSPDXAnalysisException; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; import org.spdx.library.ModelCopyManager; -import org.spdx.library.SpdxConstants; -import org.spdx.library.model.Checksum; -import org.spdx.library.model.Relationship; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.SpdxFile; -import org.spdx.library.model.SpdxPackage; -import org.spdx.library.model.SpdxPackageVerificationCode; -import org.spdx.library.model.SpdxSnippet; -import org.spdx.library.model.enumerations.ChecksumAlgorithm; -import org.spdx.library.model.enumerations.FileType; -import org.spdx.library.model.enumerations.RelationshipType; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.LicenseInfoFactory; -import org.spdx.library.model.pointer.ByteOffsetPointer; -import org.spdx.library.model.pointer.LineCharPointer; -import org.spdx.library.model.pointer.SinglePointer; -import org.spdx.library.model.pointer.StartEndPointer; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v2.Relationship; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.SpdxFile; +import org.spdx.library.model.v2.SpdxPackage; +import org.spdx.library.model.v2.SpdxPackageVerificationCode; +import org.spdx.library.model.v2.SpdxSnippet; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v2.enumerations.FileType; +import org.spdx.library.model.v2.enumerations.RelationshipType; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.pointer.ByteOffsetPointer; +import org.spdx.library.model.v2.pointer.LineCharPointer; +import org.spdx.library.model.v2.pointer.SinglePointer; +import org.spdx.library.model.v2.pointer.StartEndPointer; +import org.spdx.maven.Checksum; import org.spdx.maven.SnippetInfo; import org.spdx.storage.simple.InMemSpdxStore; -public class TestSpdxFileCollector +public class TestSpdxV2FileCollector { @BeforeClass public static void setUpBeforeClass() throws Exception { + SpdxModelFactory.init(); } @AfterClass @@ -85,10 +87,10 @@ public static void tearDownAfterClass() throws Exception private static final String DEFAULT_SNIPPET_LICENSE_COMMENT = "Snippet License Comment"; private static final String DEFAULT_SNIPPET_COPYRIGHT = "Snippet Copyright"; - private static final Set sha1Algorithm = new HashSet<>(); + private static final Set sha1Algorithm = new HashSet<>(); static { - sha1Algorithm.add( ChecksumAlgorithm.SHA1 ); + sha1Algorithm.add( ChecksumAlgorithm.SHA1.toString() ); } private SpdxDefaultFileInformation defaultFileInformation; @@ -102,17 +104,18 @@ public static void tearDownAfterClass() throws Exception @Before public void setUp() throws Exception { + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); spdxDoc = new SpdxDocument( new InMemSpdxStore(), TEST_SPDX_DOCUMENT_URL, new ModelCopyManager(), true ); this.defaultFileInformation = new SpdxDefaultFileInformation(); this.defaultFileInformation.setComment( DEFAULT_COMMENT ); - AnyLicenseInfo concludedLicense = LicenseInfoFactory.parseSPDXLicenseString( DEFAULT_CONCLUDED_LICENSE, - spdxDoc.getModelStore(), spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); - this.defaultFileInformation.setConcludedLicense( concludedLicense ); + AnyLicenseInfo concludedLicense = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( DEFAULT_CONCLUDED_LICENSE, + spdxDoc.getModelStore(), spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); + this.defaultFileInformation.setConcludedLicense( DEFAULT_CONCLUDED_LICENSE ); this.defaultFileInformation.setContributors( DEFAULT_CONTRIBUTORS ); this.defaultFileInformation.setCopyright( DEFAULT_COPYRIGHT ); - AnyLicenseInfo declaredLicense = LicenseInfoFactory.parseSPDXLicenseString( DEFAULT_DECLARED_LICENSE, + AnyLicenseInfo declaredLicense = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( DEFAULT_DECLARED_LICENSE, spdxDoc.getModelStore(), spdxDoc.getDocumentUri(), spdxDoc.getCopyManager() ); - this.defaultFileInformation.setDeclaredLicense( declaredLicense ); + this.defaultFileInformation.setDeclaredLicense( DEFAULT_DECLARED_LICENSE ); this.defaultFileInformation.setLicenseComment( DEFAULT_LICENSE_COMMENT ); this.defaultFileInformation.setNotice( DEFAULT_NOTICE ); SnippetInfo si = new SnippetInfo(); @@ -128,7 +131,7 @@ public void setUp() throws Exception snippets.add( si ); this.defaultFileInformation.setSnippets( snippets ); - this.directory = Files.createTempDirectory( "spdxfilecollector" ).toFile(); + this.directory = Files.createTempDirectory( "SpdxV2FileCollector" ).toFile(); int numFiles = FILE_NAMES.length; for ( String[] subdirFile : SUBDIR_FILES ) { @@ -173,7 +176,7 @@ public void setUp() throws Exception dirFileSet.setDirectory( directory.getPath() ); dirFileSet.setOutputDirectory( this.directory.getName() ); this.fileSets = Arrays.asList( dirFileSet ); - this.spdxPackage = spdxDoc.createPackage( SpdxConstants.SPDX_ELEMENT_REF_PRENUM+"test", + this.spdxPackage = spdxDoc.createPackage( SpdxConstantsCompatV2.SPDX_ELEMENT_REF_PRENUM+"test", "TestPackage", concludedLicense, "Package copyright", @@ -232,9 +235,9 @@ else if ( child.isDirectory() ) } @Test - public void testSpdxFileCollector() throws InvalidSPDXAnalysisException + public void testSpdxV2FileCollector() throws InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); Collection files = collector.getFiles(); assertEquals( 0, files.size() ); } @@ -242,7 +245,7 @@ public void testSpdxFileCollector() throws InvalidSPDXAnalysisException @Test public void testCollectFilesInDirectory() throws InvalidSPDXAnalysisException, SpdxCollectionException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); assertEquals( 0, SpdxFiles.length ); @@ -268,7 +271,7 @@ public void testCollectFileInDirectoryPattern() throws SpdxCollectionException, skipBin.setDirectory( this.fileSets.get(0).getDirectory() ); skipBin.addExclude( "**/*.bin" ); skipBin.setOutputDirectory( this.fileSets.get(0).getOutputDirectory() ); - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); assertEquals( 0, SpdxFiles.length ); @@ -291,7 +294,7 @@ public void testCollectFileInDirectoryPattern() throws SpdxCollectionException, @Test public void testGetExtension() throws InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); File noExtension = new File( "noextension" ); String result = collector.getExtension( noExtension ); assertTrue( result.isEmpty() ); @@ -310,14 +313,14 @@ public void testGetExtension() throws InvalidSPDXAnalysisException @Test public void testExtensionToFileType() throws InvalidSPDXAnalysisException { - assertEquals( FileType.VIDEO, SpdxFileCollector.extensionToFileType( "SWF" )); - assertEquals( FileType.OTHER, SpdxFileCollector.extensionToFileType( "somerandom" )); + assertEquals( FileType.VIDEO, SpdxV2FileCollector.extensionToFileType( "SWF" )); + assertEquals( FileType.OTHER, SpdxV2FileCollector.extensionToFileType( "somerandom" )); } @Test public void testGetFiles() throws SpdxCollectionException, InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); assertEquals( 0, SpdxFiles.length ); @@ -335,8 +338,8 @@ public void testGetFiles() throws SpdxCollectionException, InvalidSPDXAnalysisEx assertEquals( DEFAULT_COPYRIGHT, SpdxFiles[i].getCopyrightText() ); if ( SpdxFileNames[i].endsWith( FILE_NAME_WITH_ID ) ) { - assertEquals(LicenseInfoFactory.parseSPDXLicenseString( FILE_WITH_IDS_DECLARED_LICENSE ), SpdxFiles[i].getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[SpdxFiles[i].getLicenseInfoFromFiles().size()] )[0] ); - assertEquals( LicenseInfoFactory.parseSPDXLicenseString( FILE_WITH_IDS__CONCLUDED_LICENSE ), SpdxFiles[i].getLicenseConcluded() ); + assertEquals(LicenseInfoFactory.parseSPDXLicenseStringCompatV2( FILE_WITH_IDS_DECLARED_LICENSE ), SpdxFiles[i].getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[SpdxFiles[i].getLicenseInfoFromFiles().size()] )[0] ); + assertEquals( LicenseInfoFactory.parseSPDXLicenseStringCompatV2( FILE_WITH_IDS__CONCLUDED_LICENSE ), SpdxFiles[i].getLicenseConcluded() ); } else { @@ -351,7 +354,7 @@ public void testGetFiles() throws SpdxCollectionException, InvalidSPDXAnalysisEx @Test public void testGetSnippets() throws SpdxCollectionException, InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); List snippets = collector.getSnippets(); assertEquals( 0, snippets.size() ); @@ -398,20 +401,20 @@ else if ( pointer instanceof LineCharPointer ) @Test public void testCollectFilesWithPattern() throws SpdxCollectionException, InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); assertEquals( 0, SpdxFiles.length ); HashMap fileSpecificInfo = new HashMap<>(); SpdxDefaultFileInformation file2Info = new SpdxDefaultFileInformation(); String file2Comment = "File 2 comment"; file2Info.setComment( file2Comment ); - AnyLicenseInfo file2License = defaultFileInformation.getDeclaredLicense(); + String file2License = defaultFileInformation.getDeclaredLicense(); file2Info.setConcludedLicense( file2License ); String[] file2Contributors = new String[] {"Person: File 2 contributor"}; file2Info.setContributors( file2Contributors ); String file2Copyright = "File 2 copyright"; file2Info.setCopyright( file2Copyright ); - AnyLicenseInfo file2DeclaredLicense = defaultFileInformation.getConcludedLicense(); + String file2DeclaredLicense = defaultFileInformation.getConcludedLicense(); file2Info.setDeclaredLicense( file2DeclaredLicense ); String file2LicenseComment = "File 2 license comment"; file2Info.setLicenseComment( file2LicenseComment ); @@ -424,13 +427,13 @@ public void testCollectFilesWithPattern() throws SpdxCollectionException, Invali SpdxDefaultFileInformation file3Info = new SpdxDefaultFileInformation(); String file3Comment = "File 3 comment"; file3Info.setComment( file3Comment ); - AnyLicenseInfo file3License = defaultFileInformation.getDeclaredLicense(); + String file3License = defaultFileInformation.getDeclaredLicense(); file3Info.setConcludedLicense( file3License ); String[] file3Contributors = new String[] {"Person: File 3 contributor"}; file3Info.setContributors( file3Contributors ); String file3Copyright = "File 3 copyright"; file3Info.setCopyright( file3Copyright ); - AnyLicenseInfo file3DeclaredLicense = defaultFileInformation.getDeclaredLicense(); + String file3DeclaredLicense = defaultFileInformation.getDeclaredLicense(); file3Info.setDeclaredLicense( file3DeclaredLicense ); String file3LicenseComment = "File 3 license comment"; file3Info.setLicenseComment( file3LicenseComment ); @@ -451,14 +454,14 @@ public void testCollectFilesWithPattern() throws SpdxCollectionException, Invali assertEquals( SpdxFileNames[i], SpdxFiles[i].getName().get() ); if ( SpdxFiles[i].getName().get().equals( SpdxFileNames[1] ) ) { - assertEquals( file2Comment, SpdxFiles[1].getComment().get() ); - assertEquals( file2License.toString(), SpdxFiles[1].getLicenseConcluded().toString() ); - assertEquals( file2Contributors.length, SpdxFiles[1].getFileContributors().size() ); - assertArrayEquals( file2Contributors, TestUtils.toSortedArray( SpdxFiles[1].getFileContributors() ) ); - assertEquals( file2Copyright, SpdxFiles[1].getCopyrightText() ); - assertEquals( file2DeclaredLicense.toString(), SpdxFiles[1].getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[SpdxFiles[1].getLicenseInfoFromFiles().size()] )[0].toString() ); - assertEquals( file2LicenseComment, SpdxFiles[1].getLicenseComments().get() ); - assertEquals( file2Notice, SpdxFiles[1].getNoticeText().get() ); + assertEquals( file2Comment, SpdxFiles[i].getComment().get() ); + assertEquals( file2License.toString(), SpdxFiles[i].getLicenseConcluded().toString() ); + assertEquals( file2Contributors.length, SpdxFiles[i].getFileContributors().size() ); + assertArrayEquals( file2Contributors, TestUtils.toSortedArray( SpdxFiles[i].getFileContributors() ) ); + assertEquals( file2Copyright, SpdxFiles[i].getCopyrightText() ); + assertEquals( file2DeclaredLicense.toString(), SpdxFiles[i].getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[SpdxFiles[1].getLicenseInfoFromFiles().size()] )[0].toString() ); + assertEquals( file2LicenseComment, SpdxFiles[i].getLicenseComments().get() ); + assertEquals( file2Notice, SpdxFiles[i].getNoticeText().get() ); } else if ( SpdxFiles[i].getName().get().startsWith( subDirAPrefix ) ) { @@ -488,7 +491,7 @@ else if ( !SpdxFiles[i].getName().get().endsWith( FILE_NAME_WITH_ID ) ) @Test public void testGetLicenseInfoFromFiles() throws SpdxCollectionException, IOException, InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); AnyLicenseInfo[] result = collector.getLicenseInfoFromFiles().toArray( new AnyLicenseInfo[collector.getLicenseInfoFromFiles().size()] ); assertEquals( 0, result.length ); @@ -499,7 +502,7 @@ public void testGetLicenseInfoFromFiles() throws SpdxCollectionException, IOExce assertEquals( 2, result.length ); if ( DEFAULT_DECLARED_LICENSE.equals( result[0].toString() ) ) { - assertEquals( LicenseInfoFactory.parseSPDXLicenseString( FILE_WITH_IDS_DECLARED_LICENSE ), result[1] ); + assertEquals( LicenseInfoFactory.parseSPDXLicenseStringCompatV2( FILE_WITH_IDS_DECLARED_LICENSE ), result[1] ); } else { @@ -517,7 +520,7 @@ public void testGetLicenseInfoFromFiles() throws SpdxCollectionException, IOExce info2.setConcludedLicense( this.defaultFileInformation.getConcludedLicense() ); info2.setCopyright( this.defaultFileInformation.getCopyright() ); String newLicenseName = "LicenseRef-newLicense"; - AnyLicenseInfo newDeclaredLicense = LicenseInfoFactory.parseSPDXLicenseString( newLicenseName ); + String newDeclaredLicense = newLicenseName ; info2.setDeclaredLicense( newDeclaredLicense ); FileSet fileSet2 = new FileSet(); fileSet2.setDirectory( tempDir2.getPath() ); @@ -529,8 +532,8 @@ public void testGetLicenseInfoFromFiles() throws SpdxCollectionException, IOExce boolean foundDefault = false; boolean foundFileWithIds = false; boolean foundNewLicense = false; - AnyLicenseInfo defaultDecaredLicense = LicenseInfoFactory.parseSPDXLicenseString( DEFAULT_DECLARED_LICENSE ); - AnyLicenseInfo fileWithIdstDecaredLicense = LicenseInfoFactory.parseSPDXLicenseString( FILE_WITH_IDS_DECLARED_LICENSE ); + AnyLicenseInfo defaultDecaredLicense = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( DEFAULT_DECLARED_LICENSE ); + AnyLicenseInfo fileWithIdstDecaredLicense = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( FILE_WITH_IDS_DECLARED_LICENSE ); for ( AnyLicenseInfo lic : result ) { if ( lic.equals( defaultDecaredLicense ) ) @@ -559,7 +562,7 @@ public void testGetLicenseInfoFromFiles() throws SpdxCollectionException, IOExce @Test public void testGetVerificationCode() throws SpdxCollectionException, NoSuchAlgorithmException, InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); assertEquals( 0, SpdxFiles.length ); @@ -577,7 +580,7 @@ public void testConvertChecksumToString() { byte[] cksumBytes = new byte[] {00, 01, 02, 03, 04, 05, 06, 07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x1E}; String expected = "000102030405060708090a0b0c0d1e"; - String result = SpdxFileCollector.convertChecksumToString( cksumBytes ); + String result = SpdxV2FileCollector.convertChecksumToString( cksumBytes ); assertEquals( expected, result ); } @@ -586,7 +589,7 @@ public void testConvertFilePathToSpdxFileName() { String dosFilePath = "subdir\\subdir2\\file.c"; String dosSpdxFile = "./subdir/subdir2/file.c"; - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); String result = collector.convertFilePathToSpdxFileName( dosFilePath ); assertEquals( dosSpdxFile, result ); @@ -599,7 +602,7 @@ public void testConvertFilePathToSpdxFileName() @Test public void testIsSourceFile() { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); assertTrue( collector.isSourceFile( toFileTypeCollection( new FileType[] {FileType.SOURCE} ) ) ); assertTrue( collector.isSourceFile( toFileTypeCollection( new FileType[] {FileType.TEXT, FileType.SOURCE} ) ) ); assertFalse( collector.isSourceFile( toFileTypeCollection( new FileType[] {FileType.BINARY, FileType.IMAGE} ) ) ); @@ -621,20 +624,20 @@ private Collection toFileTypeCollection( FileType[] fileTypes ) @Test public void testGenerateChecksums() throws SpdxCollectionException, InvalidSPDXAnalysisException { - SpdxFileCollector collector = new SpdxFileCollector(); + SpdxV2FileCollector collector = new SpdxV2FileCollector(); collector.collectFiles( this.fileSets, this.directory.getAbsolutePath(), this.defaultFileInformation, new HashMap<>(), spdxPackage, RelationshipType.GENERATES, spdxDoc, sha1Algorithm ); File spdxFile = new File( filePaths[0] ); - Set checksumAlgorithmSet = new HashSet<>(); - checksumAlgorithmSet.add(ChecksumAlgorithm.SHA1 ); - checksumAlgorithmSet.add( ChecksumAlgorithm.SHA256 ); + Set checksumAlgorithmSet = new HashSet<>(); + checksumAlgorithmSet.add(ChecksumAlgorithm.SHA1.toString() ); + checksumAlgorithmSet.add( ChecksumAlgorithm.SHA256.toString() ); Set expectedChecksums = new HashSet<>(); - expectedChecksums.add( spdxDoc.createChecksum( ChecksumAlgorithm.SHA1, "1834453c87b9188024c7b18d179eb64f95f29fcf" ) ); - expectedChecksums.add( spdxDoc.createChecksum( ChecksumAlgorithm.SHA256, "1c94046c63f61f5dbe5c15cc6c4e34510132ab262aa266735b344d836ef8cb3c") ); + expectedChecksums.add( new Checksum( ChecksumAlgorithm.SHA1.toString(), "1834453c87b9188024c7b18d179eb64f95f29fcf" ) ); + expectedChecksums.add( new Checksum( ChecksumAlgorithm.SHA256.toString(), "1c94046c63f61f5dbe5c15cc6c4e34510132ab262aa266735b344d836ef8cb3c") ); // Checksum does not override equals currently. Hence, need to compare manually - Set actualChecksums = SpdxFileCollector.generateChecksum( spdxFile, checksumAlgorithmSet, spdxDoc ); + Set actualChecksums = SpdxV2FileCollector.generateChecksum( spdxFile, checksumAlgorithmSet ); for ( Checksum expectedChecksum : expectedChecksums ) { boolean found = false; diff --git a/src/test/java/org/spdx/maven/utils/TestLicenseManager.java b/src/test/java/org/spdx/maven/utils/TestSpdxV2LicenseManager.java similarity index 86% rename from src/test/java/org/spdx/maven/utils/TestLicenseManager.java rename to src/test/java/org/spdx/maven/utils/TestSpdxV2LicenseManager.java index 055b15e..57dd487 100644 --- a/src/test/java/org/spdx/maven/utils/TestLicenseManager.java +++ b/src/test/java/org/spdx/maven/utils/TestSpdxV2LicenseManager.java @@ -27,24 +27,26 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import org.spdx.library.InvalidSPDXAnalysisException; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; import org.spdx.library.ModelCopyManager; -import org.spdx.library.model.SpdxDocument; -import org.spdx.library.model.license.AnyLicenseInfo; -import org.spdx.library.model.license.ConjunctiveLicenseSet; -import org.spdx.library.model.license.ExtractedLicenseInfo; -import org.spdx.library.model.license.LicenseInfoFactory; -import org.spdx.library.model.license.SpdxListedLicense; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v2.SpdxDocument; +import org.spdx.library.model.v2.license.AnyLicenseInfo; +import org.spdx.library.model.v2.license.ConjunctiveLicenseSet; +import org.spdx.library.model.v2.license.ExtractedLicenseInfo; +import org.spdx.library.model.v2.license.SpdxListedLicense; import org.spdx.maven.NonStandardLicense; import org.spdx.storage.simple.InMemSpdxStore; import org.apache.maven.model.License; /** - * Unit tests for LicenseManager class + * Unit tests for SpdxV2LicenseManager class * * @author Gary O'Neall */ -public class TestLicenseManager +public class TestSpdxV2LicenseManager { private static final String TEST_SPDX_DOCUMENT_URL = "http://www.spdx.org/documents/test"; static final String APACHE_CROSS_REF_URL2 = "http://www.apache.org/licenses/LICENSE-2.0"; @@ -63,6 +65,7 @@ public class TestLicenseManager @BeforeClass public static void setUpBeforeClass() throws Exception { + SpdxModelFactory.init(); } /** @@ -79,6 +82,7 @@ public static void tearDownAfterClass() throws Exception @Before public void setUp() throws Exception { + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); spdxDoc = new SpdxDocument( new InMemSpdxStore(), TEST_SPDX_DOCUMENT_URL, new ModelCopyManager(), true ); } @@ -92,7 +96,7 @@ public void tearDown() throws Exception } /** - * Test method for {@link org.spdx.maven.utils.LicenseManager#LicenseManager(org.spdx.rdfparser.SpdxDocument,boolean)}. + * Test method for {@link org.spdx.maven.utils.SpdxV2LicenseManager#LicenseManager(org.spdx.rdfparser.SpdxDocument,boolean)}. * * @throws LicenseMapperException */ @@ -100,11 +104,11 @@ public void tearDown() throws Exception public void testLicenseManager() throws LicenseMapperException { @SuppressWarnings( "unused" ) - LicenseManager licenseManager = new LicenseManager( spdxDoc, false ); + SpdxV2LicenseManager licenseManager = new SpdxV2LicenseManager( spdxDoc); } /** - * Test method for {@link org.spdx.maven.utils.LicenseManager#addExtractedLicense(org.spdx.maven.NonStandardLicense)}. + * Test method for {@link org.spdx.maven.utils.SpdxV2LicenseManager#addExtractedLicense(org.spdx.maven.NonStandardLicense)}. * * @throws MalformedURLException * @throws LicenseManagerException @@ -114,7 +118,7 @@ public void testLicenseManager() throws LicenseMapperException @Test public void testAddNonStandardLicense() throws MalformedURLException, LicenseManagerException, InvalidSPDXAnalysisException, LicenseMapperException { - LicenseManager licenseManager = new LicenseManager( spdxDoc, false ); + SpdxV2LicenseManager licenseManager = new SpdxV2LicenseManager( spdxDoc); NonStandardLicense lic = new NonStandardLicense(); final String COMMENT = "comment"; final String[] CROSS_REF_STR = new String[] {"http://www.licenseRef1", "http://www.licenseref2"}; @@ -159,7 +163,7 @@ public void testAddNonStandardLicense() throws MalformedURLException, LicenseMan } /** - * Test method for {@link org.spdx.maven.utils.LicenseManager#mavenLicenseListToSpdxLicense(java.util.List)}. + * Test method for {@link org.spdx.maven.utils.SpdxV2LicenseManager#mavenLicenseListToSpdxLicense(java.util.List)}. * * @throws LicenseManagerException * @throws LicenseMapperException @@ -182,7 +186,7 @@ public void testMavenLicenseListToSpdxLicense() throws LicenseManagerException, licenseList.add( apache ); licenseList.add( apsl ); - LicenseManager licenseManager = new LicenseManager( spdxDoc, true ); + SpdxV2LicenseManager licenseManager = new SpdxV2LicenseManager( spdxDoc); AnyLicenseInfo result = licenseManager.mavenLicenseListToSpdxLicense( licenseList ); assertTrue( result instanceof ConjunctiveLicenseSet ); @@ -206,7 +210,7 @@ public void testMavenLicenseListToSpdxLicense() throws LicenseManagerException, } /** - * Test method for {@link org.spdx.maven.utils.LicenseManager#mavenLicenseToSpdxLicense(org.apache.maven.model.License)}. + * Test method for {@link org.spdx.maven.utils.SpdxV2LicenseManager#mavenLicenseToSpdxLicense(org.apache.maven.model.License)}. * * @throws LicenseManagerException * @throws MalformedURLException @@ -222,7 +226,7 @@ public void testMavenLicenseToSpdxLicense() throws LicenseManagerException, Malf License apache = new License(); apache.setName( LICENSE1_NAME ); apache.setUrl( APACHE_CROSS_REF_URL2 ); - LicenseManager licenseManager = new LicenseManager( spdxDoc, true ); + SpdxV2LicenseManager licenseManager = new SpdxV2LicenseManager( spdxDoc); AnyLicenseInfo result = licenseManager.mavenLicenseToSpdxLicense( apache ); assertTrue( result instanceof SpdxListedLicense ); @@ -258,7 +262,7 @@ public void testMavenLicenseToSpdxLicense() throws LicenseManagerException, Malf } /** - * Test method for {@link org.spdx.maven.utils.LicenseManager#spdxLicenseToMavenLicense(org.spdx.rdfparser.AnyLicenseInfo)}. + * Test method for {@link org.spdx.maven.utils.SpdxV2LicenseManager#spdxLicenseToMavenLicense(org.spdx.rdfparser.AnyLicenseInfo)}. * * @throws LicenseManagerException * @throws LicenseMapperException @@ -267,9 +271,9 @@ public void testMavenLicenseToSpdxLicense() throws LicenseManagerException, Malf @Test public void testSpdxLicenseToMavenLicense() throws LicenseManagerException, LicenseMapperException, InvalidSPDXAnalysisException { - LicenseManager licenseManager = new LicenseManager( spdxDoc, false ); + SpdxV2LicenseManager licenseManager = new SpdxV2LicenseManager( spdxDoc); // standard license - AnyLicenseInfo licenseInfo = LicenseInfoFactory.parseSPDXLicenseString( APACHE_LICENSE_ID ); + AnyLicenseInfo licenseInfo = LicenseInfoFactory.parseSPDXLicenseStringCompatV2( APACHE_LICENSE_ID ); License result = licenseManager.spdxLicenseToMavenLicense( licenseInfo ); assertEquals( result.getName(), ( (SpdxListedLicense) licenseInfo ).getName() ); String resultUrl = result.getUrl().replace( "https", "http" ); diff --git a/src/test/java/org/spdx/maven/utils/TestSpdxV3FileCollector.java b/src/test/java/org/spdx/maven/utils/TestSpdxV3FileCollector.java new file mode 100644 index 0000000..9cd03c1 --- /dev/null +++ b/src/test/java/org/spdx/maven/utils/TestSpdxV3FileCollector.java @@ -0,0 +1,665 @@ +package org.spdx.maven.utils; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.maven.shared.model.fileset.FileSet; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.ModelCopyManager; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v2.SpdxConstantsCompatV2; +import org.spdx.library.model.v2.enumerations.ChecksumAlgorithm; +import org.spdx.library.model.v3_0_1.SpdxConstantsV3; +import org.spdx.library.model.v3_0_1.core.DictionaryEntry; +import org.spdx.library.model.v3_0_1.core.Element; +import org.spdx.library.model.v3_0_1.core.PositiveIntegerRange; +import org.spdx.library.model.v3_0_1.core.Relationship; +import org.spdx.library.model.v3_0_1.core.RelationshipType; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.library.model.v3_0_1.software.Snippet; +import org.spdx.library.model.v3_0_1.software.SpdxFile; +import org.spdx.library.model.v3_0_1.software.SpdxPackage; +import org.spdx.maven.Checksum; +import org.spdx.maven.SnippetInfo; +import org.spdx.storage.simple.InMemSpdxStore; + + +public class TestSpdxV3FileCollector +{ + + + @BeforeClass + public static void setUpBeforeClass() throws Exception + { + SpdxModelFactory.init(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception + { + } + private static final String TEST_SPDX_DOCUMENT_URL = "http://www.spdx.org/documents/test"; + static final String[] FILE_NAMES = new String[] {"file1.bin", "file2.c", "file3.php", "file4.zip"}; + static final String FILE_NAME_WITH_ID = "FileWithSpdxIds.java"; + static final String FILE_WITH_ID_CONTENT = "/**\n *SPDX-License-Identifier: MIT\n *\n *SPDX-License-Identifier: Apache-2.0\n**/"; + static final String FILE_WITH_IDS_DECLARED_LICENSE = "(MIT AND Apache-2.0)"; + static final String FILE_WITH_IDS__CONCLUDED_LICENSE = FILE_WITH_IDS_DECLARED_LICENSE; + static final String SNIPPET_NAMES = "Snippet Name"; + static final String[] SUB_DIRS = new String[] {"dirA", "folderB"}; + static final String[][] SUBDIR_FILES = new String[][] {new String[] {"subfile1.c", "subfile2.bin"}, new String[] {"sub2files.php"}}; + private static final String DEFAULT_COMMENT = "Default comment"; + private static final String DEFAULT_CONCLUDED_LICENSE = "Apache-2.0"; + private static final String[] DEFAULT_CONTRIBUTORS = new String[] {"Contrib1", "Contrib2"}; + private static final String DEFAULT_COPYRIGHT = "Default copyright"; + private static final String DEFAULT_DECLARED_LICENSE = "APSL-1.1"; + private static final String DEFAULT_LICENSE_COMMENT = "Default license comment"; + private static final String DEFAULT_NOTICE = "Default notice"; + private static final String DEFAULT_SNIPPET_BYTE_RANGE = "12:5234"; + private static final String DEFAULT_SNIPPET_LINE_RANGE = "88:99"; + private static final String DEFAULT_SNIPPET_COMMENT = "Snippet comment"; + private static final String DEFAULT_SNIPPET_CONCLUDED_LICENSE = "CC-BY-3.0"; + private static final String DEFAULT_SNIPPET_DECLARED_LICENSE = "LGPL-2.0"; + private static final String DEFAULT_SNIPPET_LICENSE_COMMENT = "Snippet License Comment"; + private static final String DEFAULT_SNIPPET_COPYRIGHT = "Snippet Copyright"; + + private static final Set sha1Algorithm = new HashSet<>(); + static + { + sha1Algorithm.add( ChecksumAlgorithm.SHA1.toString() ); + } + + private SpdxDefaultFileInformation defaultFileInformation; + private File directory; + private String[] filePaths; + private String[] SpdxFileNames; + private List fileSets; + private SpdxPackage spdxPackage; + private List customIdMap = new ArrayList<>(); + SpdxDocument spdxDoc = null; + Comparator elementComparer = new Comparator() { + + @Override + public int compare( Element o1, Element o2 ) + { + try + { + return o1.getName().orElse( "" ).compareTo(o2.getName().orElse( "" )); + } + catch ( InvalidSPDXAnalysisException e ) + { + fail( "Error getting element names" ); + return 0; + } + } + + }; + + @Before + public void setUp() throws Exception + { + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); + spdxDoc = new SpdxDocument( new InMemSpdxStore(), TEST_SPDX_DOCUMENT_URL + "/Document", new ModelCopyManager(), true, TEST_SPDX_DOCUMENT_URL + "/" ); + this.defaultFileInformation = new SpdxDefaultFileInformation(); + this.defaultFileInformation.setComment( DEFAULT_COMMENT ); + AnyLicenseInfo concludedLicense = LicenseInfoFactory.parseSPDXLicenseString( DEFAULT_CONCLUDED_LICENSE, spdxDoc.getModelStore(), + spdxDoc.getIdPrefix(), spdxDoc.getCopyManager(), customIdMap ); + this.defaultFileInformation.setConcludedLicense( DEFAULT_CONCLUDED_LICENSE ); + this.defaultFileInformation.setContributors( DEFAULT_CONTRIBUTORS ); + this.defaultFileInformation.setCopyright( DEFAULT_COPYRIGHT ); + AnyLicenseInfo declaredLicense = LicenseInfoFactory.parseSPDXLicenseString( DEFAULT_DECLARED_LICENSE, + spdxDoc.getModelStore(), spdxDoc.getIdPrefix(), + spdxDoc.getCopyManager(), customIdMap ); + this.defaultFileInformation.setDeclaredLicense( DEFAULT_DECLARED_LICENSE ); + this.defaultFileInformation.setLicenseComment( DEFAULT_LICENSE_COMMENT ); + this.defaultFileInformation.setNotice( DEFAULT_NOTICE ); + SnippetInfo si = new SnippetInfo(); + si.setByteRange( DEFAULT_SNIPPET_BYTE_RANGE ); + si.setComment( DEFAULT_SNIPPET_COMMENT ); + si.setConcludedLicense( DEFAULT_SNIPPET_CONCLUDED_LICENSE ); + si.setLicenseInfoInSnippet( DEFAULT_SNIPPET_DECLARED_LICENSE ); + si.setLicenseComment( DEFAULT_SNIPPET_LICENSE_COMMENT ); + si.setCopyrightText( DEFAULT_SNIPPET_COPYRIGHT ); + si.setLineRange( DEFAULT_SNIPPET_LINE_RANGE ); + si.setName( SNIPPET_NAMES ); + ArrayList snippets = new ArrayList<>(); + snippets.add( si ); + this.defaultFileInformation.setSnippets( snippets ); + + this.directory = Files.createTempDirectory( "SpdxV3FileCollector" ).toFile(); + int numFiles = FILE_NAMES.length; + for ( String[] subdirFile : SUBDIR_FILES ) + { + numFiles = numFiles + subdirFile.length; + } + numFiles++; // for the SPDX file with license IDs + this.filePaths = new String[numFiles]; + this.SpdxFileNames = new String[numFiles]; + int fpi = 0; // file path index + for ( String fileName : FILE_NAMES ) + { + File newFile = new File( this.directory.getPath() + File.separator + fileName ); + newFile.createNewFile(); + createUniqueContent( newFile ); + this.filePaths[fpi] = newFile.getPath(); + this.SpdxFileNames[fpi++] = "./" + this.directory.getName() + "/" + fileName; + } + for ( int i = 0; i < SUB_DIRS.length; i++ ) + { + File newDir = new File( this.directory.getPath() + File.separator + SUB_DIRS[i] ); + newDir.mkdir(); + for ( int j = 0; j < SUBDIR_FILES[i].length; j++ ) + { + File newFile = new File( newDir.getPath() + File.separator + SUBDIR_FILES[i][j] ); + newFile.createNewFile(); + createUniqueContent( newFile ); + this.filePaths[fpi] = newFile.getPath(); + this.SpdxFileNames[fpi++] = "./" + this.directory.getName() + "/" + SUB_DIRS[i] + "/" + SUBDIR_FILES[i][j]; + } + } + File fileWithIds = new File( this.directory.getPath() + File.separator + FILE_NAME_WITH_ID ); + fileWithIds.createNewFile(); + try ( PrintWriter writer = new PrintWriter( fileWithIds ) ) + { + writer.print( FILE_WITH_ID_CONTENT ); + } + this.filePaths[fpi] = fileWithIds.getPath(); + this.SpdxFileNames[fpi++] = "./" + this.directory.getName() + "/" + FILE_NAME_WITH_ID; + Arrays.sort( this.filePaths ); + Arrays.sort( SpdxFileNames ); + FileSet dirFileSet = new FileSet(); + dirFileSet.setDirectory( directory.getPath() ); + dirFileSet.setOutputDirectory( this.directory.getName() ); + this.fileSets = Arrays.asList( dirFileSet ); + this.spdxPackage = spdxDoc.createSpdxPackage( spdxDoc.getIdPrefix() + SpdxConstantsCompatV2.SPDX_ELEMENT_REF_PRENUM+"test" ) + .setName( "TestPackage" ) + .setCopyrightText( "Package copyright" ) + .setDownloadLocation( "NOASSERTION" ) + .build(); + spdxDoc.createRelationship( spdxDoc.getIdPrefix() + SpdxConstantsCompatV2.SPDX_ELEMENT_REF_PRENUM+"pkgdeclared" ) + .addTo( declaredLicense ) + .setFrom( spdxPackage ) + .setRelationshipType( RelationshipType.HAS_DECLARED_LICENSE ) + .build(); + spdxDoc.createRelationship( spdxDoc.getIdPrefix() + SpdxConstantsCompatV2.SPDX_ELEMENT_REF_PRENUM+"pkgconcluded" ) + .addTo( concludedLicense ) + .setFrom( spdxPackage ) + .setRelationshipType( RelationshipType.HAS_CONCLUDED_LICENSE ) + .build(); + } + + private void createUniqueContent( File file ) throws FileNotFoundException + { + try ( PrintWriter writer = new PrintWriter( file ) ) + { + writer.println( file.getPath() ); + writer.println( System.nanoTime() ); + } + } + + @After + public void tearDown() throws Exception + { + spdxDoc = null; + deleteDirectory( this.directory ); + } + + private void deleteDirectory( File dir ) + { + if ( dir.isFile() ) + { + if ( !dir.delete() ) + { + System.console().writer().println( "Unable to delete " + dir.getPath() ); + } + } + else if ( dir.isDirectory() ) + { + File[] children = dir.listFiles(); + if ( children != null ) + { + for ( File child : children ) + { + if ( child.isFile() ) + { + if ( !child.delete() ) + { + System.console().writer().println( "Unable to delete " + child.getPath() ); + } + } + else if ( child.isDirectory() ) + { + deleteDirectory( child ); + } + } + } + } + } + + @Test + public void testSpdxV3FileCollector() throws InvalidSPDXAnalysisException + { + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + Collection files = collector.getFiles(); + assertEquals( 0, files.size() ); + } + + @Test + public void testCollectFilesInDirectory() throws InvalidSPDXAnalysisException, SpdxCollectionException + { + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + SpdxFile[] spdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( 0, spdxFiles.length ); + + collector.collectFiles( this.fileSets, this.directory.getAbsolutePath(), this.defaultFileInformation, + new HashMap<>(), spdxPackage, RelationshipType.GENERATES, spdxDoc, sha1Algorithm ); + spdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( filePaths.length, spdxFiles.length ); + Arrays.sort( spdxFiles, elementComparer ); + @SuppressWarnings( "unchecked" ) + List allRelationships = (List)SpdxModelFactory + .getSpdxObjects( spdxDoc.getModelStore(), + spdxDoc.getCopyManager(), + SpdxConstantsV3.CORE_RELATIONSHIP, + null, spdxDoc.getIdPrefix() ).collect( Collectors.toList() ); + for ( int i = 0; i < spdxFiles.length; i++ ) + { + boolean foundGenerates = false; + for ( Relationship relationship : allRelationships ) + { + if ( relationship.getFrom().equals( spdxFiles[i] ) && + RelationshipType.GENERATES.equals( relationship.getRelationshipType() ) ) + { + Element[] tos = relationship.getTos().toArray( new Element[relationship.getTos().size()] ); + assertEquals( 1, tos.length ); + assertEquals( spdxPackage, tos[0] ); + foundGenerates = true; + break; + } + } + assertTrue( foundGenerates ); + } + } + + @Test + public void testCollectFileInDirectoryPattern() throws SpdxCollectionException, InvalidSPDXAnalysisException + { + FileSet skipBin = new FileSet(); + skipBin.setDirectory( this.fileSets.get(0).getDirectory() ); + skipBin.addExclude( "**/*.bin" ); + skipBin.setOutputDirectory( this.fileSets.get(0).getOutputDirectory() ); + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( 0, SpdxFiles.length ); + + collector.collectFiles( Arrays.asList( skipBin ), this.directory.getAbsolutePath(), this.defaultFileInformation, + new HashMap<>(), spdxPackage, RelationshipType.GENERATES, spdxDoc, sha1Algorithm ); + SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( filePaths.length - 2, SpdxFiles.length ); + Arrays.sort( SpdxFiles, elementComparer ); + int SpdxFilesIndex = 0; + for ( String spdxFileName : SpdxFileNames ) + { + if ( spdxFileName.endsWith( ".bin" ) ) + { + continue; + } + assertEquals( spdxFileName, SpdxFiles[SpdxFilesIndex++].getName().get() ); + } + } + + @Test + public void testGetExtension() throws InvalidSPDXAnalysisException + { + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + File noExtension = new File( "noextension" ); + String result = collector.getExtension( noExtension ); + assertTrue( result.isEmpty() ); + String ext = "abcd"; + File abcd = new File( "fileName" + "." + ext ); + result = collector.getExtension( abcd ); + assertEquals( ext, result ); + File startsWithDot = new File( ".configfile" ); + result = collector.getExtension( startsWithDot ); + assertTrue( result.isEmpty() ); + File multipleDots = new File( "file.with.more.dots." + ext ); + result = collector.getExtension( multipleDots ); + assertEquals( ext, result ); + } + + @Test + public void testGetFiles() throws SpdxCollectionException, InvalidSPDXAnalysisException + { + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( 0, SpdxFiles.length ); + + collector.collectFiles( this.fileSets, this.directory.getAbsolutePath(), this.defaultFileInformation, + new HashMap<>(), spdxPackage, RelationshipType.GENERATES, spdxDoc, sha1Algorithm ); + SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( filePaths.length, SpdxFiles.length ); + Arrays.sort( SpdxFiles, elementComparer ); + @SuppressWarnings( "unchecked" ) + List allRelationships = (List)SpdxModelFactory + .getSpdxObjects( spdxDoc.getModelStore(), + spdxDoc.getCopyManager(), + SpdxConstantsV3.CORE_RELATIONSHIP, + null, spdxDoc.getIdPrefix() ).collect( Collectors.toList() ); + for ( int i = 0; i < SpdxFiles.length; i++ ) + { + assertEquals( SpdxFileNames[i], SpdxFiles[i].getName().get() ); + assertTrue( SpdxFiles[i].getComment().get().startsWith( DEFAULT_COMMENT ) ); + assertEquals( DEFAULT_CONTRIBUTORS.length, SpdxFiles[i].getOriginatedBys().size() ); + List contributors = new ArrayList<>(); + SpdxFiles[i].getOriginatedBys().spliterator().forEachRemaining( agent -> { + try + { + contributors.add( agent.getName().get() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + fail( "SPDX Exception getting agent name "); + } + } ); + assertArrayEquals( DEFAULT_CONTRIBUTORS, TestUtils.toSortedArray( contributors ) ); + assertEquals( DEFAULT_COPYRIGHT, SpdxFiles[i].getCopyrightText().get() ); + AnyLicenseInfo declaredLicense = null; + AnyLicenseInfo concludedLicense = null; + for ( Relationship relationship : allRelationships ) + { + if ( SpdxFiles[i].equals( relationship.getFrom() )) + { + if ( RelationshipType.HAS_CONCLUDED_LICENSE.equals( relationship.getRelationshipType() ) ) + { + assertEquals( 1, relationship.getTos().size() ); + concludedLicense = (AnyLicenseInfo) relationship.getTos().iterator().next(); + } else if ( RelationshipType.HAS_DECLARED_LICENSE.equals( relationship.getRelationshipType() ) ) + { + assertEquals( 1, relationship.getTos().size() ); + declaredLicense = (AnyLicenseInfo) relationship.getTos().iterator().next(); + } + } + } + if ( SpdxFileNames[i].endsWith( FILE_NAME_WITH_ID ) ) + { + assertEquals(LicenseInfoFactory.parseSPDXLicenseString( FILE_WITH_IDS_DECLARED_LICENSE ), declaredLicense ); + assertEquals( LicenseInfoFactory.parseSPDXLicenseString( FILE_WITH_IDS__CONCLUDED_LICENSE ), concludedLicense ); + } + else + { + assertEquals( DEFAULT_DECLARED_LICENSE, declaredLicense.toString() ); + assertTrue( SpdxFiles[i].getComment().get().endsWith( DEFAULT_LICENSE_COMMENT ) ); + assertEquals( DEFAULT_CONCLUDED_LICENSE, concludedLicense.toString() ); + } + assertEquals( 1, SpdxFiles[i].getAttributionTexts().size() ); + assertEquals( DEFAULT_NOTICE, SpdxFiles[i].getAttributionTexts().iterator().next() ); + } + } + + @Test + public void testGetSnippets() throws SpdxCollectionException, InvalidSPDXAnalysisException + { + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + List snippets = collector.getSnippets(); + assertEquals( 0, snippets.size() ); + + collector.collectFiles( this.fileSets, this.directory.getAbsolutePath(), this.defaultFileInformation, + new HashMap<>(), spdxPackage, RelationshipType.GENERATES, spdxDoc, sha1Algorithm ); + snippets = collector.getSnippets(); + assertEquals( filePaths.length, snippets.size() ); + @SuppressWarnings( "unchecked" ) + List allRelationships = (List)SpdxModelFactory + .getSpdxObjects( spdxDoc.getModelStore(), + spdxDoc.getCopyManager(), + SpdxConstantsV3.CORE_RELATIONSHIP, + null, spdxDoc.getIdPrefix() ).collect( Collectors.toList() ); + for ( Snippet snippet : snippets ) + { + AnyLicenseInfo declaredLicense = null; + AnyLicenseInfo concludedLicense = null; + for ( Relationship relationship : allRelationships ) + { + if ( snippet.equals( relationship.getFrom() )) + { + if ( RelationshipType.HAS_CONCLUDED_LICENSE.equals( relationship.getRelationshipType() ) ) + { + assertEquals( 1, relationship.getTos().size() ); + concludedLicense = (AnyLicenseInfo) relationship.getTos().iterator().next(); + } else if ( RelationshipType.HAS_DECLARED_LICENSE.equals( relationship.getRelationshipType() ) ) + { + assertEquals( 1, relationship.getTos().size() ); + declaredLicense = (AnyLicenseInfo) relationship.getTos().iterator().next(); + } + } + } + assertEquals( SNIPPET_NAMES, snippet.getName().get() ); + assertTrue( snippet.getComment().get().startsWith( DEFAULT_SNIPPET_COMMENT ) ); + assertEquals( DEFAULT_SNIPPET_CONCLUDED_LICENSE, concludedLicense.toString() ); + assertEquals( DEFAULT_SNIPPET_COPYRIGHT, snippet.getCopyrightText().get() ); + assertEquals( DEFAULT_SNIPPET_DECLARED_LICENSE, declaredLicense.toString() ); + assertTrue( snippet.getComment().get().endsWith( DEFAULT_SNIPPET_LICENSE_COMMENT ) ); + assertEquals( DEFAULT_SNIPPET_BYTE_RANGE, positiveIntegerRangeToString( snippet.getByteRange().get() ) ); + assertEquals( DEFAULT_SNIPPET_LINE_RANGE, positiveIntegerRangeToString( snippet.getLineRange().get() ) ); + } + } + + + public static String positiveIntegerRangeToString( PositiveIntegerRange range ) throws InvalidSPDXAnalysisException + { + return Integer.toString( range.getBeginIntegerRange() ) + ":" + Integer.toString( range.getEndIntegerRange() ); + } + + @Test + public void testCollectFilesWithPattern() throws SpdxCollectionException, InvalidSPDXAnalysisException + { + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + SpdxFile[] SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( 0, SpdxFiles.length ); + HashMap fileSpecificInfo = new HashMap<>(); + SpdxDefaultFileInformation file2Info = new SpdxDefaultFileInformation(); + String file2Comment = "File 2 comment"; + file2Info.setComment( file2Comment ); + String file2License = defaultFileInformation.getDeclaredLicense(); + file2Info.setConcludedLicense( file2License ); + String[] file2Contributors = new String[] {"Person: File 2 contributor"}; + file2Info.setContributors( file2Contributors ); + String file2Copyright = "File 2 copyright"; + file2Info.setCopyright( file2Copyright ); + String file2DeclaredLicense = defaultFileInformation.getConcludedLicense(); + file2Info.setDeclaredLicense( file2DeclaredLicense ); + String file2LicenseComment = "File 2 license comment"; + file2Info.setLicenseComment( file2LicenseComment ); + String file2Notice = "file 2 notice"; + file2Info.setNotice( file2Notice ); + fileSpecificInfo.put( + filePaths[1].substring( this.directory.getAbsolutePath().length() + 1 ).replace( '\\', '/' ), + file2Info ); + + SpdxDefaultFileInformation file3Info = new SpdxDefaultFileInformation(); + String file3Comment = "File 3 comment"; + file3Info.setComment( file3Comment ); + String file3License = defaultFileInformation.getDeclaredLicense(); + file3Info.setConcludedLicense( file3License ); + String[] file3Contributors = new String[] {"Person: File 3 contributor"}; + file3Info.setContributors( file3Contributors ); + String file3Copyright = "File 3 copyright"; + file3Info.setCopyright( file3Copyright ); + String file3DeclaredLicense = defaultFileInformation.getDeclaredLicense(); + file3Info.setDeclaredLicense( file3DeclaredLicense ); + String file3LicenseComment = "File 3 license comment"; + file3Info.setLicenseComment( file3LicenseComment ); + String file3Notice = "file 3 notice"; + file3Info.setNotice( file3Notice ); + fileSpecificInfo.put( SUB_DIRS[0], file3Info ); + + //TODO: Test directory patterns + collector.collectFiles( this.fileSets, this.directory.getAbsolutePath(), this.defaultFileInformation, + fileSpecificInfo, spdxPackage, RelationshipType.GENERATES, spdxDoc, sha1Algorithm ); + SpdxFiles = collector.getFiles().toArray( new SpdxFile[collector.getFiles().size()] ); + assertEquals( filePaths.length, SpdxFiles.length ); + Arrays.sort( SpdxFiles, elementComparer ); + String subDirAPrefix = "./" + directory.getName() + "/" + SUB_DIRS[0]; + + @SuppressWarnings( "unchecked" ) + List allRelationships = (List)SpdxModelFactory + .getSpdxObjects( spdxDoc.getModelStore(), + spdxDoc.getCopyManager(), + SpdxConstantsV3.CORE_RELATIONSHIP, + null, spdxDoc.getIdPrefix() ).collect( Collectors.toList() ); + for ( int i = 0; i < SpdxFiles.length; i++ ) + { + AnyLicenseInfo declaredLicense = null; + AnyLicenseInfo concludedLicense = null; + for ( Relationship relationship : allRelationships ) + { + if ( SpdxFiles[i].equals( relationship.getFrom() )) + { + if ( RelationshipType.HAS_CONCLUDED_LICENSE.equals( relationship.getRelationshipType() ) ) + { + assertEquals( 1, relationship.getTos().size() ); + concludedLicense = (AnyLicenseInfo) relationship.getTos().iterator().next(); + } else if ( RelationshipType.HAS_DECLARED_LICENSE.equals( relationship.getRelationshipType() ) ) + { + assertEquals( 1, relationship.getTos().size() ); + declaredLicense = (AnyLicenseInfo) relationship.getTos().iterator().next(); + } + } + } + List contributors = new ArrayList<>(); + SpdxFiles[i].getOriginatedBys().spliterator().forEachRemaining( agent -> { + try + { + contributors.add( agent.getName().get() ); + } + catch ( InvalidSPDXAnalysisException e ) + { + fail( "SPDX Exception getting agent name "); + } + } ); + assertEquals( SpdxFileNames[i], SpdxFiles[i].getName().get() ); + if ( SpdxFiles[i].getName().get().equals( SpdxFileNames[1] ) ) + { + assertTrue( SpdxFiles[i].getComment().get().startsWith( file2Comment ) ); + assertEquals( file2License.toString(), concludedLicense.toString() ); + assertEquals( file2Contributors.length, contributors.size() ); + + assertArrayEquals( file2Contributors, TestUtils.toSortedArray( contributors ) ); + assertEquals( file2Copyright, SpdxFiles[1].getCopyrightText().get() ); + assertEquals( file2DeclaredLicense.toString(), declaredLicense.toString() ); + assertTrue( SpdxFiles[i].getComment().get().endsWith( file2LicenseComment ) ); + assertEquals( 1, SpdxFiles[i].getAttributionTexts().size() ); + assertEquals( file2Notice, SpdxFiles[i].getAttributionTexts().iterator().next() ); + } + else if ( SpdxFiles[i].getName().get().startsWith( subDirAPrefix ) ) + { + assertTrue( SpdxFiles[i].getComment().get().startsWith( file3Comment ) ); + assertEquals( file3License.toString(), concludedLicense.toString() ); + assertEquals( file3Contributors.length, contributors.size() ); + assertArrayEquals( file3Contributors, TestUtils.toSortedArray( contributors ) ); + assertEquals( file3Copyright, SpdxFiles[i].getCopyrightText().get() ); + assertEquals( file3DeclaredLicense.toString(), declaredLicense.toString() ); + assertTrue( SpdxFiles[i].getComment().get().endsWith( file3LicenseComment ) ); + assertEquals( 1, SpdxFiles[i].getAttributionTexts().size() ); + assertEquals( file3Notice, SpdxFiles[i].getAttributionTexts().iterator().next() ); + } + else if ( !SpdxFiles[i].getName().get().endsWith( FILE_NAME_WITH_ID ) ) + { + assertTrue( SpdxFiles[i].getComment().get().startsWith( DEFAULT_COMMENT ) ); + assertEquals( DEFAULT_CONCLUDED_LICENSE, concludedLicense.toString() ); + assertEquals( DEFAULT_CONTRIBUTORS.length, contributors.size() ); + assertArrayEquals( DEFAULT_CONTRIBUTORS, TestUtils.toSortedArray( contributors ) ); + assertEquals( DEFAULT_COPYRIGHT, SpdxFiles[i].getCopyrightText().get() ); + assertEquals( DEFAULT_DECLARED_LICENSE, declaredLicense.toString() ); + assertTrue( SpdxFiles[i].getComment().get().endsWith( DEFAULT_LICENSE_COMMENT ) ); + assertEquals( 1, SpdxFiles[i].getAttributionTexts().size() ); + assertEquals( DEFAULT_NOTICE, SpdxFiles[i].getAttributionTexts().iterator().next() ); + } + } + } + + @Test + public void testConvertChecksumToString() + { + byte[] cksumBytes = new byte[] {00, 01, 02, 03, 04, 05, 06, 07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x1E}; + String expected = "000102030405060708090a0b0c0d1e"; + String result = SpdxV3FileCollector.convertChecksumToString( cksumBytes ); + assertEquals( expected, result ); + } + + @Test + public void testConvertFilePathToSpdxFileName() + { + String dosFilePath = "subdir\\subdir2\\file.c"; + String dosSpdxFile = "./subdir/subdir2/file.c"; + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + String result = collector.convertFilePathToSpdxFileName( dosFilePath ); + assertEquals( dosSpdxFile, result ); + + String unixFilePath = "unixFolder/subfolder/file.sh"; + String unixSpdxFile = "./unixFolder/subfolder/file.sh"; + result = collector.convertFilePathToSpdxFileName( unixFilePath ); + assertEquals( unixSpdxFile, result ); + } + + @Test + public void testGenerateChecksums() throws SpdxCollectionException, InvalidSPDXAnalysisException + { + SpdxV3FileCollector collector = new SpdxV3FileCollector( customIdMap ); + collector.collectFiles( this.fileSets, this.directory.getAbsolutePath(), this.defaultFileInformation, + new HashMap<>(), spdxPackage, RelationshipType.GENERATES, spdxDoc, sha1Algorithm ); + File spdxFile = new File( filePaths[0] ); + + Set checksumAlgorithmSet = new HashSet<>(); + checksumAlgorithmSet.add(ChecksumAlgorithm.SHA1.toString() ); + checksumAlgorithmSet.add( ChecksumAlgorithm.SHA256.toString() ); + + Set expectedChecksums = new HashSet<>(); + expectedChecksums.add( new Checksum( ChecksumAlgorithm.SHA1.toString(), "1834453c87b9188024c7b18d179eb64f95f29fcf" ) ); + expectedChecksums.add( new Checksum( ChecksumAlgorithm.SHA256.toString(), "1c94046c63f61f5dbe5c15cc6c4e34510132ab262aa266735b344d836ef8cb3c") ); + // Checksum does not override equals currently. Hence, need to compare manually + Set actualChecksums = SpdxV3FileCollector.generateChecksum( spdxFile, checksumAlgorithmSet ); + for ( Checksum expectedChecksum : expectedChecksums ) + { + boolean found = false; + for ( Checksum actualChecksum : actualChecksums ) + { + if ( expectedChecksum.getAlgorithm().equals( actualChecksum.getAlgorithm() ) ) + { + if ( expectedChecksum.getValue().equals( actualChecksum.getValue() ) ) + { + found = true; + } + else + { + fail("Expected checksum : " + expectedChecksum + "does not match actual checksum : " + actualChecksum); + } + } + } + if ( !found ) + { + fail("Expected checksum : " + expectedChecksum + "not found in actual checksums : " + actualChecksums); + } + } + } +} diff --git a/src/test/java/org/spdx/maven/utils/TestSpdxV3LicenseManager.java b/src/test/java/org/spdx/maven/utils/TestSpdxV3LicenseManager.java new file mode 100644 index 0000000..8e45eda --- /dev/null +++ b/src/test/java/org/spdx/maven/utils/TestSpdxV3LicenseManager.java @@ -0,0 +1,319 @@ +/* + * Copyright 2014 Source Auditor Inc. + * + * Licensed 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 + * + * http://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.spdx.maven.utils; + +import static org.junit.Assert.*; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.spdx.core.DefaultModelStore; +import org.spdx.core.InvalidSPDXAnalysisException; +import org.spdx.library.LicenseInfoFactory; +import org.spdx.library.ModelCopyManager; +import org.spdx.library.SpdxModelFactory; +import org.spdx.library.model.v3_0_1.SpdxConstantsV3; +import org.spdx.library.model.v3_0_1.core.SpdxDocument; +import org.spdx.library.model.v3_0_1.expandedlicensing.ConjunctiveLicenseSet; +import org.spdx.library.model.v3_0_1.expandedlicensing.CustomLicense; +import org.spdx.library.model.v3_0_1.expandedlicensing.ListedLicense; +import org.spdx.library.model.v3_0_1.simplelicensing.AnyLicenseInfo; +import org.spdx.maven.NonStandardLicense; +import org.spdx.storage.simple.InMemSpdxStore; +import org.apache.maven.model.License; + +/** + * Unit tests for SpdxV3LicenseManager class + * + * @author Gary O'Neall + */ +public class TestSpdxV3LicenseManager +{ + private static final String TEST_SPDX_DOCUMENT_URL = "http://www.spdx.org/documents/test"; + static final String APACHE_CROSS_REF_URL2 = "http://www.apache.org/licenses/LICENSE-2.0"; + static final String APACHE_CROSS_REF_URL3 = "http://opensource.org/licenses/Apache-2.0"; + static final String APACHE_LICENSE_ID = "Apache-2.0"; + static final String APACHE_LICENSE_NAME = "Apache License 2.0"; + static final String APSL_CROSS_REF_URL = "http://www.opensource.apple.com/source/IOSerialFamily/IOSerialFamily-7/APPLE_LICENSE"; + static final String APSL_LICENSE_ID = "APSL-1.1"; + + SpdxDocument spdxDoc = null; + + + /** + * @throws java.lang.Exception + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception + { + SpdxModelFactory.init(); + } + + /** + * @throws java.lang.Exception + */ + @AfterClass + public static void tearDownAfterClass() throws Exception + { + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception + { + DefaultModelStore.initialize(new InMemSpdxStore(), "http://default/namespace", new ModelCopyManager()); + spdxDoc = new SpdxDocument( new InMemSpdxStore(), TEST_SPDX_DOCUMENT_URL + "#DOCUMENT", new ModelCopyManager(), + true, TEST_SPDX_DOCUMENT_URL ); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception + { + spdxDoc = null; + } + + /** + * Test method for {@link org.spdx.maven.utils.SpdxV3LicenseManager#LicenseManager(org.spdx.rdfparser.SpdxDocument,boolean)}. + * + * @throws LicenseMapperException + */ + @Test + public void testLicenseManager() throws LicenseMapperException + { + @SuppressWarnings( "unused" ) + SpdxV3LicenseManager licenseManager = new SpdxV3LicenseManager( spdxDoc ); + } + + /** + * Test method for {@link org.spdx.maven.utils.SpdxV3LicenseManager#addExtractedLicense(org.spdx.maven.NonStandardLicense)}. + * + * @throws MalformedURLException + * @throws LicenseManagerException + * @throws InvalidSPDXAnalysisException + * @throws LicenseMapperException + */ + @SuppressWarnings( "unchecked" ) + @Test + public void testAddNonStandardLicense() throws MalformedURLException, LicenseManagerException, InvalidSPDXAnalysisException, LicenseMapperException + { + SpdxV3LicenseManager licenseManager = new SpdxV3LicenseManager( spdxDoc ); + NonStandardLicense lic = new NonStandardLicense(); + final String COMMENT = "comment"; + final String[] CROSS_REF_STR = new String[] {"http://www.licenseRef1", "http://www.licenseref2"}; + final URL[] CROSS_REF = new URL[] {new URL( CROSS_REF_STR[0] ), new URL( CROSS_REF_STR[1] )}; + final String EXTRACTED_TEXT = "extracted text"; + final String LICENSE_ID = "LicenseRef-licenseId"; + final String LICENSE_NAME = "licenseName"; + lic.setComment( COMMENT ); + lic.setCrossReference( CROSS_REF ); + lic.setExtractedText( EXTRACTED_TEXT ); + lic.setLicenseId( LICENSE_ID ); + lic.setName( LICENSE_NAME ); + + licenseManager.addExtractedLicense( lic ); + List result = (List) SpdxModelFactory.getSpdxObjects( spdxDoc.getModelStore(), + spdxDoc.getCopyManager(), + SpdxConstantsV3.EXPANDED_LICENSING_CUSTOM_LICENSE, + null, TEST_SPDX_DOCUMENT_URL ) + .collect( Collectors.toList() ); + assertEquals( 1, result.size() ); + assertEquals( COMMENT, result.get( 0 ).getComment().get() ); + assertArrayEquals( CROSS_REF_STR, TestUtils.toSortedArray( result.get( 0 ).getSeeAlsos() ) ); + assertEquals( EXTRACTED_TEXT, result.get( 0 ).getLicenseText() ); + assertEquals( spdxDoc.getIdPrefix() + LICENSE_ID, result.get( 0 ).getObjectUri() ); + assertEquals( LICENSE_NAME, result.get( 0 ).getName().get() ); + + NonStandardLicense lic2 = new NonStandardLicense(); + final String LICENSE_ID2 = "LicenseRef-licenseId2"; + final String EXTRACTED_TEXT2 = "Second extracted text"; + lic2.setLicenseId( LICENSE_ID2 ); + lic2.setExtractedText( EXTRACTED_TEXT2 ); + + licenseManager.addExtractedLicense( lic2 ); + + result = (List) SpdxModelFactory.getSpdxObjects( spdxDoc.getModelStore(), + spdxDoc.getCopyManager(), + SpdxConstantsV3.EXPANDED_LICENSING_CUSTOM_LICENSE, + null, TEST_SPDX_DOCUMENT_URL ) + .collect( Collectors.toList() );assertEquals( 2, result.size() ); + CustomLicense licResult = result.get( 1 ); + if ( !licResult.getObjectUri().endsWith( LICENSE_ID2 ) ) + { + licResult = result.get( 0 ); + } + assertEquals( EXTRACTED_TEXT2, licResult.getLicenseText() ); + assertEquals( spdxDoc.getIdPrefix() + LICENSE_ID2, licResult.getObjectUri() ); + + } + + /** + * Test method for {@link org.spdx.maven.utils.SpdxV3LicenseManager#mavenLicenseListToSpdxLicense(java.util.List)}. + * + * @throws LicenseManagerException + * @throws LicenseMapperException + * @throws InvalidSPDXAnalysisException + */ + @Test + public void testMavenLicenseListToSpdxLicense() throws LicenseManagerException, LicenseMapperException, InvalidSPDXAnalysisException + { + final String LICENSE1_NAME = "Apachelicense1"; + final String LICENSE2_NAME = "APSLlicense2"; + + License apache = new License(); + apache.setName( LICENSE1_NAME ); + apache.setUrl( APACHE_CROSS_REF_URL2 ); + License apsl = new License(); + apsl.setName( LICENSE2_NAME ); + apsl.setUrl( APSL_CROSS_REF_URL ); + + ArrayList licenseList = new ArrayList<>(); + licenseList.add( apache ); + licenseList.add( apsl ); + + SpdxV3LicenseManager licenseManager = new SpdxV3LicenseManager( spdxDoc ); + + AnyLicenseInfo result = licenseManager.mavenLicenseListToSpdxLicense( licenseList ); + assertTrue( result instanceof ConjunctiveLicenseSet ); + Collection members = ( (ConjunctiveLicenseSet) result ).getMembers(); + AnyLicenseInfo[] resultLicenses = members.toArray( new AnyLicenseInfo[members.size()] ); + assertEquals( 2, resultLicenses.length ); + assertTrue( resultLicenses[0] instanceof ListedLicense ); + if ( !( (ListedLicense) resultLicenses[0] ).getObjectUri().endsWith( + APACHE_LICENSE_ID ) && !( (ListedLicense) resultLicenses[0] ).getObjectUri().endsWith( + APSL_LICENSE_ID ) ) + { + fail( "Unrecognized first license " + ( (ListedLicense) resultLicenses[0] ).getObjectUri() ); + } + assertTrue( resultLicenses[1] instanceof ListedLicense ); + if ( !( (ListedLicense) resultLicenses[1] ).getObjectUri().endsWith( + APACHE_LICENSE_ID ) && !( (ListedLicense) resultLicenses[1] ).getObjectUri().endsWith( + APSL_LICENSE_ID ) ) + { + fail( "Unrecognized second license " + ( (ListedLicense) resultLicenses[1] ).getObjectUri() ); + } + } + + /** + * Test method for {@link org.spdx.maven.utils.SpdxV3LicenseManager#mavenLicenseToSpdxLicense(org.apache.maven.model.License)}. + * + * @throws LicenseManagerException + * @throws MalformedURLException + * @throws LicenseMapperException + * @throws InvalidSPDXAnalysisException + */ + @Test + public void testMavenLicenseToSpdxLicense() throws LicenseManagerException, MalformedURLException, LicenseMapperException, InvalidSPDXAnalysisException + { + // unmapped license - can not test without valid log + // standard license + final String LICENSE1_NAME = "Apachelicense1"; + License apache = new License(); + apache.setName( LICENSE1_NAME ); + apache.setUrl( APACHE_CROSS_REF_URL2 ); + SpdxV3LicenseManager licenseManager = new SpdxV3LicenseManager( spdxDoc ); + + AnyLicenseInfo result = licenseManager.mavenLicenseToSpdxLicense( apache ); + assertTrue( result instanceof ListedLicense ); + assertEquals( SpdxConstantsV3.SPDX_LISTED_LICENSE_NAMESPACE + APACHE_LICENSE_ID, ( (ListedLicense) result ).getObjectUri() ); + + // nonstandard license + NonStandardLicense lic = new NonStandardLicense(); + final String COMMENT = "comment"; + final String[] CROSS_REF_STR = new String[] {"http://www.licenseRef1"}; + final URL[] CROSS_REF = new URL[] {new URL( CROSS_REF_STR[0] )}; + final String EXTRACTED_TEXT = "extracted text"; + final String LICENSE_ID = "LicenseRef-licenseId"; + final String LICENSE_NAME = "licenseName"; + lic.setComment( COMMENT ); + lic.setCrossReference( CROSS_REF ); + lic.setExtractedText( EXTRACTED_TEXT ); + lic.setLicenseId( LICENSE_ID ); + lic.setName( LICENSE_NAME ); + licenseManager.addExtractedLicense( lic ); + License nonStd = new License(); + nonStd.setComments( COMMENT ); + nonStd.setName( LICENSE_NAME ); + nonStd.setUrl( CROSS_REF_STR[0] ); + result = licenseManager.mavenLicenseToSpdxLicense( nonStd ); + + assertTrue( result instanceof CustomLicense ); + CustomLicense nonStdResult = (CustomLicense) result; + assertEquals( COMMENT, nonStdResult.getComment().get() ); + assertArrayEquals( CROSS_REF_STR, TestUtils.toSortedArray( nonStdResult.getSeeAlsos() ) ); + assertEquals( EXTRACTED_TEXT, nonStdResult.getLicenseText() ); + assertEquals( spdxDoc.getIdPrefix() + LICENSE_ID, nonStdResult.getObjectUri() ); + assertEquals( LICENSE_NAME, nonStdResult.getName().get() ); + } + + /** + * Test method for {@link org.spdx.maven.utils.SpdxV3LicenseManager#spdxLicenseToMavenLicense(org.spdx.rdfparser.AnyLicenseInfo)}. + * + * @throws LicenseManagerException + * @throws LicenseMapperException + * @throws InvalidSPDXAnalysisException + */ + @Test + public void testSpdxLicenseToMavenLicense() throws LicenseManagerException, LicenseMapperException, InvalidSPDXAnalysisException + { + SpdxV3LicenseManager licenseManager = new SpdxV3LicenseManager( spdxDoc ); + // standard license + AnyLicenseInfo licenseInfo = LicenseInfoFactory.parseSPDXLicenseString( APACHE_LICENSE_ID ); + License result = licenseManager.spdxLicenseToMavenLicense( licenseInfo ); + assertEquals( result.getName(), ( (ListedLicense) licenseInfo ).getName().get() ); + String resultUrl = result.getUrl().replace( "https", "http" ); + assertTrue( APACHE_CROSS_REF_URL2.equals( resultUrl ) || APACHE_CROSS_REF_URL3.equals( resultUrl ) ); + + // non standard license + final String LICENSE_ID = "LicenseRef-nonStd1"; + final String LICENSE_TEXT = "License Text"; + final ArrayList CROSS_REF_URLS = new ArrayList<>(); + CROSS_REF_URLS.add( "http://nonStd.url" ); + final String LICENSE_NAME = "License Name"; + final String LICENSE_COMMENT = "License Comment"; + CustomLicense nonStd = new CustomLicense( spdxDoc.getModelStore(), spdxDoc.getIdPrefix() + LICENSE_ID, + spdxDoc.getCopyManager(), true, spdxDoc.getIdPrefix() ); + nonStd.setLicenseText( LICENSE_TEXT ); + nonStd.getSeeAlsos().addAll( CROSS_REF_URLS ); + nonStd.setName( LICENSE_NAME ); + nonStd.setComment( LICENSE_COMMENT ); + result = licenseManager.spdxLicenseToMavenLicense( nonStd ); + assertEquals( LICENSE_NAME, result.getName() ); + assertEquals( CROSS_REF_URLS.get( 0 ), result.getUrl() ); + + // non-standard without name + final String LICENSE_ID2 = "LicenseRef-second"; + final String LICENSE_TEXT2 = "second text"; + CustomLicense noName = new CustomLicense( spdxDoc.getModelStore(), spdxDoc.getIdPrefix() + LICENSE_ID2, + spdxDoc.getCopyManager(), true, spdxDoc.getIdPrefix() ); + noName.setLicenseText( LICENSE_TEXT2 ); + result = licenseManager.spdxLicenseToMavenLicense( noName ); + assertEquals( LICENSE_ID2, result.getName() ); + } +} diff --git a/src/test/resources/unit/spdx-maven-plugin-test/json-pom-dependencies-v3.xml b/src/test/resources/unit/spdx-maven-plugin-test/json-pom-dependencies-v3.xml new file mode 100644 index 0000000..478b50b --- /dev/null +++ b/src/test/resources/unit/spdx-maven-plugin-test/json-pom-dependencies-v3.xml @@ -0,0 +1,107 @@ + + 4.0.0 + + org.spdx + spdx-maven-plugin-test + 1.0-SNAPSHOT + jar + Test SPDX Plugin + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + http://spdx.org/tools + + Linux Foundation + http://www.linuxfoundation.org + + + UTF-8 + + + + + junit + junit + 4.13.1 + test + + + + + src + Test + + + resources + false + resources + + **/* + + + + META-INF + false + . + + NOTICE + LICENSE + README.txt + changelog + + + + src + + **/*.java + + + + + + Test + + **/*.java + + + + false + TestFiles + + **/* + + + + + + + org.spdx + spdx-maven-plugin + 1.0-SNAPSHOT + + + build-spdx + prepare-package + + createSPDX + + + + + target/test-classes/unit/spdx-maven-plugin-test/test.json-ld.json + JSON-LD + true + http://spdx.org/documents/spdx%20toolsv2.0%20rc1 + Apache-2.0 + true + + + + + diff --git a/src/test/resources/unit/spdx-maven-plugin-test/pom-v3.xml b/src/test/resources/unit/spdx-maven-plugin-test/pom-v3.xml new file mode 100644 index 0000000..90ced3f --- /dev/null +++ b/src/test/resources/unit/spdx-maven-plugin-test/pom-v3.xml @@ -0,0 +1,243 @@ + + 4.0.0 + + org.spdx + spdx maven plugin test + 1.0-SNAPSHOT + jar + Test SPDX Plugin + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + http://spdx.org/tools + + Linux Foundation + http://www.linuxfoundation.org + + + UTF-8 + + + + + junit + junit + 4.13.1 + test + + + org.spdx + java-spdx-library + 1.1.0 + + + + + src + Test + + + resources + false + resources + + **/* + + + + META-INF + false + . + + NOTICE + LICENSE + README.txt + changelog + + + + src + + **/*.java + + + + + + Test + + **/*.java + + + + false + TestFiles + + **/* + + + + + + + org.spdx + spdx-maven-plugin + + + build-spdx + prepare-package + + createSPDX + + + + + + + + target/test-classes/unit/spdx-maven-plugin-test/test.json-ld.json + JSON-LD + http://spdx.org/spdxpackages/spdx toolsv2.0 rc1 + Document Comment + + + Annotation1 + REVIEW + 2010-01-29T18:30:22Z + Person:Test Person + + + Annotation2 + OTHER + 2012-11-29T18:30:22Z + Organization:Test Organization + + + + + PackageAnnotation + REVIEW + 2015-01-29T18:30:22Z + Person:Test Package Person + + + + + LicenseRef-testLicense + Test license text + Test License + + http://www.test.url/testLicense.html + + Test license comment + + + LicenseRef-testLicense2 + Second est license text + Second Test License + + http://www.test.url/testLicense2.html + http://www.test.url/testLicense2-alt.html + + Second Test license comment + + + Copyright (c) 2012, 2013, 2014 Source Auditor Inc. + Default file comment + + First contributor + Second contributor + + Apache-1.1 + Apache-2.0 + Default file license comment + Default file notice + BSD-2-Clause + BSD-3-Clause + Creator comment + + Person: Creator1 + Person: Creator2 + + License comments + Organization: Originating org. + Source info + Copyright Text for Package + + + + SECURITY + + + cpe22Type + + + example-locator-CPE22Type + + + extref comment1 + + + + + PACKAGE-MANAGER + + + maven-central + + + org.apache.tomcat:tomcat:9.0.0.M4 + + + extref comment2 + + + + + + src/main/java/CommonCode.java + Comment for CommonCode + + Contributor to CommonCode + + Common Code Copyright + License Comment for Common Code + Notice for Commmon Code + EPL-1.0 + ISC + + + SnippetName + Snippet Comment + BSD-2-Clause + 44:55 + 1231:3442 + Snippet License Comment + Snippet Copyright Text + BSD-2-Clause-FreeBSD + + + SnippetName2 + Snippet Comment2 + MITNFA + 444:554 + 31231:33442 + Snippet2 License Comment + Snippet2 Copyright Text + LicenseRef-testLicense + + + + + false + + + + +