diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java index fe6f21f2..1f073fbe 100644 --- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java +++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java @@ -18,8 +18,11 @@ */ package org.cyclonedx.maven; +import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; +import org.apache.maven.model.Developer; +import org.apache.maven.model.Organization; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.plugin.AbstractMojo; @@ -42,6 +45,8 @@ import org.cyclonedx.model.LifecycleChoice; import org.cyclonedx.model.Lifecycles; import org.cyclonedx.model.Metadata; +import org.cyclonedx.model.OrganizationalContact; +import org.cyclonedx.model.OrganizationalEntity; import org.cyclonedx.model.Property; import org.cyclonedx.parsers.JsonParser; import org.cyclonedx.parsers.Parser; @@ -355,6 +360,7 @@ public void execute() throws MojoExecutionException { final Component rootComponent = metadata.getComponent(); componentMap.remove(rootComponent.getPurl()); + setManufacturer(project, rootComponent); projectDependenciesConverter.cleanupBomDependencies(metadata, componentMap, dependencyMap); @@ -362,6 +368,85 @@ public void execute() throws MojoExecutionException { } } + protected void setManufacturer(MavenProject mavenProject, Component projectBomComponent) { + getLog().debug("setManufacturer for " + mavenProject.getGroupId() + ":" + + mavenProject.getArtifactId() + ":" + mavenProject.getVersion()); + List developers = mavenProject.getDevelopers(); + Organization organization = mavenProject.getOrganization(); + if (organization != null || (developers != null && !developers.isEmpty())) { + projectBomComponent.setManufacturer(createManufacturer(organization, developers)); + } + } + + OrganizationalEntity createManufacturer(Organization organization, List developers) { + OrganizationalEntity manufacturer = new OrganizationalEntity(); + if (organization != null) { + if (isNotNullOrEmpty(organization.getName())) { + manufacturer.setName(organization.getName()); + } + if (isNotNullOrEmpty(organization.getUrl())) { + addUrl(manufacturer, organization.getUrl()); + } + } + if (developers != null) { + DeveloperInformation information = createListOfContacts(developers); + if (!information.getContacts().isEmpty()) { + manufacturer.setContacts(information.getContacts()); + } + if (manufacturer.getName() == null) { + manufacturer.setName(information.getOrganization()); + } + for (String url : information.getUrls()) { + addUrl(manufacturer, url); + } + } + getLog().debug("Set manufacturer information name=" + manufacturer.getName()); + return manufacturer; + } + + DeveloperInformation createListOfContacts(List developers) { + DeveloperInformation developerInformation = new DeveloperInformation(); + for (Developer developer : developers) { + OrganizationalContact contact = new OrganizationalContact(); + if (isNotNullOrEmpty(developer.getName())) { + contact.setName(developer.getName()); + } + if (isNotNullOrEmpty(developer.getEmail())) { + contact.setEmail(developer.getEmail()); + } + developerInformation.addOrganizationalContact(contact); + developerInformation.setOrganization(developer.getOrganization()); + developerInformation.addUrl(developer.getOrganizationUrl()); + developerInformation.addUrl(developer.getUrl()); + } + return developerInformation; + } + + void addUrl(OrganizationalEntity manufacturer, String url) { + List urls = manufacturer.getUrls(); + if (urls == null) { + urls = new ArrayList<>(); + } + urls.add(url); + manufacturer.setUrls(urls); + } + + /** + * @param text Some text + * @return {@code true} if there is any text + */ + boolean isNotNullOrEmpty(String text) { + return text != null && !text.trim().isEmpty(); + } + + /** + * @param text Some text + * @return {@code true} if there is any text + */ + boolean isNullOrEmpty(String text) { + return !isNotNullOrEmpty(text); + } + private Property newProperty(String name, String value) { Property property = new Property(); property.setName(name); diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java index 8573c33c..a9f9a17d 100644 --- a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java +++ b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java @@ -129,6 +129,7 @@ protected String extractComponentsAndDependencies(final Set topLevelComp final Map projectDependencies = bomDependencies.getDependencies(); final Component projectBomComponent = convertMavenDependency(mavenProject.getArtifact()); + setManufacturer(mavenProject, projectBomComponent); components.put(projectBomComponent.getPurl(), projectBomComponent); topLevelComponents.add(projectBomComponent.getPurl()); diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java index 828d1037..874cabd2 100644 --- a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java +++ b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java @@ -68,6 +68,7 @@ protected String extractComponentsAndDependencies(Set topLevelComponents final Component projectBomComponent = convertMavenDependency(mavenProject.getArtifact()); components.put(projectBomComponent.getPurl(), projectBomComponent); + setManufacturer(mavenProject, projectBomComponent); topLevelComponents.add(projectBomComponent.getPurl()); populateComponents(topLevelComponents, components, bomDependencies.getArtifacts(), null); diff --git a/src/main/java/org/cyclonedx/maven/DeveloperInformation.java b/src/main/java/org/cyclonedx/maven/DeveloperInformation.java new file mode 100644 index 00000000..94075cfa --- /dev/null +++ b/src/main/java/org/cyclonedx/maven/DeveloperInformation.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) Giesecke+Devrient Mobile Security GmbH 2018-2024 + */ +package org.cyclonedx.maven; + +import java.util.ArrayList; +import java.util.List; +import org.cyclonedx.model.OrganizationalContact; + +/** + * Help class for parse a list of developers + */ +class DeveloperInformation { + + private final List contacts = new ArrayList<>(); + private String organization; + private final List urls = new ArrayList<>(); + + /** + * Add contact information + * + * @param contact The contact + */ + void addOrganizationalContact(OrganizationalContact contact) { + contacts.add(contact); + } + + /** + * If Maven section "" is missing, see if we can find any organization information from + * a developers section + * @param organization The organization name + */ + void setOrganization(String organization) { + if (this.organization == null && organization != null) { + this.organization = organization; + } + } + + /** + * Add a defined url + * @param url The url + */ + void addUrl(String url) { + if (url != null) { + urls.add(url); + } + } + + /** + * @return List of contacts + */ + public List getContacts() { + return contacts; + } + + /** + * @return First organization name if found + */ + public String getOrganization() { + return organization; + } + + /** + * @return List of configured urls + */ + public List getUrls() { + return urls; + } +} diff --git a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java new file mode 100644 index 00000000..08fbb44f --- /dev/null +++ b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java @@ -0,0 +1,169 @@ +package org.cyclonedx.maven; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.maven.model.Developer; +import org.apache.maven.model.Organization; +import org.apache.maven.project.MavenProject; +import org.cyclonedx.model.Component; +import org.cyclonedx.model.Dependency; +import org.cyclonedx.model.OrganizationalContact; +import org.cyclonedx.model.OrganizationalEntity; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BaseCycloneDxMojoTest { + + /** + * Minimal test class that can create an instance of BaseCycloneDxMojo + * so we can do some unit testing. + */ + private static class BaseCycloneDxMojoImpl extends BaseCycloneDxMojo { + + @Override + protected String extractComponentsAndDependencies(Set topLevelComponents, + Map components, Map dependencies) { + return ""; + } + } + + @Test + @DisplayName("Using developers information only") + void setManufacturer1() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + List developers = new ArrayList<>(); + Developer developer = new Developer(); + developer.setName("Developer"); + developers.add(developer); + developer = new Developer(); + developer.setEmail("Developer@foo.com"); + developers.add(developer); + developer = new Developer(); + developer.setOrganization("My Organization"); + developers.add(developer); + developer = new Developer(); + developer.setOrganizationUrl("http://foo.com"); + developers.add(developer); + Component projectBomComponent = new Component(); + MavenProject mavenProject = new MavenProject(); + mavenProject.setDevelopers(developers); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertEquals(4, manufacturer.getContacts().size()); + assertEquals("Developer", manufacturer.getContacts().get(0).getName()); + assertEquals("My Organization", manufacturer.getName()); + } + + @Test + @DisplayName("Using developers information with empty organization") + void setManufacturer2() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + List developers = new ArrayList<>(); + Developer developer = new Developer(); + developer.setName("Developer"); + developers.add(developer); + developer = new Developer(); + developer.setEmail("Developer@foo.com"); + developers.add(developer); + developer = new Developer(); + developer.setOrganization("My Organization"); + developers.add(developer); + developer = new Developer(); + developer.setOrganizationUrl("http://foo.com"); + developers.add(developer); + Component projectBomComponent = new Component(); + MavenProject mavenProject = new MavenProject(); + mavenProject.setDevelopers(developers); + mavenProject.setOrganization(new Organization()); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertEquals(4, manufacturer.getContacts().size()); + assertEquals("Developer", manufacturer.getContacts().get(0).getName()); + assertEquals("My Organization", manufacturer.getName()); + } + + @Test + @DisplayName("Using developers and organization information") + void setManufacturer3() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + + MavenProject mavenProject = new MavenProject(); + List developers = new ArrayList<>(); + Developer developer = new Developer(); + developer.setName("Developer 2"); + developer.setEmail("Developer@foo.com"); + developer.setOrganization("My Organization"); + developer.setOrganizationUrl("http://foo.com"); + developers.add(developer); + mavenProject.setDevelopers(developers); + + Organization organization = new Organization(); + organization.setName("My Company"); + organization.setUrl("http://example.com"); + mavenProject.setOrganization(organization); + + Component projectBomComponent = new Component(); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertEquals(1, manufacturer.getContacts().size()); + assertEquals("Developer 2", manufacturer.getContacts().get(0).getName()); + assertEquals("My Company", manufacturer.getName()); + } + + @Test + @DisplayName("Using organization information only") + void setManufacturer4() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + + MavenProject mavenProject = new MavenProject(); + List developers = new ArrayList<>(); + Organization organization = new Organization(); + organization.setName("My Organization"); + organization.setUrl("http://example.org"); + mavenProject.setOrganization(organization); + + mavenProject.setDevelopers(developers); + mavenProject.setOrganization(organization); + + Component projectBomComponent = new Component(); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertNull(manufacturer.getContacts()); + assertEquals("My Organization", manufacturer.getName()); + } + + @Test + @DisplayName("Verify addUrl") + void addUrl() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + OrganizationalEntity manufacturer = new OrganizationalEntity(); + assertNull(manufacturer.getUrls()); + mojo.addUrl(manufacturer, "http://foo.com"); + assertNotNull(manufacturer.getUrls()); + assertEquals(1, manufacturer.getUrls().size()); + mojo.addUrl(manufacturer, "http://example.com"); + assertEquals(2, manufacturer.getUrls().size()); + } + + + @Test + @DisplayName("Verify that check of String content works") + void isNotNullOrEmpty() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + String value = null; + assertFalse(mojo.isNotNullOrEmpty(value)); + value = ""; + assertFalse(mojo.isNotNullOrEmpty(value)); + value = "null"; + assertTrue(mojo.isNotNullOrEmpty(value)); + } + +} \ No newline at end of file