com.metaeffekt.artifact.analysis.bom.cyclonedx.ModelBaseMapper Maven / Gradle / Ivy
The newest version!
package com.metaeffekt.artifact.analysis.bom.cyclonedx;
import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;
import com.metaeffekt.artifact.analysis.bom.BomConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.cyclonedx.model.*;
import org.metaeffekt.core.inventory.processor.model.AbstractModelBase;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.AssetMetaData;
import org.metaeffekt.core.inventory.processor.model.Constants;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Counterpart to the {@link ComponentMapper} responsible for mapping component back to either an artifact or aseet.
*/
@Slf4j
public class ModelBaseMapper {
/**
* Main entrypoint when mapping a component.
*
* @param component the component to map.
* @return an AbstractModelBase object, either an instance of asset or artifact.
*/
public AbstractModelBase map(Component component, boolean includeMetadataComponent) {
AbstractModelBase modelBase;
if (isAsset(component)) {
modelBase = new AssetMetaData();
} else {
modelBase = new Artifact();
}
mapOrganizationInformation(component, modelBase);
mapBaseInformation(component, modelBase);
mapPurlRelated(component, modelBase);
mapChecksums(component, modelBase);
mapFileLocationInformation(component, modelBase);
mapLicenses(component, modelBase);
if (includeMetadataComponent) {
List properties = component.getProperties().stream().filter(p -> !p.getName().equals(BomConstants.INVENTORY_CLASS)).collect(Collectors.toList());
Property property = new Property();
property.setName(BomConstants.INVENTORY_CLASS);
property.setValue("artifact");
properties.add(property);
component.setProperties(properties);
map(component, false);
}
return modelBase;
}
/**
* Maps basic information which should be contained in either all or most components, depending on the stage during
* which the bom was generated.
*
* @param component the component
* @param modelBase the artifact or asset
*/
private void mapBaseInformation(Component component, AbstractModelBase modelBase) {
if (isAsset(component)) {
setIfNotBlank(modelBase, Constants.KEY_ASSET_ID, component.getName());
} else {
setIfNotBlank(modelBase, Constants.KEY_ID, component.getName());
}
setIfNotBlank(modelBase, Constants.KEY_VERSION, component.getVersion());
setIfNotBlank(modelBase, Constants.KEY_DESCRIPTION, component.getDescription());
List properties = component.getProperties();
if (properties != null) {
Property property =
properties.stream().filter(p -> p.getName().equals(BomConstants.SPECIFIC_TYPE)).findFirst()
.orElse(null);
if (property != null) {
setIfNotBlank(modelBase, Constants.KEY_TYPE, property.getValue());
}
property = properties.stream().filter(p -> p.getName().equals(Artifact.Attribute.COMMENT.getKey())).findFirst().orElse(null);
if (property != null) {
setIfNotBlank(modelBase, Artifact.Attribute.COMMENT.getKey(), property.getValue());
}
}
}
private void mapPurlRelated(Component component, AbstractModelBase modelBase) {
final String purl = component.getPurl();
if (purl != null) {
setIfNotBlank(modelBase, Constants.KEY_PURL, purl);
try {
PackageURL packageURL = new PackageURL(purl);
setIfNotBlank(modelBase, Artifact.Attribute.COMPONENT.getKey(), packageURL.getName());
} catch (MalformedPackageURLException e) {
throw new RuntimeException(e);
}
}
}
/**
* Maps checksums and hashes.
*
* @param component the component
* @param modelBase the artifact or asset
*/
private void mapChecksums(Component component, AbstractModelBase modelBase) {
if (component.getHashes() == null || component.getHashes().isEmpty()) {
return;
}
for (Hash hash : component.getHashes()) {
if (hash.getAlgorithm().equals(Hash.Algorithm.MD5.getSpec())) {
modelBase.set(Constants.KEY_CHECKSUM, hash.getValue());
} else if (hash.getAlgorithm().equals(Hash.Algorithm.SHA1.getSpec())) {
modelBase.set(Constants.KEY_HASH_SHA1, hash.getValue());
} else if (hash.getAlgorithm().equals(Hash.Algorithm.SHA_256.getSpec())) {
modelBase.set(Constants.KEY_HASH_SHA256, hash.getValue());
} else if (hash.getAlgorithm().equals(Hash.Algorithm.SHA_512.getSpec())) {
modelBase.set(Constants.KEY_HASH_SHA512, hash.getValue());
}
}
}
/**
* Maps the file location information.
*
* @param component the component
* @param modelBase the asset or artifact
*/
private void mapFileLocationInformation(Component component, AbstractModelBase modelBase) {
setIfPropertyExists(modelBase, Constants.KEY_PROJECTS, "Path", component);
}
/**
* Maps all license to the "Component Specified License" field. As license handling is quite complicated and
* translating all licenses back to their original state before the bom was created is not possible, this is
* done to
* keep the process as simple as possible.
*
* @param component the component
* @param modelBase the asset or artifact
*/
private void mapLicenses(Component component, AbstractModelBase modelBase) {
String licenseString = null;
final LicenseChoice licenseChoice = component.getLicenses();
if (licenseChoice != null) {
if (licenseChoice.getExpression() != null) {
licenseString = licenseChoice.getExpression().getValue();
}
final List licenseList = licenseChoice.getLicenses();
if (licenseList != null) {
licenseString = licenseList.stream().map(this::licenseToName).collect(Collectors.joining(", "));
}
}
setIfNotBlank(modelBase, Constants.KEY_COMPONENT_SPECIFIED_LICENSE, licenseString);
}
/**
* Maps organization information.
*
* @param component the component
* @param modelBase the artifact or asset
*/
private void mapOrganizationInformation(Component component, AbstractModelBase modelBase) {
setIfNotBlank(modelBase, Constants.KEY_URL, component.getPublisher());
setIfNotBlank(modelBase, Constants.KEY_GROUP_ID, component.getGroup());
setIfPropertyExists(modelBase, Constants.KEY_ORGANIZATION_URL, Constants.KEY_ORGANIZATION_URL, component);
setIfPropertyExists(modelBase, Constants.KEY_ORGANIZATION, Constants.KEY_ORGANIZATION, component);
}
/**
* Tries to determine if a component was an artifact or asset. This only works reliably if includeTechnicalProperties
* in {@link com.metaeffekt.artifact.analysis.bom.spdx.DocumentSpec} was set to true during the export from the original inventory.
*
* @param component the component
* @return true if the component used to be an asset.
*/
private boolean isAsset(Component component) {
try {
return component.getProperties().stream()
.anyMatch(p -> p.getName().equals(BomConstants.INVENTORY_CLASS) && p.getValue().equals("asset"));
} catch (NullPointerException e) {
// Fallback if required property does not exist.
return component.getName().startsWith("CID") || component.getName().startsWith("AID");
}
}
private void setIfNotBlank(AbstractModelBase modelBase, String key, String value) {
if (StringUtils.isNotBlank(value)) {
modelBase.set(key, value);
}
}
/**
* Looks up a property by the name associated with it and adds the value to the respective Attribute of the given
* AbstractModelBase object.
*
* @param modelBase the artifact or asset
* @param key the attribute key of the artifact/asset for which to add the value
* @param name the name of the property
* @param component contains the list of properties
*/
private void setIfPropertyExists(AbstractModelBase modelBase, String key, String name, Component component) {
List properties = component.getProperties();
if (properties != null) {
Optional optionalProperty =
properties.stream().filter(p -> p.getName().equals(name)).findFirst();
if (optionalProperty.isPresent()) {
Property property = optionalProperty.get();
setIfNotBlank(modelBase, key, property.getValue());
}
}
}
private String licenseToName(License l) {
if (l.getId() != null) {
return l.getId();
}
return l.getName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy