All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.spdx.maven.utils.SpdxDocumentBuilder Maven / Gradle / Ivy

/*
 * 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.FileNotFoundException;
import java.io.FileOutputStream;
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.List;
import java.util.Map;
import java.util.Set;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.model.fileset.FileSet;

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.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.maven.ExternalReference;
import org.spdx.maven.NonStandardLicense;
import org.spdx.maven.OutputFormat;
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.
 *
 * @author Gary O'Neall
 */
public class SpdxDocumentBuilder
{
    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;

    /**
     * @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
     */
    public SpdxDocumentBuilder( MavenProject project, boolean generatePurls, File spdxFile, URI spdxDocumentNamespace,
                                boolean useStdLicenseSourceUrls, OutputFormat outputFormat ) throws SpdxBuilderException, LicenseMapperException
    {
        this.project = project;
        this.generatePurls = generatePurls;
        this.spdxFile = spdxFile;

        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() );
        }

        // create the SPDX document
        try
        {
            modelStore = outputFormat == OutputFormat.RDF_XML ? new RdfStore() :  new MultiFormatStore( new InMemSpdxStore(), Format.JSON_PRETTY );
            copyManager = new ModelCopyManager();
            spdxDoc = SpdxModelFactory.createSpdxDocument( modelStore, spdxDocumentNamespace.toString(), copyManager );
        }
        catch ( InvalidSPDXAnalysisException e )
        {
            throw new SpdxBuilderException( "Error creating SPDX document", e );
        }

        // process the licenses
        licenseManager = new LicenseManager( spdxDoc, useStdLicenseSourceUrls );
    }

    /**
     * Add non-standard licenses to the SPDX document.
     *
     * @param nonStandardLicenses
     * @throws SpdxBuilderException
     */
    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
     */
    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
            SpdxListedLicense dataLicense = LicenseInfoFactory.getListedLicenseById( SpdxConstants.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 );
            Relationship documentContainsRelationship = spdxDoc.createRelationship( projectPackage, RelationshipType.DESCRIBES, "" );
            spdxDoc.addRelationship( documentContainsRelationship );
        }
        catch ( InvalidSPDXAnalysisException | MojoExecutionException e )
        {
            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 ) );
        }
        return retval;
    }

    private SpdxPackage createSpdxPackage( SpdxProjectInformation projectInformation ) throws SpdxBuilderException
    {
        //TODO: Add annotations
        //TODO: Add relationships
        //TODO: Add comment
        String copyrightText = projectInformation.getCopyrightText();
        if ( copyrightText == null )
        {
            copyrightText = UNSPECIFIED;
        }
        String downloadUrl = null;
        
        if ( SpdxVerificationHelper.isValidUri( projectInformation.getDownloadUrl() ))
        {
            downloadUrl = projectInformation.getDownloadUrl();
        }
        else
        {
            LOG.warn( "Invalid download location in POM file: " + projectInformation.getDownloadUrl() );
        }
        if ( downloadUrl == null )
        {
            downloadUrl = UNSPECIFIED;
        }
        SpdxPackageVerificationCode nullPackageVerificationCode;
        try
        {
            nullPackageVerificationCode = spdxDoc.createPackageVerificationCode( NULL_SHA1, new ArrayList() );
        }
        catch ( InvalidSPDXAnalysisException e )
        {
            throw new SpdxBuilderException( "Error creating null package verification code", e );
        }
        SpdxPackage pkg;
        try
        {
            pkg = spdxDoc.createPackage( spdxDoc.getModelStore().getNextId( IdType.SpdxId, spdxDoc.getDocumentUri() ), 
                                                     projectInformation.getName(), projectInformation.getConcludedLicense(),
                                                     copyrightText, projectInformation.getDeclaredLicense() )
                            .setDownloadLocation( downloadUrl )
                            .setPackageVerificationCode( nullPackageVerificationCode )
                            .setPrimaryPurpose( projectInformation.getPrimaryPurpose() )
                            .setExternalRefs( SpdxExternalRefBuilder.getDefaultExternalRefs( spdxDoc, generatePurls, project ) )
                            .build();
        }
        catch ( InvalidSPDXAnalysisException e )
        {
            throw new SpdxBuilderException( "Error creating initial package", e );
        }
        // Annotations
        if ( projectInformation.getPackageAnnotations() != null && projectInformation.getPackageAnnotations().length > 0 )
        {
            try
            {
                pkg.setAnnotations( toSpdxAnnotations( projectInformation.getPackageAnnotations() ) );
            }
            catch ( InvalidSPDXAnalysisException | 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 )
            {
                pkg.setPackageFileName( projectInformation.getPackageArchiveFileName() );
            }
            // 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.setLicenseComments( projectInformation.getLicenseComment() );
            }
            // originator
            if ( projectInformation.getOriginator() != null )
            {
                pkg.setOriginator( projectInformation.getOriginator() );
            }
            // short description
            if ( projectInformation.getShortDescription() != null )
            {
                pkg.setSummary( projectInformation.getShortDescription() );
            }
            // supplier
            if ( projectInformation.getSupplier() != null )
            {
                pkg.setSupplier( projectInformation.getSupplier() );
            }
            // version info
            if ( projectInformation.getVersionInfo() != null )
            {
                pkg.setVersionInfo( projectInformation.getVersionInfo() );
            }
        }
        catch ( InvalidSPDXAnalysisException e )
        {
            throw new SpdxBuilderException( "Error adding package properties", e );
        }

        // sha1 checksum
        if ( projectInformation.getChecksums() != null )
        {
            try
            {
                pkg.getChecksums().addAll( projectInformation.getChecksums() );
            }
            catch ( InvalidSPDXAnalysisException e )
            {
                throw new SpdxBuilderException(
                        "Error adding package information to SPDX document - Invalid checksum provided", e );
            }
        }
        // external references
        ExternalReference[] externalRefs = projectInformation.getExternalRefs();
        if ( externalRefs != null && externalRefs.length > 0 )
        {
            for ( ExternalReference externalRef : externalRefs )
            {
                try
                {
                    pkg.getExternalRefs().add( externalRef.getExternalRef( spdxDoc ) );
                }
                catch ( MojoExecutionException | InvalidSPDXAnalysisException e )
                {
                    throw new SpdxBuilderException(
                            "Error adding package information to SPDX document - Invalid external refs provided", e );
                }
            }
        }
        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
     */
    public void collectSpdxFileInformation( List sources, String baseDir,
                                            SpdxDefaultFileInformation defaultFileInformation,
                                            Map pathSpecificInformation, 
                                            Set algorithms ) throws SpdxBuilderException
    {
        SpdxFileCollector fileCollector = new SpdxFileCollector();
        try
        {
            fileCollector.collectFiles( sources, baseDir, defaultFileInformation,
                    pathSpecificInformation, projectPackage, RelationshipType.GENERATES, spdxDoc, algorithms );
            projectPackage.getFiles().addAll( fileCollector.getFiles() );
            projectPackage.getLicenseInfoFromFiles().addAll( fileCollector.getLicenseInfoFromFiles() );
        }
        catch ( SpdxCollectionException|InvalidSPDXAnalysisException e )
        {
            throw new SpdxBuilderException( "Error collecting SPDX file information", e );
        }
        try
        {
            String spdxFileName = spdxFile.getPath().replace( "\\", "/" );
            projectPackage.setPackageVerificationCode( fileCollector.getVerificationCode( spdxFileName, spdxDoc ) );
        }
        catch ( NoSuchAlgorithmException e )
        {
            throw new SpdxBuilderException( "Unable to calculate verification code", e );
        }
        catch ( InvalidSPDXAnalysisException e )
        {
            throw new SpdxBuilderException( "Unable to update verification code", e );
        }
    }

    public LicenseManager getLicenseManager()
    {
        return this.licenseManager;
    }

    public SpdxPackage getProjectPackage()
    {
        return projectPackage;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy