/* * 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.MigrationInfo; import org.flywaydb.core.api.MigrationInfoService; import org.flywaydb.core.api.MigrationState; import org.flywaydb.core.api.MigrationType; import org.flywaydb.core.api.MigrationVersion; import org.flywaydb.core.api.resolver.MigrationResolver; import org.flywaydb.core.api.resolver.ResolvedMigration; import org.flywaydb.core.internal.metadatatable.AppliedMigration; import org.flywaydb.core.internal.metadatatable.MetaDataTable; import org.flywaydb.core.internal.util.ObjectUtils; import org.flywaydb.core.internal.util.Pair; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Default implementation of MigrationInfoService. */ public class MigrationInfoServiceImpl implements MigrationInfoService { /** * The migration resolver for available migrations. */ private final MigrationResolver migrationResolver; /** * The metadata table for applied migrations. */ private final MetaDataTable metaDataTable; /** * The target version up to which to retrieve the info. */ private MigrationVersion target; /** * Allows migrations to be run "out of order". * <p>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.</p> * <p>(default: {@code false})</p> */ private boolean outOfOrder; /** * Whether pending migrations are allowed. */ private final boolean pending; /** * Whether missing migrations are allowed. */ private final boolean missing; /** * Whether future migrations are allowed. */ private final boolean future; /** * The migrations infos calculated at the last refresh. */ private List<MigrationInfoImpl> migrationInfos; /** * Creates a new MigrationInfoServiceImpl. * * @param migrationResolver The migration resolver for available migrations. * @param metaDataTable The metadata table for applied migrations. * @param target The target version up to which to retrieve the info. * @param outOfOrder Allows migrations to be run "out of order". * @param pending Whether pending migrations are allowed. * @param missing Whether missing migrations are allowed. * @param future Whether future migrations are allowed. */ public MigrationInfoServiceImpl(MigrationResolver migrationResolver, MetaDataTable metaDataTable, MigrationVersion target, boolean outOfOrder, boolean pending, boolean missing, boolean future) { this.migrationResolver = migrationResolver; this.metaDataTable = metaDataTable; this.target = target; this.outOfOrder = outOfOrder; this.pending = pending; this.missing = missing; this.future = future; } /** * Refreshes the info about all known migrations from both the classpath and the DB. */ public void refresh() { Collection<ResolvedMigration> availableMigrations = migrationResolver.resolveMigrations(); List<AppliedMigration> appliedMigrations = metaDataTable.allAppliedMigrations(); MigrationInfoContext context = new MigrationInfoContext(); context.outOfOrder = outOfOrder; context.pending = pending; context.missing = missing; context.future = future; context.target = target; Map<MigrationVersion, ResolvedMigration> resolvedMigrationsMap = new TreeMap<MigrationVersion, ResolvedMigration>(); Map<String, ResolvedMigration> resolvedRepeatableMigrationsMap = new TreeMap<String, ResolvedMigration>(); for (ResolvedMigration resolvedMigration : availableMigrations) { MigrationVersion version = resolvedMigration.getVersion(); if (version != null) { if (version.compareTo(context.lastResolved) > 0) { context.lastResolved = version; } resolvedMigrationsMap.put(version, resolvedMigration); } else { resolvedRepeatableMigrationsMap.put(resolvedMigration.getDescription(), resolvedMigration); } } Map<MigrationVersion, Pair<AppliedMigration, Boolean>> appliedMigrationsMap = new TreeMap<MigrationVersion, Pair<AppliedMigration, Boolean>>(); List<AppliedMigration> appliedRepeatableMigrations = new ArrayList<AppliedMigration>(); for (AppliedMigration appliedMigration : appliedMigrations) { MigrationVersion version = appliedMigration.getVersion(); boolean outOfOrder1 = false; if (version != null) { if (version.compareTo(context.lastApplied) > 0) { context.lastApplied = version; } else { outOfOrder1 = true; } } if (appliedMigration.getType() == MigrationType.SCHEMA) { context.schema = version; } if (appliedMigration.getType() == MigrationType.BASELINE) { context.baseline = version; } if (version != null) { appliedMigrationsMap.put(version, Pair.of(appliedMigration, outOfOrder1)); } else { appliedRepeatableMigrations.add(appliedMigration); } } if (MigrationVersion.CURRENT == target) { context.target = context.lastApplied; } Set<MigrationVersion> allVersions = new HashSet<MigrationVersion>(); allVersions.addAll(resolvedMigrationsMap.keySet()); allVersions.addAll(appliedMigrationsMap.keySet()); List<MigrationInfoImpl> migrationInfos1 = new ArrayList<MigrationInfoImpl>(); for (MigrationVersion version : allVersions) { ResolvedMigration resolvedMigration = resolvedMigrationsMap.get(version); Pair<AppliedMigration, Boolean> appliedMigrationInfo = appliedMigrationsMap.get(version); if (appliedMigrationInfo == null) { migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, null, context, false)); } else { migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, appliedMigrationInfo.getLeft(), context, appliedMigrationInfo.getRight())); } } for (AppliedMigration appliedRepeatableMigration : appliedRepeatableMigrations) { if (!context.latestRepeatableRuns.containsKey(appliedRepeatableMigration.getDescription()) || (appliedRepeatableMigration.getInstalledRank() > context.latestRepeatableRuns.get(appliedRepeatableMigration.getDescription()))) { context.latestRepeatableRuns.put(appliedRepeatableMigration.getDescription(), appliedRepeatableMigration.getInstalledRank()); } } Set<ResolvedMigration> pendingResolvedRepeatableMigrations = new HashSet<ResolvedMigration>(resolvedRepeatableMigrationsMap.values()); for (AppliedMigration appliedRepeatableMigration : appliedRepeatableMigrations) { ResolvedMigration resolvedMigration = resolvedRepeatableMigrationsMap.get(appliedRepeatableMigration.getDescription()); int latestRank = context.latestRepeatableRuns.get(appliedRepeatableMigration.getDescription()); if (resolvedMigration != null && appliedRepeatableMigration.getInstalledRank() == latestRank && ObjectUtils.nullSafeEquals(appliedRepeatableMigration.getChecksum(), resolvedMigration.getChecksum())) { pendingResolvedRepeatableMigrations.remove(resolvedMigration); } migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, appliedRepeatableMigration, context, false)); } for (ResolvedMigration pendingResolvedRepeatableMigration : pendingResolvedRepeatableMigrations) { migrationInfos1.add(new MigrationInfoImpl(pendingResolvedRepeatableMigration, null, context, false)); } Collections.sort(migrationInfos1); migrationInfos = migrationInfos1; } public MigrationInfo[] all() { return migrationInfos.toArray(new MigrationInfoImpl[migrationInfos.size()]); } public MigrationInfo current() { MigrationInfo current = null; for (MigrationInfoImpl migrationInfo : migrationInfos) { if (migrationInfo.getState().isApplied() && migrationInfo.getVersion() != null && (current == null || migrationInfo.getVersion().compareTo(current.getVersion()) > 0)) { current = migrationInfo; } } if (current != null) { return current; } // If no versioned migration has been applied so far, fall back to the latest repeatable one for (int i = migrationInfos.size() - 1; i >= 0; i--) { MigrationInfoImpl migrationInfo = migrationInfos.get(i); if (migrationInfo.getAppliedMigration() != null) { return migrationInfo; } } return null; } public MigrationInfoImpl[] pending() { List<MigrationInfoImpl> pendingMigrations = new ArrayList<MigrationInfoImpl>(); for (MigrationInfoImpl migrationInfo : migrationInfos) { if (MigrationState.PENDING == migrationInfo.getState()) { pendingMigrations.add(migrationInfo); } } return pendingMigrations.toArray(new MigrationInfoImpl[pendingMigrations.size()]); } public MigrationInfo[] applied() { List<MigrationInfo> appliedMigrations = new ArrayList<MigrationInfo>(); for (MigrationInfo migrationInfo : migrationInfos) { if (migrationInfo.getState().isApplied()) { appliedMigrations.add(migrationInfo); } } return appliedMigrations.toArray(new MigrationInfo[appliedMigrations.size()]); } /** * Retrieves the full set of infos about the migrations resolved on the classpath. * * @return The resolved migrations. An empty array if none. */ public MigrationInfo[] resolved() { List<MigrationInfo> resolvedMigrations = new ArrayList<MigrationInfo>(); for (MigrationInfo migrationInfo : migrationInfos) { if (migrationInfo.getState().isResolved()) { resolvedMigrations.add(migrationInfo); } } return resolvedMigrations.toArray(new MigrationInfo[resolvedMigrations.size()]); } /** * Retrieves the full set of infos about the migrations that failed. * * @return The failed migrations. An empty array if none. */ public MigrationInfo[] failed() { List<MigrationInfo> failedMigrations = new ArrayList<MigrationInfo>(); for (MigrationInfo migrationInfo : migrationInfos) { if (migrationInfo.getState().isFailed()) { failedMigrations.add(migrationInfo); } } return failedMigrations.toArray(new MigrationInfo[failedMigrations.size()]); } /** * Retrieves the full set of infos about future migrations applied to the DB. * * @return The future migrations. An empty array if none. */ public MigrationInfo[] future() { List<MigrationInfo> futureMigrations = new ArrayList<MigrationInfo>(); for (MigrationInfo migrationInfo : migrationInfos) { if ((migrationInfo.getState() == MigrationState.FUTURE_SUCCESS) || (migrationInfo.getState() == MigrationState.FUTURE_FAILED)) { futureMigrations.add(migrationInfo); } } return futureMigrations.toArray(new MigrationInfo[futureMigrations.size()]); } /** * Retrieves the full set of infos about out of order migrations applied to the DB. * * @return The out of order migrations. An empty array if none. */ public MigrationInfo[] outOfOrder() { List<MigrationInfo> outOfOrderMigrations = new ArrayList<MigrationInfo>(); for (MigrationInfo migrationInfo : migrationInfos) { if (migrationInfo.getState() == MigrationState.OUT_OF_ORDER) { outOfOrderMigrations.add(migrationInfo); } } return outOfOrderMigrations.toArray(new MigrationInfo[outOfOrderMigrations.size()]); } /** * Validate all migrations for consistency. * * @return The error message, or {@code null} if everything is fine. */ public String validate() { for (MigrationInfoImpl migrationInfo : migrationInfos) { String message = migrationInfo.validate(); if (message != null) { return message; } } return null; } }