/**
* 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.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import org.apache.camel.api.management.ManagedAttribute;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.support.ReloadStrategySupport;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
/**
* A file based {@link org.apache.camel.spi.ReloadStrategy} which watches a file folder
* for modified files and reload on file changes.
* <p/>
* 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 FileWatcherReloadStrategy")
public class FileWatcherReloadStrategy extends ReloadStrategySupport {
private String folder;
private ExecutorService executorService;
private WatchFileChangesTask task;
public FileWatcherReloadStrategy() {
}
public FileWatcherReloadStrategy(String directory) {
setFolder(directory);
}
public void setFolder(String folder) {
this.folder = folder;
}
@ManagedAttribute(description = "Folder being watched")
public String getFolder() {
return folder;
}
@ManagedAttribute(description = "Whether the watcher is running")
public boolean isRunning() {
return task != null && task.isRunning();
}
@Override
protected void doStart() throws Exception {
super.doStart();
if (folder == null) {
// no folder configured
return;
}
File dir = new File(folder);
if (dir.exists() && dir.isDirectory()) {
log.info("Starting ReloadStrategy to watch directory: {}", dir);
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<WatchEvent.Modifier> 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.info("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();
WatchService watcher = path.getFileSystem().newWatchService();
// we cannot support deleting files as we don't know which routes that would be
if (modifier != null) {
path.register(watcher, new WatchEvent.Kind<?>[]{ENTRY_CREATE, ENTRY_MODIFY}, modifier);
} else {
path.register(watcher, ENTRY_CREATE, ENTRY_MODIFY);
}
task = new WatchFileChangesTask(watcher, path);
executorService = getCamelContext().getExecutorServiceManager().newSingleThreadExecutor(this, "FileWatcherReloadStrategy");
executorService.submit(task);
} catch (IOException e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
}
@Override
protected void doStop() throws Exception {
super.doStop();
if (executorService != null) {
getCamelContext().getExecutorServiceManager().shutdownGraceful(executorService);
executorService = null;
}
}
/**
* 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("ReloadStrategy is starting watching folder: {}", folder);
// allow to run while starting Camel
while (isStarting() || isRunAllowed()) {
running = true;
WatchKey key;
try {
log.trace("ReloadStrategy is polling for file changes in directory: {}", folder);
// wait for a key to be available
key = watcher.poll(2, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
break;
}
if (key != null) {
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent<Path> we = (WatchEvent<Path>) event;
Path path = we.context();
String name = folder.resolve(path).toAbsolutePath().toFile().getAbsolutePath();
log.trace("Modified/Created file: {}", name);
// must be an .xml file
if (name.toLowerCase(Locale.US).endsWith(".xml")) {
log.debug("Modified/Created XML file: {}", name);
try {
FileInputStream fis = new FileInputStream(name);
onReloadXml(getCamelContext(), name, fis);
IOHelper.close(fis);
} catch (Exception e) {
log.warn("Error reloading routes from file: " + name + " due " + e.getMessage() + ". This exception is ignored.", e);
}
}
}
// the key must be reset after processed
boolean valid = key.reset();
if (!valid) {
break;
}
}
}
running = false;
log.info("ReloadStrategy is stopping watching folder: {}", folder);
}
}
}