Java tutorial
/* * Copyright 2015-2019 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 org.springframework.cloud.config.monitor; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; 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.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.config.server.environment.AbstractScmEnvironmentRepository; import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.http.HttpHeaders; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.util.PatternMatchUtils; /** * Configuration for a file watcher that detects changes in local files related to the * environment repository. If any files change the {@link PropertyPathEndpoint} is pinged * with the paths of the files. This applies to the source files of a local git repository * (i.e. a git repository with a "file:" URI) or to a native repository. * * @author Dave Syer * @author Gilles Robert * */ @Configuration @EnableScheduling public class FileMonitorConfiguration implements SmartLifecycle, ResourceLoaderAware { private static final Log log = LogFactory.getLog(FileMonitorConfiguration.class); @Autowired private PropertyPathEndpoint endpoint; @Autowired(required = false) private List<AbstractScmEnvironmentRepository> scmRepositories; @Autowired(required = false) private NativeEnvironmentRepository nativeEnvironmentRepository; private boolean running; private WatchService watcher; private Set<Path> directory; private int phase; private boolean autoStartup = true; private ResourceLoader resourceLoader; private String[] excludes = new String[] { ".*", "#*", "*#" }; @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public int getPhase() { return this.phase; } /** * see {@link #getPhase()}. * @param phase the phase. */ public void setPhase(int phase) { this.phase = phase; } @Override public boolean isRunning() { return this.running; } /** * @param running true if running. * @see #isRunning() */ public void setRunning(boolean running) { this.running = running; } @Override public boolean isAutoStartup() { return this.autoStartup; } /** * @param autoStartup true to auto start. * @see #isAutoStartup() */ public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } @Override public synchronized void start() { if (!this.running) { this.directory = getFileRepo(); if (this.directory != null && !this.directory.isEmpty()) { log.info("Monitoring for local config changes: " + this.directory); try { this.watcher = FileSystems.getDefault().newWatchService(); for (Path path : this.directory) { walkDirectory(path); } } catch (IOException e) { } } else { log.info("Not monitoring for local config changes"); } this.running = true; } } @Override public synchronized void stop() { if (this.running) { if (this.watcher != null) { try { this.watcher.close(); } catch (IOException e) { log.error("Failed to close watcher for " + this.directory.toString(), e); } } this.running = false; } } @Override public void stop(Runnable callback) { stop(); callback.run(); } @Scheduled(fixedRateString = "${spring.cloud.config.server.monitor.fixedDelay:5000}") public void poll() { for (File file : filesFromEvents()) { this.endpoint.notifyByPath(new HttpHeaders(), Collections.<String, Object>singletonMap("path", file.getAbsolutePath())); } } private Set<Path> getFileRepo() { if (this.scmRepositories != null) { String repositoryUri = null; Set<Path> paths = new LinkedHashSet<>(); try { for (AbstractScmEnvironmentRepository repository : scmRepositories) { repositoryUri = repository.getUri(); Resource resource = this.resourceLoader.getResource(repositoryUri); if (resource instanceof FileSystemResource) { paths.add(Paths.get(resource.getURI())); } } return paths; } catch (IOException e) { log.error("Cannot resolve URI for path: " + repositoryUri); } } if (this.nativeEnvironmentRepository != null) { Set<Path> paths = new LinkedHashSet<>(); for (String path : this.nativeEnvironmentRepository.getSearchLocations()) { Resource resource = this.resourceLoader.getResource(path); if (resource.exists()) { try { paths.add(Paths.get(resource.getURI())); } catch (Exception e) { log.error("Cannot resolve URI for path: " + path); } } } return paths; } return null; } private Set<File> filesFromEvents() { Set<File> files = new LinkedHashSet<File>(); if (this.watcher == null) { return files; } WatchKey key = this.watcher.poll(); while (key != null) { for (WatchEvent<?> event : key.pollEvents()) { if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE || event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) { Path item = (Path) event.context(); File file = new File( ((Path) key.watchable()).toAbsolutePath() + File.separator + item.getFileName()); if (file.isDirectory()) { files.addAll(walkDirectory(file.toPath())); } else { if (!file.getPath().contains(".git") && !PatternMatchUtils.simpleMatch(this.excludes, file.getName())) { if (log.isDebugEnabled()) { log.debug("Watch Event: " + event.kind() + ": " + file); } files.add(file); } } } else if (event.kind() == StandardWatchEventKinds.OVERFLOW) { if (log.isDebugEnabled()) { log.debug("Watch Event: " + event.kind() + ": context: " + event.context()); } if (event.context() != null && event.context() instanceof Path) { files.addAll(walkDirectory((Path) event.context())); } else { for (Path path : this.directory) { files.addAll(walkDirectory(path)); } } } else { if (log.isDebugEnabled()) { log.debug("Watch Event: " + event.kind() + ": context: " + event.context()); } } } key.reset(); key = this.watcher.poll(); } return files; } private Set<File> walkDirectory(Path directory) { final Set<File> walkedFiles = new LinkedHashSet<File>(); try { registerWatch(directory); Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { FileVisitResult fileVisitResult = super.preVisitDirectory(dir, attrs); // No need to monitor the git metadata if (dir.toFile().getPath().contains(".git")) { return FileVisitResult.SKIP_SUBTREE; } registerWatch(dir); return fileVisitResult; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { FileVisitResult fileVisitResult = super.visitFile(file, attrs); walkedFiles.add(file.toFile()); return fileVisitResult; } }); } catch (IOException e) { log.error("Failed to walk directory: " + directory.toString(), e); } return walkedFiles; } private void registerWatch(Path dir) throws IOException { if (log.isDebugEnabled()) { log.debug("registering: " + dir + " for file creation events"); } try { dir.register(this.watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException("Cannot register watcher for " + dir, e); } } }