org.springframework.cloud.config.monitor.FileMonitorConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.config.monitor.FileMonitorConfiguration.java

Source

/*
 * 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);
        }
    }

}