/*
* Copyright 2010-2017 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.core.internal.info;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationState;
import org.flywaydb.core.api.MigrationType;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.resolver.ResolvedMigration;
import org.flywaydb.core.internal.metadatatable.AppliedMigration;
import org.flywaydb.core.internal.util.ObjectUtils;
import java.util.Date;
/**
* Default implementation of MigrationInfo.
*/
public class MigrationInfoImpl implements MigrationInfo {
/**
* The resolved migration to aggregate the info from.
*/
private final ResolvedMigration resolvedMigration;
/**
* The applied migration to aggregate the info from.
*/
private final AppliedMigration appliedMigration;
/**
* The current context.
*/
private final MigrationInfoContext context;
/**
* Whether this migration was applied out of order.
*/
private final boolean outOfOrder;
/**
* Creates a new MigrationInfoImpl.
*
* @param resolvedMigration The resolved migration to aggregate the info from.
* @param appliedMigration The applied migration to aggregate the info from.
* @param context The current context.
* @param outOfOrder Whether this migration was applied out of order.
*/
public MigrationInfoImpl(ResolvedMigration resolvedMigration, AppliedMigration appliedMigration,
MigrationInfoContext context, boolean outOfOrder) {
this.resolvedMigration = resolvedMigration;
this.appliedMigration = appliedMigration;
this.context = context;
this.outOfOrder = outOfOrder;
}
/**
* @return The resolved migration to aggregate the info from.
*/
public ResolvedMigration getResolvedMigration() {
return resolvedMigration;
}
/**
* @return The applied migration to aggregate the info from.
*/
public AppliedMigration getAppliedMigration() {
return appliedMigration;
}
public MigrationType getType() {
if (appliedMigration != null) {
return appliedMigration.getType();
}
return resolvedMigration.getType();
}
public Integer getChecksum() {
if (appliedMigration != null) {
return appliedMigration.getChecksum();
}
return resolvedMigration.getChecksum();
}
public MigrationVersion getVersion() {
if (appliedMigration != null) {
return appliedMigration.getVersion();
}
return resolvedMigration.getVersion();
}
public String getDescription() {
if (appliedMigration != null) {
return appliedMigration.getDescription();
}
return resolvedMigration.getDescription();
}
public String getScript() {
if (appliedMigration != null) {
return appliedMigration.getScript();
}
return resolvedMigration.getScript();
}
public MigrationState getState() {
if (appliedMigration == null) {
if (resolvedMigration.getVersion() != null) {
if (resolvedMigration.getVersion().compareTo(context.baseline) < 0) {
return MigrationState.BELOW_BASELINE;
}
if (resolvedMigration.getVersion().compareTo(context.target) > 0) {
return MigrationState.ABOVE_TARGET;
}
if ((resolvedMigration.getVersion().compareTo(context.lastApplied) < 0) && !context.outOfOrder) {
return MigrationState.IGNORED;
}
}
return MigrationState.PENDING;
}
if (resolvedMigration == null) {
if (MigrationType.SCHEMA == appliedMigration.getType()) {
return MigrationState.SUCCESS;
}
if (MigrationType.BASELINE == appliedMigration.getType()) {
return MigrationState.BASELINE;
}
if ((appliedMigration.getVersion() == null) || getVersion().compareTo(context.lastResolved) < 0) {
if (appliedMigration.isSuccess()) {
return MigrationState.MISSING_SUCCESS;
}
return MigrationState.MISSING_FAILED;
} else {
if (appliedMigration.isSuccess()) {
return MigrationState.FUTURE_SUCCESS;
}
return MigrationState.FUTURE_FAILED;
}
}
if (!appliedMigration.isSuccess()) {
return MigrationState.FAILED;
}
if (appliedMigration.getVersion() == null) {
if (appliedMigration.getInstalledRank() == context.latestRepeatableRuns.get(appliedMigration.getDescription())) {
if (ObjectUtils.nullSafeEquals(appliedMigration.getChecksum(), resolvedMigration.getChecksum())) {
return MigrationState.SUCCESS;
}
return MigrationState.OUTDATED;
}
return MigrationState.SUPERSEEDED;
}
if (outOfOrder) {
return MigrationState.OUT_OF_ORDER;
}
return MigrationState.SUCCESS;
}
public Date getInstalledOn() {
if (appliedMigration != null) {
return appliedMigration.getInstalledOn();
}
return null;
}
@Override
public String getInstalledBy() {
if (appliedMigration != null) {
return appliedMigration.getInstalledBy();
}
return null;
}
@Override
public Integer getInstalledRank() {
if (appliedMigration != null) {
return appliedMigration.getInstalledRank();
}
return null;
}
public Integer getExecutionTime() {
if (appliedMigration != null) {
return appliedMigration.getExecutionTime();
}
return null;
}
/**
* Validates this migrationInfo for consistency.
*
* @return The error message, or {@code null} if everything is fine.
*/
public String validate() {
if (getState().isFailed()
&& (!context.future || MigrationState.FUTURE_FAILED != getState())) {
if (getVersion() == null) {
return "Detected failed repeatable migration: " + getDescription();
}
return "Detected failed migration to version " + getVersion() + " (" + getDescription() + ")";
}
if ((resolvedMigration == null)
&& (appliedMigration.getType() != MigrationType.SCHEMA)
&& (appliedMigration.getType() != MigrationType.BASELINE)
&& (appliedMigration.getVersion() != null)
&& (!context.missing || (MigrationState.MISSING_SUCCESS != getState() && MigrationState.MISSING_FAILED != getState()))
&& (!context.future || (MigrationState.FUTURE_SUCCESS != getState() && MigrationState.FUTURE_FAILED != getState()))) {
return "Detected applied migration not resolved locally: " + getVersion();
}
if (!context.pending && MigrationState.PENDING == getState() || MigrationState.IGNORED == getState()) {
if (getVersion() != null) {
return "Detected resolved migration not applied to database: " + getVersion();
}
return "Detected resolved repeatable migration not applied to database: " + getDescription();
}
if (!context.pending && MigrationState.OUTDATED == getState()) {
return "Detected outdated resolved repeatable migration that should be re-applied to database: " + getDescription();
}
if (resolvedMigration != null && appliedMigration != null) {
Object migrationIdentifier = appliedMigration.getVersion();
if (migrationIdentifier == null) {
// Repeatable migrations
migrationIdentifier = appliedMigration.getScript();
}
if (getVersion() == null || getVersion().compareTo(context.baseline) > 0) {
if (resolvedMigration.getType() != appliedMigration.getType()) {
return createMismatchMessage("type", migrationIdentifier,
appliedMigration.getType(), resolvedMigration.getType());
}
if (resolvedMigration.getVersion() != null
|| (context.pending &&
((MigrationState.OUTDATED != getState()) && (MigrationState.SUPERSEEDED != getState())))) {
if (!ObjectUtils.nullSafeEquals(resolvedMigration.getChecksum(), appliedMigration.getChecksum())) {
return createMismatchMessage("checksum", migrationIdentifier,
appliedMigration.getChecksum(), resolvedMigration.getChecksum());
}
}
if (!resolvedMigration.getDescription().equals(appliedMigration.getDescription())) {
return createMismatchMessage("description", migrationIdentifier,
appliedMigration.getDescription(), resolvedMigration.getDescription());
}
}
}
return null;
}
/**
* Creates a message for a mismatch.
*
* @param mismatch The type of mismatch.
* @param migrationIdentifier The offending version.
* @param applied The applied value.
* @param resolved The resolved value.
* @return The message.
*/
private String createMismatchMessage(String mismatch, Object migrationIdentifier, Object applied, Object resolved) {
return String.format("Migration " + mismatch + " mismatch for migration %s\n" +
"-> Applied to database : %s\n" +
"-> Resolved locally : %s",
migrationIdentifier, applied, resolved);
}
@SuppressWarnings("NullableProblems")
public int compareTo(MigrationInfo o) {
if ((getInstalledRank() != null) && (o.getInstalledRank() != null)) {
return getInstalledRank() - o.getInstalledRank();
}
MigrationState state = getState();
MigrationState oState = o.getState();
if (((getInstalledRank() != null) || (o.getInstalledRank() != null))
&& (!(state == MigrationState.BELOW_BASELINE || oState == MigrationState.BELOW_BASELINE
|| state == MigrationState.IGNORED || oState == MigrationState.IGNORED))) {
if (getInstalledRank() != null) {
return Integer.MIN_VALUE;
}
if (o.getInstalledRank() != null) {
return Integer.MAX_VALUE;
}
}
if (getVersion() != null && o.getVersion() != null) {
return getVersion().compareTo(o.getVersion());
}
// Versioned pending migrations go before repeatable ones
if (getVersion() != null) {
return Integer.MIN_VALUE;
}
if (o.getVersion() != null) {
return Integer.MAX_VALUE;
}
return getDescription().compareTo(o.getDescription());
}
@SuppressWarnings("SimplifiableIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MigrationInfoImpl that = (MigrationInfoImpl) o;
if (appliedMigration != null ? !appliedMigration.equals(that.appliedMigration) : that.appliedMigration != null)
return false;
if (!context.equals(that.context)) return false;
return !(resolvedMigration != null ? !resolvedMigration.equals(that.resolvedMigration) : that.resolvedMigration != null);
}
@Override
public int hashCode() {
int result = resolvedMigration != null ? resolvedMigration.hashCode() : 0;
result = 31 * result + (appliedMigration != null ? appliedMigration.hashCode() : 0);
result = 31 * result + context.hashCode();
return result;
}
}