All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ac.simons.neo4j.migrations.core.DiscoveryService Maven / Gradle / Ivy

/*
 * Copyright 2020-2022 the original author or authors.
 *
 * 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
 *
 *      https://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 ac.simons.neo4j.migrations.core;

import ac.simons.neo4j.migrations.core.catalog.Catalog;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Orchestrates {@link Discoverer discoverers}.
 *
 * @author Michael J. Simons
 * @since 0.0.3
 */
final class DiscoveryService {

	private final List> migrationDiscoverers;

	private final List> callbackDiscoverers;

	DiscoveryService() {
		this(new JavaBasedMigrationDiscoverer(), new DefaultClasspathResourceScanner());
	}

	DiscoveryService(Discoverer migrationClassesDiscoverer, ClasspathResourceScanner resourceScanner) {

		this.migrationDiscoverers = Collections.unmodifiableList(Arrays.asList(migrationClassesDiscoverer, ResourceDiscoverer.forMigrations(resourceScanner)));
		this.callbackDiscoverers = Collections.singletonList(ResourceDiscoverer.forCallbacks(resourceScanner));
	}

	/**
	 * @return An unmodifiable list of migrations sorted by version.
	 */
	List findMigrations(MigrationContext context) {

		List migrations = new ArrayList<>();
		try {
			for (Discoverer discoverer : this.migrationDiscoverers) {
				migrations.addAll(discoverer.discover(context));
			}
		} catch (Exception e) {
			throw new MigrationsException("Unexpected error while scanning for migrations", e);
		}

		List cypherBasedMigrations = migrations.stream().filter(MigrationWithPreconditions.class::isInstance)
			.map(MigrationWithPreconditions.class::cast)
			.collect(Collectors.toList());
		Map> migrationsAndPreconditions = new HashMap<>();
		computeAlternativeChecksums(cypherBasedMigrations, migrationsAndPreconditions);

		migrations.removeIf(migration -> hasUnmetPreconditions(migrationsAndPreconditions, migration, context));
		migrations.sort(Comparator.comparing(Migration::getVersion, new MigrationVersion.VersionComparator()));
		Catalog catalog = context.getCatalog();
		if (catalog instanceof WriteableCatalog) {
			WriteableCatalog writeableSchema = (WriteableCatalog) catalog;
			migrations.stream().filter(CatalogBasedMigration.class::isInstance)
				.map(CatalogBasedMigration.class::cast)
				.forEach(m -> writeableSchema.addAll(m.getVersion(), m.getCatalog(), m.isResetCatalog()));
		}
		return Collections.unmodifiableList(migrations);
	}

	private void computeAlternativeChecksums(List migrations,
		Map> migrationsAndPreconditions) {
		migrations.forEach(m -> {
			List preconditions = m.getPreconditions();
			migrationsAndPreconditions.put(m, preconditions);
		});

		migrations.forEach(m -> {
			if (migrationsAndPreconditions.get(m).isEmpty()) {
				return;
			}
			List alternativeChecksums = migrations.stream()
				.filter(o -> o != m && o.getSource().equals(m.getSource()) && !migrationsAndPreconditions.get(o).isEmpty())
				.flatMap(o -> {
					Stream checksum = o.getChecksum().map(Stream::of).orElseGet(Stream::empty);
					return Stream.concat(checksum, o.getAlternativeChecksums().stream()).distinct();
				})
				.collect(Collectors.toList());
			m.setAlternativeChecksums(alternativeChecksums);
		});
	}

	boolean hasUnmetPreconditions(Map> migrationsAndPreconditions, Migration migration, MigrationContext context) {

		if (!(migration instanceof MigrationWithPreconditions)) {
			return false;
		}

		Map> preconditions = migrationsAndPreconditions.get(migration)
			.stream().collect(Collectors.groupingBy(Precondition::getType));

		if (preconditions.isEmpty()) {
			return false;
		}

		for (Precondition assertion : preconditions.getOrDefault(Precondition.Type.ASSERTION, Collections.emptyList())) {
			if (!assertion.isMet(context)) {
				throw new MigrationsException("Could not satisfy `" + assertion + "`.");
			}
		}

		List unmet = preconditions.getOrDefault(Precondition.Type.ASSUMPTION, Collections.emptyList())
			.stream().filter(precondition -> !precondition.isMet(context)).collect(Collectors.toList());

		if (unmet.isEmpty()) {
			return false;
		}

		Migrations.LOGGER.log(Level.INFO,
			() -> String.format("Skipping %s due to unmet preconditions:%n%s", Migrations.toString(migration),
				unmet.stream().map(Precondition::toString).collect(
					Collectors.joining(System.lineSeparator()))));

		return true;
	}

	Map> findCallbacks(MigrationContext context) {

		return Collections.unmodifiableMap(this.callbackDiscoverers.stream()
			.flatMap(d -> d.discover(context).stream())
			.collect(Collectors.groupingBy(Callback::getPhase, Collectors.collectingAndThen(Collectors.toList(), l -> {
				l.sort(Comparator.comparing(c -> c.getOptionalDescription().orElse("")));
				return Collections.unmodifiableList(l);
			}))));
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy