
org.flywaydb.maven.AbstractFlywayMojo Maven / Gradle / Ivy
/**
* Copyright 2010-2016 Boxfuse GmbH
*
* 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 org.flywaydb.maven;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.internal.util.ExceptionUtils;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.logging.Log;
import org.flywaydb.core.internal.util.logging.LogFactory;
import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
import org.sonatype.plexus.components.cipher.PlexusCipherException;
import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.Properties;
/**
* Common base class for all mojos with all common attributes.
*
* @requiresDependencyResolution test
* @configurator include-project-dependencies
* @phase pre-integration-test
*/
@SuppressWarnings({"JavaDoc", "FieldCanBeLocal", "UnusedDeclaration"})
abstract class AbstractFlywayMojo extends AbstractMojo {
/**
* Property name prefix for placeholders that are configured through properties.
*/
private static final String PLACEHOLDERS_PROPERTY_PREFIX = "flyway.placeholders.";
protected Log log;
protected Flyway flyway = new Flyway();
/**
* Whether to skip the execution of the Maven Plugin for this module.
* Also configurable with Maven or System Property: ${flyway.skip}
*
* @parameter property="flyway.skip"
*/
/* private -> for testing */ boolean skip;
/**
* The fully qualified classname of the jdbc driver to use to connect to the database.
* By default, the driver is autodetected based on the url.
* Also configurable with Maven or System Property: ${flyway.driver}
*
* @parameter property="flyway.driver"
*/
/* private -> for testing */ String driver;
/**
* The jdbc url to use to connect to the database.
* Also configurable with Maven or System Property: ${flyway.url}
*
* @parameter property="flyway.url"
*/
/* private -> for testing */ String url;
/**
* The user to use to connect to the database. (default: blank)
* The credentials can be specified by user/password or {@code serverId} from settings.xml
* Also configurable with Maven or System Property: ${flyway.user}
*
* @parameter property="flyway.user"
*/
/* private -> for testing */ String user;
/**
* The password to use to connect to the database. (default: blank)
* Also configurable with Maven or System Property: ${flyway.password}
*
* @parameter property="flyway.password"
*/
private String password;
/**
* List of the schemas managed by Flyway. These schema names are case-sensitive.
* (default: The default schema for the datasource connection)
* Consequences:
*
* - The first schema in the list will be automatically set as the default one during the migration.
* - The first schema in the list will also be the one containing the metadata table.
* - The schemas will be cleaned in the order of this list.
*
* Also configurable with Maven or System Property: ${flyway.schemas} (comma-separated list)
*
* @parameter property="flyway.schemas"
*/
private String[] schemas;
/**
* The name of the metadata table that will be used by Flyway. (default: schema_version)
* By default (single-schema mode) the
* metadata table is placed in the default schema for the connection provided by the datasource.
When the
* {@code flyway.schemas} property is set (multi-schema mode), the metadata table is placed in the first schema of
* the list.
* Also configurable with Maven or System Property: ${flyway.table}
*
* @parameter property="flyway.table"
*/
private String table = flyway.getTable();
/**
* The version to tag an existing schema with when executing baseline. (default: 1)
* Also configurable with Maven or System Property: ${flyway.baselineVersion}
*
* @parameter property="flyway.baselineVersion"
*/
private String baselineVersion;
/**
* The description to tag an existing schema with when executing baseline. (default: << Flyway Baseline >>)
* Also configurable with Maven or System Property: ${flyway.baselineDescription}
*
* @parameter property="flyway.baselineDescription"
*/
private String baselineDescription;
/**
* Locations on the classpath to scan recursively for migrations. Locations may contain both sql
* and java-based migrations. (default: filesystem:src/main/resources/db/migration)
* Also configurable with Maven or System Property: ${flyway.locations} (Comma-separated list)
*
* @parameter
*/
private String[] locations;
/**
* The fully qualified class names of the custom MigrationResolvers to be used in addition or as replacement
* (if skipDefaultResolvers is true) to the built-in ones for resolving Migrations to apply.
* (default: none)
* Also configurable with Maven or System Property: ${flyway.resolvers} (Comma-separated list)
*
* @parameter
*/
private String[] resolvers = new String[0];
/**
* When set to true, default resolvers are skipped, i.e. only custom resolvers as defined by 'resolvers'
* are used. (default: false)
Also configurable with Maven or System Property:
* ${flyway.skipDefaultResolvers}
*
* @parameter property="flyway.skipDefaultResolvers"
*/
private boolean skipDefaultResolvers;
/**
* The encoding of Sql migrations. (default: UTF-8)
Also configurable with Maven or System Property:
* ${flyway.encoding}
*
* @parameter property="flyway.encoding"
*/
private String encoding = flyway.getEncoding();
/**
* The file name prefix for Sql migrations (default: V) Also configurable with Maven or System Property:
* ${flyway.sqlMigrationPrefix}
*
* Sql migrations have the following file name structure: prefixVERSIONseparatorDESCRIPTIONsuffix ,
* which using the defaults translates to V1_1__My_description.sql
*
* @parameter property="flyway.sqlMigrationPrefix"
*/
private String sqlMigrationPrefix = flyway.getSqlMigrationPrefix();
/**
* The file name prefix for repeatable sql migrations (default: R) Also configurable with Maven or System Property:
* ${flyway.repeatableSqlMigrationPrefix}
*
* Repeatable sql migrations have the following file name structure: prefixSeparatorDESCRIPTIONsuffix ,
* which using the defaults translates to R__My_description.sql
*
* @parameter property="flyway.repeatableSqlMigrationPrefix"
*/
private String repeatableSqlMigrationPrefix = flyway.getRepeatableSqlMigrationPrefix();
/**
* The file name separator for Sql migrations (default: __) Also configurable with Maven or System Property:
* ${flyway.sqlMigrationSeparator}
*
* Sql migrations have the following file name structure: prefixVERSIONseparatorDESCRIPTIONsuffix ,
* which using the defaults translates to V1_1__My_description.sql
*
* @parameter property="flyway.sqlMigrationSeparator"
*/
private String sqlMigrationSeparator = flyway.getSqlMigrationSeparator();
/**
* The file name suffix for Sql migrations (default: .sql) Also configurable with Maven or System Property:
* ${flyway.sqlMigrationSuffix}
*
* Sql migrations have the following file name structure: prefixVERSIONseparatorDESCRIPTIONsuffix ,
* which using the defaults translates to V1_1__My_description.sql
*
* @parameter property="flyway.sqlMigrationSuffix"
*/
private String sqlMigrationSuffix = flyway.getSqlMigrationSuffix();
/**
* Whether to automatically call clean or not when a validation error occurs. (default: {@code false})
* This is exclusively intended as a convenience for development. Even tough we
* strongly recommend not to change migration scripts once they have been checked into SCM and run, this provides a
* way of dealing with this case in a smooth manner. The database will be wiped clean automatically, ensuring that
* the next migration will bring you back to the state checked into SCM.
* Warning ! Do not enable in production !
* Also configurable with Maven or System Property: ${flyway.cleanOnValidationError}
*
* @parameter property="flyway.cleanOnValidationError"
*/
private boolean cleanOnValidationError = flyway.isCleanOnValidationError();
/**
* Whether to disable clean. (default: {@code false})
* This is especially useful for production environments where running clean can be quite a career limiting move.
* Also configurable with Maven or System Property: ${flyway.cleanDisabled}
*
* @parameter property="flyway.cleanDisabled"
*/
private boolean cleanDisabled;
/**
* The target version up to which Flyway should consider migrations.
* Migrations with a higher version number will be ignored.
* The special value {@code current} designates the current version of the schema. (default: the latest version)
* Also configurable with Maven or System Property: ${flyway.target}
*
* @parameter property="flyway.target"
*/
private String target = flyway.getTarget().getVersion();
/**
* Allows migrations to be run "out of order" (default: {@code false}).
* If you already have versions 1 and 3 applied, and now a version 2 is found,
* it will be applied too instead of being ignored.
* Also configurable with Maven or System Property: ${flyway.outOfOrder}
*
* @parameter property="flyway.outOfOrder"
*/
private boolean outOfOrder = flyway.isOutOfOrder();
/**
* Ignore future migrations when reading the metadata table. These are migrations that were performed by a
* newer deployment of the application that are not yet available in this version. For example: we have migrations
* available on the classpath up to version 3.0. The metadata table indicates that a migration to version 4.0
* (unknown to us) has already been applied. Instead of bombing out (fail fast) with an exception, a
* warning is logged and Flyway continues normally. This is useful for situations where one must be able to redeploy
* an older version of the application after the database has been migrated by a newer one. (default: {@code true})
* Also configurable with Maven or System Property: ${flyway.ignoreFutureMigrations}
*
* @parameter property="flyway.ignoreFutureMigrations"
*/
private boolean ignoreFutureMigrations = true;
/**
* Ignores failed future migrations when reading the metadata table. These are migrations that we performed by a
* newer deployment of the application that are not yet available in this version. For example: we have migrations
* available on the classpath up to version 3.0. The metadata table indicates that a migration to version 4.0
* (unknown to us) has already been attempted and failed. Instead of bombing out (fail fast) with an exception, a
* warning is logged and Flyway terminates normally. This is useful for situations where a database rollback is not
* an option. An older version of the application can then be redeployed, even though a newer one failed due to a
* bad migration. (default: false)
* Also configurable with Maven or System Property: ${flyway.ignoreFailedFutureMigration}
*
* @parameter property="flyway.ignoreFailedFutureMigration"
*
* @deprecated Use the more generic ignoreFutureMigrations
instead. Will be removed in Flyway 5.0.
*/
@Deprecated
private boolean ignoreFailedFutureMigration;
/**
* Whether placeholders should be replaced. (default: true)
* Also configurable with Maven or System Property: ${flyway.placeholderReplacement}
*
* @parameter property="flyway.placeholderReplacement"
*/
private boolean placeholderReplacement = flyway.isPlaceholderReplacement();
/**
* A map of <placeholder, replacementValue> to apply to sql migration scripts.
*
* Also configurable with Maven or System Properties like ${flyway.placeholders.myplaceholder} or ${flyway.placeholders.otherone}
*
* @parameter
*/
private Map placeholders = flyway.getPlaceholders();
/**
* The prefix of every placeholder. (default: ${ )
* Also configurable with Maven or System Property: ${flyway.placeholderPrefix}
*
* @parameter property="flyway.placeholderPrefix"
*/
private String placeholderPrefix = flyway.getPlaceholderPrefix();
/**
* The suffix of every placeholder. (default: } )
* Also configurable with Maven or System Property: ${flyway.placeholderSuffix}
*
* @parameter property="flyway.placeholderSuffix"
*/
private String placeholderSuffix = flyway.getPlaceholderSuffix();
/**
* An array of FlywayCallback implementations. (default: empty )
* Also configurable with Maven or System Property: ${flyway.callbacks}
*
* @parameter
*/
private String[] callbacks = new String[0];
/**
* When set to true, default callbacks are skipped, i.e. only custom callbacks as defined by 'resolvers'
* are used. (default: false)
Also configurable with Maven or System Property:
* ${flyway.skipDefaultCallbacks}
*
* @parameter property="flyway.skipDefaultCallbacks"
*/
private boolean skipDefaultCallbacks;
/**
*
* Whether to automatically call baseline when migrate is executed against a non-empty schema with no metadata table.
* This schema will then be baselined with the {@code initialVersion} before executing the migrations.
* Only migrations above {@code initialVersion} will then be applied.
*
*
* This is useful for initial Flyway production deployments on projects with an existing DB.
*
*
* Be careful when enabling this as it removes the safety net that ensures
* Flyway does not migrate the wrong database in case of a configuration mistake! (default: {@code false})
*
* Also configurable with Maven or System Property: ${flyway.baselineOnMigrate}
*
* @parameter property="flyway.baselineOnMigrate"
*/
private Boolean baselineOnMigrate;
/**
* Whether to automatically call validate or not when running migrate. (default: {@code true})
* Also configurable with Maven or System Property: ${flyway.validationErrorMode}
*
* @parameter property="flyway.validateOnMigrate"
*/
private boolean validateOnMigrate = flyway.isValidateOnMigrate();
/**
* Properties file from which to load the Flyway configuration. The names of the individual properties match the ones you would
* use as Maven or System properties. The encoding of the file must be the same as the encoding defined with the
* flyway.encoding property, which is UTF-8 by default. Relative paths are relative to the POM. (default: flyway.properties)
*
* Also configurable with Maven or System Property: ${flyway.configFile}
*
* @parameter property="flyway.configFile"
*/
private File configFile;
/**
* The id of the server tag in settings.xml (default: flyway-db)
* The credentials can be specified by user/password or {@code serverId} from settings.xml
* Also configurable with Maven or System Property: ${flyway.serverId}
*
* @parameter property="flyway.serverId"
*/
private String serverId = "flyway-db";
/**
* The link to the settings.xml
*
* @parameter property="settings"
* @required
* @readonly
*/
/* private -> for testing */ Settings settings;
/**
* Reference to the current project that includes the Flyway Maven plugin.
*
* @parameter property="project" required="true"
*/
/* private -> for testing */ MavenProject mavenProject;
/**
* Load username password from settings
*
* @throws FlywayException when the credentials could not be loaded.
*/
private void loadCredentialsFromSettings() throws FlywayException {
final Server server = settings.getServer(serverId);
if (user == null) {
if (server != null) {
user = server.getUsername();
try {
SecDispatcher secDispatcher = new DefaultSecDispatcher() {{
_cipher = new DefaultPlexusCipher();
}};
password = secDispatcher.decrypt(server.getPassword());
} catch (SecDispatcherException e) {
throw new FlywayException("Unable to decrypt password", e);
} catch (PlexusCipherException e) {
throw new FlywayException("Unable to initialize password decryption", e);
}
}
} else if (server != null) {
throw new FlywayException("You specified credentials both in the Flyway config and settings.xml. Use either one or the other");
}
}
/**
* Retrieves the value of this boolean property, based on the matching System on the Maven property.
*
* @param systemPropertyName The name of the System property.
* @param mavenPropertyValue The value of the Maven property.
* @return The value to use.
*/
/* private -> for testing */ boolean getBooleanProperty(String systemPropertyName, boolean mavenPropertyValue) {
String systemPropertyValue = System.getProperty(systemPropertyName);
if (systemPropertyValue != null) {
return Boolean.getBoolean(systemPropertyName);
}
return mavenPropertyValue;
}
public final void execute() throws MojoExecutionException, MojoFailureException {
LogFactory.setLogCreator(new MavenLogCreator(this));
log = LogFactory.getLog(getClass());
if (getBooleanProperty("flyway.skip", skip)) {
log.info("Skipping Flyway execution");
return;
}
try {
loadCredentialsFromSettings();
flyway.setClassLoader(Thread.currentThread().getContextClassLoader());
flyway.setSchemas(schemas);
flyway.setTable(table);
if (baselineVersion != null) {
flyway.setBaselineVersionAsString(baselineVersion);
}
if (baselineDescription != null) {
flyway.setBaselineDescription(baselineDescription);
}
if (locations != null) {
for (int i = 0; i < locations.length; i++) {
if (locations[i].startsWith(Location.FILESYSTEM_PREFIX)) {
String newLocation = locations[i].substring(Location.FILESYSTEM_PREFIX.length());
File file = new File(newLocation);
if (!file.isAbsolute()) {
file = new File(mavenProject.getBasedir(), newLocation);
}
locations[i] = Location.FILESYSTEM_PREFIX + file.getAbsolutePath();
}
}
} else {
locations = new String[] {
Location.FILESYSTEM_PREFIX + mavenProject.getBasedir().getAbsolutePath() + "/src/main/resources/db/migration"
};
}
flyway.setLocations(locations);
flyway.setResolversAsClassNames(resolvers);
flyway.setSkipDefaultResolvers(skipDefaultResolvers);
flyway.setCallbacksAsClassNames(callbacks);
flyway.setSkipDefaultCallbacks(skipDefaultCallbacks);
flyway.setEncoding(encoding);
flyway.setSqlMigrationPrefix(sqlMigrationPrefix);
flyway.setRepeatableSqlMigrationPrefix(repeatableSqlMigrationPrefix);
flyway.setSqlMigrationSeparator(sqlMigrationSeparator);
flyway.setSqlMigrationSuffix(sqlMigrationSuffix);
flyway.setCleanOnValidationError(cleanOnValidationError);
flyway.setCleanDisabled(cleanDisabled);
flyway.setOutOfOrder(outOfOrder);
flyway.setTargetAsString(target);
flyway.setIgnoreFutureMigrations(ignoreFutureMigrations);
if (ignoreFailedFutureMigration) {
flyway.setIgnoreFailedFutureMigration(ignoreFailedFutureMigration);
}
flyway.setPlaceholderReplacement(placeholderReplacement);
flyway.setPlaceholderPrefix(placeholderPrefix);
flyway.setPlaceholderSuffix(placeholderSuffix);
if (baselineOnMigrate != null) {
flyway.setBaselineOnMigrate(baselineOnMigrate);
}
flyway.setValidateOnMigrate(validateOnMigrate);
Properties properties = new Properties();
properties.putAll(mavenProject.getProperties());
if (driver != null) {
properties.setProperty("flyway.driver", driver);
}
if (url != null) {
properties.setProperty("flyway.url", url);
}
if (user != null) {
properties.setProperty("flyway.user", user);
}
if (password != null) {
properties.setProperty("flyway.password", password);
}
for (String placeholer : placeholders.keySet()) {
String value = placeholders.get(placeholer);
properties.setProperty("flyway.placeholders." + placeholer, value == null ? "" : value);
}
properties.putAll(getConfigFileProperties());
properties.putAll(System.getProperties());
filterProperties(properties);
flyway.configure(properties);
doExecute(flyway);
} catch (Exception e) {
throw new MojoExecutionException(e.toString(), ExceptionUtils.getRootCause(e));
}
}
/**
* Filters there properties to remove the Flyway Maven Plugin-specific ones.
*
* @param properties The properties to filter.
*/
private static void filterProperties(Properties properties) {
properties.remove("flyway.configFile");
properties.remove("flyway.current");
}
/**
* Retrieve the properties from the config file (if specified).
*/
private Properties getConfigFileProperties() throws IOException {
Properties properties = new Properties();
String configFileProp = System.getProperty("flyway.configFile");
if (configFileProp != null) {
configFile = new File(configFileProp);
if (!configFile.isAbsolute()) {
configFile = new File(mavenProject.getBasedir(), configFileProp);
}
}
if (configFile == null) {
File file = new File(mavenProject.getBasedir(), "flyway.properties");
if (file.isFile() && file.canRead()) {
configFile = file;
} else {
log.debug("flyway.properties not found. Skipping.");
return properties;
}
} else if (!configFile.canRead() || !configFile.isFile()) {
throw new FlywayException("Unable to read config file: " + configFile.getAbsolutePath());
}
properties.load(new InputStreamReader(new FileInputStream(configFile), encoding));
return properties;
}
/**
* Retrieves this property from either the system or the maven properties.
*
* @param name The name of the property to retrieve.
* @return The property value. {@code null} if not found.
*/
protected String getProperty(String name) {
String systemProperty = System.getProperty(name);
if (systemProperty != null) {
return systemProperty;
}
return mavenProject.getProperties().getProperty(name);
}
/**
* Executes this mojo.
*
* @param flyway The flyway instance to operate on.
* @throws Exception any exception
*/
protected abstract void doExecute(Flyway flyway) throws Exception;
}