/** * Copyright 2010-2015 Axel Fontaine * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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 com.contrastsecurity.cassandra.migration.info; import com.contrastsecurity.cassandra.migration.config.MigrationType; import com.contrastsecurity.cassandra.migration.dao.SchemaVersionDAO; import com.contrastsecurity.cassandra.migration.resolver.MigrationResolver; import java.util.*; public class MigrationInfoService { private final MigrationResolver migrationResolver; private final SchemaVersionDAO schemaVersionDAO; /** * 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 pendingOrFuture migrations are allowed. */ private final boolean pendingOrFuture; /** * The migrations infos calculated at the last refresh. */ private List<MigrationInfo> migrationInfos; public MigrationInfoService(MigrationResolver migrationResolver, SchemaVersionDAO schemaVersionDAO, MigrationVersion target, boolean outOfOrder, boolean pendingOrFuture) { this.migrationResolver = migrationResolver; this.schemaVersionDAO = schemaVersionDAO; this.target = target; this.outOfOrder = outOfOrder; this.pendingOrFuture = pendingOrFuture; } /** * 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 = schemaVersionDAO.findAppliedMigrations(); migrationInfos = mergeAvailableAndAppliedMigrations(availableMigrations, appliedMigrations); if (MigrationVersion.CURRENT == target) { target = current().getVersion(); } } /** * Merges the available and the applied migrations to produce one fully aggregated and consolidated list. * * @param resolvedMigrations The available migrations. * @param appliedMigrations The applied migrations. * @return The complete list of migrations. */ /* private -> testing */ List<MigrationInfo> mergeAvailableAndAppliedMigrations(Collection<ResolvedMigration> resolvedMigrations, List<AppliedMigration> appliedMigrations) { MigrationInfoContext context = new MigrationInfoContext(); context.outOfOrder = outOfOrder; context.pendingOrFuture = pendingOrFuture; context.target = target; Map<MigrationVersion, ResolvedMigration> resolvedMigrationsMap = new TreeMap<MigrationVersion, ResolvedMigration>(); for (ResolvedMigration resolvedMigration : resolvedMigrations) { MigrationVersion version = resolvedMigration.getVersion(); if (version.compareTo(context.lastResolved) > 0) { context.lastResolved = version; } resolvedMigrationsMap.put(version, resolvedMigration); } Map<MigrationVersion, AppliedMigration> appliedMigrationsMap = new TreeMap<MigrationVersion, AppliedMigration>(); for (AppliedMigration appliedMigration : appliedMigrations) { MigrationVersion version = appliedMigration.getVersion(); if (version.compareTo(context.lastApplied) > 0) { context.lastApplied = version; } if (appliedMigration.getType() == MigrationType.SCHEMA) { context.schema = version; } if (appliedMigration.getType() == MigrationType.BASELINE) { context.baseline = version; } appliedMigrationsMap.put(version, appliedMigration); } Set<MigrationVersion> allVersions = new HashSet<MigrationVersion>(); allVersions.addAll(resolvedMigrationsMap.keySet()); allVersions.addAll(appliedMigrationsMap.keySet()); List<MigrationInfo> migrationInfos = new ArrayList<>(); for (MigrationVersion version : allVersions) { ResolvedMigration resolvedMigration = resolvedMigrationsMap.get(version); AppliedMigration appliedMigration = appliedMigrationsMap.get(version); migrationInfos.add(new MigrationInfo(resolvedMigration, appliedMigration, context)); } Collections.sort(migrationInfos); return migrationInfos; } public MigrationInfo[] all() { return migrationInfos.toArray(new MigrationInfo[migrationInfos.size()]); } public MigrationInfo current() { for (int i = migrationInfos.size() - 1; i >= 0; i--) { MigrationInfo migrationInfo = migrationInfos.get(i); if (migrationInfo.getState().isApplied()) { return migrationInfo; } } return null; } public MigrationInfo[] pending() { List<MigrationInfo> pendingMigrations = new ArrayList<MigrationInfo>(); for (MigrationInfo migrationInfo : migrationInfos) { if (MigrationState.PENDING == migrationInfo.getState()) { pendingMigrations.add(migrationInfo); } } return pendingMigrations.toArray(new MigrationInfo[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 (MigrationInfo migrationInfo : migrationInfos) { String message = migrationInfo.validate(); if (message != null) { return message; } } return null; } }