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

org.apache.camel.support.FileWatcherResourceReloadStrategy Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 *
 *      http://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.apache.camel.support;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.camel.RuntimeCamelException;
import org.apache.camel.api.management.ManagedAttribute;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.spi.Resource;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

/**
 * A file based {@link org.apache.camel.spi.ResourceReloadStrategy} which watches a file folder for modified files and
 * reload on file changes.
 * 

* This implementation uses the JDK {@link WatchService} to watch for when files are created or modified. Mac OS X users * should be noted the osx JDK does not support native file system changes and therefore the watch service is much * slower than on Linux or Windows systems. */ @ManagedResource(description = "Managed FileWatcherResourceReloadStrategy") public class FileWatcherResourceReloadStrategy extends ResourceReloadStrategySupport { private static final Logger LOG = LoggerFactory.getLogger(FileWatcherResourceReloadStrategy.class); WatchService watcher; ExecutorService executorService; WatchFileChangesTask task; Map folderKeys; FileFilter fileFilter; String folder; boolean isRecursive; boolean scheduler = true; long pollTimeout = 2000; public FileWatcherResourceReloadStrategy() { setRecursive(false); } public FileWatcherResourceReloadStrategy(String directory) { setFolder(directory); setRecursive(false); } public FileWatcherResourceReloadStrategy(String directory, boolean isRecursive) { setFolder(directory); setRecursive(isRecursive); } public void setFolder(String folder) { // clip file: prefix if mistakenly specified if (folder != null && folder.startsWith("file:")) { folder = folder.substring(5); } this.folder = folder; } public void setRecursive(boolean isRecursive) { this.isRecursive = isRecursive; } public void setScheduler(boolean scheduler) { this.scheduler = scheduler; } /** * Sets the poll timeout in millis. The default value is 2000. */ public void setPollTimeout(long pollTimeout) { this.pollTimeout = pollTimeout; } @ManagedAttribute(description = "Folder being watched") public String getFolder() { return folder; } @ManagedAttribute(description = "Whether the reload strategy watches directory recursively") public boolean isRecursive() { return isRecursive; } @ManagedAttribute(description = "Whether the watcher is running") public boolean isRunning() { return task != null && task.isRunning(); } public FileFilter getFileFilter() { return fileFilter; } /** * To use a custom filter for accepting files. */ public void setFileFilter(FileFilter fileFilter) { this.fileFilter = fileFilter; } @Override public void onReload(Object source) { // this implementation uses a watcher to automatic reload } @Override protected void doStart() throws Exception { super.doStart(); if (folder == null) { // no folder configured return; } if (!scheduler) { // do not start scheduler so exit start phase return; } File dir = new File(folder); if (dir.exists() && dir.isDirectory()) { String msg = startupMessage(dir); if (msg != null) { LOG.info(msg); } WatchEvent.Modifier modifier = null; // if its mac OSX then attempt to apply workaround or warn its slower String os = ObjectHelper.getSystemProperty("os.name", ""); if (os.toLowerCase(Locale.US).startsWith("mac")) { // this modifier can speedup the scanner on mac osx (as java on mac has no native file notification integration) Class clazz = getCamelContext().getClassResolver() .resolveClass("com.sun.nio.file.SensitivityWatchEventModifier", WatchEvent.Modifier.class); if (clazz != null) { WatchEvent.Modifier[] modifiers = clazz.getEnumConstants(); for (WatchEvent.Modifier mod : modifiers) { if ("HIGH".equals(mod.name())) { modifier = mod; break; } } } if (modifier != null) { LOG.debug( "On Mac OS X the JDK WatchService is slow by default so enabling SensitivityWatchEventModifier.HIGH as workaround"); } else { LOG.warn( "On Mac OS X the JDK WatchService is slow and it may take up till 10 seconds to notice file changes"); } } try { Path path = dir.toPath(); watcher = path.getFileSystem().newWatchService(); // we cannot support deleting files as we don't know which routes that would be if (isRecursive) { this.folderKeys = new HashMap<>(); registerRecursive(watcher, path, modifier); } else { registerPathToWatcher(modifier, path, watcher); } task = new WatchFileChangesTask(watcher, path); executorService = getCamelContext().getExecutorServiceManager().newSingleThreadExecutor(this, "FileWatcherReloadStrategy"); executorService.submit(task); } catch (IOException e) { throw RuntimeCamelException.wrapRuntimeCamelException(e); } } } protected String startupMessage(File dir) { return "Starting ReloadStrategy to watch directory: " + dir; } private WatchKey registerPathToWatcher(WatchEvent.Modifier modifier, Path path, WatchService watcher) throws IOException { WatchKey key; if (modifier != null) { key = path.register(watcher, new WatchEvent.Kind[] { ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE }, modifier); } else { key = path.register(watcher, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); } return key; } private void registerRecursive(final WatchService watcher, final Path root, final WatchEvent.Modifier modifier) throws IOException { Files.walkFileTree(root, new SimpleFileVisitor<>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { WatchKey key = registerPathToWatcher(modifier, dir, watcher); folderKeys.put(key, dir); return FileVisitResult.CONTINUE; } }); } @Override protected void doStop() throws Exception { super.doStop(); if (executorService != null) { getCamelContext().getExecutorServiceManager().shutdown(executorService); executorService = null; } if (watcher != null) { IOHelper.close(watcher); } } /** * Background task which watches for file changes */ protected class WatchFileChangesTask implements Runnable { private final WatchService watcher; private final Path folder; private volatile boolean running; public WatchFileChangesTask(WatchService watcher, Path folder) { this.watcher = watcher; this.folder = folder; } public boolean isRunning() { return running; } public void run() { LOG.debug("FileReloadStrategy is starting watching folder: {}", folder); // allow running while starting Camel while (isStarting() || isRunAllowed()) { running = true; WatchKey key; try { LOG.trace("FileReloadStrategy is polling for file changes in directory: {}", folder); // wait for a key to be available key = watcher.poll(pollTimeout, TimeUnit.MILLISECONDS); } catch (InterruptedException ex) { LOG.info("Interrupted while polling for file changes"); Thread.currentThread().interrupt(); break; } if (key != null) { Path pathToReload; if (isRecursive) { pathToReload = folderKeys.get(key); } else { pathToReload = folder; } for (WatchEvent event : key.pollEvents()) { WatchEvent we = (WatchEvent) event; Path path = we.context(); File file = pathToReload.resolve(path).toFile(); LOG.trace("File watch-event: {} on file: {}", we, file); if (file.isDirectory()) { continue; } String name = FileUtil.compactPath(file.getPath()); LOG.debug("Detected Modified/Created file: {}", name); boolean accept = fileFilter == null || fileFilter.accept(file); if (accept) { LOG.debug("Accepted Modified/Created file: {}", name); try { setLastError(null); // must use file resource loader as we cannot load from classpath Resource resource = PluginHelper.getResourceLoader(getCamelContext()).resolveResource("file:" + name); getResourceReload().onReload(name, resource); incSucceededCounter(); } catch (Exception e) { setLastError(e); incFailedCounter(); LOG.warn("Error reloading routes from file: {} due to: {}. This exception is ignored.", name, e.getMessage(), e); } } } // the key must be reset after processed boolean valid = key.reset(); if (!valid) { break; } } } running = false; LOG.debug("FileReloadStrategy is stopping watching folder: {}", folder); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy