Repository: CycloneDX/cyclonedx-maven-plugin (java)

Issue:
Default component type is set to "library" for non-library submodules in multimodule Maven project
### Description

In a multimodule Maven project, some submodules are configured with the projectType: application setting in the CycloneDX Maven plugin. However, when generating the aggregated SBOM (Software Bill of Materials), these submodules still have the component type set to "library" by default. This behavior results in an incorrect component type assignment for those submodules.

### Steps to reproduce:

Create a multimodule Maven project.
Configure the CycloneDX Maven plugin in the parent POM.
In one of the submodules, set the projectType configuration to application.
Generate the aggregated SBOM using the CycloneDX Maven plugin.

### Expected behavior:
The aggregated SBOM should reflect the correct component type for each submodule based on their individual projectType configurations. Submodules configured as application should have their component type set to application.

### Actual behavior:
Submodules configured with projectType: application still have their component type set to library in the aggregated SBOM.


I also have a solution to address this issue.



Pull request description:
Fix incorrect component type in aggregated SBOM (Multi-module project)
In a multimodule Maven project, some submodules configured with `projectType: application` were incorrectly set to "library" in the aggregated SBOM. This commit fixes the issue by ensuring the correct component type is reflected based on the `projectType` configuration.

Tests were also added to verify the correct component type assignment.

Resolves #521

Patch:
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index ac01cc9..83893cb 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -8,14 +8,14 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v4.1.7
+    - uses: actions/checkout@v4.2.1
     - name: Set up JDK 1.8
       uses: actions/setup-java@v4
       with:
         distribution: 'adopt'
         java-version: '8'
     - name: Build with Maven
-      run: mvn --no-transfer-progress package site
+      run: mvn --no-transfer-progress verify site
     - name: Deploy documentation
       if: ${{ github.ref == 'refs/heads/master' }}
       uses: JamesIves/github-pages-deploy-action@releases/v3
diff --git a/README.md b/README.md
index 6a0f002..64da153 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Default Values
         <artifactId>cyclonedx-maven-plugin</artifactId>
         <configuration>
             <projectType>library</projectType>
-            <schemaVersion>1.5</schemaVersion>
+            <schemaVersion>1.6</schemaVersion>
             <includeBomSerialNumber>true</includeBomSerialNumber>
             <includeCompileScope>true</includeCompileScope>
             <includeProvidedScope>true</includeProvidedScope>
