001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.impl; 018 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.nio.file.Path; 023import java.nio.file.WatchEvent; 024import java.nio.file.WatchKey; 025import java.nio.file.WatchService; 026import java.util.Locale; 027import java.util.concurrent.ExecutorService; 028import java.util.concurrent.TimeUnit; 029 030import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; 031import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; 032 033import org.apache.camel.api.management.ManagedAttribute; 034import org.apache.camel.api.management.ManagedResource; 035import org.apache.camel.support.ReloadStrategySupport; 036import org.apache.camel.util.IOHelper; 037import org.apache.camel.util.ObjectHelper; 038 039/** 040 * A file based {@link org.apache.camel.spi.ReloadStrategy} which watches a file folder 041 * for modified files and reload on file changes. 042 * <p/> 043 * This implementation uses the JDK {@link WatchService} to watch for when files are 044 * created or modified. Mac OS X users should be noted the osx JDK does not support 045 * native file system changes and therefore the watch service is much slower than on 046 * linux or windows systems. 047 */ 048@ManagedResource(description = "Managed FileWatcherReloadStrategy") 049public class FileWatcherReloadStrategy extends ReloadStrategySupport { 050 051 private String folder; 052 private ExecutorService executorService; 053 private WatchFileChangesTask task; 054 055 public FileWatcherReloadStrategy() { 056 } 057 058 public FileWatcherReloadStrategy(String directory) { 059 setFolder(directory); 060 } 061 062 public void setFolder(String folder) { 063 this.folder = folder; 064 } 065 066 @ManagedAttribute(description = "Folder being watched") 067 public String getFolder() { 068 return folder; 069 } 070 071 @ManagedAttribute(description = "Whether the watcher is running") 072 public boolean isRunning() { 073 return task != null && task.isRunning(); 074 } 075 076 @Override 077 protected void doStart() throws Exception { 078 super.doStart(); 079 080 if (folder == null) { 081 // no folder configured 082 return; 083 } 084 085 File dir = new File(folder); 086 if (dir.exists() && dir.isDirectory()) { 087 log.info("Starting ReloadStrategy to watch directory: {}", dir); 088 089 WatchEvent.Modifier modifier = null; 090 091 // if its mac OSX then attempt to apply workaround or warn its slower 092 String os = ObjectHelper.getSystemProperty("os.name", ""); 093 if (os.toLowerCase(Locale.US).startsWith("mac")) { 094 // this modifier can speedup the scanner on mac osx (as java on mac has no native file notification integration) 095 Class<WatchEvent.Modifier> clazz = getCamelContext().getClassResolver().resolveClass("com.sun.nio.file.SensitivityWatchEventModifier", WatchEvent.Modifier.class); 096 if (clazz != null) { 097 WatchEvent.Modifier[] modifiers = clazz.getEnumConstants(); 098 for (WatchEvent.Modifier mod : modifiers) { 099 if ("HIGH".equals(mod.name())) { 100 modifier = mod; 101 break; 102 } 103 } 104 } 105 if (modifier != null) { 106 log.info("On Mac OS X the JDK WatchService is slow by default so enabling SensitivityWatchEventModifier.HIGH as workaround"); 107 } else { 108 log.warn("On Mac OS X the JDK WatchService is slow and it may take up till 10 seconds to notice file changes"); 109 } 110 } 111 112 try { 113 Path path = dir.toPath(); 114 WatchService watcher = path.getFileSystem().newWatchService(); 115 // we cannot support deleting files as we don't know which routes that would be 116 if (modifier != null) { 117 path.register(watcher, new WatchEvent.Kind<?>[]{ENTRY_CREATE, ENTRY_MODIFY}, modifier); 118 } else { 119 path.register(watcher, ENTRY_CREATE, ENTRY_MODIFY); 120 } 121 122 task = new WatchFileChangesTask(watcher, path); 123 124 executorService = getCamelContext().getExecutorServiceManager().newSingleThreadExecutor(this, "FileWatcherReloadStrategy"); 125 executorService.submit(task); 126 } catch (IOException e) { 127 throw ObjectHelper.wrapRuntimeCamelException(e); 128 } 129 } 130 } 131 132 @Override 133 protected void doStop() throws Exception { 134 super.doStop(); 135 136 if (executorService != null) { 137 getCamelContext().getExecutorServiceManager().shutdownGraceful(executorService); 138 executorService = null; 139 } 140 } 141 142 /** 143 * Background task which watches for file changes 144 */ 145 protected class WatchFileChangesTask implements Runnable { 146 147 private final WatchService watcher; 148 private final Path folder; 149 private volatile boolean running; 150 151 public WatchFileChangesTask(WatchService watcher, Path folder) { 152 this.watcher = watcher; 153 this.folder = folder; 154 } 155 156 public boolean isRunning() { 157 return running; 158 } 159 160 public void run() { 161 log.debug("ReloadStrategy is starting watching folder: {}", folder); 162 163 // allow to run while starting Camel 164 while (isStarting() || isRunAllowed()) { 165 running = true; 166 167 WatchKey key; 168 try { 169 log.trace("ReloadStrategy is polling for file changes in directory: {}", folder); 170 // wait for a key to be available 171 key = watcher.poll(2, TimeUnit.SECONDS); 172 } catch (InterruptedException ex) { 173 break; 174 } 175 176 if (key != null) { 177 for (WatchEvent<?> event : key.pollEvents()) { 178 WatchEvent<Path> we = (WatchEvent<Path>) event; 179 Path path = we.context(); 180 String name = folder.resolve(path).toAbsolutePath().toFile().getAbsolutePath(); 181 log.trace("Modified/Created file: {}", name); 182 183 // must be an .xml file 184 if (name.toLowerCase(Locale.US).endsWith(".xml")) { 185 log.debug("Modified/Created XML file: {}", name); 186 try { 187 FileInputStream fis = new FileInputStream(name); 188 onReloadXml(getCamelContext(), name, fis); 189 IOHelper.close(fis); 190 } catch (Exception e) { 191 log.warn("Error reloading routes from file: " + name + " due " + e.getMessage() + ". This exception is ignored.", e); 192 } 193 } 194 } 195 196 // the key must be reset after processed 197 boolean valid = key.reset(); 198 if (!valid) { 199 break; 200 } 201 } 202 } 203 204 running = false; 205 206 log.info("ReloadStrategy is stopping watching folder: {}", folder); 207 } 208 } 209 210}