/** * Initializes the redeployment cycle. In "redeploy mode", the application is launched as background, and is * restarted after every change. A {@link Watcher} instance is responsible for monitoring files and triggering the * redeployment. */ protected synchronized void initializeRedeployment() { if (watcher != null) { throw new IllegalStateException("Redeployment already started ? The watcher already exists"); } // Compute the application id. We append "-redeploy" to ease the identification in the process list. vertxApplicationBackgroundId = UUID.randomUUID().toString() + "-redeploy"; watcher = new Watcher(getCwd(), redeploy, this::startAsBackgroundApplication, // On deploy this::stopBackgroundApplication, // On undeploy onRedeployCommand, // In between command redeployGracePeriod, // The redeploy grace period redeployScanPeriod); // The redeploy scan period // Close the watcher when the JVM is terminating. // Notice that the vert.x finalizer is not registered when we run in redeploy mode. Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { shutdownRedeployment(); } }); // Start the watching process, it triggers the initial deployment. watcher.watch(); }
/** * The watching thread runnable method. */ @Override public void run() { try { while (!closed) { if (changesHaveOccurred()) { trigger(); } // Wait for the next scan. Thread.sleep(scanPeriod); } } catch (Throwable e) { LOGGER.error("An error have been encountered while watching resources - leaving the redeploy mode", e); close(); } }
/** * Creates a new {@link Watcher}. * * @param root the root directory * @param includes the list of include patterns, should not be {@code null} or empty * @param deploy the function called when deployment is required * @param undeploy the function called when un-deployment is required * @param onRedeployCommand an optional command executed after the un-deployment and before the deployment * @param gracePeriod the amount of time in milliseconds to wait between two redeploy even * if there are changes * @param scanPeriod the time in millisecond between 2 file system scans */ public Watcher(File root, List<String> includes, Handler<Handler<Void>> deploy, Handler<Handler<Void>> undeploy, String onRedeployCommand, long gracePeriod, long scanPeriod) { this.gracePeriod = gracePeriod; this.includes = sanitizeIncludePatterns(includes); this.roots = extractRoots(root, this.includes); this.cwd = root; LOGGER.info("Watched paths: " + this.roots); this.deploy = deploy; this.undeploy = undeploy; this.cmd = onRedeployCommand; this.scanPeriod = scanPeriod; addFilesToWatchedList(roots); }
@Test public void testWithANonMatchingFile() throws IOException, InterruptedException { watcher.watch(); // Initial deployment assertWaitUntil(() -> deploy.get() == 1); File file = new File(root, "foo.nope"); file.createNewFile(); Thread.sleep(500); assertThat(undeploy.get()).isEqualTo(0); assertThat(deploy.get()).isEqualTo(1); }
/** * Stop the redeployment if started. */ protected synchronized void shutdownRedeployment() { if (watcher != null) { watcher.close(); watcher = null; } }
@Before public void prepare() { root = new File("target/junk/watcher"); deleteRecursive(root); root.mkdirs(); deploy = new AtomicInteger(); undeploy = new AtomicInteger(); watcher = new Watcher(root, Collections.unmodifiableList( Arrays.asList("**" + File.separator + "*.txt", "windows\\*.win", "unix/*.nix", "FOO.bar")), next -> { deploy.incrementAndGet(); if (next != null) { next.handle(null); } }, next -> { undeploy.incrementAndGet(); if (next != null) { next.handle(null); } }, null, 10, 10); }
List<String> patterns = new ArrayList<>(); patterns.add("src/main/java/**/*.java"); List<File> results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath());
if (match(currFile)) { changed = true; if (match(currFile)) { changed = true; addFileToWatchedList(newFile); if (match(newFile)) { changed = true;
/** * Redeployment process. */ private void trigger() { long begin = System.currentTimeMillis(); LOGGER.info("Redeploying!"); // 1) undeploy.handle(v1 -> { // 2) executeUserCommand(v2 -> { // 3) deploy.handle(v3 -> { long end = System.currentTimeMillis(); LOGGER.info("Redeployment done in " + (end - begin) + " ms."); }); }); }); }
private void addFileToWatchedList(File file) { filesToWatch.add(file); Map<File, FileInfo> map = new HashMap<>(); if (file.isDirectory()) { // We're watching a directory contents and its children for changes File[] children = file.listFiles(); if (children != null) { for (File child : children) { map.put(child, new FileInfo(child.lastModified(), child.length())); if (child.isDirectory()) { addFileToWatchedList(child); } } } } else { // Not a directory - we're watching a specific file - e.g. a jar map.put(file, new FileInfo(file.lastModified(), file.length())); } fileMap.put(file, map); }
@Test public void testFileDeletion() throws IOException, InterruptedException { File file = new File(root, "foo.txt"); file.createNewFile(); watcher.watch(); // Initial deployment assertWaitUntil(() -> deploy.get() == 1); // Wait until the file monitoring is set up (ugly, but I don't know any way to detect this). Thread.sleep(500); file.delete(); // undeployment followed by redeployment assertWaitUntil(() -> undeploy.get() == 1 && deploy.get() == 2); }
@After public void close() { watcher.close(); }
@Before public void prepare() { root = new File("target/junk/watcher"); File otherRoot = new File(root.getParentFile(), "abs-test"); deleteRecursive(otherRoot); deleteRecursive(root); otherRoot.mkdirs(); root.mkdirs(); deploy = new AtomicInteger(); undeploy = new AtomicInteger(); watcher = new Watcher(otherRoot, Collections.unmodifiableList( Arrays.asList( root.getAbsolutePath() + File.separator + "**" + File.separator + "*.txt", root.getAbsolutePath() + File.separator + "windows\\*.win", root.getAbsolutePath() + File.separator + "unix/*.nix", root.getAbsolutePath() + File.separator + "FOO.bar")), next -> { deploy.incrementAndGet(); if (next != null) { next.handle(null); } }, next -> { undeploy.incrementAndGet(); if (next != null) { next.handle(null); } }, null, 10, 10); } }
List<String> patterns = new ArrayList<>(); patterns.add("src/main/java/**/*.java"); List<File> results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath());
if (match(currFile)) { changed = true; if (match(currFile)) { changed = true; addFileToWatchedList(newFile); if (match(newFile)) { changed = true;
/** * Redeployment process. */ private void trigger() { long begin = System.currentTimeMillis(); LOGGER.info("Redeploying!"); // 1) undeploy.handle(v1 -> { // 2) executeUserCommand(v2 -> { // 3) deploy.handle(v3 -> { long end = System.currentTimeMillis(); LOGGER.info("Redeployment done in " + (end - begin) + " ms."); }); }); }); }
private void addFileToWatchedList(File file) { filesToWatch.add(file); Map<File, FileInfo> map = new HashMap<>(); if (file.isDirectory()) { // We're watching a directory contents and its children for changes File[] children = file.listFiles(); if (children != null) { for (File child : children) { map.put(child, new FileInfo(child.lastModified(), child.length())); if (child.isDirectory()) { addFileToWatchedList(child); } } } } else { // Not a directory - we're watching a specific file - e.g. a jar map.put(file, new FileInfo(file.lastModified(), file.length())); } fileMap.put(file, map); }
@Test public void testFileAddition() throws IOException { watcher.watch(); // Initial deployment assertWaitUntil(() -> deploy.get() == 1); File file = new File(root, "foo.txt"); file.createNewFile(); // undeployment followed by redeployment assertWaitUntil(() -> undeploy.get() == 1 && deploy.get() == 2); }
/** * The watching thread runnable method. */ @Override public void run() { try { while (!closed) { if (changesHaveOccurred()) { trigger(); } // Wait for the next scan. Thread.sleep(scanPeriod); } } catch (Throwable e) { LOGGER.error("An error have been encountered while watching resources - leaving the redeploy mode", e); close(); } }
/** * Creates a new {@link Watcher}. * * @param root the root directory * @param includes the list of include patterns, should not be {@code null} or empty * @param deploy the function called when deployment is required * @param undeploy the function called when un-deployment is required * @param onRedeployCommand an optional command executed after the un-deployment and before the deployment * @param gracePeriod the amount of time in milliseconds to wait between two redeploy even * if there are changes * @param scanPeriod the time in millisecond between 2 file system scans */ public Watcher(File root, List<String> includes, Handler<Handler<Void>> deploy, Handler<Handler<Void>> undeploy, String onRedeployCommand, long gracePeriod, long scanPeriod) { this.gracePeriod = gracePeriod; this.includes = sanitizeIncludePatterns(includes); this.roots = extractRoots(root, this.includes); this.cwd = root; LOGGER.info("Watched paths: " + this.roots); this.deploy = deploy; this.undeploy = undeploy; this.cmd = onRedeployCommand; this.scanPeriod = scanPeriod; addFilesToWatchedList(roots); }