diff --git a/frontend/src/components/results/ResultTitle.vue b/frontend/src/components/results/ResultTitle.vue
index 54a5a6450..dcb99be1e 100644
--- a/frontend/src/components/results/ResultTitle.vue
+++ b/frontend/src/components/results/ResultTitle.vue
@@ -11,11 +11,6 @@
-
diff --git a/frontend/src/components/results/modal/GitInfoPrompt.vue b/frontend/src/components/results/modal/GitInfoPrompt.vue
index 4f3c25947..339ca1f0b 100644
--- a/frontend/src/components/results/modal/GitInfoPrompt.vue
+++ b/frontend/src/components/results/modal/GitInfoPrompt.vue
@@ -80,7 +80,7 @@ export default {
}
},
resetModal: function () {
- this.gitLink = model.codeOrigin.scanUrl;
+ this.gitLink = model.codeOrigin.gitUrl;
this.gitBranch = model.codeOrigin.revision;
this.commitID = model.codeOrigin.commitID;
diff --git a/frontend/src/helpers/cbom.js b/frontend/src/helpers/cbom.js
index 668d33a9f..2fe3bb916 100644
--- a/frontend/src/helpers/cbom.js
+++ b/frontend/src/helpers/cbom.js
@@ -262,7 +262,6 @@ export function setCbom(cbom) {
if (Object.hasOwn(cbom, "metadata")) {
if (Object.hasOwn(cbom.metadata, "properties") && Array.isArray(cbom.metadata.properties)) {
- model.codeOrigin.purls = []
cbom.metadata.properties.forEach(function (prop) {
if (Object.hasOwn(prop, "name") && Object.hasOwn(prop, "value")) {
switch (prop.name) {
@@ -275,9 +274,6 @@ export function setCbom(cbom) {
case "subfolder":
model.codeOrigin.subfolder = prop.value
break;
- case "purl":
- model.codeOrigin.purls.push(prop.value)
- break;
case "commit":
model.codeOrigin.commitID = prop.value
}
diff --git a/frontend/src/helpers/general.js b/frontend/src/helpers/general.js
index 989c1c49d..5775f4300 100644
--- a/frontend/src/helpers/general.js
+++ b/frontend/src/helpers/general.js
@@ -51,7 +51,7 @@ export function openGitRepo(gitUrl) {
}
export function canOpenOnline() {
- let gitUrl = model.codeOrigin.scanUrl;
+ let gitUrl = model.codeOrigin.gitUrl;
let branch = model.codeOrigin.revision;
let commitID = model.codeOrigin.commitID;
diff --git a/frontend/src/helpers/scan.js b/frontend/src/helpers/scan.js
index 12dd2b07c..85066abb6 100644
--- a/frontend/src/helpers/scan.js
+++ b/frontend/src/helpers/scan.js
@@ -146,8 +146,12 @@ function handleMessage(messageJson) {
let cbomString = obj["message"];
setCbom(JSON.parse(cbomString));
console.log("Received CBOM from scanning:", model.cbom);
+ } else if (obj["type"] === "GITURL") {
+ model.codeOrigin.gitUrl = obj["message"];
} else if (obj["type"] === "BRANCH") {
model.codeOrigin.revision = obj["message"];
+ } else if (obj["type"] === "FOLDER") {
+ model.codeOrigin.subfolder = obj["message"];
} else if (obj["type"] === "SCANNED_FILE_COUNT") {
model.scanning.numberOfFiles = obj["message"];
} else if (obj["type"] === "SCANNED_NUMBER_OF_LINES") {
diff --git a/frontend/src/model.js b/frontend/src/model.js
index 6e3dbc00d..09d4571b8 100644
--- a/frontend/src/model.js
+++ b/frontend/src/model.js
@@ -27,7 +27,6 @@ export const model = reactive({
revision: null,
subfolder: null,
commitID: null,
- purls: [],
uploadedFileName: null,
},
credentials: {
@@ -69,7 +68,6 @@ export const model = reactive({
model.codeOrigin.revision = null;
model.codeOrigin.subfolder = null;
model.codeOrigin.commitID = null;
- model.codeOrigin.purls = [];
model.codeOrigin.uploadedFileName = null;
},
resetCredentials() {
diff --git a/pom.xml b/pom.xml
index 8b410ac9f..d7eea18cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -151,6 +151,17 @@
google-java-format
${google-java-format.version}
+
+
+ org.apache.maven
+ maven-model
+ 3.9.9
+
+
+ org.tomlj
+ tomlj
+ 1.1.1
+
diff --git a/src/main/java/com/ibm/domain/scanning/ScanAggregate.java b/src/main/java/com/ibm/domain/scanning/ScanAggregate.java
index 9eaf09789..b000a7a22 100644
--- a/src/main/java/com/ibm/domain/scanning/ScanAggregate.java
+++ b/src/main/java/com/ibm/domain/scanning/ScanAggregate.java
@@ -26,15 +26,18 @@
import com.ibm.domain.scanning.errors.CommitHashAlreadyExists;
import com.ibm.domain.scanning.errors.GitUrlAlreadyResolved;
import com.ibm.domain.scanning.errors.InvalidScanUrl;
+import com.ibm.domain.scanning.errors.PackageFolderAlreadyExists;
import com.ibm.domain.scanning.errors.ScanResultForLanguageAlreadyExists;
import com.ibm.domain.scanning.events.CommitHashIdentifiedEvent;
import com.ibm.domain.scanning.events.GitUrlResolvedEvent;
import com.ibm.domain.scanning.events.LanguageScanDoneEvent;
+import com.ibm.domain.scanning.events.PackageFolderResolvedEvent;
import com.ibm.domain.scanning.events.PurlScanRequestedEvent;
import com.ibm.domain.scanning.events.ScanFinishedEvent;
import com.ibm.domain.scanning.events.ScanRequestedEvent;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
@@ -46,6 +49,7 @@ public final class ScanAggregate extends AggregateRoot {
@Nullable private GitUrl gitUrl;
@Nullable private PackageURL purl;
@Nonnull private Revision revision;
+ @Nullable private Path packageFolder;
@Nullable private Commit commit;
@Nullable private Map languageScans;
@@ -66,11 +70,13 @@ private ScanAggregate(
@Nonnull ScanRequest scanRequest,
@Nullable GitUrl gitUrl,
@Nullable PackageURL purl,
+ @Nullable Path packageFolder,
@Nullable Commit commit,
@Nullable Map languageScans) {
this(id, scanRequest);
this.gitUrl = gitUrl;
this.purl = purl;
+ this.packageFolder = packageFolder;
this.commit = commit;
this.languageScans = languageScans;
}
@@ -111,6 +117,14 @@ public void setCommitHash(@Nonnull Commit commit) throws CommitHashAlreadyExists
this.apply(new CommitHashIdentifiedEvent(this.getId()));
}
+ public void setPackageFolder(@Nonnull Path packageFolder) throws PackageFolderAlreadyExists {
+ if (this.packageFolder != null) {
+ throw new PackageFolderAlreadyExists(this.getId());
+ }
+ this.packageFolder = packageFolder;
+ this.apply(new PackageFolderResolvedEvent(this.getId()));
+ }
+
public void reportScanResults(@Nonnull LanguageScan scan)
throws ScanResultForLanguageAlreadyExists {
if (languageScans == null) {
@@ -153,6 +167,10 @@ public Revision getRevision() {
return revision;
}
+ @Nullable public Path getPackageFolder() {
+ return packageFolder;
+ }
+
@Nonnull
public Optional> getLanguageScans() {
return Optional.ofNullable(languageScans).map(Map::values).map(ArrayList::new);
@@ -194,8 +212,10 @@ public static ScanAggregate reconstruct(
@Nonnull ScanRequest scanRequest,
@Nullable GitUrl gitUrl,
@Nullable PackageURL purl,
+ @Nullable Path packageFolder,
@Nullable Commit commit,
@Nullable Map languageScans) {
- return new ScanAggregate(id, scanRequest, gitUrl, purl, commit, languageScans);
+ return new ScanAggregate(
+ id, scanRequest, gitUrl, purl, packageFolder, commit, languageScans);
}
}
diff --git a/src/main/java/com/ibm/domain/scanning/errors/PackageFolderAlreadyExists.java b/src/main/java/com/ibm/domain/scanning/errors/PackageFolderAlreadyExists.java
new file mode 100644
index 000000000..a24ac90ad
--- /dev/null
+++ b/src/main/java/com/ibm/domain/scanning/errors/PackageFolderAlreadyExists.java
@@ -0,0 +1,30 @@
+/*
+ * CBOMkit
+ * Copyright (C) 2024 IBM
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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
+ *
+ * https://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 com.ibm.domain.scanning.errors;
+
+import app.bootstrap.core.ddd.DomainException;
+import com.ibm.domain.scanning.ScanId;
+import jakarta.annotation.Nonnull;
+
+public class PackageFolderAlreadyExists extends DomainException {
+ public PackageFolderAlreadyExists(@Nonnull ScanId scanId) {
+ super("Subfolder already exists in scan " + scanId);
+ }
+}
diff --git a/src/main/java/com/ibm/domain/scanning/events/PackageFolderResolvedEvent.java b/src/main/java/com/ibm/domain/scanning/events/PackageFolderResolvedEvent.java
new file mode 100644
index 000000000..2435f012d
--- /dev/null
+++ b/src/main/java/com/ibm/domain/scanning/events/PackageFolderResolvedEvent.java
@@ -0,0 +1,43 @@
+/*
+ * CBOMkit
+ * Copyright (C) 2024 IBM
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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
+ *
+ * https://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 com.ibm.domain.scanning.events;
+
+import app.bootstrap.core.ddd.DomainEvent;
+import com.ibm.domain.scanning.ScanId;
+import jakarta.annotation.Nonnull;
+
+public final class PackageFolderResolvedEvent extends DomainEvent {
+ @Nonnull private final ScanId scanId;
+
+ public PackageFolderResolvedEvent(@Nonnull ScanId scanId) {
+ this.scanId = scanId;
+ }
+
+ @Nonnull
+ public ScanId getScanId() {
+ return scanId;
+ }
+
+ @Nonnull
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "[id=" + scanId + "]";
+ }
+}
diff --git a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java
index 0f3db4880..8a40e842d 100644
--- a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java
+++ b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java
@@ -39,7 +39,15 @@
@Entity
@Cacheable
@JsonInclude(JsonInclude.Include.NON_NULL)
-@JsonPropertyOrder({"projectIdentifier", "gitUrl", "branch", "commit", "createdAt", "bom"})
+@JsonPropertyOrder({
+ "projectIdentifier",
+ "gitUrl",
+ "branch",
+ "folder",
+ "commit",
+ "createdAt",
+ "bom"
+})
public class CBOMReadModel extends PanacheEntityBase implements IReadModel {
@JsonIgnore @Id @Nonnull public UUID id;
@@ -52,6 +60,9 @@ public class CBOMReadModel extends PanacheEntityBase implements IReadModel
@JsonProperty("branch")
@Nullable protected String revision;
+ @JsonProperty("folder")
+ @Nullable protected String packageFolder;
+
@Nullable protected String commit;
@Nonnull protected Timestamp createdAt;
@@ -64,6 +75,7 @@ public CBOMReadModel(
@Nonnull String projectIdentifier,
@Nonnull String repository,
@Nullable String revision,
+ @Nullable String packageFolder,
@Nullable String commit,
@Nonnull Timestamp createdAt,
@Nonnull JsonNode bom) {
@@ -71,6 +83,7 @@ public CBOMReadModel(
this.projectIdentifier = projectIdentifier;
this.repository = repository;
this.revision = revision;
+ this.packageFolder = packageFolder;
this.commit = commit;
this.createdAt = createdAt;
this.bom = bom;
@@ -97,6 +110,10 @@ public String getRepository() {
return revision;
}
+ @Nullable public String getPackageFolder() {
+ return packageFolder;
+ }
+
@Nullable public String getCommit() {
return commit;
}
diff --git a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java
index bdd5b8b53..a1c085094 100644
--- a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java
+++ b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java
@@ -32,6 +32,7 @@
import jakarta.inject.Singleton;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -49,26 +50,17 @@ public CBOMReadRepository(@Nonnull IDomainEventBus domainEventBus) {
}
@Override
- public @Nonnull Optional findBy(@Nonnull GitUrl gitUrl, @Nonnull Commit commit) {
- return findByRepository(gitUrl.value(), commit);
- }
-
- @Override
- public @Nonnull Optional findBy(@Nonnull GitUrl gitUrl) {
- return findByRepository(gitUrl.value(), null);
+ public @Nonnull Optional findBy(
+ @Nonnull GitUrl gitUrl, @Nullable Commit commit, @Nullable Path packageFolder) {
+ return findByRepository(gitUrl.value(), commit, packageFolder);
}
@Override
public @Nonnull Optional findBy(
- @Nonnull PackageURL purl, @Nonnull Commit commit) {
+ @Nonnull PackageURL purl, @Nullable Commit commit) {
return findByProjectIdentifier(purl.canonicalize(), commit);
}
- @Override
- public @Nonnull Optional findBy(@Nonnull PackageURL purl) {
- return findByProjectIdentifier(purl.canonicalize(), null);
- }
-
@Override
public @Nonnull Optional findBy(@Nonnull String projectIdentifier) {
return findByProjectIdentifier(projectIdentifier, null);
@@ -170,16 +162,21 @@ public void delete(@Nonnull UUID uuid) {
}
private @Nonnull Optional findByRepository(
- @Nonnull String repository, @Nullable Commit commit) {
+ @Nonnull String repository, @Nullable Commit commit, @Nullable Path packageFolder) {
final EntityManager entityManager = CBOMReadModel.getEntityManager();
final ArcContainer container = Arc.container();
container.requestContext().activate();
try {
QuarkusTransaction.begin();
String qString =
- commit != null
- ? "SELECT read FROM CBOMReadModel read WHERE read.commit = :commit AND read.repository = :repository"
- : "SELECT read FROM CBOMReadModel read WHERE read.repository = :repository";
+ "SELECT read FROM CBOMReadModel read WHERE read.repository = :repository";
+
+ if (commit != null) {
+ qString += " AND read.commit = :commit";
+ }
+ if (packageFolder != null) {
+ qString += " AND read.packageFolder = :packageFolder";
+ }
qString += " ORDER BY createdAt desc";
TypedQuery query =
@@ -190,6 +187,9 @@ public void delete(@Nonnull UUID uuid) {
if (commit != null) {
query.setParameter("commit", commit.hash());
}
+ if (packageFolder != null) {
+ query.setParameter("packageFolder", packageFolder.toString());
+ }
Optional match = query.getResultStream().findFirst();
QuarkusTransaction.commit();
return match;
diff --git a/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java b/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java
index 2f67ec829..30406cf7b 100644
--- a/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java
+++ b/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java
@@ -24,29 +24,28 @@
import com.ibm.domain.scanning.Commit;
import com.ibm.domain.scanning.GitUrl;
import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
public interface ICBOMReadRepository extends IReadRepository {
- @Nonnull
- Optional findBy(@Nonnull GitUrl gitUrl, @Nonnull Commit commit);
-
/**
- * Returns latest CBOM related to the git url.
+ * Returns latest CBOM related to the git url, commit and packageFolder.
*
* @param gitUrl the git url that identifies the CBOM.
+ * @param commit the commit id (optional)
+ * @param packageFolder the packageFolder (optional)
* @return CBOM read model.
*/
@Nonnull
- Optional findBy(@Nonnull GitUrl gitUrl);
-
- @Nonnull
- Optional findBy(@Nonnull PackageURL purl, @Nonnull Commit commit);
+ Optional findBy(
+ @Nonnull GitUrl gitUrl, @Nullable Commit commit, @Nullable Path packageFolder);
@Nonnull
- Optional findBy(@Nonnull PackageURL purl);
+ Optional findBy(@Nonnull PackageURL purl, @Nullable Commit commit);
@Nonnull
Optional findBy(@Nonnull String projectIdentifier);
diff --git a/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java b/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java
index 0f5b4f95a..dee1be5e9 100644
--- a/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java
+++ b/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java
@@ -24,7 +24,9 @@ public enum ProgressMessageType {
DETECTION,
ERROR,
CBOM,
+ GITURL,
BRANCH,
+ FOLDER,
REVISION_HASH,
SCANNED_FILE_COUNT,
SCANNED_NUMBER_OF_LINES,
diff --git a/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java b/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java
index 3dcb46708..51be34bdb 100644
--- a/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java
+++ b/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java
@@ -43,6 +43,7 @@
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
@@ -65,6 +66,7 @@ class Scan extends PanacheEntityBase {
@Nonnull public String revision;
@Nullable public String commitHash;
@Nullable public String subFolder;
+ @Nullable public String packageFolder;
@Nullable public String purl;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@@ -84,6 +86,9 @@ protected Scan() {}
} else {
this.revision = aggregate.getScanRequest().revision().value();
}
+ this.subFolder = aggregate.getScanRequest().subFolder();
+ this.packageFolder =
+ Optional.ofNullable(aggregate.getPackageFolder()).map(Path::toString).orElse(null);
this.commitHash = aggregate.getCommit().map(Commit::hash).orElse(null);
final Optional> languageScans = aggregate.getLanguageScans();
@@ -139,6 +144,7 @@ protected ScanAggregate asAggregate() throws AggregateReconstructionFailed {
new ScanUrl(this.scanUrl), new Revision(this.revision), this.subFolder),
Optional.ofNullable(this.gitUrl).map(GitUrl::new).orElse(null),
optionalPackageURL.orElse(null),
+ Optional.ofNullable(this.packageFolder).map(Path::of).orElse(null),
Optional.ofNullable(this.commitHash).map(Commit::new).orElse(null),
languageScans);
} catch (MalformedPackageURLException | CBOMSerializationFailed e) {
diff --git a/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java b/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java
index 49f483fe4..7cb549c32 100644
--- a/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java
+++ b/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java
@@ -56,7 +56,8 @@ public ComplianceResource(@Nonnull IQueryBus queryBus) {
public Response checkStored(
@Nullable @RestQuery("policyIdentifier") String policyIdentifier,
@Nullable @RestQuery("gitUrl") String gitUrl,
- @Nullable @RestQuery("commit") String commit)
+ @Nullable @RestQuery("commit") String commit,
+ @Nullable @RestQuery("folder") String folder)
throws ExecutionException, InterruptedException {
if (policyIdentifier == null) {
return Response.status(Response.Status.BAD_REQUEST).build();
@@ -69,7 +70,7 @@ public Response checkStored(
return this.queryBus
.send(
new RequestComplianceCheckForScannedGitRepositoryQuery(
- policyIdentifier, gitUrl, commit))
+ policyIdentifier, gitUrl, commit, folder))
.thenApply(res -> Response.ok(res).build())
.get();
}
diff --git a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java
index 48ff0c061..cd348df06 100644
--- a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java
+++ b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java
@@ -25,5 +25,8 @@
import jakarta.annotation.Nullable;
public record RequestComplianceCheckForScannedGitRepositoryQuery(
- @Nonnull String policyIdentifier, @Nonnull String gitUrl, @Nullable String commit)
+ @Nonnull String policyIdentifier,
+ @Nonnull String gitUrl,
+ @Nullable String commit,
+ @Nullable String packageFolder)
implements IQuery {}
diff --git a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java
index 4ce870e77..6f9bf8554 100644
--- a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java
+++ b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java
@@ -36,6 +36,7 @@
import jakarta.annotation.Nonnull;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
@@ -73,6 +74,11 @@ public RequestComplianceCheckForScannedGitRepositoryQueryHandler(
Optional.ofNullable(
requestComplianceCheckForScannedGitRepositoryQuery.commit())
.map(Commit::new)
+ .orElse(null),
+ Optional.ofNullable(
+ requestComplianceCheckForScannedGitRepositoryQuery
+ .packageFolder())
+ .map(Path::of)
.orElse(null));
final PolicyIdentifier policyIdentifier =
diff --git a/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java b/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java
index 84d17fcf5..36f7368c2 100644
--- a/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java
+++ b/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java
@@ -32,6 +32,7 @@
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import java.util.Collection;
import org.cyclonedx.exception.ParseException;
import org.cyclonedx.model.Bom;
@@ -43,25 +44,17 @@ public final class CompliancePreparationService {
public Collection receiveCryptographicAssets(
@Nonnull ICBOMReadRepository readRepository,
@Nonnull GitUrl gitUrl,
- @Nullable Commit commit)
+ @Nullable Commit commit,
+ @Nullable Path packageFolder)
throws CBOMSerializationFailed, CouldNotFindCBOMForGitRepository, InvalidScanUrl {
// validate
gitUrl.validate();
- final CBOMReadModel cbomReadModel;
- if (commit != null) {
- cbomReadModel =
- readRepository
- .findBy(gitUrl, commit)
- .orElseThrow(
- () -> new CouldNotFindCBOMForGitRepository(gitUrl.value()));
- } else {
- cbomReadModel =
- readRepository
- .findBy(gitUrl)
- .orElseThrow(
- () -> new CouldNotFindCBOMForGitRepository(gitUrl.value()));
- }
+ final CBOMReadModel cbomReadModel =
+ readRepository
+ .findBy(gitUrl, commit, packageFolder)
+ .orElseThrow(() -> new CouldNotFindCBOMForGitRepository(gitUrl.value()));
+
final CBOM cbom = CBOM.formJSON(cbomReadModel.getBom());
return cbom.cycloneDXbom().getComponents().stream()
.map(component -> new CryptographicAsset(component.getBomRef(), component))
diff --git a/src/main/java/com/ibm/usecases/scanning/commands/SetPackageFolderCommand.java b/src/main/java/com/ibm/usecases/scanning/commands/SetPackageFolderCommand.java
new file mode 100644
index 000000000..5b996cb4b
--- /dev/null
+++ b/src/main/java/com/ibm/usecases/scanning/commands/SetPackageFolderCommand.java
@@ -0,0 +1,26 @@
+/*
+ * CBOMkit
+ * Copyright (C) 2024 IBM
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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
+ *
+ * https://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 com.ibm.usecases.scanning.commands;
+
+import app.bootstrap.core.cqrs.ICommand;
+import com.ibm.domain.scanning.ScanId;
+import jakarta.annotation.Nonnull;
+
+public record SetPackageFolderCommand(@Nonnull ScanId id) implements ICommand {}
diff --git a/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java b/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java
index 83b17b2b8..a94e0b3df 100644
--- a/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java
+++ b/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java
@@ -32,10 +32,10 @@
import com.ibm.domain.scanning.ScanAggregate;
import com.ibm.domain.scanning.ScanId;
import com.ibm.domain.scanning.ScanMetadata;
-import com.ibm.domain.scanning.ScanRequest;
import com.ibm.domain.scanning.errors.CBOMSerializationFailed;
import com.ibm.domain.scanning.errors.CommitHashAlreadyExists;
import com.ibm.domain.scanning.errors.GitUrlAlreadyResolved;
+import com.ibm.domain.scanning.errors.PackageFolderAlreadyExists;
import com.ibm.domain.scanning.errors.ScanResultForLanguageAlreadyExists;
import com.ibm.infrastructure.errors.ClientDisconnected;
import com.ibm.infrastructure.errors.EntityNotFoundById;
@@ -48,6 +48,7 @@
import com.ibm.usecases.scanning.commands.IndexModulesCommand;
import com.ibm.usecases.scanning.commands.RequestScanCommand;
import com.ibm.usecases.scanning.commands.ScanCommand;
+import com.ibm.usecases.scanning.commands.SetPackageFolderCommand;
import com.ibm.usecases.scanning.errors.GitCloneFailed;
import com.ibm.usecases.scanning.errors.GitCloneResultNotAvailable;
import com.ibm.usecases.scanning.errors.NoCommitProvided;
@@ -62,12 +63,16 @@
import com.ibm.usecases.scanning.services.indexing.JavaIndexService;
import com.ibm.usecases.scanning.services.indexing.ProjectModule;
import com.ibm.usecases.scanning.services.indexing.PythonIndexService;
+import com.ibm.usecases.scanning.services.pkg.JavaPackageFinderService;
+import com.ibm.usecases.scanning.services.pkg.PythonPackageFinderService;
import com.ibm.usecases.scanning.services.scan.ScanResultDTO;
import com.ibm.usecases.scanning.services.scan.java.JavaScannerService;
import com.ibm.usecases.scanning.services.scan.python.PythonScannerService;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@@ -103,6 +108,8 @@ public void handle(@Nonnull ICommand command) throws Exception {
this.handleFetchDataFromDepsDevCommand(fetchDataFromDepsDevCommand);
case CloneGitRepositoryCommand cloneGitRepositoryCommand ->
this.handleCloneGitRepositoryCommand(cloneGitRepositoryCommand);
+ case SetPackageFolderCommand setPackageFolderCommmand ->
+ this.handleSetPackageFolderCommand(setPackageFolderCommmand);
case IndexModulesCommand indexModulesCommand ->
this.handleIndexModulesCommand(indexModulesCommand);
case ScanCommand scanCommand -> this.handleScanCommand(scanCommand);
@@ -116,7 +123,8 @@ private void handleFetchDataFromDepsDevCommand(@Nonnull FetchDataFromDepsDevComm
throws EntityNotFoundById,
NoDataAvailableInDepsDevForPurl,
GitUrlAlreadyResolved,
- NoPurlSpecifiedForScan {
+ NoPurlSpecifiedForScan,
+ ClientDisconnected {
if (this.scanId != command.id()) {
return;
}
@@ -127,13 +135,17 @@ private void handleFetchDataFromDepsDevCommand(@Nonnull FetchDataFromDepsDevComm
// Fetch source repo from deps.dev
final DepsDevService depsDev = new DepsDevService();
final String gitUrl =
- depsDev.fetch(
+ depsDev.getSourceRepo(
scanAggregate
.getPurl()
.map(PackageURL::canonicalize)
.orElseThrow(() -> new NoPurlSpecifiedForScan(scanId)));
// update aggregate
scanAggregate.setResolvedGitUrl(gitUrl);
+ if (gitUrl != null) {
+ this.progressDispatcher.send(
+ new ProgressMessage(ProgressMessageType.GITURL, gitUrl));
+ }
this.repository.save(scanAggregate);
this.commandBus.send(
@@ -176,8 +188,8 @@ private void handleCloneGitRepositoryCommand(@Nonnull CloneGitRepositoryCommand
// update aggregate
scanAggregate.setCommitHash(cloneResultDTO.commit());
this.repository.save(scanAggregate);
- // start indexing
- this.commandBus.send(new IndexModulesCommand(command.id()));
+ // set subfolder
+ this.commandBus.send(new SetPackageFolderCommand(command.id()));
} catch (GitCloneFailed gitCloneFailed) {
// if previous attempted failed with `main`, try `master`
if (scanAggregate.getScanRequest().revision().value().equalsIgnoreCase("main")) {
@@ -206,6 +218,54 @@ private void handleCloneGitRepositoryCommand(@Nonnull CloneGitRepositoryCommand
}
}
+ private void handleSetPackageFolderCommand(@Nonnull SetPackageFolderCommand command)
+ throws EntityNotFoundById, IOException, PackageFolderAlreadyExists, ClientDisconnected {
+ if (this.scanId != command.id()) {
+ return;
+ }
+
+ try {
+ final Optional possibleScanAggregate =
+ this.repository.read(command.id());
+ final ScanAggregate scanAggregate =
+ possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(command.id()));
+
+ // Initialize packagePath from subFolder
+ Optional packagePath =
+ Optional.ofNullable(scanAggregate.getScanRequest().subFolder()).map(Path::of);
+
+ // Extract packagePath from purl and overwrite
+ if (scanAggregate.getPurl().isPresent()) {
+ PackageURL purl = scanAggregate.getPurl().get();
+ if (purl.getType().equals("maven")) {
+ JavaPackageFinderService jpfs =
+ new JavaPackageFinderService(this.projectDirectory.toPath());
+ packagePath = jpfs.findPackage(purl);
+ // TODO: find gradle package
+ } else if (purl.getType().equals("pypi")) {
+ PythonPackageFinderService ppfs =
+ new PythonPackageFinderService(this.projectDirectory.toPath());
+ packagePath = ppfs.findPackage(purl);
+ }
+ }
+ if (packagePath.isPresent()) {
+ scanAggregate.setPackageFolder(packagePath.get());
+ this.progressDispatcher.send(
+ new ProgressMessage(
+ ProgressMessageType.FOLDER, packagePath.get().toString()));
+ }
+
+ this.repository.save(scanAggregate);
+ // start indexing
+ this.commandBus.send(new IndexModulesCommand(command.id()));
+ } catch (Exception e) {
+ this.progressDispatcher.send(
+ new ProgressMessage(ProgressMessageType.ERROR, e.getMessage()));
+ this.compensate(command.id());
+ throw e;
+ }
+ }
+
private void handleIndexModulesCommand(@Nonnull IndexModulesCommand command)
throws EntityNotFoundById, GitCloneResultNotAvailable, ClientDisconnected {
if (this.scanId != command.id()) {
@@ -217,7 +277,6 @@ private void handleIndexModulesCommand(@Nonnull IndexModulesCommand command)
this.repository.read(command.id());
final ScanAggregate scanAggregate =
possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(command.id()));
- final ScanRequest scanRequest = scanAggregate.getScanRequest();
this.index = new EnumMap<>(Language.class);
// java
final JavaIndexService javaIndexService = new JavaIndexService(this.progressDispatcher);
@@ -225,13 +284,13 @@ private void handleIndexModulesCommand(@Nonnull IndexModulesCommand command)
Optional.ofNullable(this.projectDirectory)
.orElseThrow(GitCloneResultNotAvailable::new);
final List javaIndex =
- javaIndexService.index(dir, scanRequest.subFolder());
+ javaIndexService.index(dir, scanAggregate.getPackageFolder());
this.index.put(Language.JAVA, javaIndex);
// python
final PythonIndexService pythonIndexService =
new PythonIndexService(this.progressDispatcher);
final List pythonIndex =
- pythonIndexService.index(dir, scanRequest.subFolder());
+ pythonIndexService.index(dir, scanAggregate.getPackageFolder());
this.index.put(Language.PYTHON, pythonIndex);
// continue with scan
this.commandBus.send(new ScanCommand(command.id()));
@@ -261,7 +320,6 @@ private void handleScanCommand(@Nonnull ScanCommand command)
this.repository.read(command.id());
final ScanAggregate scanAggregate =
possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(command.id()));
- final ScanRequest scanRequest = scanAggregate.getScanRequest();
final Commit commit = scanAggregate.getCommit().orElseThrow(NoCommitProvided::new);
// progress scan statistics
@@ -284,7 +342,7 @@ private void handleScanCommand(@Nonnull ScanCommand command)
.orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)),
scanAggregate.getRevision(),
commit,
- scanRequest.subFolder(),
+ scanAggregate.getPackageFolder(),
Optional.ofNullable(this.index)
.map(i -> i.get(Language.JAVA))
.orElseThrow(NoIndexForProject::new));
@@ -320,7 +378,7 @@ private void handleScanCommand(@Nonnull ScanCommand command)
.orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)),
scanAggregate.getRevision(),
commit,
- scanRequest.subFolder(),
+ scanAggregate.getPackageFolder(),
Optional.ofNullable(this.index)
.map(i -> i.get(Language.PYTHON))
.orElseThrow(NoIndexForProject::new));
diff --git a/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java b/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java
index 02b9ae04c..e6201a88a 100644
--- a/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java
+++ b/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java
@@ -29,7 +29,6 @@
import com.ibm.domain.scanning.LanguageScan;
import com.ibm.domain.scanning.ScanAggregate;
import com.ibm.domain.scanning.ScanId;
-import com.ibm.domain.scanning.ScanRequest;
import com.ibm.domain.scanning.errors.CBOMSerializationFailed;
import com.ibm.domain.scanning.events.ScanFinishedEvent;
import com.ibm.infrastructure.database.readmodels.CBOMReadModel;
@@ -41,6 +40,7 @@
import jakarta.annotation.Nonnull;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
+import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -81,7 +81,6 @@ private void handleScanFinishedEvent(@Nonnull ScanFinishedEvent scanFinishedEven
final Optional possibleScanAggregate = this.sourceRepository.read(scanId);
final ScanAggregate scanAggregate =
possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(scanId));
- final ScanRequest scanRequest = scanAggregate.getScanRequest();
// check for existing read model
if (this.repository instanceof ICBOMReadRepository cbomReadRepository) {
final Optional possibleCBOMReadModel =
@@ -89,7 +88,8 @@ private void handleScanFinishedEvent(@Nonnull ScanFinishedEvent scanFinishedEven
scanAggregate
.getGitUrl()
.orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)),
- scanAggregate.getCommit().orElseThrow(NoCBOMForScan::new));
+ scanAggregate.getCommit().orElseThrow(NoCBOMForScan::new),
+ scanAggregate.getPackageFolder());
if (possibleCBOMReadModel.isPresent()) {
LOGGER.info(
"No need to update CBOM read model, since scan request didn't change for {}",
@@ -120,12 +120,15 @@ private void handleScanFinishedEvent(@Nonnull ScanFinishedEvent scanFinishedEven
final CBOMReadModel cbomReadModel =
new CBOMReadModel(
scanAggregate.getId().getUuid(),
- scanRequest.scanUrl().getIdentifier(),
+ scanAggregate.getScanRequest().scanUrl().getIdentifier(),
scanAggregate
.getGitUrl()
.map(GitUrl::value)
.orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)),
scanAggregate.getRevision().value(),
+ Optional.ofNullable(scanAggregate.getPackageFolder())
+ .map(Path::toString)
+ .orElse(null),
scanAggregate.getCommit().map(Commit::hash).orElse(null),
scanFinishedEvent.getTimestamp(),
mergedCBOM.toJSON());
diff --git a/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java b/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java
index 7f0745475..c5bf6f017 100644
--- a/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java
+++ b/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java
@@ -28,10 +28,13 @@
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
-import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,40 +43,47 @@ public class DepsDevService {
private static final String DEPS_DEV_URI = "https://api.deps.dev/v3alpha/purl/";
private static final String SOURCE_REPO = "SOURCE_REPO";
+ private final class DepsDevResponseHandler implements HttpClientResponseHandler {
+ @Override
+ public String handleResponse(ClassicHttpResponse httpResponse)
+ throws ClientProtocolException, IOException {
+ if (httpResponse.getCode() != HttpStatus.SC_OK) {
+ return null;
+ }
+ return extractSourceRepo(httpResponse.getEntity().getContent());
+ }
+
+ @Nonnull
+ public String extractSourceRepo(InputStream in) throws IOException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ JsonNode rootNode = objectMapper.readTree(in);
+ JsonNode version = rootNode.get("version");
+ if (version != null) {
+ ArrayNode links = (ArrayNode) version.get("links");
+ if (links != null) {
+ for (JsonNode link : links) {
+ if (SOURCE_REPO.equals(link.get("label").textValue())) {
+ return link.get("url").textValue();
+ }
+ }
+ }
+ }
+ throw new IOException("Invalid deps.dev response");
+ }
+ }
+
@Nonnull
- public String fetch(@Nonnull String purl) throws NoDataAvailableInDepsDevForPurl {
+ public String getSourceRepo(@Nonnull String purl) throws NoDataAvailableInDepsDevForPurl {
LOGGER.info("Sending DepsDev request for " + purl);
final HttpGet request =
new HttpGet(DEPS_DEV_URI + URLEncoder.encode(purl, StandardCharsets.UTF_8));
- try (final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
- final CloseableHttpResponse response = httpClient.execute(request); ) {
- if (response.getCode() != 200) {
- throw new NoDataAvailableInDepsDevForPurl(
- purl, "bad status code: " + response.getCode());
- }
- final InputStream in = response.getEntity().getContent();
- return extractSourceRepo(in);
+ try (final CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
+ final String srcRepo = httpClient.execute(request, new DepsDevResponseHandler());
+ LOGGER.info("Source code repository: {}", srcRepo);
+ return srcRepo;
} catch (IOException ioe) {
throw new NoDataAvailableInDepsDevForPurl(purl, ioe.getMessage());
}
}
-
- @Nonnull
- public String extractSourceRepo(InputStream in) throws IOException {
- ObjectMapper objectMapper = new ObjectMapper();
- JsonNode rootNode = objectMapper.readTree(in);
- JsonNode version = rootNode.get("version");
- if (version != null) {
- ArrayNode links = (ArrayNode) version.get("links");
- if (links != null) {
- for (JsonNode link : links) {
- if (SOURCE_REPO.equals(link.get("label").textValue())) {
- return link.get("url").textValue();
- }
- }
- }
- }
- throw new IOException();
- }
}
diff --git a/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java b/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java
index 7eb8f6740..c7154e68f 100644
--- a/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java
+++ b/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java
@@ -29,6 +29,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -55,10 +56,10 @@ protected IndexingService(
}
@Nonnull
- public List index(@Nonnull File projectDirectory, @Nullable String subFolder)
+ public List index(@Nonnull File projectDirectory, @Nullable Path packageFolder)
throws ClientDisconnected {
- if (subFolder != null) {
- projectDirectory = new File(projectDirectory.getPath() + File.separator + subFolder);
+ if (packageFolder != null) {
+ projectDirectory = projectDirectory.toPath().resolve(packageFolder).toFile();
}
this.progressDispatcher.send(
new ProgressMessage(ProgressMessageType.LABEL, "Indexing projects ..."));
diff --git a/src/main/java/com/ibm/usecases/scanning/services/pkg/JavaPackageFinderService.java b/src/main/java/com/ibm/usecases/scanning/services/pkg/JavaPackageFinderService.java
new file mode 100644
index 000000000..2c200258e
--- /dev/null
+++ b/src/main/java/com/ibm/usecases/scanning/services/pkg/JavaPackageFinderService.java
@@ -0,0 +1,47 @@
+/*
+ * CBOMkit
+ * Copyright (C) 2025 IBM
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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
+ *
+ * https://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 com.ibm.usecases.scanning.services.pkg;
+
+import jakarta.annotation.Nonnull;
+import java.io.FileReader;
+import java.nio.file.Path;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+
+public class JavaPackageFinderService extends PackageFinderService {
+ private MavenXpp3Reader reader = new MavenXpp3Reader();
+
+ public JavaPackageFinderService(@Nonnull Path root) throws IllegalArgumentException {
+ super(root);
+
+ this.reader = new MavenXpp3Reader();
+ }
+
+ @Override
+ public boolean isBuildFile(@Nonnull Path file) {
+ return file.endsWith("pom.xml");
+ }
+
+ @Override
+ public String getPackageName(@Nonnull Path buildFile) throws Exception {
+ Model model = reader.read(new FileReader(buildFile.toFile()));
+ return model.getArtifactId();
+ }
+}
diff --git a/src/main/java/com/ibm/usecases/scanning/services/pkg/PackageFinderService.java b/src/main/java/com/ibm/usecases/scanning/services/pkg/PackageFinderService.java
new file mode 100644
index 000000000..b363baf62
--- /dev/null
+++ b/src/main/java/com/ibm/usecases/scanning/services/pkg/PackageFinderService.java
@@ -0,0 +1,82 @@
+/*
+ * CBOMkit
+ * Copyright (C) 2025 IBM
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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
+ *
+ * https://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 com.ibm.usecases.scanning.services.pkg;
+
+import com.github.packageurl.PackageURL;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class PackageFinderService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PackageFinderService.class);
+
+ protected Path root;
+
+ public PackageFinderService(@Nonnull Path root) throws IllegalArgumentException {
+ if (!Files.isDirectory(root)) {
+ throw new IllegalArgumentException("Path must be a directory!");
+ }
+ this.root = root;
+ }
+
+ @Nullable public Optional findPackage(@Nonnull PackageURL purl) throws IOException {
+ List poms;
+
+ LOGGER.info("Trying to find package path for purl {}", purl);
+
+ try (Stream walk = Files.walk(this.root)) {
+ poms =
+ walk.filter(p -> !Files.isDirectory(p))
+ // .map(p -> p.toString().toLowerCase())
+ // .filter(p -> p.endsWith(getBuildFileName()))
+ .filter(p -> isBuildFile(p))
+ .collect(Collectors.toList());
+ }
+
+ for (Path pom : poms) {
+ try {
+ if (purl.getName().equals(getPackageName(pom))) {
+ Path pkgPath = this.root.relativize(pom.getParent());
+ LOGGER.info(
+ "Package path: {}", pkgPath.equals(Paths.get("")) ? "" : pkgPath);
+ return Optional.of(pkgPath);
+ }
+ } catch (Exception e) {
+ continue;
+ }
+ }
+
+ LOGGER.warn("Package path not found");
+ return Optional.empty();
+ }
+
+ public abstract boolean isBuildFile(@Nonnull Path file);
+
+ public abstract String getPackageName(@Nonnull Path buildFile) throws Exception;
+}
diff --git a/src/main/java/com/ibm/usecases/scanning/services/pkg/PythonPackageFinderService.java b/src/main/java/com/ibm/usecases/scanning/services/pkg/PythonPackageFinderService.java
new file mode 100644
index 000000000..e8218ce98
--- /dev/null
+++ b/src/main/java/com/ibm/usecases/scanning/services/pkg/PythonPackageFinderService.java
@@ -0,0 +1,43 @@
+/*
+ * CBOMkit
+ * Copyright (C) 2025 IBM
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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
+ *
+ * https://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 com.ibm.usecases.scanning.services.pkg;
+
+import jakarta.annotation.Nonnull;
+import java.nio.file.Path;
+import org.tomlj.Toml;
+import org.tomlj.TomlParseResult;
+
+public class PythonPackageFinderService extends PackageFinderService {
+
+ public PythonPackageFinderService(@Nonnull Path root) throws IllegalArgumentException {
+ super(root);
+ }
+
+ @Override
+ public boolean isBuildFile(Path file) {
+ return file.endsWith("pyproject.toml");
+ }
+
+ @Override
+ public String getPackageName(Path buildFile) throws Exception {
+ TomlParseResult result = Toml.parse(buildFile);
+ return result.getString(("project.name"));
+ }
+}
diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java
index 0f3566e58..70e47f6f7 100644
--- a/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java
+++ b/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java
@@ -26,6 +26,7 @@
import com.ibm.usecases.scanning.services.indexing.ProjectModule;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
+import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
@@ -36,7 +37,7 @@ ScanResultDTO scan(
@Nonnull GitUrl gitUrl,
@Nonnull Revision revision,
@Nonnull Commit commit,
- @Nullable String subFolder,
+ @Nullable Path subFolder,
@Nonnull List index)
throws Exception;
}
diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java
index e06187349..edbdf1e9d 100644
--- a/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java
+++ b/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java
@@ -35,6 +35,7 @@
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.io.File;
+import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -92,7 +93,7 @@ protected synchronized Optional receiveBom(
@Nonnull GitUrl gitUrl,
@Nonnull Revision revision,
@Nonnull Commit commit,
- @Nullable String subFolder) {
+ @Nullable Path subFolder) {
final Bom bom = this.cbomOutputFile.getBom();
// sanitizeOccurrence
@@ -118,7 +119,7 @@ protected synchronized Optional receiveBom(
if (subFolder != null) {
final Property subFolderProperty = new Property();
subFolderProperty.setName("subfolder");
- subFolderProperty.setValue(subFolder);
+ subFolderProperty.setValue(subFolder.toString());
metadata.addProperty(subFolderProperty);
}
diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java
index 0cf8ef979..4753bfd6d 100644
--- a/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java
+++ b/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java
@@ -33,6 +33,7 @@
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.io.File;
+import java.nio.file.Path;
import java.util.List;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultFileSystem;
@@ -66,7 +67,7 @@ public synchronized ScanResultDTO scan(
@Nonnull GitUrl gitUrl,
@Nonnull Revision revision,
@Nonnull Commit commit,
- @Nullable String subFolder,
+ @Nullable Path subFolder,
@Nonnull List index)
throws ClientDisconnected {
final List visitors = List.of(new JavaDetectionCollectionRule(this));
diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java
index 664fc02c0..b4ee1d0a5 100644
--- a/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java
+++ b/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java
@@ -33,6 +33,7 @@
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.io.File;
+import java.nio.file.Path;
import java.util.List;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.plugins.python.api.PythonCheck;
@@ -51,7 +52,7 @@ public PythonScannerService(
@Nonnull GitUrl gitUrl,
@Nonnull Revision revision,
@Nonnull Commit commit,
- @Nullable String subFolder,
+ @Nullable Path subFolder,
@Nonnull List index)
throws ClientDisconnected {
final PythonCheck visitor = new PythonDetectionCollectionRule(this);