From 4da876a5d4a4afb03a7e3408c41821a5b03fed29 Mon Sep 17 00:00:00 2001 From: cristhiank Date: Sat, 23 Mar 2019 07:36:05 -0500 Subject: [PATCH] Flyway extension --- bom/pom.xml | 13 +- build-parent/pom.xml | 10 ++ .../builditem/FeatureBuildItem.java | 1 + extensions/flyway/deployment/pom.xml | 58 +++++++ .../io/quarkus/flyway/FlywayBuildStep.java | 110 ++++++++++++++ extensions/flyway/pom.xml | 37 +++++ extensions/flyway/runtime/pom.xml | 63 ++++++++ ...mpositeMigrationResolverSubstitutions.java | 70 +++++++++ .../runtime/FeatureDetectorSubstitutions.java | 66 ++++++++ ...reSQLSqlStatementBuilderSubstitutions.java | 56 +++++++ .../runtime/QuarkusPathLocationScanner.java | 141 ++++++++++++++++++ .../flyway/runtime/ScannerSubstitutions.java | 58 +++++++ extensions/pom.xml | 3 + integration-tests/flyway/pom.xml | 141 ++++++++++++++++++ .../flyway/FlywayFunctionalityResource.java | 73 +++++++++ .../src/main/resources/application.properties | 21 +++ .../db/migration/V1.0.0__Quarkus.sql | 7 + .../flyway/FlywayFunctionalityNativeIT.java | 23 +++ .../flyway/FlywayFunctionalityTest.java | 36 +++++ .../example/flyway/FlywayTestResources.java | 24 +++ integration-tests/pom.xml | 1 + 21 files changed, 1011 insertions(+), 1 deletion(-) create mode 100644 extensions/flyway/deployment/pom.xml create mode 100644 extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayBuildStep.java create mode 100644 extensions/flyway/pom.xml create mode 100644 extensions/flyway/runtime/pom.xml create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/CompositeMigrationResolverSubstitutions.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FeatureDetectorSubstitutions.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/PostgreSQLSqlStatementBuilderSubstitutions.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/ScannerSubstitutions.java create mode 100644 integration-tests/flyway/pom.xml create mode 100644 integration-tests/flyway/src/main/java/io/quarkus/example/flyway/FlywayFunctionalityResource.java create mode 100644 integration-tests/flyway/src/main/resources/application.properties create mode 100644 integration-tests/flyway/src/main/resources/db/migration/V1.0.0__Quarkus.sql create mode 100644 integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityNativeIT.java create mode 100644 integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityTest.java create mode 100644 integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayTestResources.java diff --git a/bom/pom.xml b/bom/pom.xml index b31e6f9fb727b8..c84d0af1270102 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -144,6 +144,7 @@ 1.6.5 1.0.2 9.4.15.v20190215 + 5.2.4 1.0.3 @@ -261,6 +262,12 @@ ${project.version} provided + + io.quarkus + quarkus-flyway + ${project.version} + provided + io.quarkus quarkus-hibernate-validator @@ -1714,7 +1721,11 @@ jetty-io ${jetty.version} - + + org.flywaydb + flyway-core + ${flyway.version} + diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 06a02ef69178c8..3ca85fc7139870 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -578,6 +578,16 @@ quarkus-kotlin ${project.version} + + io.quarkus + quarkus-flyway + ${project.version} + + + io.quarkus + quarkus-flyway-runtime + ${project.version} + diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index 2069f7468de519..6afc0f85697b47 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -45,6 +45,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String UNDERTOW_WEBSOCKETS = "undertow-websockets"; public static final String VERTX = "vertx"; public static final String VERTX_WEB = "vertx-web"; + public static final String FLYWAY = "flyway"; private final String info; diff --git a/extensions/flyway/deployment/pom.xml b/extensions/flyway/deployment/pom.xml new file mode 100644 index 00000000000000..ad5501ba00baf7 --- /dev/null +++ b/extensions/flyway/deployment/pom.xml @@ -0,0 +1,58 @@ + + + + + + quarkus-flyway-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-flyway + Quarkus - Flyway - Deployment + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-flyway-runtime + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayBuildStep.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayBuildStep.java new file mode 100644 index 00000000000000..05040c277fb5df --- /dev/null +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayBuildStep.java @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.flyway; + +import static java.nio.file.Files.walk; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.stream.Collectors; + +import org.jboss.logging.Logger; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; + +class FlywayBuildStep { + + private static final String FLYWAY_DATABASES_PATH_ROOT = "org/flywaydb/core/internal/database"; + private static final String FLYWAY_METADATA_TABLE_FILENAME = "createMetaDataTable.sql"; + private static final String[] FLYWAY_DATABASES_WITH_SQL_FILE = { + "cockroachdb", + "derby", + "h2", + "hsqldb", + "mysql", + "oracle", + "postgresql", + "redshift", + "saphana", + "sqlite", + "sybasease" + }; + + private static final Logger LOGGER = Logger.getLogger(FlywayBuildStep.class); + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FeatureBuildItem.FLYWAY); + } + + @Record(ExecutionTime.STATIC_INIT) + @BuildStep(providesCapabilities = "io.quarkus.flyway") + void registerSubstrateResources( + BuildProducer resource, + ApplicationArchivesBuildItem appArchives) throws IOException, URISyntaxException { + Path root = appArchives.getRootArchive().getArchiveRoot(); + List resources = generateDatabasesSQLFiles(); + resources.addAll(discoverApplicationMigrations(root)); + resource.produce(new SubstrateResourceBuildItem(resources.toArray(new String[0]))); + } + + private List discoverApplicationMigrations(Path root) throws IOException, URISyntaxException { + List resources = new ArrayList<>(); + try { + // TODO: this should be configurable, using flyway default + String location = "db/migration"; + Enumeration migrations = Thread.currentThread().getContextClassLoader().getResources(location); + while (migrations.hasMoreElements()) { + URL path = migrations.nextElement(); + LOGGER.info("Adding application migrations in path: " + path); + List applicationMigrations = walk(Paths.get(path.toURI())) + .filter(Files::isRegularFile) + .map(it -> Paths.get(location, it.getFileName().toString()).toString()) + .peek(it -> LOGGER.info("Discovered: " + it)) + .collect(Collectors.toList()); + resources.addAll(applicationMigrations); + } + return resources; + } catch (IOException | URISyntaxException e) { + LOGGER.fatal("Error discovering application migrations: " + e.getMessage(), e); + throw e; + } + } + + private List generateDatabasesSQLFiles() { + List result = new ArrayList<>(); + for (String database : FLYWAY_DATABASES_WITH_SQL_FILE) { + String filePath = FLYWAY_DATABASES_PATH_ROOT + "/" + database + "/" + FLYWAY_METADATA_TABLE_FILENAME; + result.add(filePath); + LOGGER.info("Adding flyway internal migration: " + filePath); + } + return result; + } +} diff --git a/extensions/flyway/pom.xml b/extensions/flyway/pom.xml new file mode 100644 index 00000000000000..4f956ae8d0e76c --- /dev/null +++ b/extensions/flyway/pom.xml @@ -0,0 +1,37 @@ + + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-flyway-parent + Quarkus - Flyway + pom + + runtime + deployment + + diff --git a/extensions/flyway/runtime/pom.xml b/extensions/flyway/runtime/pom.xml new file mode 100644 index 00000000000000..01fb81ad3ce1fe --- /dev/null +++ b/extensions/flyway/runtime/pom.xml @@ -0,0 +1,63 @@ + + + + + + quarkus-flyway-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-flyway-runtime + Quarkus - Flyway - Runtime + + + + org.flywaydb + flyway-core + + + com.oracle.substratevm + svm + compile + + + + + + + + maven-dependency-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/CompositeMigrationResolverSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/CompositeMigrationResolverSubstitutions.java new file mode 100644 index 00000000000000..8f3196d3d57fef --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/CompositeMigrationResolverSubstitutions.java @@ -0,0 +1,70 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.flyway.runtime; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.flywaydb.core.api.configuration.Configuration; +import org.flywaydb.core.api.resolver.MigrationResolver; +import org.flywaydb.core.internal.clazz.ClassProvider; +import org.flywaydb.core.internal.database.base.Database; +import org.flywaydb.core.internal.resolver.java.JavaMigrationResolver; +import org.flywaydb.core.internal.resolver.jdbc.JdbcMigrationResolver; +import org.flywaydb.core.internal.resolver.sql.SqlMigrationResolver; +import org.flywaydb.core.internal.resource.ResourceProvider; +import org.flywaydb.core.internal.sqlscript.SqlStatementBuilderFactory; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "org.flywaydb.core.internal.resolver.CompositeMigrationResolver") +public final class CompositeMigrationResolverSubstitutions { + @Alias + private Collection migrationResolvers = new ArrayList<>(); + + /** + * Substitution to remove Spring-data migration resolver as the spring-data dependency is optional. + * This method removes the inclusion of {@link org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationResolver} + * in the resolvers list to avoid native image errors because of the incomplete classpath + * + * @see org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationResolver + * @see org.flywaydb.core.internal.resolver.CompositeMigrationResolver#CompositeMigrationResolver(Database, + * ResourceProvider, ClassProvider, Configuration, SqlStatementBuilderFactory, MigrationResolver...) + */ + @Substitute + public CompositeMigrationResolverSubstitutions( + Database database, + ResourceProvider resourceProvider, + ClassProvider classProvider, + Configuration configuration, + SqlStatementBuilderFactory sqlStatementBuilderFactory, + MigrationResolver... customMigrationResolvers) { + if (!configuration.isSkipDefaultResolvers()) { + migrationResolvers.add(new SqlMigrationResolver( + database, + resourceProvider, + sqlStatementBuilderFactory, + configuration)); + migrationResolvers.add(new JavaMigrationResolver(classProvider, configuration)); + migrationResolvers.add(new JdbcMigrationResolver(classProvider, configuration)); + } + migrationResolvers.addAll(Arrays.asList(customMigrationResolvers)); + } +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FeatureDetectorSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FeatureDetectorSubstitutions.java new file mode 100644 index 00000000000000..ad2521a84ca152 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FeatureDetectorSubstitutions.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.flyway.runtime; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@Substitute +@TargetClass(className = "org.flywaydb.core.internal.util.FeatureDetector") +public final class FeatureDetectorSubstitutions { + + @Substitute + public FeatureDetectorSubstitutions(ClassLoader classLoader) { + + } + + @Substitute + public boolean isApacheCommonsLoggingAvailable() { + return false; + } + + @Substitute + public boolean isSlf4jAvailable() { + return false; + } + + @Substitute + public boolean isSpringJdbcAvailable() { + return false; + } + + @Substitute + public boolean isJBossVFSv2Available() { + return false; + } + + @Substitute + public boolean isJBossVFSv3Available() { + return false; + } + + @Substitute + public boolean isOsgiFrameworkAvailable() { + return false; + } + + @Substitute + public boolean isAndroidAvailable() { + return false; + } + +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/PostgreSQLSqlStatementBuilderSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/PostgreSQLSqlStatementBuilderSubstitutions.java new file mode 100644 index 00000000000000..2c26a09258e3ae --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/PostgreSQLSqlStatementBuilderSubstitutions.java @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.flyway.runtime; + +import java.util.ArrayList; +import java.util.List; + +import org.flywaydb.core.internal.line.Line; +import org.flywaydb.core.internal.sqlscript.Delimiter; +import org.flywaydb.core.internal.sqlscript.SqlStatement; +import org.flywaydb.core.internal.sqlscript.StandardSqlStatement; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * This substitution removes de PosgreSQL COPY statement support in Flyway to allow native image compilation + * Without this substitution the native image generation will fail unless the PosgreSQL dependency is specified + */ +@TargetClass(className = "org.flywaydb.core.internal.database.postgresql.PostgreSQLSqlStatementBuilder") +public final class PostgreSQLSqlStatementBuilderSubstitutions { + @Alias + protected List lines = new ArrayList<>(); + @Alias + protected Delimiter delimiter; + @Alias + private boolean pgCopy; + + /** + * Returns only a {@link StandardSqlStatement} as the normal SQL migrations do or throw an exception + * if pgCopy flag is true + * + */ + @Substitute + public SqlStatement getSqlStatement() { + if (pgCopy) { + throw new IllegalStateException("pgCopy is not supported yet!"); + } + return new StandardSqlStatement(lines, delimiter); + } +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java new file mode 100644 index 00000000000000..cd48236d946af7 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java @@ -0,0 +1,141 @@ +package io.quarkus.flyway.runtime; + +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.flywaydb.core.api.Location; +import org.flywaydb.core.api.logging.Log; +import org.flywaydb.core.api.logging.LogFactory; +import org.flywaydb.core.internal.resource.LoadableResource; +import org.flywaydb.core.internal.resource.classpath.ClassPathResource; +import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner; + +public final class QuarkusPathLocationScanner implements ResourceAndClassScanner { + private static final Log LOG = LogFactory.getLog(QuarkusPathLocationScanner.class); + // TODO: this should be configurable, using flyway default + private final static String DEFAULT_NATIVE_LOCATION = "db/migration"; + private static Set ALL_SQL_RESOURCES; + + static { + try { + ALL_SQL_RESOURCES = discoverApplicationMigrations(); + } catch (IOException | URISyntaxException e) { + throw new IllegalStateException("Could not discover Flyway migrations", e); + } + } + + public QuarkusPathLocationScanner(Location location) { + if (!location.getPath().equals(DEFAULT_NATIVE_LOCATION)) { + LOG.error("Invalid migrations location: " + location.getPath() + ". Flyway migrations must be located in " + + DEFAULT_NATIVE_LOCATION); + } + } + + private static Set discoverApplicationMigrations() throws IOException, URISyntaxException { + Set resources = new HashSet<>(); + try { + Enumeration migrations = Thread.currentThread().getContextClassLoader().getResources( + DEFAULT_NATIVE_LOCATION); + while (migrations.hasMoreElements()) { + URL path = migrations.nextElement(); + LOG.info("Adding application migrations in path: " + path); + if ("jar".equals(path.getProtocol())) { + resources = scanInJar(path); + } else { + throw new IllegalStateException("Expecting jar file. Protocol not supported:" + path.getProtocol()); + } + + } + return resources; + } catch (IOException e) { + LOG.error("Error discovering application migrations: " + e.getMessage(), e); + throw e; + } + } + + public static Set scanInJar(URL locationUrl) { + JarFile jarFile; + try { + jarFile = getJarFromUrl(locationUrl); + } catch (IOException e) { + LOG.warn("Unable to determine jar from url (" + locationUrl + "): " + e.getMessage()); + return Collections.emptySet(); + } + + try { + return findResourceNamesFromJarFile(jarFile); + } finally { + try { + jarFile.close(); + } catch (IOException e) { + // Ignore + } + } + } + + private static JarFile getJarFromUrl(URL locationUrl) throws IOException { + URLConnection con = locationUrl.openConnection(); + if (con instanceof JarURLConnection) { + // Should usually be the case for traditional JAR files. + JarURLConnection jarCon = (JarURLConnection) con; + jarCon.setUseCaches(false); + return jarCon.getJarFile(); + } + throw new IllegalStateException("Could not open the jar file " + locationUrl); + } + + private static Set findResourceNamesFromJarFile(JarFile jarFile) { + Set resourceNames = new TreeSet<>(); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + String entryName = entries.nextElement().getName(); + if (entryName.startsWith(DEFAULT_NATIVE_LOCATION) && !entryName.endsWith("/")) { + LOG.info("Discovered " + entryName); + resourceNames.add(entryName); + } + } + + return resourceNames; + } + + /** + * Scans the classpath for resources under the configured DEFAULT_NATIVE_LOCATION. + * + * @return The resources that were found. + */ + @Override + public Collection scanForResources() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Set resources = new HashSet<>(); + Location defaultLocation = new Location(DEFAULT_NATIVE_LOCATION); + for (String file : ALL_SQL_RESOURCES) { + LOG.info("Loading " + file); + resources.add(new ClassPathResource(defaultLocation, file, classLoader, StandardCharsets.UTF_8)); + } + return resources; + } + + /** + * Scans the classpath for concrete classes under the specified package implementing this interface. + * Non-instantiable abstract classes are filtered out. + * + * @return The non-abstract classes that were found. + */ + @Override + public Collection> scanForClasses() { + // Classes are not supported in native mode + return Collections.emptyList(); + } +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/ScannerSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/ScannerSubstitutions.java new file mode 100644 index 00000000000000..805999e9b0f3ed --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/ScannerSubstitutions.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.flyway.runtime; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.flywaydb.core.api.Location; +import org.flywaydb.core.internal.resource.LoadableResource; +import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * This substitution removes the {@link org.flywaydb.core.internal.scanner.android.AndroidScanner} dependency + */ +@TargetClass(className = "org.flywaydb.core.internal.scanner.Scanner") +public final class ScannerSubstitutions { + + @Alias + private List resources = new ArrayList<>(); + @Alias + private List> classes = new ArrayList<>(); + + /** + * Creates only {@link QuarkusPathLocationScanner} instances. + * Replaces the original method that tries to detect migrations using reflection techniques that are not allowed + * in native mode + * + * @see org.flywaydb.core.internal.scanner.Scanner#Scanner(Collection, ClassLoader, Charset) + */ + @Substitute + public ScannerSubstitutions(Collection locations, ClassLoader classLoader, Charset encoding) { + for (Location location : locations) { + ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner(location); + resources.addAll(quarkusScanner.scanForResources()); + classes.addAll(quarkusScanner.scanForClasses()); + } + } +} diff --git a/extensions/pom.xml b/extensions/pom.xml index 2272bd42bea45c..d885eeec1ce985 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -95,6 +95,9 @@ kotlin + + + flyway diff --git a/integration-tests/flyway/pom.xml b/integration-tests/flyway/pom.xml new file mode 100644 index 00000000000000..178dd764c34e3b --- /dev/null +++ b/integration-tests/flyway/pom.xml @@ -0,0 +1,141 @@ + + + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-flyway + Quarkus - Integration Tests - Flyway + Module that contains Flyway related tests + + + + io.quarkus + quarkus-flyway + provided + + + + io.quarkus + quarkus-jdbc-h2 + provided + + + + io.quarkus + quarkus-resteasy + provided + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-h2 + test + + + io.rest-assured + rest-assured + test + + + + + + + ${project.groupId} + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + ${project.groupId} + quarkus-maven-plugin + + + native-image + + native-image + + + false + true + true + ${graalvmHome} + false + false + + + + + + + + + + + diff --git a/integration-tests/flyway/src/main/java/io/quarkus/example/flyway/FlywayFunctionalityResource.java b/integration-tests/flyway/src/main/java/io/quarkus/example/flyway/FlywayFunctionalityResource.java new file mode 100644 index 00000000000000..e639d0d195e146 --- /dev/null +++ b/integration-tests/flyway/src/main/java/io/quarkus/example/flyway/FlywayFunctionalityResource.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.example.flyway; + +import static java.sql.DriverManager.getConnection; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.flywaydb.core.Flyway; + +import org.jboss.logging.Logger; + +@Path("/flyway") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class FlywayFunctionalityResource { + private static final Logger LOGGER = Logger.getLogger(FlywayFunctionalityResource.class); + @ConfigProperty(name = "datasource.url") + String dbURL; + @ConfigProperty(name = "datasource.username") + String dbUser; + @ConfigProperty(name = "datasource.password") + String dbPassword; + + @GET + @Path("/migrate") + public String doMigrate() throws SQLException { + Flyway flyway = Flyway.configure() + .dataSource(dbURL, dbUser, dbPassword) + .load(); + flyway.migrate(); + int rows = countTableRows(); + return "OK " + rows; + } + + private int countTableRows() throws SQLException { + try (Connection connection = getConnection(dbURL, dbUser, dbPassword)) { + try (Statement statement = connection.createStatement()) { + try (ResultSet execute = statement.executeQuery("SELECT COUNT(*) FROM quarkus; ")) { + if (execute.next()) { + return execute.getInt(1); + } else { + return -1; //ERROR + } + } + } + } + } +} diff --git a/integration-tests/flyway/src/main/resources/application.properties b/integration-tests/flyway/src/main/resources/application.properties new file mode 100644 index 00000000000000..f92b3cf5111742 --- /dev/null +++ b/integration-tests/flyway/src/main/resources/application.properties @@ -0,0 +1,21 @@ +# +# Copyright 2018 Red Hat, Inc. +# +# 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. +# +datasource.url=jdbc:h2:tcp://localhost/mem:test;DB_CLOSE_DELAY=-1 +datasource.username=sa +datasource.password=sa +quarkus.log.console.level=DEBUG +quarkus.log.category."org.flywaydb.core".level=DEBUG +quarkus.log.category."io.quarkus.flyway".level=DEBUG \ No newline at end of file diff --git a/integration-tests/flyway/src/main/resources/db/migration/V1.0.0__Quarkus.sql b/integration-tests/flyway/src/main/resources/db/migration/V1.0.0__Quarkus.sql new file mode 100644 index 00000000000000..96cf84445b3056 --- /dev/null +++ b/integration-tests/flyway/src/main/resources/db/migration/V1.0.0__Quarkus.sql @@ -0,0 +1,7 @@ +CREATE TABLE quarkus +( + id INT, + name VARCHAR(20) +); +INSERT INTO quarkus(id, name) +VALUES (1, 'QUARKED'); \ No newline at end of file diff --git a/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityNativeIT.java b/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityNativeIT.java new file mode 100644 index 00000000000000..7a5afbf806b206 --- /dev/null +++ b/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityNativeIT.java @@ -0,0 +1,23 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.example.flyway; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class FlywayFunctionalityNativeIT extends FlywayFunctionalityTest { +} diff --git a/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityTest.java b/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityTest.java new file mode 100644 index 00000000000000..931587299ef919 --- /dev/null +++ b/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayFunctionalityTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * 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.quarkus.example.flyway; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@DisplayName("Tests flyway extension") +public class FlywayFunctionalityTest { + + @Test + @DisplayName("Migrates a schema correctly") + public void testFlywayFunctionality() throws Exception { + when().get("/flyway/migrate").then().body(is("OK 1")); + } +} diff --git a/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayTestResources.java b/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayTestResources.java new file mode 100644 index 00000000000000..978a0708f7c643 --- /dev/null +++ b/integration-tests/flyway/src/test/java/io/quarkus/example/flyway/FlywayTestResources.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * 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.quarkus.example.flyway; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class FlywayTestResources { +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index c996ef63ced7c0..e694e2c8cc891a 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -56,6 +56,7 @@ camel-core camel-salesforce camel-aws-s3 + flyway