diff --git a/microservices-modules/micronaut-configuration/README.md b/microservices-modules/micronaut-configuration/README.md new file mode 100644 index 000000000000..29425602829f --- /dev/null +++ b/microservices-modules/micronaut-configuration/README.md @@ -0,0 +1,6 @@ +## Micronaut + +This module contains articles about Micronaut version 4 major. + +### Relevant Articles: +- diff --git a/microservices-modules/micronaut-configuration/pom.xml b/microservices-modules/micronaut-configuration/pom.xml new file mode 100644 index 000000000000..1dc87f6a29c8 --- /dev/null +++ b/microservices-modules/micronaut-configuration/pom.xml @@ -0,0 +1,157 @@ + + + 4.0.0 + com.baeldung.micronaut + micronaut-configuration + 0.1 + ${packaging} + micronaut-configuration + + + com.baeldung + microservices-modules + 1.0.0-SNAPSHOT + + + + + io.micronaut + micronaut-runtime + ${micronaut.version} + + + io.micronaut + micronaut-http-client + ${micronaut.version} + + + io.micronaut + micronaut-inject + ${micronaut.version} + + + io.micronaut + micronaut-http-server-netty + ${micronaut.version} + + + io.micronaut + micronaut-jackson-databind + ${micronaut.version} + + + io.micronaut.validation + micronaut-validation + ${micronaut.validation.version} + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.yaml + snakeyaml + 2.2 + + + + org.projectlombok + lombok + 1.18.34 + provided + true + + + + ch.qos.logback + logback-classic + 1.4.12 + runtime + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + org.junit + junit-bom + ${junit.jupiter.version} + pom + test + + + io.micronaut.test + micronaut-test-junit5 + ${micronaut.test.version} + test + + + io.micronaut.test + micronaut-test-rest-assured + ${micronaut.test.version} + test + + + io.reactivex.rxjava3 + rxjava + 3.1.6 + compile + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${jdk.version} + ${release.version} + + + org.projectlombok + lombok + 1.18.34 + + + io.micronaut + micronaut-inject-java + ${micronaut.version} + + + io.micronaut.validation + micronaut-validation-processor + ${micronaut.validation.version} + + + + -Amicronaut.processing.group=${project.groupId} + -Amicronaut.processing.module=${project.artifactId} + + + + + + + + 17 + 17 + UTF-8 + 17 + 17 + jar + + 4.4.3 + com.baeldung.micronaut.httpfilters.ServerApplication + 4.6.1 + netty + 4.3.0 + 5.9.1 + + diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/ServerApplication.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/ServerApplication.java new file mode 100644 index 000000000000..edabcae286a8 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/ServerApplication.java @@ -0,0 +1,10 @@ +package com.baeldung.micronaut.httpfilters; + +import io.micronaut.runtime.Micronaut; + +public class ServerApplication { + + public static void main(String[] args) { + Micronaut.run(ServerApplication.class, args); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/controller/FilteredController.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/controller/FilteredController.java new file mode 100644 index 000000000000..7b4e033cdaea --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/controller/FilteredController.java @@ -0,0 +1,21 @@ +package com.baeldung.micronaut.httpfilters.controller; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Controller("/filters-annotations") +public class FilteredController { + + @Get("/endpoint1") + public HttpResponse endpoint1() { + return HttpResponse.ok("Endpoint 1"); + } + + @Get("/endpoint2") + public HttpResponse endpoint2() { + return HttpResponse.ok("Endpoint 2"); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/CustomFilter.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/CustomFilter.java new file mode 100644 index 000000000000..d84877740db5 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/CustomFilter.java @@ -0,0 +1,38 @@ +package com.baeldung.micronaut.httpfilters.filters; + +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.CUSTOM_HEADER_KEY; +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.X_TRACE_HEADER_KEY; + +import io.micronaut.core.order.Ordered; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.MutableHttpResponse; +import io.micronaut.http.annotation.RequestFilter; +import io.micronaut.http.annotation.ResponseFilter; +import io.micronaut.http.annotation.ServerFilter; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ServerFilter(patterns = { "**/endpoint*" }) +public class CustomFilter implements Ordered { + + @RequestFilter + @ExecuteOn(TaskExecutors.BLOCKING) + public void filterRequest(HttpRequest request) { + String customRequestHeader = request.getHeaders() + .get(CUSTOM_HEADER_KEY); + log.info("request header: {}", customRequestHeader); + } + + @ResponseFilter + public void filterResponse(MutableHttpResponse res) { + res.getHeaders() + .add(X_TRACE_HEADER_KEY, "true"); + } + + @Override + public int getOrder() { + return 2; + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/PrivilegedUsersEndpointFilter.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/PrivilegedUsersEndpointFilter.java new file mode 100644 index 000000000000..40fe7c947e6f --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/PrivilegedUsersEndpointFilter.java @@ -0,0 +1,33 @@ +package com.baeldung.micronaut.httpfilters.filters; + +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.PRIVILEGED_USER_HEADER_KEY; + +import org.reactivestreams.Publisher; + +import io.micronaut.core.order.Ordered; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.MutableHttpResponse; +import io.micronaut.http.annotation.Filter; +import io.micronaut.http.filter.HttpServerFilter; +import io.micronaut.http.filter.ServerFilterChain; +import io.reactivex.rxjava3.core.Flowable; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Filter(patterns = { "**/*1" }) +public class PrivilegedUsersEndpointFilter implements HttpServerFilter, Ordered { + + @Override + public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { + log.info("Privileged user was filtered"); + + return Flowable.fromPublisher(chain.proceed(request)) + .doOnNext(response -> response.getHeaders() + .add(PRIVILEGED_USER_HEADER_KEY, "true")); + } + + @Override + public int getOrder() { + return 3; + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/RequestIDFilter.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/RequestIDFilter.java new file mode 100644 index 000000000000..519215a1f115 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/filters/RequestIDFilter.java @@ -0,0 +1,42 @@ +package com.baeldung.micronaut.httpfilters.filters; + +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.REQUEST_ID_HEADER_KEY; + +import java.util.UUID; + +import io.micronaut.core.annotation.Order; +import io.micronaut.core.order.Ordered; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.MutableHttpResponse; +import io.micronaut.http.annotation.RequestFilter; +import io.micronaut.http.annotation.ServerFilter; +import io.micronaut.http.filter.FilterContinuation; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ServerFilter(patterns = { "**/endpoint*" }) +@Order(1) +public class RequestIDFilter implements Ordered { + + @RequestFilter + @ExecuteOn(TaskExecutors.BLOCKING) + public void filterRequest(HttpRequest request, FilterContinuation> continuation) { + String requestIdHeader = request.getHeaders() + .get(REQUEST_ID_HEADER_KEY); + if (requestIdHeader == null || requestIdHeader.trim() + .isEmpty()) { + requestIdHeader = UUID.randomUUID() + .toString(); + log.info("request ID not received. Created and will return one with value: [{}]", requestIdHeader); + } else { + log.info("request ID received. Request ID: [{}]", requestIdHeader); + } + + MutableHttpResponse res = continuation.proceed(); + + res.getHeaders() + .add(REQUEST_ID_HEADER_KEY, requestIdHeader); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/utils/CustomHttpHeaders.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/utils/CustomHttpHeaders.java new file mode 100644 index 000000000000..c5fb29386660 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/httpfilters/utils/CustomHttpHeaders.java @@ -0,0 +1,12 @@ +package com.baeldung.micronaut.httpfilters.utils; + +public class CustomHttpHeaders { + + private CustomHttpHeaders() { + } + + public static final String X_TRACE_HEADER_KEY = "X-Trace-Enabled"; + public static final String REQUEST_ID_HEADER_KEY = "Request-ID"; + public static final String CUSTOM_HEADER_KEY = "custom-header"; + public static final String PRIVILEGED_USER_HEADER_KEY = "Privileged-User"; +} diff --git a/microservices-modules/micronaut-configuration/src/main/resources/application.yml b/microservices-modules/micronaut-configuration/src/main/resources/application.yml new file mode 100644 index 000000000000..f21def890862 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/resources/application.yml @@ -0,0 +1,4 @@ +micronaut: + server: + port: 21000 + context-path: /micronaut-configuration-tutorials diff --git a/microservices-modules/micronaut-configuration/src/main/resources/logback.xml b/microservices-modules/micronaut-configuration/src/main/resources/logback.xml new file mode 100644 index 000000000000..4781b201da04 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/httpfilters/FiltersOrderTest.java b/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/httpfilters/FiltersOrderTest.java new file mode 100644 index 000000000000..44ad21772175 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/httpfilters/FiltersOrderTest.java @@ -0,0 +1,102 @@ +package com.baeldung.micronaut.httpfilters; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +import com.baeldung.micronaut.httpfilters.filters.CustomFilter; +import com.baeldung.micronaut.httpfilters.filters.PrivilegedUsersEndpointFilter; +import com.baeldung.micronaut.httpfilters.filters.RequestIDFilter; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.restassured.specification.RequestSpecification; + +@MicronautTest +class FiltersOrderTest { + + private ListAppender customFilterLogs; + private ListAppender requestIDFilterLogs; + private ListAppender privilegedUsersEndpointFilterLogs; + + @BeforeEach + void setUp() { + customFilterLogs = getListAppenderForClass(CustomFilter.class); + requestIDFilterLogs = getListAppenderForClass(RequestIDFilter.class); + privilegedUsersEndpointFilterLogs = getListAppenderForClass(PrivilegedUsersEndpointFilter.class); + } + + @Test + public void givenFilterOrder_whenRequestIsFilteredByAllFilters_thenTheOrderIsRight(RequestSpecification spec) { + spec.given() + .basePath("micronaut-configuration-tutorials/filters-annotations") + .when() + .get("/endpoint1"); + + Optional customFilterLog = customFilterLogs.list.stream() + .filter(e -> e.getMessage() + .contains("request header:")) + .findFirst(); + Optional requestIdFilterLog = requestIDFilterLogs.list.stream() + .filter(e -> e.getMessage() + .contains("request ID")) + .findFirst(); + Optional privilegedUserFilterLog = privilegedUsersEndpointFilterLogs.list.stream() + .filter(e -> e.getMessage() + .contains("Privileged user was filtered")) + .findFirst(); + + assertThat(customFilterLog).isPresent(); + assertThat(requestIdFilterLog).isPresent(); + assertThat(privilegedUserFilterLog).isPresent(); + assertThat(customFilterLog.get() + .getNanoseconds()).isGreaterThan(requestIdFilterLog.get() + .getNanoseconds()); + assertThat(privilegedUserFilterLog.get() + .getNanoseconds()).isGreaterThan(customFilterLog.get() + .getNanoseconds()); + } + + @Test + public void givenFilterOrder_whenRequestIsFilteredBy2Filters_thenTheOrderIsRight(RequestSpecification spec) { + spec.given() + .basePath("micronaut-configuration-tutorials/filters-annotations") + .when() + .get("/endpoint2"); + + Optional customFilterLog = customFilterLogs.list.stream() + .filter(e -> e.getMessage() + .contains("request header:")) + .findFirst(); + Optional requestIdFilterLog = requestIDFilterLogs.list.stream() + .filter(e -> e.getMessage() + .contains("request ID")) + .findFirst(); + Optional privilegedUserFilterLog = privilegedUsersEndpointFilterLogs.list.stream() + .filter(e -> e.getMessage() + .contains("Privileged user was filtered")) + .findFirst(); + + assertThat(customFilterLog).isPresent(); + assertThat(requestIdFilterLog).isPresent(); + assertThat(privilegedUserFilterLog).isEmpty(); + assertThat(customFilterLog.get() + .getNanoseconds()).isGreaterThan(requestIdFilterLog.get() + .getNanoseconds()); + } + + private static ListAppender getListAppenderForClass(Class clazz) { + Logger logger = (Logger) LoggerFactory.getLogger(clazz); + ListAppender loggingEventListAppender = new ListAppender<>(); + loggingEventListAppender.start(); + logger.addAppender(loggingEventListAppender); + + return loggingEventListAppender; + } +} diff --git a/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/httpfilters/ServerApplicationTest.java b/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/httpfilters/ServerApplicationTest.java new file mode 100644 index 000000000000..5be31e36b853 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/httpfilters/ServerApplicationTest.java @@ -0,0 +1,98 @@ +package com.baeldung.micronaut.httpfilters; + +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.CUSTOM_HEADER_KEY; +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.PRIVILEGED_USER_HEADER_KEY; +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.REQUEST_ID_HEADER_KEY; +import static com.baeldung.micronaut.httpfilters.utils.CustomHttpHeaders.X_TRACE_HEADER_KEY; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.matchesPattern; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.restassured.specification.RequestSpecification; + +@MicronautTest +class ServerApplicationTest { + + private static final String FILTERED_ENDPOINTS_PATH = "micronaut-configuration-tutorials/filters-annotations"; + + @Test + public void givenFilterForPrivilegedUsers_whenPrivilegedEndpointUsed_thenResponseContainsTag(RequestSpecification spec) { + spec.given() + .basePath(FILTERED_ENDPOINTS_PATH) + .when() + .get("/endpoint1") + .then() + .statusCode(200) + .body(is("Endpoint 1")) + .header(PRIVILEGED_USER_HEADER_KEY, "true"); + } + + @Test + public void givenFilterForPrivilegedUsers_whenNonPrivilegedEndpointUsed_thenResponseNotContainsTag(RequestSpecification spec) { + spec.given() + .basePath(FILTERED_ENDPOINTS_PATH) + .when() + .get("/endpoint2") + .then() + .statusCode(200) + .body(is("Endpoint 2")) + .header(PRIVILEGED_USER_HEADER_KEY, emptyOrNullString()); + } + + @Test + public void givenFilteredPath_whenRequestFiltered1_thenFilteredResponseReturned(RequestSpecification spec) { + spec.given() + .basePath(FILTERED_ENDPOINTS_PATH) + .header(CUSTOM_HEADER_KEY, "custom-value") + .when() + .get("/endpoint1") + .then() + .statusCode(200) + .body(is("Endpoint 1")) + .header(X_TRACE_HEADER_KEY, "true"); + } + + @Test + public void givenFilteredPath_whenRequestFiltered2_thenFilteredResponseReturned(RequestSpecification spec) { + spec.given() + .basePath(FILTERED_ENDPOINTS_PATH) + .when() + .get("/endpoint2") + .then() + .statusCode(200) + .body(is("Endpoint 2")) + .header(X_TRACE_HEADER_KEY, "true"); + } + + @Test + public void givenFilterWithContinuation_whenRequestIDNotInRequest_thenNewUUIDValuePassedInResponse(RequestSpecification spec) { + spec.given() + .basePath(FILTERED_ENDPOINTS_PATH) + .when() + .get("/endpoint1") + .then() + .statusCode(200) + .body(is("Endpoint 1")) + .header(REQUEST_ID_HEADER_KEY, matchesPattern("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")); + } + + @Test + public void givenFilterWithContinuation_whenRequestIDInRequest_thenSameValuePassedInResponse(RequestSpecification spec) { + UUID requestId = UUID.randomUUID(); + + spec.given() + .basePath(FILTERED_ENDPOINTS_PATH) + .header(REQUEST_ID_HEADER_KEY, requestId) + .when() + .get("/endpoint1") + .then() + .statusCode(200) + .body(is("Endpoint 1")) + .header(REQUEST_ID_HEADER_KEY, requestId.toString()); + } +} diff --git a/microservices-modules/pom.xml b/microservices-modules/pom.xml index 74575c6c0eaa..199a618e70b9 100644 --- a/microservices-modules/pom.xml +++ b/microservices-modules/pom.xml @@ -17,6 +17,7 @@ helidon micronaut + micronaut-configuration micronaut-reactive msf4j