ac.simons.neo4j.migrations.core.ResourceDiscoverer 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 java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
/**
* Abstract base class for implementing discoverer discovering resources.
*
* @author Michael J. Simons
* @param The concrete type to be instantiated with a discovered resource
* @since 1.2.2
*/
final class ResourceDiscoverer implements Discoverer {
static Discoverer forMigrations(ClasspathResourceScanner resourceScanner) {
List> allDiscoveres = new ArrayList<>();
for (ResourceBasedMigrationProvider provider : ResourceBasedMigrationProvider.unique()) {
Predicate filter = pathOrUrl -> {
try {
String path = URLDecoder.decode(pathOrUrl, Defaults.CYPHER_SCRIPT_ENCODING.name());
if (provider.supportsArbitraryResourceNames()) {
return path.endsWith(provider.getExtension());
}
Matcher matcher = MigrationVersion.VERSION_PATTERN.matcher(path);
return matcher.find() && provider.getExtension().equals(matcher.group("ext"));
} catch (UnsupportedEncodingException e) {
throw new MigrationsException("Somethings broken: UTF-8 encoding not supported.");
}
};
allDiscoveres.add(new ResourceDiscoverer<>(resourceScanner, filter, provider::handle));
}
return new AggregatingMigrationDiscoverer(allDiscoveres);
}
static ResourceDiscoverer forCallbacks(ClasspathResourceScanner resourceScanner) {
Predicate filter = LifecyclePhase::canParse;
filter = filter.and(fullPath -> {
final int lastSlashIdx = fullPath.lastIndexOf('/');
final int lastDotIdx = fullPath.lastIndexOf('.');
return lastDotIdx > lastSlashIdx && fullPath.substring(lastDotIdx + 1).equalsIgnoreCase(Defaults.CYPHER_SCRIPT_EXTENSION);
});
return new ResourceDiscoverer<>(resourceScanner, filter,
ctx -> Collections.singletonList(new CypherBasedCallback(ctx.getUrl(), ctx.getConfig().isAutocrlf())));
}
private static final Logger LOGGER = Logger.getLogger(ResourceDiscoverer.class.getName());
private final ClasspathResourceScanner scanner;
private final Predicate resourceFilter;
private final Function> mapper;
private ResourceDiscoverer(ClasspathResourceScanner scanner, Predicate resourceFilter, Function> mapper) {
this.scanner = scanner;
this.resourceFilter = resourceFilter;
this.mapper = mapper;
}
/**
* @return All Cypher-based migrations. Empty list if no package to scan is configured.
*/
@Override
public Collection discover(MigrationContext context) {
MigrationsConfig config = context.getConfig();
List listOfMigrations = new ArrayList<>();
List classpathLocations = new ArrayList<>();
List filesystemLocations = new ArrayList<>();
for (String prefixAndLocation : config.getLocationsToScan()) {
Location location = Location.of(prefixAndLocation);
if (location.getType() == Location.LocationType.CLASSPATH) {
classpathLocations.add(location.getName());
} else if (location.getType() == Location.LocationType.FILESYSTEM) {
filesystemLocations.add(location.toUri());
}
}
listOfMigrations.addAll(scanClasspathLocations(classpathLocations, context.getConfig()));
listOfMigrations.addAll(scanFilesystemLocations(filesystemLocations, context.getConfig()));
return listOfMigrations;
}
private List scanClasspathLocations(List classpathLocations, MigrationsConfig config) {
if (classpathLocations.isEmpty()) {
return Collections.emptyList();
}
LOGGER.log(Level.FINE, "Scanning for classpath resources in {0}", classpathLocations);
return this.scanner.scan(classpathLocations)
.stream()
.filter(r -> resourceFilter.test(r.getPath()))
.map(resource -> ResourceContext.of(resource, config))
.map(mapper)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
private List scanFilesystemLocations(List filesystemLocations, MigrationsConfig config) {
if (filesystemLocations.isEmpty()) {
return Collections.emptyList();
}
LOGGER.log(Level.FINE, "Scanning for filesystem resources in {0}", filesystemLocations);
List resources = new ArrayList<>();
for (URI location : filesystemLocations) {
Path path = Paths.get(location);
if (!Files.isDirectory(path)) {
continue;
}
try {
Files.walkFileTree(path, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String fullPath = file.toString();
if (attrs.isRegularFile() && resourceFilter.test(fullPath)) {
ResourceContext context = ResourceContext.of(file.toFile().toURI().toURL(), config);
resources.addAll(mapper.apply(context));
return FileVisitResult.CONTINUE;
}
return super.visitFile(file, attrs);
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return resources;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy