Skip to content
This repository has been archived by the owner on Jul 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #14 from ralf-ueberfuhr-ars/feature/interceptor
Browse files Browse the repository at this point in the history
Introduce interceptor.
  • Loading branch information
ralf-ueberfuhr-ars authored Jun 20, 2024
2 parents caa3eb6 + ad5aa84 commit 0e30a8f
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package de.schulung.sample.quarkus.domain;

import de.schulung.sample.quarkus.domain.events.CustomerCreatedEvent;
import de.schulung.sample.quarkus.shared.interceptors.LogPerformance;
import de.schulung.sample.quarkus.shared.interceptors.FireEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.jboss.logging.Logger;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -19,9 +21,6 @@ public class CustomersService {

private final Map<UUID, Customer> customers = new HashMap<>();

// TODO: can we use an interceptor
private final Event<CustomerCreatedEvent> eventPublisher;

public Stream<Customer> getAll() {
return this.customers
.values()
Expand All @@ -33,10 +32,11 @@ public Stream<Customer> getByState(@NotNull Customer.CustomerState state) {
.filter(c -> c.getState() == state);
}

@LogPerformance(Logger.Level.DEBUG)
@FireEvent(CustomerCreatedEvent.class)
public void createCustomer(@Valid Customer customer) {
customer.setUuid(UUID.randomUUID());
customers.put(customer.getUuid(), customer);
eventPublisher.fire(new CustomerCreatedEvent(customer));
}

public Optional<Customer> getByUuid(@NotNull UUID uuid) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.schulung.sample.quarkus.shared.interceptors;

import lombok.experimental.UtilityClass;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Optional;

@UtilityClass
class AnnotationUtils {
<A extends Annotation> Optional<A> findAnnotation(Method method, Class<A> annotationClass) {
return Optional
.ofNullable(method.getAnnotation(annotationClass))
.or(() -> findAnnotation(method.getDeclaringClass(), annotationClass));
}

<A extends Annotation> Optional<A> findAnnotation(Class<?> clazz, Class<A> annotationClass) {
return Optional
.ofNullable(clazz.getAnnotation(annotationClass));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package de.schulung.sample.quarkus.shared.interceptors;

import jakarta.enterprise.util.Nonbinding;
import jakarta.interceptor.InterceptorBinding;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.lang.annotation.*;

/**
* Annotate a method to get an event fired after method execution.
*/
@Inherited
@Documented
@InterceptorBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FireEvent {

/**
* The event class. This class needs a constructor with the same parameters as the method.
*
* @return the event class
*/
@Nonbinding Class<?> value();

/**
* Whether the event has to be fired synchronously, asynchronously or both.
* Defaults to both.
*
* @return the mode
*/
@Nonbinding FireMode mode() default FireMode.SYNC_AND_ASYNC;

@RequiredArgsConstructor
@Getter(AccessLevel.PACKAGE)
enum FireMode {

ONLY_SYNC(true, false),
ONLY_ASYNC(false, true),
SYNC_AND_ASYNC(true, true);

private final boolean fireSync;
private final boolean fireAsync;

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package de.schulung.sample.quarkus.shared.interceptors;

import jakarta.annotation.Priority;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
import lombok.SneakyThrows;

import java.util.Optional;

@Priority(5)
@Interceptor
@FireEvent(Object.class)
public class FireEventInterceptor {

@Inject
Event<Object> eventPublisher;

@SneakyThrows
private static <T> T createEventObject(InvocationContext invocation, Class<T> eventType) {
return eventType
.getConstructor(invocation.getMethod().getParameterTypes())
.newInstance(invocation.getParameters());
}

@AroundInvoke
public Object fireEvent(InvocationContext invocation) throws Exception {
final Optional<FireEvent> annotation = AnnotationUtils
.findAnnotation(invocation.getMethod(), FireEvent.class);
@SuppressWarnings("unchecked") final Optional<Class<Object>> eventType = AnnotationUtils
.findAnnotation(invocation.getMethod(), FireEvent.class)
.map((FireEvent publishEvent) -> (Class<Object>) publishEvent.value());
final FireEvent.FireMode mode = annotation
.map(FireEvent::mode)
.orElse(FireEvent.FireMode.SYNC_AND_ASYNC);
final Optional<Object> event = eventType
.map(clazz -> createEventObject(invocation, clazz));
// if something is wrong until here, we do not invoke the service's create-method
// now, we invoke the service
final Object result = invocation.proceed();
// if an exception occured, the event is not fired
// now, we fire the event
event.ifPresent(
e -> eventType
.map(eventPublisher::select)
.ifPresent(publisher -> {
// fire synchronous events
if (mode.isFireSync()) {
publisher.fire(e);
}
// if no error occured, fire asynchronous events
if (mode.isFireAsync()) {
publisher.fireAsync(e);
}
})
);
// and we need to return the service's result to the invoker (the controller)
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.schulung.sample.quarkus.shared.interceptors;

import jakarta.enterprise.util.Nonbinding;
import jakarta.interceptor.InterceptorBinding;
import org.jboss.logging.Logger;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@InterceptorBinding
public @interface LogPerformance {

@Nonbinding
Logger.Level value() default Logger.Level.INFO;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package de.schulung.sample.quarkus.shared.interceptors;

import io.quarkus.arc.log.LoggerName;
import jakarta.annotation.Priority;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
import org.jboss.logging.Logger;

@Interceptor
@LogPerformance
@Priority(10)
public class LogPerformanceInterceptor {

@LoggerName("performance")
Logger log;

private Logger.Level findLevel(InvocationContext ic) {
return AnnotationUtils
.findAnnotation(ic.getMethod(), LogPerformance.class)
.map(LogPerformance::value)
.orElse(Logger.Level.INFO);
}

@AroundInvoke
public Object logPerformance(InvocationContext ic) throws Exception {
var methodName = ic.getMethod().getName();
var level = findLevel(ic);
var ts1 = System.currentTimeMillis();
try {
return ic.proceed();
} finally {
var ts2 = System.currentTimeMillis();
log.log(level, "Methode " + methodName + ": " + (ts2 - ts1) + "ms");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
quarkus.log.category."performance".level=DEBUG
quarkus.log.min-level=DEBUG
quarkus.log.console.enable=true
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n
quarkus.log.console.level=DEBUG
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package de.schulung.sample.quarkus.domain;

import de.schulung.sample.quarkus.domain.events.CustomerCreatedEvent;
import jakarta.enterprise.event.Event;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDate;
Expand All @@ -16,8 +13,9 @@
@ExtendWith(MockitoExtension.class)
class CustomersServiceTests {

@Mock
Event<CustomerCreatedEvent> event;
// nicht mehr notwendig wegen @FireEvent interceptor
//@Mock
//Event<CustomerCreatedEvent> event;
@InjectMocks
CustomersService service;

Expand Down

0 comments on commit 0e30a8f

Please sign in to comment.