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

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