Skip to content

Commit

Permalink
Introduce isLatest project flag
Browse files Browse the repository at this point in the history
Co-Authored-By: rkg-mm <[email protected]>
  • Loading branch information
sahibamittal and rkg-mm committed Oct 4, 2024
1 parent 6232195 commit 74a814e
Show file tree
Hide file tree
Showing 20 changed files with 752 additions and 132 deletions.
12 changes: 12 additions & 0 deletions src/main/java/org/dependencytrack/model/Policy.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ public enum ViolationState {
@Column(name = "INCLUDE_CHILDREN", allowsNull = "true") // New column, must allow nulls on existing data bases)
private boolean includeChildren;

@Persistent
@Column(name = "ONLY_LATEST_PROJECT_VERSION", defaultValue = "false")
private boolean onlyLatestProjectVersion = false;

public long getId() {
return id;
}
Expand Down Expand Up @@ -224,4 +228,12 @@ public boolean isIncludeChildren() {
public void setIncludeChildren(boolean includeChildren) {
this.includeChildren = includeChildren;
}

public boolean isOnlyLatestProjectVersion() {
return onlyLatestProjectVersion;
}

public void setOnlyLatestProjectVersion(boolean onlyLatestProjectVersion) {
this.onlyLatestProjectVersion = onlyLatestProjectVersion;
}
}
18 changes: 17 additions & 1 deletion src/main/java/org/dependencytrack/model/Project.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
Expand Down Expand Up @@ -94,7 +95,8 @@
@Persistent(name = "properties"),
@Persistent(name = "tags"),
@Persistent(name = "accessTeams"),
@Persistent(name = "metadata")
@Persistent(name = "metadata"),
@Persistent(name = "isLatest")
}),
@FetchGroup(name = "METADATA", members = {
@Persistent(name = "metadata")
Expand Down Expand Up @@ -309,6 +311,11 @@ public enum FetchGroup {
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private ProjectMetadata metadata;

@Persistent
@Index(name = "PROJECT_IS_LATEST_IDX")
@Column(name = "IS_LATEST", defaultValue = "false")
private boolean isLatest = false;

private transient String bomRef;

private transient ProjectMetrics metrics;
Expand Down Expand Up @@ -589,6 +596,15 @@ public String setAuthor(String author){
return this.author=author;
}

@JsonProperty("isLatest")
public boolean isLatest() {
return isLatest;
}

public void setIsLatest(Boolean latest) {
isLatest = latest != null ? latest : false;
}

@Override
public String toString() {
if (getPurl() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,13 @@ public Policy getPolicy(final String name) {
* @param violationState the violation state
* @return the created Policy
*/
public Policy createPolicy(String name, Policy.Operator operator, Policy.ViolationState violationState) {
public Policy createPolicy(String name, Policy.Operator operator, Policy.ViolationState violationState,
boolean onlyLatestProjectVersion) {
final Policy policy = new Policy();
policy.setName(name);
policy.setOperator(operator);
policy.setViolationState(violationState);
policy.setOnlyLatestProjectVersion(onlyLatestProjectVersion);
return persist(policy);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ ProjectQueryFilterBuilder withParent(UUID uuid){
return this;
}

public ProjectQueryFilterBuilder onlyLatestVersion() {
filterCriteria.add("(isLatest == true)");
return this;
}

String buildFilter() {
return String.join(" && ", this.filterCriteria);
}
Expand Down
116 changes: 87 additions & 29 deletions src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;

final class ProjectQueryManager extends QueryManager implements IQueryManager {

Expand Down Expand Up @@ -272,6 +273,37 @@ public Project getProject(final String name, final String version) {
return project;
}

/**
* Returns the latest version of a project by its name.
*
* @param name the name of the Project (required)
* @return a Project object representing the latest version, or null if not found
*/
@Override
public Project getLatestProjectVersion(final String name) {
final Query<Project> query = pm.newQuery(Project.class);

final var filterBuilder = new ProjectQueryFilterBuilder()
.withName(name)
.onlyLatestVersion();

final String queryFilter = filterBuilder.buildFilter();
final Map<String, Object> params = filterBuilder.getParams();

preprocessACLs(query, queryFilter, params, false);
query.setFilter(queryFilter);
query.setRange(0, 1);

final Project project = singleResult(query.executeWithMap(params));
if (project != null) {
// set Metrics to prevent extra round trip
project.setMetrics(getMostRecentProjectMetrics(project));
// set ProjectVersions to prevent extra round trip
project.setVersions(getProjectVersions(project));
}
return project;
}

/**
* Returns a list of projects that are accessible by the specified team.
*
Expand Down Expand Up @@ -393,6 +425,11 @@ public PaginatedResult getProjects(final Tag tag) {
return getProjects(tag, false, false, false);
}

@Override
public Project createProject(String name, String description, String version, List<Tag> tags, Project parent,
PackageURL purl, boolean active, boolean commitIndex) {
return createProject(name, description, version, tags, parent, purl, active, false, commitIndex);
}

/**
* Creates a new Project.
Expand All @@ -405,35 +442,21 @@ public PaginatedResult getProjects(final Tag tag) {
* @param purl an optional Package URL
* @param active specified if the project is active
* @param commitIndex specifies if the search index should be committed (an expensive operation)
* @param isLatest specified if the project version is latest
* @return the created Project
*/
@Override
public Project createProject(String name, String description, String version, List<Tag> tags, Project parent, PackageURL purl, boolean active, boolean commitIndex) {
public Project createProject(String name, String description, String version, List<Tag> tags, Project parent,
PackageURL purl, boolean active, boolean isLatest, boolean commitIndex) {
final Project project = new Project();
project.setName(name);
project.setDescription(description);
project.setVersion(version);
if (parent != null) {
if (!Boolean.TRUE.equals(parent.isActive())) {
throw new IllegalArgumentException("An inactive Parent cannot be selected as parent");
}
project.setParent(parent);
}
project.setParent(parent);
project.setPurl(purl);
project.setActive(active);
final Project result = persist(project);

final List<Tag> resolvedTags = resolveTags(tags);
bind(project, resolvedTags);

new KafkaEventDispatcher().dispatchNotification(new Notification()
.scope(NotificationScope.PORTFOLIO)
.group(NotificationGroup.PROJECT_CREATED)
.level(NotificationLevel.INFORMATIONAL)
.title(NotificationConstants.Title.PROJECT_CREATED)
.content(result.getName() + " was created")
.subject(pm.detachCopy(result)));
return result;
project.setIsLatest(isLatest);
return createProject(project, tags, commitIndex);
}

/**
Expand All @@ -449,10 +472,18 @@ public Project createProject(final Project project, List<Tag> tags, boolean comm
if (project.getParent() != null && !Boolean.TRUE.equals(project.getParent().isActive())) {
throw new IllegalArgumentException("An inactive Parent cannot be selected as parent");
}
final Project result = persist(project);
final List<Tag> resolvedTags = resolveTags(tags);
bind(project, resolvedTags);

final Project oldLatestProject = project.isLatest() ? getLatestProjectVersion(project.getName()) : null;
final Project result = callInTransaction(() -> {
// Remove isLatest flag from current latest project version, if the new project will be the latest
if(oldLatestProject != null) {
oldLatestProject.setIsLatest(false);
persist(oldLatestProject);
}
final Project newProject = persist(project);
final List<Tag> resolvedTags = resolveTags(tags);
bind(project, resolvedTags);
return newProject;
});
new KafkaEventDispatcher().dispatchNotification(new Notification()
.scope(NotificationScope.PORTFOLIO)
.group(NotificationGroup.PROJECT_CREATED)
Expand Down Expand Up @@ -492,6 +523,14 @@ public Project updateProject(Project transientProject, boolean commitIndex) {
}
project.setActive(transientProject.isActive());

final Project oldLatestProject;
if(Boolean.TRUE.equals(transientProject.isLatest()) && Boolean.FALSE.equals(project.isLatest())) {
oldLatestProject = getLatestProjectVersion(project.getName());
} else {
oldLatestProject = null;
}
project.setIsLatest(transientProject.isLatest());

if (transientProject.getParent() != null && transientProject.getParent().getUuid() != null) {
if (project.getUuid().equals(transientProject.getParent().getUuid())) {
throw new IllegalArgumentException("A project cannot select itself as a parent");
Expand All @@ -509,10 +548,17 @@ public Project updateProject(Project transientProject, boolean commitIndex) {
project.setParent(null);
}

final List<Tag> resolvedTags = resolveTags(transientProject.getTags());
bind(project, resolvedTags);
final Project result = callInTransaction(() -> {
// Remove isLatest flag from current latest project version, if this project will be the latest now
if(oldLatestProject != null) {
oldLatestProject.setIsLatest(false);
persist(oldLatestProject);
}

final Project result = persist(project);
final List<Tag> resolvedTags = resolveTags(transientProject.getTags());
bind(project, resolvedTags);
return persist(project);
});
return result;
}

Expand All @@ -526,10 +572,11 @@ public Project clone(
final boolean includeServices,
final boolean includeAuditHistory,
final boolean includeACL,
final boolean includePolicyViolations
final boolean includePolicyViolations,
final boolean makeCloneLatest
) {
final AtomicReference<Project> oldLatestProject = new AtomicReference<>();
final var jsonMapper = new JsonMapper();

return callInTransaction(() -> {
final Project source = getObjectByUuid(Project.class, from, Project.FetchGroup.ALL.name());
if (source == null) {
Expand All @@ -543,6 +590,11 @@ public Project clone(
Project was supposed to be cloned to version %s, \
but that version already exists""".formatted(newVersion));
}
if(makeCloneLatest) {
oldLatestProject.set(source.isLatest() ? source : getLatestProjectVersion(source.getName()));
} else {
oldLatestProject.set(null);
}
Project project = new Project();
project.setAuthors(source.getAuthors());
project.setManufacturer(source.getManufacturer());
Expand All @@ -554,13 +606,19 @@ public Project clone(
project.setVersion(newVersion);
project.setClassifier(source.getClassifier());
project.setActive(source.isActive());
project.setIsLatest(makeCloneLatest);
project.setCpe(source.getCpe());
project.setPurl(source.getPurl());
project.setSwidTagId(source.getSwidTagId());
if (source.getDirectDependencies() != null && includeComponents && includeServices) {
project.setDirectDependencies(source.getDirectDependencies());
}
project.setParent(source.getParent());
// Remove isLatest flag from current latest project version, if this project will be the latest now
if(oldLatestProject.get() != null) {
oldLatestProject.get().setIsLatest(false);
persist(oldLatestProject.get());
}
project = persist(project);

if (source.getMetadata() != null) {
Expand Down
19 changes: 16 additions & 3 deletions src/main/java/org/dependencytrack/persistence/QueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,10 @@ public PaginatedResult getProjects(final Team team, final boolean excludeInactiv
return getProjectQueryManager().getProjects(team, excludeInactive, bypass, onlyRoot);
}

public Project getLatestProjectVersion(final String name) {
return getProjectQueryManager().getLatestProjectVersion(name);
}

public PaginatedResult getProjectsWithoutDescendantsOf(final boolean excludeInactive, final Project project) {
return getProjectQueryManager().getProjectsWithoutDescendantsOf(excludeInactive, project);
}
Expand Down Expand Up @@ -625,6 +629,11 @@ public Project createProject(final Project project, List<Tag> tags, boolean comm
return getProjectQueryManager().createProject(project, tags, commitIndex);
}

public Project createProject(String name, String description, String version, List<Tag> tags, Project parent,
PackageURL purl, boolean active, boolean isLatest, boolean commitIndex) {
return getProjectQueryManager().createProject(name, description, version, tags, parent, purl, active, isLatest, commitIndex);
}

public Project updateProject(Project transientProject, boolean commitIndex) {
return getProjectQueryManager().updateProject(transientProject, commitIndex);
}
Expand All @@ -635,9 +644,9 @@ public boolean updateNewProjectACL(Project transientProject, Principal principal

public Project clone(UUID from, String newVersion, boolean includeTags, boolean includeProperties,
boolean includeComponents, boolean includeServices, boolean includeAuditHistory,
boolean includeACL, boolean includePolicyViolations) {
boolean includeACL, boolean includePolicyViolations, boolean makeCloneLatest) {
return getProjectQueryManager().clone(from, newVersion, includeTags, includeProperties,
includeComponents, includeServices, includeAuditHistory, includeACL, includePolicyViolations);
includeComponents, includeServices, includeAuditHistory, includeACL, includePolicyViolations, makeCloneLatest);
}

public Project updateLastBomImport(Project p, Date date, String bomFormat) {
Expand Down Expand Up @@ -792,7 +801,11 @@ public Policy getPolicy(final String name) {
}

public Policy createPolicy(String name, Policy.Operator operator, Policy.ViolationState violationState) {
return getPolicyQueryManager().createPolicy(name, operator, violationState);
return this.createPolicy(name, operator, violationState, false);
}

public Policy createPolicy(String name, Policy.Operator operator, Policy.ViolationState violationState, boolean onlyLatestProjectVersion) {
return getPolicyQueryManager().createPolicy(name, operator, violationState, onlyLatestProjectVersion);
}

public void removeProjectFromPolicies(final Project project) {
Expand Down
Loading

0 comments on commit 74a814e

Please sign in to comment.