Skip to content

Commit

Permalink
flyway multitenant extension
Browse files Browse the repository at this point in the history
  • Loading branch information
rmanibus committed Oct 15, 2024
1 parent b51b778 commit 49af62c
Show file tree
Hide file tree
Showing 112 changed files with 5,278 additions and 5 deletions.
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,16 @@
<artifactId>quarkus-flyway-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway-multitenant</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway-multitenant-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway-postgresql</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum Feature {
ELASTICSEARCH_REST_HIGH_LEVEL_CLIENT,
ELASTICSEARCH_JAVA_CLIENT,
FLYWAY,
FLYWAY_MULTITENANT,
GRPC_CLIENT,
GRPC_SERVER,
HIBERNATE_ORM,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/asciidoc/flyway.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ NOTE: Without configuration, Flyway is set up for every datasource using the def

== Customizing Flyway

In cases where Flyway needs to be configured in addition to the configuration options that Quarkus provides, the `io.quarkus.flyway.FlywayConfigurationCustomizer` class comes in handy.
In cases where Flyway needs to be configured in addition to the configuration options that Quarkus provides, the `io.quarkus.flyway.multitenant.FlywayConfigurationCustomizer` class comes in handy.

To customize Flyway for the default datasource, simply add a bean like so:

Expand Down
97 changes: 97 additions & 0 deletions extensions/flyway-multitenant/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-flyway-multitenant-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-flyway-multitenant-deployment</artifactId>
<name>Quarkus - Flyway - Multitenant - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway-multitenant</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-dev-ui-tests</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-AlegacyConfigRoot=true</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.flyway.multitenant.deployment;

import java.util.function.BooleanSupplier;

import io.quarkus.flyway.multitenant.runtime.FlywayMultiTenantBuildTimeConfig;

/**
* Supplier that can be used to only run build steps
* if the Flyway extension is enabled.
*/
public class FlywayEnabled implements BooleanSupplier {

private final FlywayMultiTenantBuildTimeConfig config;

FlywayEnabled(FlywayMultiTenantBuildTimeConfig config) {
this.config = config;
}

@Override
public boolean getAsBoolean() {
return config.enabled;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.flyway.multitenant.deployment;

import io.quarkus.deployment.Feature;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;

public class FlywayMultiTenantAlwaysEnabledProcessor {

@BuildStep
void build(BuildProducer<FeatureBuildItem> featureProducer) {
featureProducer.produce(new FeatureBuildItem(Feature.FLYWAY_MULTITENANT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.quarkus.flyway.multitenant.deployment;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.flywaydb.core.api.callback.Callback;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.flyway.multitenant.runtime.FlywayMultiTenantBuildTimeConfig;

/**
* Logic to locate and process Flyway {@link Callback} classes.
* This class also helps to keep the {@link FlywayMultiTenantProcessor} class as lean as possible to make it easier to maintain
*/
class FlywayMultiTenantCallbacksLocator {
private final Collection<String> persistenceUnitNames;
private final FlywayMultiTenantBuildTimeConfig flywayBuildConfig;
private final CombinedIndexBuildItem combinedIndexBuildItem;
private final BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer;

private FlywayMultiTenantCallbacksLocator(Collection<String> persistenceUnitNames,
FlywayMultiTenantBuildTimeConfig flywayBuildConfig,
CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer) {
this.persistenceUnitNames = persistenceUnitNames;
this.flywayBuildConfig = flywayBuildConfig;
this.combinedIndexBuildItem = combinedIndexBuildItem;
this.reflectiveClassProducer = reflectiveClassProducer;
}

public static FlywayMultiTenantCallbacksLocator with(Collection<String> persistenceUnitNames,
FlywayMultiTenantBuildTimeConfig flywayBuildConfig,
CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer) {
return new FlywayMultiTenantCallbacksLocator(persistenceUnitNames, flywayBuildConfig, combinedIndexBuildItem,
reflectiveClassProducer);
}

/**
* Main logic to identify callbacks and return them to be processed by the {@link FlywayMultiTenantProcessor}
*
* @return Map containing the callbacks for each datasource. The datasource name is the map key
* @exception ClassNotFoundException if the {@link Callback} class cannot be located by the Quarkus class loader
* @exception InstantiationException if the {@link Callback} class represents an abstract class.
* @exception InvocationTargetException if the underlying constructor throws an exception.
* @exception IllegalAccessException if the {@link Callback} constructor is enforcing Java language access control
* and the underlying constructor is inaccessible
*/
public Map<String, Collection<Callback>> getCallbacks()
throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
final Map<String, Collection<Callback>> callbacks = new HashMap<>();
for (String dataSourceName : persistenceUnitNames) {
final Collection<Callback> instances = callbacksForPersistenceUnit(dataSourceName);
callbacks.put(dataSourceName, instances);
}
return callbacks;
}

/**
*
* Reads the configuration, instantiates the {@link Callback} class. Also, adds it to the reflective producer
*
* @return List of callbacks for the datasource
* @exception ClassNotFoundException if the {@link Callback} class cannot be located by the Quarkus class loader
* @exception InstantiationException if the {@link Callback} class represents an abstract class.
* @exception InvocationTargetException if the underlying constructor throws an exception.
* @exception IllegalAccessException if the {@link Callback} constructor is enforcing Java language access control
* and the underlying constructor is inaccessible
*/
private Collection<Callback> callbacksForPersistenceUnit(String persistenceUnitName)
throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException {
final Optional<List<String>> callbackConfig = flywayBuildConfig
.getConfigForPersistenceUnitName(persistenceUnitName).callbacks;
if (!callbackConfig.isPresent()) {
return Collections.emptyList();
}
final Collection<String> callbacks = callbackConfig.get();
final Collection<Callback> instances = new ArrayList<>(callbacks.size());
for (String callback : callbacks) {
final ClassInfo clazz = combinedIndexBuildItem.getIndex().getClassByName(DotName.createSimple(callback));
Objects.requireNonNull(clazz,
"Flyway callback not found, please verify the fully qualified name for the class: " + callback);
if (Modifier.isAbstract(clazz.flags()) || !clazz.hasNoArgsConstructor()) {
throw new IllegalArgumentException(
"Invalid Flyway callback. It shouldn't be abstract and must have a default constructor");
}
final Class<?> clazzType = Class.forName(callback, false, Thread.currentThread().getContextClassLoader());
final Callback instance = (Callback) clazzType.getConstructors()[0].newInstance();
instances.add(instance);
reflectiveClassProducer
.produce(ReflectiveClassBuildItem.builder(clazz.name().toString()).build());
}
return instances;
}
}
Loading

0 comments on commit 49af62c

Please sign in to comment.