Skip to content

Commit 5902e3d

Browse files
committed
Ensure tag name uniqueness
Signed-off-by: nscuro <[email protected]>
1 parent 4ba30a9 commit 5902e3d

24 files changed

+293
-143
lines changed

src/main/java/org/dependencytrack/integrations/kenna/KennaDataTransformer.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.HashMap;
3737
import java.util.List;
3838
import java.util.Map;
39+
import java.util.Set;
3940

4041
import static org.dependencytrack.persistence.jdbi.JdbiFactory.withJdbiHandle;
4142

@@ -115,7 +116,7 @@ private JSONObject generateKdiAsset(final Project project, final String external
115116
asset.put("application", application);
116117
asset.put("external_id", externalId);
117118
// If the project has tags, add them to the KDI
118-
final List<Tag> tags = project.getTags();
119+
final Set<Tag> tags = project.getTags();
119120
if (CollectionUtils.isNotEmpty(tags)) {
120121
final ArrayList<String> tagArray = new ArrayList<>();
121122
for (final Tag tag: tags) {

src/main/java/org/dependencytrack/model/NotificationRule.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,9 @@ public class NotificationRule implements Serializable {
119119
private List<Project> projects;
120120

121121
@Persistent(table = "NOTIFICATIONRULE_TAGS", defaultFetchGroup = "true", mappedBy = "notificationRules")
122-
@Join(column = "NOTIFICATIONRULE_ID", foreignKey = "NOTIFICATIONRULE_TAGS_NOTIFICATIONRULE_FK", deleteAction = ForeignKeyAction.CASCADE)
122+
@Join(column = "NOTIFICATIONRULE_ID", primaryKey = "NOTIFICATIONRULE_TAGS_PK", foreignKey = "NOTIFICATIONRULE_TAGS_NOTIFICATIONRULE_FK", deleteAction = ForeignKeyAction.CASCADE)
123123
@Element(column = "TAG_ID", foreignKey = "NOTIFICATIONRULE_TAGS_TAG_FK", deleteAction = ForeignKeyAction.CASCADE)
124-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
125-
private List<Tag> tags;
124+
private Set<Tag> tags;
126125

127126
@Persistent(table = "NOTIFICATIONRULE_TEAMS", defaultFetchGroup = "true")
128127
@Join(column = "NOTIFICATIONRULE_ID", primaryKey = "NOTIFICATIONRULE_TEAMS_PK", foreignKey = "NOTIFICATIONRULE_TEAMS_NOTIFICATIONRULE_FK", deleteAction = ForeignKeyAction.CASCADE)
@@ -223,11 +222,11 @@ public void setProjects(List<Project> projects) {
223222
this.projects = projects;
224223
}
225224

226-
public List<Tag> getTags() {
225+
public Set<Tag> getTags() {
227226
return tags;
228227
}
229228

230-
public void setTags(final List<Tag> tags) {
229+
public void setTags(final Set<Tag> tags) {
231230
this.tags = tags;
232231
}
233232

src/main/java/org/dependencytrack/model/Policy.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2424
import com.fasterxml.jackson.annotation.JsonInclude;
2525

26+
import jakarta.validation.constraints.NotBlank;
27+
import jakarta.validation.constraints.NotNull;
28+
import jakarta.validation.constraints.Pattern;
29+
import jakarta.validation.constraints.Size;
2630
import javax.jdo.annotations.Column;
2731
import javax.jdo.annotations.Element;
2832
import javax.jdo.annotations.Extension;
@@ -35,13 +39,10 @@
3539
import javax.jdo.annotations.Persistent;
3640
import javax.jdo.annotations.PrimaryKey;
3741
import javax.jdo.annotations.Unique;
38-
import jakarta.validation.constraints.NotBlank;
39-
import jakarta.validation.constraints.NotNull;
40-
import jakarta.validation.constraints.Pattern;
41-
import jakarta.validation.constraints.Size;
4242
import java.io.Serializable;
4343
import java.util.ArrayList;
4444
import java.util.List;
45+
import java.util.Set;
4546
import java.util.UUID;
4647

4748
/**
@@ -122,10 +123,9 @@ public enum ViolationState {
122123
* A list of zero-to-n tags
123124
*/
124125
@Persistent(table = "POLICY_TAGS", defaultFetchGroup = "true", mappedBy = "policies")
125-
@Join(column = "POLICY_ID", foreignKey = "POLICY_TAGS_POLICY_FK", deleteAction = ForeignKeyAction.CASCADE)
126+
@Join(column = "POLICY_ID", primaryKey = "POLICY_TAGS_PK", foreignKey = "POLICY_TAGS_POLICY_FK", deleteAction = ForeignKeyAction.CASCADE)
126127
@Element(column = "TAG_ID", foreignKey = "POLICY_TAGS_TAG_FK", deleteAction = ForeignKeyAction.CASCADE)
127-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
128-
private List<Tag> tags;
128+
private Set<Tag> tags;
129129

130130
/**
131131
* The unique identifier of the object.
@@ -206,11 +206,11 @@ public boolean isGlobal() {
206206
return (projects == null || projects.size() == 0) && (tags == null || tags.size() == 0);
207207
}
208208

209-
public List<Tag> getTags() {
209+
public Set<Tag> getTags() {
210210
return tags;
211211
}
212212

213-
public void setTags(List<Tag> tags) {
213+
public void setTags(Set<Tag> tags) {
214214
this.tags = tags;
215215
}
216216

src/main/java/org/dependencytrack/model/Project.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,9 @@ public enum FetchGroup {
265265
private List<ProjectProperty> properties;
266266

267267
@Persistent(table = "PROJECTS_TAGS", defaultFetchGroup = "true", mappedBy = "projects")
268-
@Join(column = "PROJECT_ID", foreignKey = "PROJECTS_TAGS_PROJECT_FK", deleteAction = ForeignKeyAction.CASCADE)
268+
@Join(column = "PROJECT_ID", primaryKey = "PROJECTS_TAGS_PK", foreignKey = "PROJECTS_TAGS_PROJECT_FK", deleteAction = ForeignKeyAction.CASCADE)
269269
@Element(column = "TAG_ID", foreignKey = "PROJECTS_TAGS_TAG_FK", deleteAction = ForeignKeyAction.CASCADE)
270-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
271-
private List<Tag> tags;
270+
private Set<Tag> tags;
272271

273272
/**
274273
* Convenience field which will contain the date of the last entry in the {@link Bom} table
@@ -487,11 +486,11 @@ public void setProperties(List<ProjectProperty> properties) {
487486
this.properties = properties;
488487
}
489488

490-
public List<Tag> getTags() {
489+
public Set<Tag> getTags() {
491490
return tags;
492491
}
493492

494-
public void setTags(List<Tag> tags) {
493+
public void setTags(Set<Tag> tags) {
495494
this.tags = tags;
496495
}
497496

src/main/java/org/dependencytrack/model/Tag.java

+22-26
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,18 @@
2424
import com.fasterxml.jackson.annotation.JsonInclude;
2525
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2626

27+
import jakarta.validation.constraints.NotBlank;
28+
import jakarta.validation.constraints.Pattern;
29+
import jakarta.validation.constraints.Size;
2730
import javax.jdo.annotations.Column;
28-
import javax.jdo.annotations.Extension;
2931
import javax.jdo.annotations.IdGeneratorStrategy;
30-
import javax.jdo.annotations.Order;
32+
import javax.jdo.annotations.Index;
3133
import javax.jdo.annotations.PersistenceCapable;
3234
import javax.jdo.annotations.Persistent;
3335
import javax.jdo.annotations.PrimaryKey;
34-
import jakarta.validation.constraints.NotBlank;
35-
import jakarta.validation.constraints.Pattern;
36-
import jakarta.validation.constraints.Size;
3736
import java.io.Serializable;
38-
import java.util.List;
3937
import java.util.Objects;
38+
import java.util.Set;
4039

4140
/**
4241
* Model for assigning tags to specific objects.
@@ -64,6 +63,7 @@ public Tag(final String name) {
6463

6564
@Persistent
6665
@Column(name = "NAME", allowsNull = "false")
66+
@Index(name = "TAG_NAME_IDX", unique = "true")
6767
@NotBlank
6868
@Size(min = 1, max = 255)
6969
@JsonDeserialize(using = TrimmedStringDeserializer.class)
@@ -72,23 +72,19 @@ public Tag(final String name) {
7272

7373
@Persistent
7474
@JsonIgnore
75-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
76-
private List<Policy> policies;
75+
private Set<Policy> policies;
7776

7877
@Persistent
7978
@JsonIgnore
80-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
81-
private List<Project> projects;
79+
private Set<Project> projects;
8280

8381
@Persistent
8482
@JsonIgnore
85-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "vulnId ASC"))
86-
private List<Vulnerability> vulnerabilities;
83+
private Set<Vulnerability> vulnerabilities;
8784

8885
@Persistent
8986
@JsonIgnore
90-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
91-
private List<NotificationRule> notificationRules;
87+
private Set<NotificationRule> notificationRules;
9288

9389
public long getId() {
9490
return id;
@@ -106,48 +102,48 @@ public void setName(String name) {
106102
this.name = name;
107103
}
108104

109-
public List<Policy> getPolicies() {
105+
public Set<Policy> getPolicies() {
110106
return policies;
111107
}
112108

113-
public void setPolicies(List<Policy> policies) {
109+
public void setPolicies(Set<Policy> policies) {
114110
this.policies = policies;
115111
}
116112

117-
public List<Project> getProjects() {
113+
public Set<Project> getProjects() {
118114
return projects;
119115
}
120116

121-
public void setProjects(List<Project> projects) {
117+
public void setProjects(Set<Project> projects) {
122118
this.projects = projects;
123119
}
124120

125-
public List<Vulnerability> getVulnerabilities() {
121+
public Set<Vulnerability> getVulnerabilities() {
126122
return vulnerabilities;
127123
}
128124

129-
public void setVulnerabilities(List<Vulnerability> vulnerabilities) {
125+
public void setVulnerabilities(Set<Vulnerability> vulnerabilities) {
130126
this.vulnerabilities = vulnerabilities;
131127
}
132128

133-
public List<NotificationRule> getNotificationRules() {
129+
public Set<NotificationRule> getNotificationRules() {
134130
return notificationRules;
135131
}
136132

137-
public void setNotificationRules(final List<NotificationRule> notificationRules) {
133+
public void setNotificationRules(final Set<NotificationRule> notificationRules) {
138134
this.notificationRules = notificationRules;
139135
}
140136

141137
@Override
142-
public boolean equals(Object object) {
143-
if (object instanceof Tag) {
144-
return this.id == ((Tag) object).id;
138+
public boolean equals(Object other) {
139+
if (other instanceof final Tag otherTag) {
140+
return Objects.equals(this.name, otherTag.name);
145141
}
146142
return false;
147143
}
148144

149145
@Override
150146
public int hashCode() {
151-
return Objects.hash(id);
147+
return Objects.hash(name);
152148
}
153149
}

src/main/java/org/dependencytrack/model/Vulnerability.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@
2626
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2727
import io.swagger.v3.oas.annotations.media.ArraySchema;
2828
import io.swagger.v3.oas.annotations.media.Schema;
29-
import jakarta.validation.constraints.NotBlank;
30-
import jakarta.validation.constraints.NotNull;
31-
import jakarta.validation.constraints.Pattern;
32-
import jakarta.validation.constraints.Size;
3329
import org.dependencytrack.persistence.CollectionIntegerConverter;
3430
import org.dependencytrack.resources.v1.serializers.CweDeserializer;
3531
import org.dependencytrack.resources.v1.serializers.CweSerializer;
3632
import org.dependencytrack.resources.v1.serializers.Iso8601DateSerializer;
3733
import org.dependencytrack.resources.v1.vo.AffectedComponent;
3834

35+
import jakarta.validation.constraints.NotBlank;
36+
import jakarta.validation.constraints.NotNull;
37+
import jakarta.validation.constraints.Pattern;
38+
import jakarta.validation.constraints.Size;
3939
import javax.jdo.annotations.Column;
4040
import javax.jdo.annotations.Convert;
4141
import javax.jdo.annotations.Element;
@@ -59,6 +59,7 @@
5959
import java.util.Date;
6060
import java.util.List;
6161
import java.util.Objects;
62+
import java.util.Set;
6263
import java.util.UUID;
6364

6465
/**
@@ -327,10 +328,9 @@ public static boolean isKnownSource(String source) {
327328
private UUID uuid;
328329

329330
@Persistent(table = "VULNERABILITIES_TAGS", defaultFetchGroup = "true", mappedBy = "vulnerabilities")
330-
@Join(column = "VULNERABILITY_ID", foreignKey = "VULNERABILITIES_TAGS_VULNERABILITY_FK", deleteAction = ForeignKeyAction.CASCADE)
331+
@Join(column = "VULNERABILITY_ID", primaryKey = "VULNERABILITIES_TAGS_PK", foreignKey = "VULNERABILITIES_TAGS_VULNERABILITY_FK", deleteAction = ForeignKeyAction.CASCADE)
331332
@Element(column = "TAG_ID", foreignKey = "VULNERABILITIES_TAGS_TAG_FK", deleteAction = ForeignKeyAction.CASCADE)
332-
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
333-
private List<Tag> tags;
333+
private Set<Tag> tags;
334334

335335
private transient Epss epss;
336336

@@ -713,11 +713,11 @@ public BigDecimal getEpssPercentile() {
713713
return epss != null ? epss.getPercentile() : null;
714714
}
715715

716-
public List<Tag> getTags() {
716+
public Set<Tag> getTags() {
717717
return tags;
718718
}
719719

720-
public void setTags(List<Tag> tags) {
720+
public void setTags(Set<Tag> tags) {
721721
this.tags = tags;
722722
}
723723
}

src/main/java/org/dependencytrack/parser/dependencytrack/NotificationModelConverter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ private static Project convert(final org.dependencytrack.model.Project project)
339339
Optional.ofNullable(project.getDescription()).ifPresent(builder::setDescription);
340340
Optional.ofNullable(project.getPurl()).map(PackageURL::canonicalize).ifPresent(builder::setPurl);
341341
Optional.ofNullable(project.getTags())
342-
.orElseGet(Collections::emptyList).stream()
342+
.orElseGet(Collections::emptySet).stream()
343343
.map(Tag::getName)
344344
.forEach(builder::addTags);
345345

src/main/java/org/dependencytrack/persistence/NotificationQueryManager.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@
3131

3232
import javax.jdo.PersistenceManager;
3333
import javax.jdo.Query;
34-
import java.util.ArrayList;
3534
import java.util.Collection;
35+
import java.util.HashSet;
3636
import java.util.List;
37+
import java.util.Set;
3738

3839
import static org.dependencytrack.util.PersistenceUtil.assertPersistent;
3940
import static org.dependencytrack.util.PersistenceUtil.assertPersistentAll;
@@ -281,8 +282,8 @@ public boolean bind(final NotificationRule notificationRule, final Collection<Ta
281282
notificationRule.getTags().add(tag);
282283

283284
if (tag.getNotificationRules() == null) {
284-
tag.setNotificationRules(new ArrayList<>(List.of(notificationRule)));
285-
} else if (!tag.getNotificationRules().contains(notificationRule)) {
285+
tag.setNotificationRules(new HashSet<>(Set.of(notificationRule)));
286+
} else {
286287
tag.getNotificationRules().add(notificationRule);
287288
}
288289

src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
import java.util.Collection;
4040
import java.util.Date;
4141
import java.util.HashMap;
42+
import java.util.HashSet;
4243
import java.util.List;
4344
import java.util.Map;
45+
import java.util.Set;
4446
import java.util.UUID;
4547

4648
import static org.dependencytrack.util.PersistenceUtil.assertPersistent;
@@ -647,8 +649,8 @@ public boolean bind(final Policy policy, final Collection<Tag> tags, final boole
647649
if (!policy.getTags().contains(tag)) {
648650
policy.getTags().add(tag);
649651
if (tag.getPolicies() == null) {
650-
tag.setPolicies(new ArrayList<>(List.of(policy)));
651-
} else if (!tag.getPolicies().contains(policy)) {
652+
tag.setPolicies(new HashSet<>(Set.of(policy)));
653+
} else {
652654
tag.getPolicies().add(policy);
653655
}
654656
modified = true;

0 commit comments

Comments
 (0)