@@ -63,7 +63,7 @@ Default Values
 </plugins>
 ```
 
-`<projectType>` default value is `library` but there are [more choices defined in the CycloneDX specification](https://cyclonedx.org/docs/1.5/json/#metadata_component_type).
+`<projectType>` default value is `library` but there are [more choices defined in the CycloneDX specification](https://cyclonedx.org/docs/1.6/json/#metadata_component_type).
 
 See also [External References](https://cyclonedx.github.io/cyclonedx-maven-plugin/external-references.html) documentation for details on this topic.
 
@@ -99,6 +99,7 @@ the CycloneDX version supported by the target system.
 
 | Version | Schema Version | Format(s) |
 |---------|----------------| --------- |
+| 2.9.x   | CycloneDX v1.6 | XML/JSON |
 | 2.8.x   | CycloneDX v1.5 | XML/JSON |
 | 2.6.x   | CycloneDX v1.4 | XML/JSON |
 | 2.5.x   | CycloneDX v1.3 | XML/JSON |
diff --git a/pom.xml b/pom.xml
index 8f51886..f914e6f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
     <groupId>org.cyclonedx</groupId>
     <artifactId>cyclonedx-maven-plugin</artifactId>
     <packaging>maven-plugin</packaging>
-    <version>2.8.1-SNAPSHOT</version>
+    <version>2.9.1-SNAPSHOT</version>
 
     <name>CycloneDX Maven plugin</name>
     <description>The CycloneDX Maven plugin generates CycloneDX Software Bill of Materials (SBOM) containing the aggregate of all direct and transitive dependencies of a project.</description>
@@ -68,10 +68,10 @@
         <maven.compiler.target>8</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-        <project.build.outputTimestamp>2024-03-23T12:35:25Z</project.build.outputTimestamp>
+        <project.build.outputTimestamp>2024-10-08T04:31:15Z</project.build.outputTimestamp>
         <junit.version>4.13.2</junit.version>
-        <junit5.version>5.10.2</junit5.version>
-        <plugin-tools.version>3.13.0</plugin-tools.version>
+        <junit5.version>5.11.2</junit5.version>
+        <plugin-tools.version>3.15.0</plugin-tools.version>
     </properties>
 
     <scm>
@@ -106,7 +106,7 @@
         <dependency>
             <groupId>org.cyclonedx</groupId>
             <artifactId>cyclonedx-core-java</artifactId>
-            <version>8.0.3</version>
+            <version>9.0.5</version>
         </dependency>
         <dependency>
             <groupId>javax.inject</groupId>
@@ -119,12 +119,12 @@
         <dependency>
             <groupId>commons-codec</groupId>
             <artifactId>commons-codec</artifactId>
-            <version>1.17.0</version>
+            <version>1.17.1</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
-            <version>3.14.0</version>
+            <version>3.17.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
@@ -207,7 +207,7 @@
         <dependency>
             <groupId>net.javacrumbs.json-unit</groupId>
             <artifactId>json-unit-assertj</artifactId>
-            <version>2.38.0</version>
+            <version>2.40.1</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
@@ -290,7 +290,7 @@
             <plugin>
                 <groupId>org.cyclonedx</groupId>
                 <artifactId>cyclonedx-maven-plugin</artifactId>
-                <version>2.7.9</version>
+                <version>2.8.0</version>
                 <executions>
                     <execution>
                         <id>cyclonedx-makeBom</id>
@@ -327,7 +327,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-jar-plugin</artifactId>
-                    <version>3.4.1</version>
+                    <version>3.4.2</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -337,7 +337,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-release-plugin</artifactId>
-                    <version>3.0.1</version>
+                    <version>3.1.1</version>
                     <configuration>
                         <releaseProfiles>release</releaseProfiles>
                     </configuration>
@@ -345,7 +345,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-javadoc-plugin</artifactId>
-                    <version>3.7.0</version>
+                    <version>3.10.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -355,7 +355,16 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-project-info-reports-plugin</artifactId>
-                    <version>3.5.0</version>
+                    <version>3.6.2</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-resources-plugin</artifactId>
+                    <version>3.3.1</version>
+                    <configuration>
+                        <!-- https://maven.apache.org/plugins/maven-resources-plugin/examples/filtering-properties-files.html -->
+                        <propertiesEncoding>UTF-8</propertiesEncoding>
+                    </configuration>
                 </plugin>
             </plugins>
         </pluginManagement>
@@ -405,7 +414,7 @@
                     <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-gpg-plugin</artifactId>
-                        <version>3.2.4</version>
+                        <version>3.2.7</version>
                         <executions>
                             <execution>
                                 <id>sign-artifacts</id>
diff --git a/src/it/makeAggregateBom/api/pom.xml b/src/it/makeAggregateBom/api/pom.xml
index 5a08ec7..d954c26 100644
--- a/src/it/makeAggregateBom/api/pom.xml
+++ b/src/it/makeAggregateBom/api/pom.xml
@@ -32,7 +32,7 @@
         <dependency>
             <groupId>org.cyclonedx</groupId>
             <artifactId>cyclonedx-core-java</artifactId>
-            <version>7.2.1</version>
+            <version>9.0.5</version>
         </dependency>
     </dependencies>
 </project>
diff --git a/src/it/makeBom/pom.xml b/src/it/makeBom/pom.xml
index 9408290..73b22c5 100644
--- a/src/it/makeBom/pom.xml
+++ b/src/it/makeBom/pom.xml
@@ -72,7 +72,7 @@
         <dependency>
             <groupId>org.cyclonedx</groupId>
             <artifactId>cyclonedx-core-java</artifactId>
-            <version>7.2.1</version>
+            <version>9.0.5</version>
         </dependency>
         <dependency>
             <groupId>com.ibm.websphere.appserver.features</groupId>
diff --git a/src/it/makeBom/verify.groovy b/src/it/makeBom/verify.groovy
index a0246ca..4d04cb4 100644
--- a/src/it/makeBom/verify.groovy
+++ b/src/it/makeBom/verify.groovy
@@ -4,11 +4,13 @@ File bomFileJson = new File(basedir, "target/bom.json")
 assert bomFileXml.exists()
 assert bomFileJson.exists()
 
-assert bomFileXml.text.contains('<reference type="website"><url>https://github.com/CycloneDX/cyclonedx-maven-plugin</url></reference>')
+assert bomFileXml.text.contains('<reference type="website">\n' +
+        '          <url>https://github.com/CycloneDX/cyclonedx-maven-plugin</url>\n' +
+        '        </reference>')
 
 assert !bomFileXml.text.contains('<property name="maven.optional.unused">')
 
-assert bomFileJson.text.contains('"specVersion" : "1.5"')
+assert bomFileJson.text.contains('"specVersion" : "1.6"')
 
 // Reproducible Builds
 assert !bomFileJson.text.contains('"timestamp"')
diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
index 386383e..dc05ed8 100644
--- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
+++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
@@ -29,8 +29,8 @@ import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.MavenProjectHelper;
 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
-import org.cyclonedx.BomGeneratorFactory;
-import org.cyclonedx.CycloneDxSchema;
+import org.cyclonedx.Version;
+import org.cyclonedx.generators.BomGeneratorFactory;
 import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.generators.json.BomJsonGenerator;
 import org.cyclonedx.generators.xml.BomXmlGenerator;
@@ -67,7 +67,7 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
 
     /**
      * The component type associated to the SBOM metadata. See
-     * <a href="https://cyclonedx.org/docs/1.5/json/#metadata_component_type">CycloneDX reference</a> for supported
+     * <a href="https://cyclonedx.org/docs/1.6/json/#metadata_component_type">CycloneDX reference</a> for supported
      * values. 
      *
      * @since 2.0.0
@@ -80,9 +80,9 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
      *
      * @since 2.1.0
      */
-    @Parameter(property = "schemaVersion", defaultValue = "1.5", required = false)
+    @Parameter(property = "schemaVersion", defaultValue = "1.6", required = false)
     private String schemaVersion;
-    private CycloneDxSchema.Version effectiveSchemaVersion = null;
+    private Version effectiveSchemaVersion = null;
 
     /**
      * The CycloneDX output format that should be generated (<code>xml</code>, <code>json</code> or <code>all</code>).
@@ -227,7 +227,7 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
     private String outputTimestamp;
 
     /**
-     * <a href="https://cyclonedx.org/docs/1.5/json/#metadata_component_externalReferences_items_type">External references</a>
+     * <a href="https://cyclonedx.org/docs/1.6/json/#metadata_component_externalReferences_items_type">External references</a>
      * to be added to the component the BOM describes <code>$.metadata.component.externalReferences[]</code>:
      * <pre>
      * &lt;externalReferences&gt;
@@ -262,7 +262,7 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
     protected static final String MESSAGE_CREATING_BOM = "CycloneDX: Creating BOM version %s with %d component(s)";
     static final String MESSAGE_CALCULATING_HASHES = "CycloneDX: Calculating Hashes";
     protected static final String MESSAGE_WRITING_BOM = "CycloneDX: Writing and validating BOM (%s): %s";
-    protected static final String MESSAGE_ATTACHING_BOM = "           attaching as %s-%s-cyclonedx.%s";
+    protected static final String MESSAGE_ATTACHING_BOM = "           attaching as %s-%s-%s.%s";
     protected static final String MESSAGE_VALIDATION_FAILURE = "The BOM does not conform to the CycloneDX BOM standard as defined by the XSD";
 
     /**
@@ -463,7 +463,7 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
         }
 
         if (!skipAttach) {
-            getLog().info(String.format(MESSAGE_ATTACHING_BOM, project.getArtifactId(), project.getVersion(), extension));
+            getLog().info(String.format(MESSAGE_ATTACHING_BOM, project.getArtifactId(), project.getVersion(), classifier, extension));
             mavenProjectHelper.attachArtifact(project, extension, classifier, bomFile);
         }
     }
@@ -477,20 +477,22 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
      * Resolves the CycloneDX schema the mojo has been requested to use.
      * @return the CycloneDX schema to use
      */
-    protected CycloneDxSchema.Version schemaVersion() {
+    protected Version schemaVersion() {
         if (effectiveSchemaVersion == null) {
             if ("1.0".equals(schemaVersion)) {
-                effectiveSchemaVersion = CycloneDxSchema.Version.VERSION_10;
+                effectiveSchemaVersion = Version.VERSION_10;
             } else if ("1.1".equals(schemaVersion)) {
-                effectiveSchemaVersion = CycloneDxSchema.Version.VERSION_11;
+                effectiveSchemaVersion = Version.VERSION_11;
             } else if ("1.2".equals(schemaVersion)) {
-                effectiveSchemaVersion = CycloneDxSchema.Version.VERSION_12;
+                effectiveSchemaVersion = Version.VERSION_12;
             } else if ("1.3".equals(schemaVersion)) {
-                effectiveSchemaVersion = CycloneDxSchema.Version.VERSION_13;
+                effectiveSchemaVersion = Version.VERSION_13;
             } else if ("1.4".equals(schemaVersion)) {
-                effectiveSchemaVersion = CycloneDxSchema.Version.VERSION_14;
+                effectiveSchemaVersion = Version.VERSION_14;
+            } else if ("1.5".equals(schemaVersion)) {
+                effectiveSchemaVersion = Version.VERSION_15;
             } else {
-                effectiveSchemaVersion = CycloneDxSchema.Version.VERSION_15;
+                effectiveSchemaVersion = Version.VERSION_16;
             }
         }
         return effectiveSchemaVersion;
diff --git a/src/main/java/org/cyclonedx/maven/DefaultModelConverter.java b/src/main/java/org/cyclonedx/maven/DefaultModelConverter.java
index 2d28c5f..d0b1da8 100644
--- a/src/main/java/org/cyclonedx/maven/DefaultModelConverter.java
+++ b/src/main/java/org/cyclonedx/maven/DefaultModelConverter.java
@@ -32,13 +32,15 @@ import org.apache.maven.project.ProjectBuilder;
 import org.apache.maven.project.ProjectBuildingException;
 import org.apache.maven.project.ProjectBuildingResult;
 import org.apache.maven.repository.RepositorySystem;
-import org.cyclonedx.CycloneDxSchema;
+import org.cyclonedx.Version;
 import org.cyclonedx.model.Component;
 import org.cyclonedx.model.ExternalReference;
+import org.cyclonedx.model.Hash;
 import org.cyclonedx.model.License;
 import org.cyclonedx.model.LicenseChoice;
 import org.cyclonedx.model.Metadata;
 import org.cyclonedx.model.Tool;
+import org.cyclonedx.model.metadata.ToolInformation;
 import org.cyclonedx.util.BomUtils;
 import org.cyclonedx.util.LicenseResolver;
 import org.eclipse.aether.artifact.ArtifactProperties;
@@ -53,15 +55,23 @@ import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
+import org.apache.maven.model.Plugin;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+import java.util.Optional;
 
 @Singleton
 @Named
 public class DefaultModelConverter implements ModelConverter {
     private final Logger logger = LoggerFactory.getLogger(DefaultModelConverter.class);
+    private static final String CYCLONEDX_GROUP_ID = "org.cyclonedx";
+    private static final String CYCLONEDX_ARTIFACT_ID = "cyclonedx-maven-plugin";
+    private static final String PROJECT_TYPE_NODE = "projectType";
 
     @Inject
     private MavenSession session;
@@ -153,28 +163,34 @@ public class DefaultModelConverter implements ModelConverter {
     }
 
     @Override
-    public Component convertMavenDependency(Artifact artifact, CycloneDxSchema.Version schemaVersion, boolean includeLicenseText) {
+    public Component convertMavenDependency(Artifact artifact, Version schemaVersion, boolean includeLicenseText) {
         final Component component = new Component();
         component.setGroup(artifact.getGroupId());
         component.setName(artifact.getArtifactId());
-        component.setVersion(artifact.getBaseVersion());
-        component.setType(Component.Type.LIBRARY);
+        component.setVersion(artifact.getBaseVersion()); 
+        component.setType(Component.Type.LIBRARY); 
+         
         try {
             logger.debug(BaseCycloneDxMojo.MESSAGE_CALCULATING_HASHES);
             component.setHashes(BomUtils.calculateHashes(artifact.getFile(), schemaVersion));
         } catch (IOException e) {
             logger.error("Error encountered calculating hashes", e);
         }
-        if (CycloneDxSchema.Version.VERSION_10 == schemaVersion) {
+        if (Version.VERSION_10 == schemaVersion) {
             component.setModified(isModified(artifact));
         }
         component.setPurl(generatePackageUrl(artifact));
-        if (CycloneDxSchema.Version.VERSION_10 != schemaVersion) {
+        if (Version.VERSION_10 != schemaVersion) {
             component.setBomRef(component.getPurl());
         }
         try {
             final MavenProject project = getEffectiveMavenProject(artifact);
+            
             if (project != null) {
+                String projectType = getProjectTypeFromPluginConfiguration(project);
+                if (projectType != null) {
+                    component.setType(resolveProjectType(projectType));
+                }
                 extractComponentMetadata(project, component, schemaVersion, includeLicenseText);
             }
         } catch (ProjectBuildingException e) {
@@ -188,6 +204,22 @@ public class DefaultModelConverter implements ModelConverter {
 
     }
 
+    public String getProjectTypeFromPluginConfiguration(MavenProject project) {
+        return Optional.ofNullable(project.getBuild())
+                .map(build -> build.getPlugins())
+                .flatMap(plugins -> plugins.stream()
+                        .filter(plugin -> CYCLONEDX_GROUP_ID.equals(plugin.getGroupId()) &&
+                                          CYCLONEDX_ARTIFACT_ID.equals(plugin.getArtifactId()))
+                        .findFirst()
+                )
+                .map(Plugin::getConfiguration)
+                .filter(Xpp3Dom.class::isInstance)
+                .map(Xpp3Dom.class::cast)
+                .map(configuration -> configuration.getChild(PROJECT_TYPE_NODE))
+                .map(Xpp3Dom::getValue)
+                .orElse(null);
+    }
+
     private static void setExternalReferences(Component component, ExternalReference[] externalReferences) {
         if (externalReferences == null || externalReferences.length == 0) {
             return;
@@ -207,7 +239,7 @@ public class DefaultModelConverter implements ModelConverter {
      * @param project the project to extract data from
      * @param component the component to add data to
      */
-    private void extractComponentMetadata(MavenProject project, Component component, CycloneDxSchema.Version schemaVersion, boolean includeLicenseText) {
+    private void extractComponentMetadata(MavenProject project, Component component, Version schemaVersion, boolean includeLicenseText) {
         if (component.getPublisher() == null) {
             // If we don't already have publisher information, retrieve it.
             if (project.getOrganization() != null) {
@@ -219,12 +251,12 @@ public class DefaultModelConverter implements ModelConverter {
             component.setDescription(project.getDescription());
         }
         if (component.getLicenseChoice() == null || component.getLicenseChoice().getLicenses() == null || component.getLicenseChoice().getLicenses().isEmpty()) {
-            // If we don't already have license information, retrieve it.
-            if (project.getLicenses() != null) {
+            // If we don't already have license information, retrieve it, as long as it is not empty.
+            if (project.getLicenses() != null && project.getLicenses().stream().anyMatch(l -> !isLicenseBlank(l))) {
                 component.setLicenseChoice(resolveMavenLicenses(project.getLicenses(), schemaVersion, includeLicenseText));
             }
         }
-        if (CycloneDxSchema.Version.VERSION_10 != schemaVersion) {
+        if (Version.VERSION_10 != schemaVersion) {
             addExternalReference(ExternalReference.Type.WEBSITE, project.getUrl(), component);
             if (project.getCiManagement() != null) {
                 addExternalReference(ExternalReference.Type.BUILD_SYSTEM, project.getCiManagement().getUrl(), component);
@@ -269,7 +301,7 @@ public class DefaultModelConverter implements ModelConverter {
     }
 
     private void addExternalReference(final ExternalReference.Type referenceType, final String url, final Component component) {
-        if (isURLBlank(url) || doesComponentHaveExternalReference(component, referenceType)) {
+        if (isBlank(url) || doesComponentHaveExternalReference(component, referenceType)) {
             return;
         }
         try {
@@ -294,7 +326,7 @@ public class DefaultModelConverter implements ModelConverter {
         return false;
     }
 
-    private LicenseChoice resolveMavenLicenses(final List<org.apache.maven.model.License> projectLicenses, final CycloneDxSchema.Version schemaVersion, boolean includeLicenseText) {
+    private LicenseChoice resolveMavenLicenses(final List<org.apache.maven.model.License> projectLicenses, final Version schemaVersion, boolean includeLicenseText) {
         final LicenseChoice licenseChoice = new LicenseChoice();
         for (org.apache.maven.model.License artifactLicense : projectLicenses) {
             boolean resolved = false;
@@ -325,14 +357,14 @@ public class DefaultModelConverter implements ModelConverter {
         return licenseChoice;
     }
 
-    private boolean resolveLicenseInfo(final LicenseChoice licenseChoice, final LicenseChoice licenseChoiceToResolve, final CycloneDxSchema.Version schemaVersion)
+    private boolean resolveLicenseInfo(final LicenseChoice licenseChoice, final LicenseChoice licenseChoiceToResolve, final Version schemaVersion)
     {
         if (licenseChoiceToResolve != null) {
             if (licenseChoiceToResolve.getLicenses() != null && !licenseChoiceToResolve.getLicenses().isEmpty()) {
                 licenseChoice.addLicense(licenseChoiceToResolve.getLicenses().get(0));
                 return true;
             }
-            else if (licenseChoiceToResolve.getExpression() != null && CycloneDxSchema.Version.VERSION_10 != schemaVersion) {
+            else if (licenseChoiceToResolve.getExpression() != null && Version.VERSION_10 != schemaVersion) {
                 licenseChoice.setExpression(licenseChoiceToResolve.getExpression());
                 return true;
             }
@@ -341,24 +373,45 @@ public class DefaultModelConverter implements ModelConverter {
     }
 
     @Override
-    public Metadata convertMavenProject(final MavenProject project, String projectType, CycloneDxSchema.Version schemaVersion, boolean includeLicenseText, ExternalReference[] externalReferences) {
-        final Tool tool = new Tool();
+    public Metadata convertMavenProject(final MavenProject project, String projectType, Version schemaVersion, boolean includeLicenseText, ExternalReference[] externalReferences) {
+        final Metadata metadata = new Metadata();
+
+        // prepare properties and hash values from the current mojo
         final Properties properties = readPluginProperties();
-        tool.setVendor(properties.getProperty("vendor"));
-        tool.setName(properties.getProperty("name"));
-        tool.setVersion(properties.getProperty("version"));
-        // Attempt to add hash values from the current mojo
+        List<Hash> hashes = null;
         final Artifact self = new DefaultArtifact(properties.getProperty("groupId"), properties.getProperty("artifactId"),
                 properties.getProperty("version"), Artifact.SCOPE_COMPILE, "jar", null, new DefaultArtifactHandler());
         final Artifact resolved = session.getLocalRepository().find(self);
         if (resolved != null) {
             try {
                 resolved.setFile(new File(resolved.getFile() + ".jar"));
-                tool.setHashes(BomUtils.calculateHashes(resolved.getFile(), schemaVersion));
+                hashes = BomUtils.calculateHashes(resolved.getFile(), schemaVersion);
             } catch (IOException e) {
                 logger.warn("Unable to calculate hashes of self", e);
             }
         }
+        if (schemaVersion.compareTo(Version.VERSION_15) < 0) {
+            // CycloneDX up to 1.4+ use metadata.tools.tool
+            final Tool tool = new Tool();
+            tool.setVendor(properties.getProperty("vendor"));
+            tool.setName(properties.getProperty("name"));
+            tool.setVersion(properties.getProperty("version"));
+            tool.setHashes(hashes);
+            metadata.addTool(tool);
+        } else {
+            // CycloneDX 1.5+: use metadata.tools.component
+            ToolInformation toolInfo = new ToolInformation();
+            Component toolComponent = new Component();
+            toolComponent.setType(Component.Type.LIBRARY);
+            toolComponent.setGroup(properties.getProperty("groupId"));
+            toolComponent.setName(properties.getProperty("artifactId"));
+            toolComponent.setVersion(properties.getProperty("version"));
+            toolComponent.setDescription(properties.getProperty("name"));
+            toolComponent.setAuthor(properties.getProperty("vendor"));
+            toolComponent.setHashes(hashes);
+            toolInfo.setComponents(Collections.singletonList(toolComponent));
+            metadata.setToolChoice(toolInfo);
+        }
 
         final Component component = new Component();
         component.setGroup(project.getGroupId());
@@ -369,10 +422,8 @@ public class DefaultModelConverter implements ModelConverter {
         component.setBomRef(component.getPurl());
         setExternalReferences(component, externalReferences);
         extractComponentMetadata(project, component, schemaVersion, includeLicenseText);
-
-        final Metadata metadata = new Metadata();
-        metadata.addTool(tool);
         metadata.setComponent(component);
+
         return metadata;
     }
 
@@ -400,7 +451,11 @@ public class DefaultModelConverter implements ModelConverter {
         return Component.Type.LIBRARY;
     }
 
-    private static boolean isURLBlank(String url) {
-        return url == null || url.isEmpty() || url.trim().length() == 0;
+    private static boolean isBlank(String s) {
+        return s == null || s.isEmpty() || s.trim().isEmpty();
+    }
+
+    private static boolean isLicenseBlank(org.apache.maven.model.License license) {
+        return isBlank(license.getName()) && isBlank(license.getUrl());
     }
 }
diff --git a/src/main/java/org/cyclonedx/maven/ModelConverter.java b/src/main/java/org/cyclonedx/maven/ModelConverter.java
index a1fd1c2..7aec1a0 100644
--- a/src/main/java/org/cyclonedx/maven/ModelConverter.java
+++ b/src/main/java/org/cyclonedx/maven/ModelConverter.java
@@ -20,7 +20,7 @@ package org.cyclonedx.maven;
 
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.project.MavenProject;
-import org.cyclonedx.CycloneDxSchema;
+import org.cyclonedx.Version;
 import org.cyclonedx.model.Component;
 import org.cyclonedx.model.ExternalReference;
 import org.cyclonedx.model.Metadata;
@@ -49,7 +49,7 @@ public interface ModelConverter {
      * @param includeLicenseText should license text be included in bom?
      * @return a CycloneDX component
      */
-    Component convertMavenDependency(Artifact artifact, CycloneDxSchema.Version schemaVersion, boolean includeLicenseText);
+    Component convertMavenDependency(Artifact artifact, Version schemaVersion, boolean includeLicenseText);
 
     /**
      * Converts a MavenProject into a CycloneDX Metadata object.
@@ -61,6 +61,6 @@ public interface ModelConverter {
      * @param externalReferences the external references
      * @return a CycloneDX Metadata object
      */
-    Metadata convertMavenProject(MavenProject project, String projectType, CycloneDxSchema.Version schemaVersion, boolean includeLicenseText, ExternalReference[] externalReferences);
+    Metadata convertMavenProject(MavenProject project, String projectType, Version schemaVersion, boolean includeLicenseText, ExternalReference[] externalReferences);
 
 }
diff --git a/src/site/markdown/external-references.md b/src/site/markdown/external-references.md
index a482c0b..c4d81fa 100644
--- a/src/site/markdown/external-references.md
+++ b/src/site/markdown/external-references.md
@@ -50,6 +50,6 @@ You can add more external references the component that the BOM describes by plu
 See valid [CycloneDX external reference types][external-reference-type].
 
 [maven-model]: https://maven.apache.org/ref/current/maven-model/maven.html
-[metadata-component]: https://cyclonedx.org/docs/1.5/json/#metadata_component
-[components]: https://cyclonedx.org/docs/1.5/json/#components
-[external-reference-type]: https://cyclonedx.org/docs/1.5/json/#metadata_component_externalReferences_items_type
+[metadata-component]: https://cyclonedx.org/docs/1.6/json/#metadata_component
+[components]: https://cyclonedx.org/docs/1.6/json/#components
+[external-reference-type]: https://cyclonedx.org/docs/1.6/json/#metadata_component_externalReferences_items_type
diff --git a/src/site/site.xml b/src/site/site.xml
index 0cd83a3..c094cc2 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -3,7 +3,7 @@
     <skin>
         <groupId>org.apache.maven.skins</groupId>
         <artifactId>maven-fluido-skin</artifactId>
-        <version>1.10.0</version>
+        <version>1.12.0</version>
     </skin>
     <custom>
         <fluidoSkin>


Test patch:
diff --git a/src/test/java/org/cyclonedx/maven/Issue314OptionalTest.java b/src/test/java/org/cyclonedx/maven/Issue314OptionalTest.java
index ae04756..ede58f9 100644
--- a/src/test/java/org/cyclonedx/maven/Issue314OptionalTest.java
+++ b/src/test/java/org/cyclonedx/maven/Issue314OptionalTest.java
@@ -16,7 +16,6 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
 
 import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
 import io.takari.maven.testing.executor.MavenVersions;
@@ -52,9 +51,8 @@ public class Issue314OptionalTest extends BaseMavenVerifier {
 
       final Document bom = readXML(new File(projDir, "dependency_A/target/bom.xml"));
 
-      final NodeList componentsList = bom.getElementsByTagName("components");
-      assertEquals("Expected a single components element", 1, componentsList.getLength());
-      final Element components = (Element)componentsList.item(0);
+      final Element components = getElement(getElement(bom, "bom"), "components");
+      assertNotNull("bom is missing components", components);
 
       final Element componentBNode = getComponentNode(components, ISSUE_314_DEPENDENCY_B);
       final Element componentBScope = getElement(componentBNode, "scope");
@@ -92,9 +90,8 @@ public class Issue314OptionalTest extends BaseMavenVerifier {
 
       final Document bom = readXML(new File(projDir, "dependency_A/target/bom.xml"));
 
-      final NodeList componentsList = bom.getElementsByTagName("components");
-      assertEquals("Expected a single components element", 1, componentsList.getLength());
-      final Element components = (Element)componentsList.item(0);
+      final Element components = getElement(getElement(bom, "bom"), "components");
+      assertNotNull("bom is missing components", components);
 
       final Element componentBNode = getComponentNode(components, ISSUE_314_DEPENDENCY_B);
       final Element componentBScope = getElement(componentBNode, "scope");
diff --git a/src/test/java/org/cyclonedx/maven/Issue382Test.java b/src/test/java/org/cyclonedx/maven/Issue382Test.java
new file mode 100644
index 0000000..a88bcbb
--- /dev/null
+++ b/src/test/java/org/cyclonedx/maven/Issue382Test.java
@@ -0,0 +1,47 @@
+package org.cyclonedx.maven;
+
+import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
+import io.takari.maven.testing.executor.MavenVersions;
+import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+import static io.takari.maven.testing.TestResources.assertFilesPresent;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Test for <a href="https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/382">issue #382</a>:
+ * Plugin does not gracefully handle present, but empty license data
+ */
+@RunWith(MavenJUnitTestRunner.class)
+@MavenVersions({"3.6.3"})
+public class Issue382Test extends BaseMavenVerifier {
+
+    public Issue382Test(MavenRuntimeBuilder runtimeBuilder) throws Exception {
+        super(runtimeBuilder);
+    }
+
+    @Test
+    public void test() throws Exception {
+        File projDir = resources.getBasedir("issue-382");
+
+        verifier
+                .forProject(projDir)
+                .withCliOption("-Dcurrent.version=" + getCurrentVersion()) // inject cyclonedx-maven-plugin version
+                .withCliOption("-X") // debug
+                .withCliOption("-B")
+                .execute("clean", "verify")
+                .assertErrorFreeLog();
+
+        assertFileNotContains(projDir, "target/bom.xml", "The BOM does not conform to the CycloneDX BOM standard");
+    }
+
+    private static void assertFileNotContains(File basedir, String expectedFile, String expectedContent) throws IOException {
+        assertFilesPresent(basedir, expectedFile);
+        String bomContents = fileRead(new File(basedir, expectedFile), true);
+        assertFalse(String.format("%s contains %s", expectedFile, expectedContent), bomContents.contains(expectedContent));
+    }
+}
diff --git a/src/test/java/org/cyclonedx/maven/Issue521Test.java b/src/test/java/org/cyclonedx/maven/Issue521Test.java
new file mode 100644
index 0000000..be44ab1
--- /dev/null
+++ b/src/test/java/org/cyclonedx/maven/Issue521Test.java
@@ -0,0 +1,104 @@
+package org.cyclonedx.maven;
+
+
+
+import org.junit.runner.RunWith;
+
+
+import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
+import io.takari.maven.testing.executor.MavenVersions;
+import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import static org.cyclonedx.maven.TestUtils.readXML;
+
+
+import java.io.File;
+import java.io.IOException;
+
+
+
+import org.junit.Test;
+
+@RunWith(MavenJUnitTestRunner.class)
+@MavenVersions({"3.6.3"})
+
+public class Issue521Test extends BaseMavenVerifier {
+
+    public Issue521Test(MavenRuntimeBuilder runtimeBuilder) throws Exception {
+        super(runtimeBuilder);
+        
+    }
+    
+    
+    @Test
+public void testFilePresence() throws Exception {
+    File projDir = cleanAndBuild("issue-521", null);
+    assertFileExists(projDir, "target/bom.json");
+    assertFileExists(projDir, "target/bom.xml");
+}
+
+@Test
+public void testBomJsonContent() throws Exception {
+    File projDir = cleanAndBuild("issue-521", null);
+    verifyBomJsonContains(projDir, "target/bom.json", "issue-521-module1", "application");
+}
+
+@Test
+public void testBomXmlContent() throws Exception {
+    File projDir = cleanAndBuild("issue-521", null);
+    checkBomXml(projDir);
+}
+
+    
+    
+
+    private static void assertFileExists(File basedir, String filePath) {
+        File file = new File(basedir, filePath);
+        assertTrue(file.exists(), String.format("File %s should exist", filePath));
+    }
+    
+    private void verifyBomJsonContains(File basedir, String filePath, String expectedName, String expectedType) throws IOException {
+        File bomJsonFile = new File(basedir, filePath);
+        assertTrue(bomJsonFile.exists(), String.format("File %s should exist", filePath));
+
+        // Leggi il contenuto del file bom.json e verifica la presenza dei campi desiderati
+        String bomContents = fileRead(bomJsonFile, true);
+        assertTrue(bomContents.contains("\"name\" : \"" + expectedName + "\""), 
+            String.format("bom.json should contain a module with name '%s'", expectedName));
+        assertTrue(bomContents.contains("\"type\" : \"" + expectedType + "\""), 
+            String.format("bom.json should contain a module with type '%s'", expectedType));
+    }
+
+    private void checkBomXml(final File projDir) throws Exception {
+        final Document bom = readXML(new File(projDir, "target/bom.xml"));
+
+        final NodeList componentsList = bom.getElementsByTagName("component");
+        assertTrue(componentsList.getLength() > 0, "Expected at least one component element");
+
+        boolean found=false;
+        for (int i = 0; i < componentsList.getLength(); i++) {
+            Element component = (Element) componentsList.item(i);
+            String name = component.getElementsByTagName("name").item(0).getTextContent();
+            String type = component.getAttribute("type");
+    
+            if ("issue-521-module1".equals(name) && "application".equals(type)) {
+                found = true;
+                break;
+            }
+        }
+
+        assertTrue(found, "Expected to find a component with name 'module1' and type 'application'");
+    }
+    
+
+     
+
+    
+
+    
+
+}
diff --git a/src/test/java/org/cyclonedx/maven/TestUtils.java b/src/test/java/org/cyclonedx/maven/TestUtils.java
index 0f6cc2e..837155d 100644
--- a/src/test/java/org/cyclonedx/maven/TestUtils.java
+++ b/src/test/java/org/cyclonedx/maven/TestUtils.java
@@ -16,7 +16,7 @@ import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
 class TestUtils {
-    static Element getElement(final Element parent, final String elementName) throws Exception {
+    static Element getElement(final Node parent, final String elementName) throws Exception {
         Element element = null;
         Node child = parent.getFirstChild();
         while (child != null) {
diff --git a/src/test/resources/issue-382/pom.xml b/src/test/resources/issue-382/pom.xml
new file mode 100644
index 0000000..15d900f
--- /dev/null
+++ b/src/test/resources/issue-382/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.example</groupId>
+    <artifactId>issue-382</artifactId>
+    <packaging>jar</packaging>
+    <version>1.0.0</version>
+
+    <name>Issue-64</name>
+
+    <licenses>
+        <license>
+            <name/>
+            <url/>
+            <distribution/>
+        </license>
+    </licenses>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency> <!-- has empty license information -->
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-ram</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.cyclonedx</groupId>
+                <artifactId>cyclonedx-maven-plugin</artifactId>
+                <version>${current.version}</version>
+                <executions>
+                    <execution>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>makeAggregateBom</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <projectType>library</projectType>
+                    <schemaVersion>1.6</schemaVersion>
+                    <includeLicenseText>true</includeLicenseText>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/src/test/resources/issue-521/module1/pom.xml b/src/test/resources/issue-521/module1/pom.xml
new file mode 100644
index 0000000..603a0df
--- /dev/null
+++ b/src/test/resources/issue-521/module1/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.example</groupId>
+        <artifactId>issue-521</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>issue-521-module1</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.cyclonedx</groupId>
+                <artifactId>cyclonedx-maven-plugin</artifactId>
+                <version>${current.version}</version>
+                <configuration>
+                    <projectType>application</projectType>
+                </configuration>
+                
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/src/test/resources/issue-521/module2/pom.xml b/src/test/resources/issue-521/module2/pom.xml
new file mode 100644
index 0000000..ef438b5
--- /dev/null
+++ b/src/test/resources/issue-521/module2/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.example</groupId>
+        <artifactId>issue-521</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>issue-521-module2</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.1</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/src/test/resources/issue-521/pom.xml b/src/test/resources/issue-521/pom.xml
new file mode 100644
index 0000000..44eb297
--- /dev/null
+++ b/src/test/resources/issue-521/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.example</groupId>
+    <artifactId>issue-521</artifactId>
+    <packaging>pom</packaging>
+    <version>${revision}</version>
+
+    <licenses>
+        <license>
+            <name>Apache-2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+
+    <modules>
+        <module>module1</module>
+        <module>module2</module>
+    </modules>
+
+    <properties>
+        <revision>1.0.0</revision>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.cyclonedx</groupId>
+                <artifactId>cyclonedx-maven-plugin</artifactId>
+                <version>${current.version}</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>makeAggregateBom</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    
+                    <schemaVersion>1.4</schemaVersion>
+                    <includeBomSerialNumber>true</includeBomSerialNumber>
+                    <includeCompileScope>true</includeCompileScope>
+                    <includeProvidedScope>false</includeProvidedScope>
+                    <includeRuntimeScope>false</includeRuntimeScope>
+                    <includeSystemScope>false</includeSystemScope>
+                    <includeTestScope>false</includeTestScope>
+                    <includeLicenseText>false</includeLicenseText>
+                    <outputFormat>all</outputFormat>
+                </configuration>
+                
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file