From 596b0c5855e11fb81a6880a0f94b1272a42d3a82 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 19 Feb 2025 11:54:06 +0000 Subject: [PATCH 1/3] Fix null exception for null config property value --- .../tasks/maintenance/ProjectMaintenanceTask.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java b/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java index 8e87c3690..adb9fa322 100644 --- a/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java +++ b/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java @@ -75,12 +75,12 @@ private Statistics informLocked() { assertLocked(); AtomicInteger numDeletedTotal = new AtomicInteger(0); - final String retentionType = withJdbiHandle(handle -> - handle.attach(ConfigPropertyDao.class).getValue(MAINTENANCE_PROJECTS_RETENTION_TYPE, String.class)); + final var retentionType = withJdbiHandle(handle -> + handle.attach(ConfigPropertyDao.class).getOptionalValue(MAINTENANCE_PROJECTS_RETENTION_TYPE, String.class)); - if (retentionType != null) { + if (!retentionType.isEmpty() && !retentionType.get().isEmpty()) { int batchSize = 100; - if (retentionType.equals("AGE")) { + if (retentionType.get().equals("AGE")) { final int retentionDays = withJdbiHandle(handle -> handle.attach(ConfigPropertyDao.class).getValue(MAINTENANCE_PROJECTS_RETENTION_DAYS, Integer.class)); From dff8d8fb308df5c775c1d4a2c6ddaf68b01a6b1f Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 19 Feb 2025 13:08:56 +0000 Subject: [PATCH 2/3] log deleted project info --- .../persistence/jdbi/ProjectDao.java | 19 +++++++++++++++---- .../maintenance/ProjectMaintenanceTask.java | 10 ++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java b/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java index ea775ca2b..91782594d 100644 --- a/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java +++ b/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java @@ -20,11 +20,13 @@ import com.fasterxml.jackson.annotation.JsonAlias; import jakarta.annotation.Nullable; +import org.jdbi.v3.core.mapper.reflect.ColumnName; import org.jdbi.v3.json.Json; import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper; import org.jdbi.v3.sqlobject.customizer.Bind; import org.jdbi.v3.sqlobject.customizer.Define; import org.jdbi.v3.sqlobject.customizer.DefineNamedBindings; +import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys; import org.jdbi.v3.sqlobject.statement.SqlQuery; import org.jdbi.v3.sqlobject.statement.SqlUpdate; @@ -228,7 +230,7 @@ record ConciseProjectMetricsRow( """) int deleteProject(@Bind final UUID projectUuid); - @SqlUpdate(""" + @SqlQuery(""" WITH "CTE" AS ( SELECT "ID" FROM "PROJECT" @@ -239,10 +241,16 @@ record ConciseProjectMetricsRow( DELETE FROM "PROJECT" WHERE "ID" IN (SELECT "ID" FROM "CTE") + RETURNING "NAME", "VERSION", "INACTIVE_SINCE" """) - int deleteInactiveProjectsForRetentionDuration(@Bind final Instant retentionCutOff, @Bind final int batchSize); + @GetGeneratedKeys({"NAME", "VERSION", "INACTIVE_SINCE"}) + @RegisterConstructorMapper(DeletedProject.class) + List deleteInactiveProjectsForRetentionDuration(@Bind final Instant retentionCutOff, @Bind final int batchSize); - @SqlUpdate(""" + record DeletedProject(@ColumnName("NAME") String name, @ColumnName("VERSION") String version, @ColumnName("INACTIVE_SINCE") Instant inactiveSince) { + } + + @SqlQuery(""" DELETE FROM "PROJECT" WHERE "PROJECT"."INACTIVE_SINCE" IS NOT NULL @@ -255,8 +263,11 @@ record ConciseProjectMetricsRow( ORDER BY "PROJECT"."INACTIVE_SINCE" DESC LIMIT :versionCountThreshold ) + RETURNING "NAME", "VERSION", "INACTIVE_SINCE" """) - int retainLastXInactiveProjects(@Bind final String projectName, @Bind final int versionCountThreshold); + @GetGeneratedKeys({"NAME", "VERSION", "INACTIVE_SINCE"}) + @RegisterConstructorMapper(DeletedProject.class) + List retainLastXInactiveProjects(@Bind final String projectName, @Bind final int versionCountThreshold); @SqlQuery(""" SELECT "PROJECT"."NAME" diff --git a/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java b/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java index adb9fa322..fcea5b5e5 100644 --- a/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java +++ b/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java @@ -88,12 +88,15 @@ private Statistics informLocked() { Instant retentionCutOff = Instant.now().minus(retentionDuration); Integer numDeletedLastBatch = null; while (numDeletedLastBatch == null || numDeletedLastBatch > 0) { - numDeletedLastBatch = withJdbiHandle( + final var deletedProjectsBatch = withJdbiHandle( batchHandle -> { final var projectDao = batchHandle.attach(ProjectDao.class); return projectDao.deleteInactiveProjectsForRetentionDuration(retentionCutOff, batchSize); }); + numDeletedLastBatch = deletedProjectsBatch.size(); numDeletedTotal.addAndGet(numDeletedLastBatch); + deletedProjectsBatch.forEach(deletedProject -> + LOGGER.info("Inactive project deleted: [name:%s, version:%s, inactive since:%s]".formatted(deletedProject.name(), deletedProject.version(), deletedProject.inactiveSince()))); } } else { final int versionCountThreshold = withJdbiHandle(handle -> @@ -105,7 +108,10 @@ private Statistics informLocked() { final var projectDao = batchHandle.attach(ProjectDao.class); List projectBatch = projectDao.getDistinctProjects(versionCountThreshold, batchSize); for (var projectName : projectBatch) { - numDeletedTotal.addAndGet(projectDao.retainLastXInactiveProjects(projectName, versionCountThreshold)); + final var deletedProjects = projectDao.retainLastXInactiveProjects(projectName, versionCountThreshold); + numDeletedTotal.addAndGet(deletedProjects.size()); + deletedProjects.forEach(deletedProject -> + LOGGER.info("Inactive project deleted: [name:%s, version:%s, inactive since:%s]".formatted(deletedProject.name(), deletedProject.version(), deletedProject.inactiveSince()))); } return projectBatch.size(); }); From c30ed6fce1678866127e235d4540dd24663a47bd Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 19 Feb 2025 15:04:52 +0000 Subject: [PATCH 3/3] added uuid mapping --- .../persistence/jdbi/ProjectDao.java | 12 +++++------ .../maintenance/ProjectMaintenanceTask.java | 4 ++-- .../ProjectMaintenanceTaskTest.java | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java b/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java index 91782594d..456c9a225 100644 --- a/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java +++ b/src/main/java/org/dependencytrack/persistence/jdbi/ProjectDao.java @@ -26,7 +26,6 @@ import org.jdbi.v3.sqlobject.customizer.Bind; import org.jdbi.v3.sqlobject.customizer.Define; import org.jdbi.v3.sqlobject.customizer.DefineNamedBindings; -import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys; import org.jdbi.v3.sqlobject.statement.SqlQuery; import org.jdbi.v3.sqlobject.statement.SqlUpdate; @@ -241,13 +240,15 @@ record ConciseProjectMetricsRow( DELETE FROM "PROJECT" WHERE "ID" IN (SELECT "ID" FROM "CTE") - RETURNING "NAME", "VERSION", "INACTIVE_SINCE" + RETURNING "NAME", "VERSION", "INACTIVE_SINCE", "UUID" """) - @GetGeneratedKeys({"NAME", "VERSION", "INACTIVE_SINCE"}) @RegisterConstructorMapper(DeletedProject.class) List deleteInactiveProjectsForRetentionDuration(@Bind final Instant retentionCutOff, @Bind final int batchSize); - record DeletedProject(@ColumnName("NAME") String name, @ColumnName("VERSION") String version, @ColumnName("INACTIVE_SINCE") Instant inactiveSince) { + record DeletedProject(@ColumnName("NAME") String name, + @ColumnName("VERSION") String version, + @ColumnName("INACTIVE_SINCE") Instant inactiveSince, + @ColumnName("UUID") UUID uuid) { } @SqlQuery(""" @@ -263,9 +264,8 @@ record DeletedProject(@ColumnName("NAME") String name, @ColumnName("VERSION") St ORDER BY "PROJECT"."INACTIVE_SINCE" DESC LIMIT :versionCountThreshold ) - RETURNING "NAME", "VERSION", "INACTIVE_SINCE" + RETURNING "NAME", "VERSION", "INACTIVE_SINCE", "UUID" """) - @GetGeneratedKeys({"NAME", "VERSION", "INACTIVE_SINCE"}) @RegisterConstructorMapper(DeletedProject.class) List retainLastXInactiveProjects(@Bind final String projectName, @Bind final int versionCountThreshold); diff --git a/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java b/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java index fcea5b5e5..066992805 100644 --- a/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java +++ b/src/main/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTask.java @@ -96,7 +96,7 @@ private Statistics informLocked() { numDeletedLastBatch = deletedProjectsBatch.size(); numDeletedTotal.addAndGet(numDeletedLastBatch); deletedProjectsBatch.forEach(deletedProject -> - LOGGER.info("Inactive project deleted: [name:%s, version:%s, inactive since:%s]".formatted(deletedProject.name(), deletedProject.version(), deletedProject.inactiveSince()))); + LOGGER.info("Inactive project deleted: [name:%s, version:%s, inactive since:%s, uuid:%s]".formatted(deletedProject.name(), deletedProject.version(), deletedProject.inactiveSince(), deletedProject.uuid()))); } } else { final int versionCountThreshold = withJdbiHandle(handle -> @@ -111,7 +111,7 @@ private Statistics informLocked() { final var deletedProjects = projectDao.retainLastXInactiveProjects(projectName, versionCountThreshold); numDeletedTotal.addAndGet(deletedProjects.size()); deletedProjects.forEach(deletedProject -> - LOGGER.info("Inactive project deleted: [name:%s, version:%s, inactive since:%s]".formatted(deletedProject.name(), deletedProject.version(), deletedProject.inactiveSince()))); + LOGGER.info("Inactive project deleted: [name:%s, version:%s, inactive since:%s, uuid:%s]".formatted(deletedProject.name(), deletedProject.version(), deletedProject.inactiveSince(), deletedProject.uuid()))); } return projectBatch.size(); }); diff --git a/src/test/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTaskTest.java b/src/test/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTaskTest.java index 129b57dfc..f78cee6a4 100644 --- a/src/test/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/maintenance/ProjectMaintenanceTaskTest.java @@ -210,4 +210,24 @@ public void testWithProjectRetentionDisabled() { assertThatNoException().isThrownBy(() -> task.inform(new ProjectMaintenanceEvent())); assertThat(qm.getProjects()).isNotNull(); } + + @Test + public void testWithProjectRetentionDisabledWithEmptyValue() { + qm.createConfigProperty( + MAINTENANCE_PROJECTS_RETENTION_TYPE.getGroupName(), + MAINTENANCE_PROJECTS_RETENTION_TYPE.getPropertyName(), + "", + MAINTENANCE_PROJECTS_RETENTION_TYPE.getPropertyType(), + MAINTENANCE_PROJECTS_RETENTION_TYPE.getDescription()); + + var project = new Project(); + project.setName("acme-app"); + project.setVersion("1.0.0"); + project.setInactiveSince(DateUtil.parseShortDate("20100109")); + qm.persist(project); + + final var task = new ProjectMaintenanceTask(); + assertThatNoException().isThrownBy(() -> task.inform(new ProjectMaintenanceEvent())); + assertThat(qm.getProjects()).isNotNull(); + } } \ No newline at end of file