From cfa3230ea64ee46221ae3fcd0e9c1171d7ae33cf Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 22 Jan 2024 22:24:13 -0500 Subject: [PATCH] chore(db): apply active record validation constraints (#257) * chore(db): apply active record validation constraints * add discovery plugins to universe tree on registration --- .../io/cryostat/credentials/Credential.java | 12 ++++++++-- .../java/io/cryostat/discovery/Discovery.java | 12 +++++++++- .../io/cryostat/discovery/DiscoveryNode.java | 9 +++++++- .../cryostat/discovery/DiscoveryPlugin.java | 5 +++++ .../cryostat/recordings/ActiveRecording.java | 22 ++++++++++--------- src/main/java/io/cryostat/rules/Rule.java | 16 +++++++++----- src/main/java/io/cryostat/rules/Rules.java | 3 +++ src/main/java/io/cryostat/targets/Target.java | 21 +++++++++++++----- 8 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/cryostat/credentials/Credential.java b/src/main/java/io/cryostat/credentials/Credential.java index af0d4f9a7..5f24dc8cd 100644 --- a/src/main/java/io/cryostat/credentials/Credential.java +++ b/src/main/java/io/cryostat/credentials/Credential.java @@ -19,6 +19,7 @@ import io.cryostat.ws.MessagingServer; import io.cryostat.ws.Notification; +import com.fasterxml.jackson.annotation.JsonProperty; import io.quarkus.hibernate.orm.panache.PanacheEntity; import io.vertx.mutiny.core.eventbus.EventBus; import jakarta.enterprise.context.ApplicationScoped; @@ -33,6 +34,8 @@ import jakarta.persistence.PostPersist; import jakarta.persistence.PostRemove; import jakarta.persistence.PostUpdate; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import org.hibernate.annotations.ColumnTransformer; import org.projectnessie.cel.tools.ScriptException; @@ -46,18 +49,23 @@ public class Credential extends PanacheEntity { @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(name = "matchExpression") + @NotNull public MatchExpression matchExpression; @ColumnTransformer( read = "pgp_sym_decrypt(username, current_setting('encrypt.key'))", write = "pgp_sym_encrypt(?, current_setting('encrypt.key'))") - @Column(nullable = false, updatable = false, columnDefinition = "bytea") + @Column(updatable = false, columnDefinition = "bytea") + @NotBlank + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public String username; @ColumnTransformer( read = "pgp_sym_decrypt(password, current_setting('encrypt.key'))", write = "pgp_sym_encrypt(?, current_setting('encrypt.key'))") - @Column(nullable = false, updatable = false, columnDefinition = "bytea") + @Column(updatable = false, columnDefinition = "bytea") + @NotBlank + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public String password; @ApplicationScoped diff --git a/src/main/java/io/cryostat/discovery/Discovery.java b/src/main/java/io/cryostat/discovery/Discovery.java index 9de683c48..afac28aa5 100644 --- a/src/main/java/io/cryostat/discovery/Discovery.java +++ b/src/main/java/io/cryostat/discovery/Discovery.java @@ -127,8 +127,11 @@ public Map register(JsonObject body) throws URISyntaxException { DiscoveryPlugin plugin = new DiscoveryPlugin(); plugin.callback = callbackUri; plugin.realm = DiscoveryNode.environment(realmName, DiscoveryNode.REALM); + plugin.builtin = false; plugin.persist(); + DiscoveryNode.getUniverse().children.add(plugin.realm); + return Map.of( "meta", Map.of( @@ -155,7 +158,13 @@ public Map> publish( plugin.realm.children.clear(); plugin.persist(); plugin.realm.children.addAll(body); - body.forEach(b -> b.persist()); + body.forEach( + b -> { + if (b.target != null) { + b.target.discoveryNode = b; + } + b.persist(); + }); plugin.persist(); return Map.of( @@ -176,6 +185,7 @@ public Map> deregister(@RestPath UUID id, @RestQuery throw new ForbiddenException(); } plugin.delete(); + DiscoveryNode.getUniverse().children.remove(plugin.realm); return Map.of( "meta", Map.of( diff --git a/src/main/java/io/cryostat/discovery/DiscoveryNode.java b/src/main/java/io/cryostat/discovery/DiscoveryNode.java index 14ecfecc3..46ccf2456 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryNode.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryNode.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonView; import io.quarkus.hibernate.orm.panache.PanacheEntity; import io.vertx.mutiny.core.eventbus.EventBus; +import jakarta.annotation.Nullable; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.persistence.CascadeType; @@ -43,6 +44,8 @@ import jakarta.persistence.PostRemove; import jakarta.persistence.PostUpdate; import jakarta.persistence.PrePersist; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; import org.jboss.logging.Logger; @@ -58,19 +61,22 @@ public class DiscoveryNode extends PanacheEntity { @Column(unique = false, nullable = false, updatable = false) @JsonView(Views.Flat.class) + @NotBlank public String name; @Column(unique = false, nullable = false, updatable = false) @JsonView(Views.Flat.class) + @NotBlank public String nodeType; @JdbcTypeCode(SqlTypes.JSON) - @Column(nullable = false) + @NotNull @JsonView(Views.Flat.class) public Map labels = new HashMap<>(); @OneToMany(fetch = FetchType.LAZY, orphanRemoval = true) @JsonView(Views.Nested.class) + @Nullable public List children = new ArrayList<>(); @OneToOne( @@ -78,6 +84,7 @@ public class DiscoveryNode extends PanacheEntity { cascade = {CascadeType.ALL}, fetch = FetchType.LAZY, orphanRemoval = true) + @Nullable @JsonInclude(value = Include.NON_NULL) @JsonView(Views.Flat.class) public Target target; diff --git a/src/main/java/io/cryostat/discovery/DiscoveryPlugin.java b/src/main/java/io/cryostat/discovery/DiscoveryPlugin.java index c49f3c986..b893ebd9e 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryPlugin.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryPlugin.java @@ -24,6 +24,7 @@ import io.cryostat.credentials.Credential; +import com.fasterxml.jackson.annotation.JsonProperty; import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -37,6 +38,7 @@ import jakarta.persistence.Id; import jakarta.persistence.OneToOne; import jakarta.persistence.PrePersist; +import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -56,6 +58,7 @@ public class DiscoveryPlugin extends PanacheEntityBase { @Column(name = "id") @GeneratedValue(generator = "UUID") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + @NotNull public UUID id; @OneToOne( @@ -63,12 +66,14 @@ public class DiscoveryPlugin extends PanacheEntityBase { cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.LAZY) + @NotNull public DiscoveryNode realm; @Column(unique = true, updatable = false) @Convert(converter = UriConverter.class) public URI callback; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public boolean builtin; @ApplicationScoped diff --git a/src/main/java/io/cryostat/recordings/ActiveRecording.java b/src/main/java/io/cryostat/recordings/ActiveRecording.java index 913dc6652..f1c07b185 100644 --- a/src/main/java/io/cryostat/recordings/ActiveRecording.java +++ b/src/main/java/io/cryostat/recordings/ActiveRecording.java @@ -35,7 +35,6 @@ import io.vertx.mutiny.core.eventbus.EventBus; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; import jakarta.persistence.FetchType; @@ -49,6 +48,9 @@ import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import jakarta.transaction.Transactional; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; import jdk.jfr.RecordingState; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; @@ -65,22 +67,22 @@ public class ActiveRecording extends PanacheEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "target_id") + @NotNull public Target target; - @Column(nullable = false) - public String name; + @NotBlank public String name; - public long remoteId; - public RecordingState state; - public long duration; - public long startTime; + @PositiveOrZero public long remoteId; + @NotNull public RecordingState state; + @PositiveOrZero public long duration; + @PositiveOrZero public long startTime; public boolean continuous; public boolean toDisk; - public long maxSize; - public long maxAge; + @PositiveOrZero public long maxSize; + @PositiveOrZero public long maxAge; @JdbcTypeCode(SqlTypes.JSON) - @Column(nullable = false) + @NotNull public Metadata metadata; public static ActiveRecording from(Target target, LinkedRecordingDescriptor descriptor) { diff --git a/src/main/java/io/cryostat/rules/Rule.java b/src/main/java/io/cryostat/rules/Rule.java index 3ed9a4aa4..0e55c3bf9 100644 --- a/src/main/java/io/cryostat/rules/Rule.java +++ b/src/main/java/io/cryostat/rules/Rule.java @@ -21,6 +21,7 @@ import io.cryostat.ws.MessagingServer; import io.cryostat.ws.Notification; +import com.fasterxml.jackson.annotation.JsonIgnore; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.quarkus.hibernate.orm.panache.PanacheEntity; import io.vertx.mutiny.core.eventbus.EventBus; @@ -38,6 +39,7 @@ import jakarta.persistence.PostUpdate; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; @Entity @@ -50,17 +52,19 @@ public class Rule extends PanacheEntity { public static final String RULE_ADDRESS = "io.cryostat.rules.Rule"; - @Column(unique = true, nullable = false, updatable = false) + @Column(unique = true, updatable = false) + @NotBlank public String name; - public String description; + @NotNull public String description; @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(name = "matchExpression") + @NotNull public MatchExpression matchExpression; @Column(nullable = false) - @NotBlank(message = "eventSpecifier cannot be blank") + @NotBlank public String eventSpecifier; @PositiveOrZero(message = "archivalPeriodSeconds must be positive or zero") @@ -72,10 +76,10 @@ public class Rule extends PanacheEntity { @PositiveOrZero(message = "archivalPeriodSeconds must be positive or zero") public int preservedArchives; - @Min(message = "maxAgeSeconds must be greater than 0 or -1", value = -1) + @Min(message = "maxAgeSeconds must be greater than -1", value = -1) public int maxAgeSeconds; - @Min(message = "maxAgeSeconds must be greater than 0 or -1", value = -1) + @Min(message = "maxAgeSeconds must be greater than -1", value = -1) public int maxSizeBytes; public boolean enabled; @@ -84,11 +88,13 @@ public String getName() { return this.name; } + @JsonIgnore public String getRecordingName() { // FIXME do something other than simply prepending "auto_" return String.format("auto_%s", name); } + @JsonIgnore public boolean isArchiver() { return preservedArchives > 0 && archivalPeriodSeconds > 0; } diff --git a/src/main/java/io/cryostat/rules/Rules.java b/src/main/java/io/cryostat/rules/Rules.java index 754ce4cc7..91c2f4ca6 100644 --- a/src/main/java/io/cryostat/rules/Rules.java +++ b/src/main/java/io/cryostat/rules/Rules.java @@ -71,6 +71,9 @@ public RestResponse create(Rule rule) { if (ruleExists) { throw new RuleExistsException(rule.name); } + if (rule.description == null) { + rule.description = ""; + } rule.persist(); return ResponseBuilder.create( Response.Status.CREATED, diff --git a/src/main/java/io/cryostat/targets/Target.java b/src/main/java/io/cryostat/targets/Target.java index f96085453..1dd7b1e6b 100644 --- a/src/main/java/io/cryostat/targets/Target.java +++ b/src/main/java/io/cryostat/targets/Target.java @@ -36,6 +36,7 @@ import io.cryostat.ws.Notification; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.quarkus.hibernate.orm.panache.PanacheEntity; import io.quarkus.vertx.ConsumeEvent; @@ -54,6 +55,8 @@ import jakarta.persistence.PostUpdate; import jakarta.persistence.PrePersist; import jakarta.transaction.Transactional; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import org.apache.commons.lang3.StringUtils; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; @@ -65,40 +68,46 @@ public class Target extends PanacheEntity { public static final String TARGET_JVM_DISCOVERY = "TargetJvmDiscovery"; - @Column(unique = true, nullable = false, updatable = false) + @Column(unique = true, updatable = false) + @NotNull public URI connectUrl; - @Column(unique = true, nullable = false) + @Column(unique = true) + @NotBlank public String alias; public String jvmId; @JdbcTypeCode(SqlTypes.JSON) - @Column(nullable = false) + @NotNull public Map labels = new HashMap<>(); @JdbcTypeCode(SqlTypes.JSON) - @Column(nullable = false) + @NotNull public Annotations annotations = new Annotations(); - @JsonIgnore @OneToMany( mappedBy = "target", cascade = {CascadeType.ALL}, orphanRemoval = true) + @NotNull + @JsonIgnore public List activeRecordings = new ArrayList<>(); - @JsonIgnore @OneToOne( cascade = {CascadeType.ALL}, orphanRemoval = true) @JoinColumn(name = "discoveryNode") + @NotNull + @JsonIgnore public DiscoveryNode discoveryNode; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public boolean isAgent() { return Set.of("http", "https", "cryostat-agent").contains(connectUrl.getScheme()); } + @JsonIgnore public String targetId() { return this.connectUrl.toString(); }