| 
 | 1 | +/*  | 
 | 2 | + * This file is part of CycloneDX Core (Java).  | 
 | 3 | + *  | 
 | 4 | + * Licensed under the Apache License, Version 2.0 (the "License");  | 
 | 5 | + * you may not use this file except in compliance with the License.  | 
 | 6 | + * You may obtain a copy of the License at  | 
 | 7 | + *  | 
 | 8 | + *     http://www.apache.org/licenses/LICENSE-2.0  | 
 | 9 | + *  | 
 | 10 | + * Unless required by applicable law or agreed to in writing, software  | 
 | 11 | + * distributed under the License is distributed on an "AS IS" BASIS,  | 
 | 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
 | 13 | + * See the License for the specific language governing permissions and  | 
 | 14 | + * limitations under the License.  | 
 | 15 | + *  | 
 | 16 | + * SPDX-License-Identifier: Apache-2.0  | 
 | 17 | + * Copyright (c) OWASP Foundation. All Rights Reserved.  | 
 | 18 | + */  | 
 | 19 | +package org.cyclonedx.util;  | 
 | 20 | + | 
 | 21 | +import java.util.ArrayDeque;  | 
 | 22 | +import java.util.ArrayList;  | 
 | 23 | +import java.util.Deque;  | 
 | 24 | +import java.util.List;  | 
 | 25 | + | 
 | 26 | +import org.apache.commons.lang3.StringUtils;  | 
 | 27 | +import org.cyclonedx.model.Bom;  | 
 | 28 | +import org.cyclonedx.model.BomReference;  | 
 | 29 | +import org.cyclonedx.model.Component;  | 
 | 30 | +import org.cyclonedx.model.Composition;  | 
 | 31 | +import org.cyclonedx.model.Dependency;  | 
 | 32 | +import org.cyclonedx.model.ExternalReference;  | 
 | 33 | +import org.cyclonedx.model.Metadata;  | 
 | 34 | +import org.cyclonedx.model.Service;  | 
 | 35 | +import org.cyclonedx.model.Tool;  | 
 | 36 | +import org.cyclonedx.model.vulnerability.Vulnerability;  | 
 | 37 | +import org.cyclonedx.model.vulnerability.Vulnerability.Affect;  | 
 | 38 | + | 
 | 39 | +public class BomMerger {  | 
 | 40 | + | 
 | 41 | +    public Bom hierarchicalMerge(Iterable<Bom> boms, Component bomSubject) {  | 
 | 42 | +        Bom result = new Bom();  | 
 | 43 | +        Metadata resultMetadata = new Metadata();  | 
 | 44 | +        result.setMetadata(resultMetadata);  | 
 | 45 | + | 
 | 46 | +        List<Dependency> bomSubjectDependencies = new ArrayList<>();  | 
 | 47 | + | 
 | 48 | +        if (bomSubject != null) {  | 
 | 49 | +            resultMetadata.setComponent(bomSubject);  | 
 | 50 | +            if (bomSubject.getBomRef() == null) {  | 
 | 51 | +                bomSubject.setBomRef(componentBomRefNamespace(bomSubject));  | 
 | 52 | +            }  | 
 | 53 | + | 
 | 54 | +            Dependency topLevelDependency = new Dependency(bomSubject.getBomRef());  | 
 | 55 | +            topLevelDependency.setDependencies(bomSubjectDependencies);  | 
 | 56 | +            result.addDependency(topLevelDependency);  | 
 | 57 | +        }  | 
 | 58 | + | 
 | 59 | +        for (Bom bom : boms) {  | 
 | 60 | +            Metadata bomMetadata = bom.getMetadata();  | 
 | 61 | +            Component bomComponent = bomMetadata == null ? null : bomMetadata.getComponent();  | 
 | 62 | +            if (bomComponent == null) {  | 
 | 63 | +                throw new IllegalArgumentException("Required metadata (top level) component is missing from BOM.");  | 
 | 64 | +            }  | 
 | 65 | + | 
 | 66 | +            List<Tool> bomTools = bomMetadata.getTools();  | 
 | 67 | +            if (bomTools != null) {  | 
 | 68 | +                bomTools.forEach(resultMetadata::addTool);  | 
 | 69 | +            }  | 
 | 70 | + | 
 | 71 | +            List<Component> bomComponents = bom.getComponents();  | 
 | 72 | +            if (bomComponents != null) {  | 
 | 73 | +                bomComponents.forEach(bomComponent::addComponent);  | 
 | 74 | +            }  | 
 | 75 | + | 
 | 76 | +            String bomRefNamespace = componentBomRefNamespace(bomComponent);  | 
 | 77 | + | 
 | 78 | +            namespaceComponentBomRefs(bomRefNamespace, bomComponent);  | 
 | 79 | + | 
 | 80 | +            if (bomComponent.getBomRef() == null) {  | 
 | 81 | +                bomComponent.setBomRef(bomRefNamespace);  | 
 | 82 | +            }  | 
 | 83 | + | 
 | 84 | +            bomSubjectDependencies.add(new Dependency(bomComponent.getBomRef()));  | 
 | 85 | + | 
 | 86 | +            result.addComponent(bomComponent);  | 
 | 87 | + | 
 | 88 | +            List<Service> bomServices = bom.getServices();  | 
 | 89 | +            if (bomServices != null) {  | 
 | 90 | +                for (Service service : bomServices) {  | 
 | 91 | +                    service.setBomRef(namespacedBomRef(bomRefNamespace, service.getBomRef()));  | 
 | 92 | +                    result.addService(service);  | 
 | 93 | +                }  | 
 | 94 | +            }  | 
 | 95 | + | 
 | 96 | +            List<ExternalReference> bomExternalRefs = bom.getExternalReferences();  | 
 | 97 | +            if (bomExternalRefs != null) {  | 
 | 98 | +                bomExternalRefs.forEach(result::addExternalReference);  | 
 | 99 | +            }  | 
 | 100 | + | 
 | 101 | +            List<Dependency> bomDependencies = bom.getDependencies();  | 
 | 102 | +            if (bomDependencies != null) {  | 
 | 103 | +                namespaceDependencyBomRefs(bomRefNamespace, bomDependencies);  | 
 | 104 | +                bomDependencies.forEach(result::addDependency);  | 
 | 105 | +            }  | 
 | 106 | + | 
 | 107 | +            List<Composition> bomCompositions = bom.getCompositions();  | 
 | 108 | +            if (bomCompositions != null) {  | 
 | 109 | +                namespaceCompositions(bomRefNamespace, bomCompositions);  | 
 | 110 | +                bomCompositions.forEach(result::addComposition);  | 
 | 111 | +            }  | 
 | 112 | + | 
 | 113 | +            List<Vulnerability> bomVulnerabilities = bom.getVulnerabilities();  | 
 | 114 | +            if (bomVulnerabilities != null) {  | 
 | 115 | +                namespaceVulnerabilityRefs(bomSubject.getBomRef(), bomVulnerabilities);  | 
 | 116 | +                bomVulnerabilities.forEach(result::addVulnerability);  | 
 | 117 | +            }  | 
 | 118 | +        }  | 
 | 119 | + | 
 | 120 | +        return result;  | 
 | 121 | +    }  | 
 | 122 | + | 
 | 123 | +    private String componentBomRefNamespace(Component component) {  | 
 | 124 | +        StringBuilder builder = new StringBuilder(256);  | 
 | 125 | +        String group = component.getGroup();  | 
 | 126 | +        if (group != null) {  | 
 | 127 | +            builder.append(group).append('.');  | 
 | 128 | +        }  | 
 | 129 | +        builder.append(component.getName()).append('@').append(component.getVersion());  | 
 | 130 | +        return builder.toString();  | 
 | 131 | +    }  | 
 | 132 | + | 
 | 133 | +    private String namespacedBomRef(String bomRefNamespace, String bomRef) {  | 
 | 134 | +        return StringUtils.isEmpty(bomRef) ? null : bomRefNamespace + ":" + bomRef;  | 
 | 135 | +    }  | 
 | 136 | + | 
 | 137 | +    private void namespaceBomRefs(String bomRefNamespace, List<BomReference> bomRefs) {  | 
 | 138 | +        if (bomRefs != null) {  | 
 | 139 | +            for (BomReference bomRef : bomRefs) {  | 
 | 140 | +                bomRef.setRef(namespacedBomRef(bomRefNamespace, bomRef.getRef()));  | 
 | 141 | +            }  | 
 | 142 | +        }  | 
 | 143 | +    }  | 
 | 144 | + | 
 | 145 | +    private void namespaceComponentBomRefs(String bomRefNamespace, Component topComponent) {  | 
 | 146 | +        Deque<Component> components = new ArrayDeque<>();  | 
 | 147 | +        components.push(topComponent);  | 
 | 148 | + | 
 | 149 | +        while (!components.isEmpty()) {  | 
 | 150 | +            Component currentComponent = components.pop();  | 
 | 151 | +            currentComponent.setBomRef(namespacedBomRef(bomRefNamespace, currentComponent.getBomRef()));  | 
 | 152 | + | 
 | 153 | +            List<Component> subComponents = currentComponent.getComponents();  | 
 | 154 | +            if (subComponents != null) {  | 
 | 155 | +                subComponents.forEach(components::push);  | 
 | 156 | +            }  | 
 | 157 | +        }  | 
 | 158 | +    }  | 
 | 159 | + | 
 | 160 | +    private void namespaceDependencyBomRefs(String bomRefNamespace, List<Dependency> dependencies) {  | 
 | 161 | +        Deque<Dependency> pendingDependencies = new ArrayDeque<>(dependencies);  | 
 | 162 | +        while (!pendingDependencies.isEmpty()) {  | 
 | 163 | +            Dependency dependency = pendingDependencies.pop();  | 
 | 164 | +            dependency.setRef(namespacedBomRef(bomRefNamespace, dependency.getRef()));  | 
 | 165 | + | 
 | 166 | +            List<Dependency> subDependencies = dependency.getDependencies();  | 
 | 167 | +            if (subDependencies != null) {  | 
 | 168 | +                subDependencies.forEach(pendingDependencies::push);  | 
 | 169 | +            }  | 
 | 170 | +        }  | 
 | 171 | +    }  | 
 | 172 | + | 
 | 173 | +    private void namespaceCompositions(String bomRefNamespace, List<Composition> compositions) {  | 
 | 174 | +        for (Composition composition : compositions) {  | 
 | 175 | +            namespaceBomRefs(bomRefNamespace, composition.getAssemblies());  | 
 | 176 | +            namespaceBomRefs(bomRefNamespace, composition.getDependencies());  | 
 | 177 | +        }  | 
 | 178 | +    }  | 
 | 179 | + | 
 | 180 | +    private void namespaceVulnerabilityRefs(String bomRefNamespace, List<Vulnerability> vulnerabilities) {  | 
 | 181 | +        for (Vulnerability vulnerability : vulnerabilities) {  | 
 | 182 | +            vulnerability.setBomRef(namespacedBomRef(bomRefNamespace, vulnerability.getBomRef()));  | 
 | 183 | + | 
 | 184 | +            List<Affect> affects = vulnerability.getAffects();  | 
 | 185 | +            if (affects != null) {  | 
 | 186 | +                for (Affect affect : affects) {  | 
 | 187 | +                    affect.setRef(bomRefNamespace);  | 
 | 188 | +                }  | 
 | 189 | +            }  | 
 | 190 | +        }  | 
 | 191 | +    }  | 
 | 192 | + | 
 | 193 | +}  | 
0 commit comments