
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 extends Migration> 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 extends Migration> 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