-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(matchexpr): refactor MatchExpression as its own resource (#39)
- Loading branch information
1 parent
7382091
commit b598524
Showing
12 changed files
with
505 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
170 changes: 170 additions & 0 deletions
170
src/main/java/io/cryostat/expressions/MatchExpression.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/* | ||
* Copyright The Cryostat Authors. | ||
* | ||
* Licensed 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 | ||
* | ||
* http://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 io.cryostat.expressions; | ||
|
||
import java.util.Collection; | ||
import java.util.HashSet; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
import io.cryostat.targets.Target; | ||
import io.cryostat.ws.MessagingServer; | ||
import io.cryostat.ws.Notification; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonValue; | ||
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.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EntityListeners; | ||
import jakarta.persistence.PostPersist; | ||
import jakarta.persistence.PostRemove; | ||
import jakarta.persistence.PostUpdate; | ||
import jakarta.persistence.PrePersist; | ||
import jakarta.validation.ValidationException; | ||
import jakarta.validation.constraints.NotBlank; | ||
import org.jboss.logging.Logger; | ||
import org.projectnessie.cel.tools.ScriptException; | ||
|
||
@Entity | ||
@EntityListeners(MatchExpression.Listener.class) | ||
public class MatchExpression extends PanacheEntity { | ||
public static final String EXPRESSION_ADDRESS = "io.cryostat.expressions.MatchExpression"; | ||
|
||
@Column(updatable = false, nullable = false) | ||
@NotBlank | ||
// TODO | ||
// when serializing matchExpressions (ex. as a field of Rules), just use the script as the | ||
// serialized form of the expression object. This is for 2.x compat only | ||
@JsonValue | ||
public String script; | ||
|
||
MatchExpression() { | ||
this.script = null; | ||
} | ||
|
||
@JsonCreator | ||
public MatchExpression(String script) { | ||
this.script = script; | ||
} | ||
|
||
@ApplicationScoped | ||
public static class TargetMatcher { | ||
@Inject MatchExpressionEvaluator evaluator; | ||
@Inject Logger logger; | ||
|
||
public MatchedExpression match(MatchExpression expr, Collection<Target> targets) | ||
throws ScriptException { | ||
Set<Target> matches = | ||
new HashSet<>(Optional.ofNullable(targets).orElseGet(() -> Set.of())); | ||
var it = matches.iterator(); | ||
while (it.hasNext()) { | ||
if (!evaluator.applies(expr, it.next())) { | ||
it.remove(); | ||
} | ||
} | ||
return new MatchedExpression(expr, matches); | ||
} | ||
|
||
public MatchedExpression match(MatchExpression expr) throws ScriptException { | ||
return match(expr, Target.listAll()); | ||
} | ||
} | ||
|
||
public static record MatchedExpression( | ||
@Nullable Long id, String expression, Collection<Target> targets) { | ||
public MatchedExpression { | ||
Objects.requireNonNull(expression); | ||
Objects.requireNonNull(targets); | ||
} | ||
|
||
MatchedExpression(MatchExpression expr, Collection<Target> targets) { | ||
this(expr.id, expr.script, targets); | ||
} | ||
} | ||
|
||
@ApplicationScoped | ||
static class Listener { | ||
@Inject EventBus bus; | ||
@Inject MatchExpressionEvaluator evaluator; | ||
@Inject Logger logger; | ||
|
||
@PrePersist | ||
public void prePersist(MatchExpression expr) throws ValidationException { | ||
try { | ||
evaluator.createScript(expr.script); | ||
} catch (Exception e) { | ||
logger.error("Invalid match expression", e); | ||
throw new ValidationException(e); | ||
} | ||
} | ||
|
||
@PostPersist | ||
public void postPersist(MatchExpression expr) { | ||
bus.publish( | ||
EXPRESSION_ADDRESS, new ExpressionEvent(ExpressionEventCategory.CREATED, expr)); | ||
notify(ExpressionEventCategory.CREATED, expr); | ||
} | ||
|
||
@PostUpdate | ||
public void postUpdate(MatchExpression expr) { | ||
bus.publish( | ||
EXPRESSION_ADDRESS, new ExpressionEvent(ExpressionEventCategory.UPDATED, expr)); | ||
notify(ExpressionEventCategory.UPDATED, expr); | ||
} | ||
|
||
@PostRemove | ||
public void postRemove(MatchExpression expr) { | ||
bus.publish( | ||
EXPRESSION_ADDRESS, new ExpressionEvent(ExpressionEventCategory.DELETED, expr)); | ||
notify(ExpressionEventCategory.DELETED, expr); | ||
} | ||
|
||
private void notify(ExpressionEventCategory category, MatchExpression expr) { | ||
bus.publish( | ||
MessagingServer.class.getName(), | ||
new Notification(category.getCategory(), expr)); | ||
} | ||
} | ||
|
||
public record ExpressionEvent(ExpressionEventCategory category, MatchExpression expression) { | ||
public ExpressionEvent { | ||
Objects.requireNonNull(category); | ||
Objects.requireNonNull(expression); | ||
} | ||
} | ||
|
||
public enum ExpressionEventCategory { | ||
CREATED("ExpressionCreated"), | ||
UPDATED("ExpressionUpdated"), | ||
DELETED("ExpressionDeleted"); | ||
|
||
private final String name; | ||
|
||
ExpressionEventCategory(String name) { | ||
this.name = name; | ||
} | ||
|
||
public String getCategory() { | ||
return name; | ||
} | ||
} | ||
} |
Oops, something went wrong.