From d7423c6e78356a65d5299559634bcaa21b4f2dfa Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 24 Apr 2024 17:56:12 +0200 Subject: [PATCH] Avoid setting the tenant id if one is preset --- .../jdbc/h2/multitenancy/TenancyBook.java | 19 +++++ .../multitenancy/TenancyBookController.java | 26 +++++++ .../TenancyBookControllerSpec.groovy | 75 +++++++++++++++++++ .../multitenancy/TenancyBookRepository.java | 17 +++++ .../TenantIdEntityEventListener.java | 4 + 5 files changed, 141 insertions(+) create mode 100644 data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBook.java create mode 100644 data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookController.java create mode 100644 data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy create mode 100644 data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookRepository.java diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBook.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBook.java new file mode 100644 index 0000000000..5d86f594d2 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBook.java @@ -0,0 +1,19 @@ +package io.micronaut.data.jdbc.h2.multitenancy; + +import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.TenantId; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable // <1> +@MappedEntity // <2> +public record TenancyBook(@Nullable + @Id // <3> + @GeneratedValue // <4> + Long id, + String title, + @TenantId // <5> + String framework) { +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookController.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookController.java new file mode 100644 index 0000000000..bb332726d8 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookController.java @@ -0,0 +1,26 @@ +package io.micronaut.data.jdbc.h2.multitenancy; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; + +import java.util.List; + +@Requires(property = "spec.name", value = "TenancyBookControllerSpec") +@Controller("/books") // <1> +class TenancyBookController { + private final TenancyBookRepository bookRepository; + + TenancyBookController(TenancyBookRepository bookRepository) { // <2> + this.bookRepository = bookRepository; + } + + @ExecuteOn(TaskExecutors.BLOCKING) // <3> + @Get + // <4> + List index() { + return bookRepository.findAll(); + } +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy new file mode 100644 index 0000000000..079506cf8d --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookControllerSpec.groovy @@ -0,0 +1,75 @@ +package io.micronaut.data.jdbc.h2.multitenancy + +import io.micronaut.context.annotation.Property +import io.micronaut.core.type.Argument +import io.micronaut.core.util.StringUtils +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpResponse +import io.micronaut.http.HttpStatus +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +import static org.junit.Assert.assertEquals + +@Property(name = "datasources.default.schema-generate", value = "CREATE_DROP") +// <1> +@Property(name = "datasources.default.url", value = "jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") +@Property(name = "datasources.default.username", value = "sa") +@Property(name = "datasources.default.password", value = "") +@Property(name = "datasources.default.dialect", value = "H2") +@Property(name = "datasources.default.driver-class-name", value = "org.h2.Driver") +@Property(name = "micronaut.multitenancy.tenantresolver.httpheader.enabled", value = StringUtils.TRUE) +@Property(name = "spec.name", value = "TenancyBookControllerSpec") +@MicronautTest(transactional = false) +// <2> +class TenancyBookControllerSpec extends Specification { + + @Inject + @Client("/") + HttpClient httpClient + + @Inject + TenancyBookRepository bookRepository + + def multitenancyRequest() { + + given: + BlockingHttpClient client = httpClient.toBlocking() + save(bookRepository, client, "Building Microservices with Micronaut", "micronaut") + save(bookRepository, client, "Introducing Micronaut", "micronaut") + save(bookRepository, client, "Grails 3 - Step by Step", "grails") + save(bookRepository, client, "Falando de Grail", "grails") + save(bookRepository, client, "Grails Goodness Notebook", "grails") + + when: + List books = fetchBooks(client, "micronaut") + then: + books + books.size() == 2 + + when: + books = fetchBooks(client, "grails") + then: + books + books.size() == 3 + + cleanup: + bookRepository.deleteAll() + } + + List fetchBooks(BlockingHttpClient client, String framework) { + HttpRequest request = HttpRequest.GET("/books").header("tenantId", framework) + Argument> responseArgument = Argument.listOf(TenancyBook.class) + HttpResponse> response = client.exchange(request, responseArgument) + assertEquals(HttpStatus.OK, response.getStatus()) + return response.body() + } + + void save(TenancyBookRepository bookRepository, BlockingHttpClient client, String title, String framework) { + bookRepository.save(new TenancyBook(null, title, framework)) + } +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookRepository.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookRepository.java new file mode 100644 index 0000000000..2bba15356a --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/multitenancy/TenancyBookRepository.java @@ -0,0 +1,17 @@ +package io.micronaut.data.jdbc.h2.multitenancy; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.data.annotation.WithoutTenantId; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@Requires(property = "spec.name", value = "TenancyBookControllerSpec") +@JdbcRepository(dialect = Dialect.H2) // <1> +public interface TenancyBookRepository extends CrudRepository { // <2> + Long save(String title); + + @WithoutTenantId + @Override + void deleteAll(); +} diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/event/listeners/TenantIdEntityEventListener.java b/data-runtime/src/main/java/io/micronaut/data/runtime/event/listeners/TenantIdEntityEventListener.java index 74a7a6111e..20f3b44ac1 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/event/listeners/TenantIdEntityEventListener.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/event/listeners/TenantIdEntityEventListener.java @@ -76,6 +76,10 @@ protected Predicate> getPropertyPredicate() { public boolean prePersist(@NonNull EntityEventContext context) { for (RuntimePersistentProperty property : getApplicableProperties(context.getPersistentEntity())) { if (property.getAnnotationMetadata().hasStereotype(TenantId.class)) { + if (property.getProperty().get(context.getEntity()) != null) { + // Skip existing value + return true; + } Argument argument = property.getArgument(); Object newValue = tenantResolver.resolveTenantIdentifier(); if (!argument.isInstance(newValue)) {