Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x
This commit is contained in:
commit
f55fbdb7eb
|
@ -143,6 +143,7 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
|
|||
_scanner.setRecursive(_recursive);
|
||||
_scanner.setFilenameFilter(_filenameFilter);
|
||||
_scanner.setReportDirs(true);
|
||||
_scanner.setScanDepth(1); //consider direct dir children of monitored dir
|
||||
_scanner.addListener(_scannerListener);
|
||||
|
||||
addBean(_scanner);
|
||||
|
|
|
@ -81,6 +81,11 @@ public class WebAppProvider extends ScanningAppProvider
|
|||
String lowername = name.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
File file = new File(dir, name);
|
||||
Resource r = Resource.newResource(file);
|
||||
if (getMonitoredResources().contains(r) && r.isDirectory())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore hidden files
|
||||
if (lowername.startsWith("."))
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
|||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.util.PathWatcher;
|
||||
import org.eclipse.jetty.util.Scanner;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.resource.PathResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -223,7 +224,7 @@ public abstract class AbstractJettyMojo extends AbstractMojo
|
|||
/**
|
||||
* A scanner to check for changes to the webapp
|
||||
*/
|
||||
protected PathWatcher scanner;
|
||||
protected Scanner scanner;
|
||||
|
||||
/**
|
||||
* A scanner to check ENTER hits on the console
|
||||
|
@ -459,7 +460,25 @@ public abstract class AbstractJettyMojo extends AbstractMojo
|
|||
// start the scanner thread (if necessary) on the main webapp
|
||||
if (isScanningEnabled())
|
||||
{
|
||||
scanner = new PathWatcher();
|
||||
scanner = new Scanner();
|
||||
scanner.setScanInterval(scanIntervalSeconds);
|
||||
scanner.setScanDepth(Scanner.MAX_SCAN_DEPTH); //always fully walk directory hierarchies
|
||||
scanner.setReportExistingFilesOnStartup(false);
|
||||
scanner.addListener(new Scanner.BulkListener()
|
||||
{
|
||||
public void filesChanged(List<String> changes)
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean reconfigure = changes.contains(project.getFile().getCanonicalPath());
|
||||
restartWebApp(reconfigure);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
|
||||
}
|
||||
}
|
||||
});
|
||||
configureScanner();
|
||||
startScanner();
|
||||
}
|
||||
|
@ -524,7 +543,6 @@ public abstract class AbstractJettyMojo extends AbstractMojo
|
|||
|
||||
XmlConfiguration xmlConfiguration = new XmlConfiguration(new PathResource(path));
|
||||
getLog().info("Applying context xml file " + contextXml);
|
||||
xmlConfiguration.configure(webApp);
|
||||
}
|
||||
|
||||
//If no contextPath was specified, go with default of project artifactid
|
||||
|
@ -563,8 +581,6 @@ public abstract class AbstractJettyMojo extends AbstractMojo
|
|||
if (!isScanningEnabled())
|
||||
return;
|
||||
|
||||
scanner.setNotifyExistingOnStart(false);
|
||||
|
||||
scanner.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -41,8 +42,7 @@ import org.apache.maven.plugins.annotations.Parameter;
|
|||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.eclipse.jetty.maven.plugin.utils.MavenProjectHelper;
|
||||
import org.eclipse.jetty.util.PathWatcher;
|
||||
import org.eclipse.jetty.util.PathWatcher.PathWatchEvent;
|
||||
import org.eclipse.jetty.util.IncludeExcludeSet;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
||||
|
@ -145,9 +145,6 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
|
||||
protected Resource originalBaseResource;
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#execute()
|
||||
*/
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException, MojoFailureException
|
||||
{
|
||||
|
@ -157,8 +154,6 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
|
||||
/**
|
||||
* Verify the configuration given in the pom.
|
||||
*
|
||||
* @see AbstractJettyMojo#checkPomConfiguration()
|
||||
*/
|
||||
@Override
|
||||
public boolean checkPomConfiguration() throws MojoExecutionException
|
||||
|
@ -229,9 +224,6 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
super.finishConfigurationBeforeStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureWebApplication()
|
||||
*/
|
||||
@Override
|
||||
public void configureWebApplication() throws Exception
|
||||
{
|
||||
|
@ -317,9 +309,6 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
getLog().info("Webapp directory = " + webAppSourceDirectory.getCanonicalPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureScanner()
|
||||
*/
|
||||
@Override
|
||||
public void configureScanner()
|
||||
throws MojoExecutionException
|
||||
|
@ -332,36 +321,6 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
{
|
||||
throw new MojoExecutionException("Error forming scan list", e);
|
||||
}
|
||||
|
||||
scanner.addListener(new PathWatcher.EventListListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onPathWatchEvents(List<PathWatchEvent> events)
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean reconfigure = false;
|
||||
if (events != null)
|
||||
{
|
||||
for (PathWatchEvent e : events)
|
||||
{
|
||||
if (e.getPath().equals(project.getFile().toPath()))
|
||||
{
|
||||
reconfigure = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
restartWebApp(reconfigure);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
getLog().error("Error reconfiguring/restarting webapp after change in watched files", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void gatherScannables() throws Exception
|
||||
|
@ -369,33 +328,37 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
if (webApp.getDescriptor() != null)
|
||||
{
|
||||
Resource r = Resource.newResource(webApp.getDescriptor());
|
||||
scanner.watch(r.getFile().toPath());
|
||||
scanner.addFile(r.getFile().toPath());
|
||||
}
|
||||
|
||||
if (webApp.getJettyEnvXml() != null)
|
||||
scanner.watch(new File(webApp.getJettyEnvXml()).toPath());
|
||||
scanner.addFile(new File(webApp.getJettyEnvXml()).toPath());
|
||||
|
||||
if (webApp.getDefaultsDescriptor() != null)
|
||||
{
|
||||
if (!WebAppContext.WEB_DEFAULTS_XML.equals(webApp.getDefaultsDescriptor()))
|
||||
scanner.watch(new File(webApp.getDefaultsDescriptor()).toPath());
|
||||
scanner.addFile(new File(webApp.getDefaultsDescriptor()).toPath());
|
||||
}
|
||||
|
||||
if (webApp.getOverrideDescriptor() != null)
|
||||
{
|
||||
scanner.watch(new File(webApp.getOverrideDescriptor()).toPath());
|
||||
scanner.addFile(new File(webApp.getOverrideDescriptor()).toPath());
|
||||
}
|
||||
|
||||
File jettyWebXmlFile = findJettyWebXmlFile(new File(webAppSourceDirectory, "WEB-INF"));
|
||||
if (jettyWebXmlFile != null)
|
||||
{
|
||||
scanner.watch(jettyWebXmlFile.toPath());
|
||||
scanner.addFile(jettyWebXmlFile.toPath());
|
||||
}
|
||||
|
||||
//make sure each of the war artifacts is added to the scanner
|
||||
for (Artifact a : getWarArtifacts())
|
||||
{
|
||||
scanner.watch(a.getFile().toPath());
|
||||
File f = a.getFile();
|
||||
if (a.getFile().isDirectory())
|
||||
scanner.addDirectory(f.toPath());
|
||||
else
|
||||
scanner.addFile(f.toPath());
|
||||
}
|
||||
|
||||
//handle the explicit extra scan targets
|
||||
|
@ -405,87 +368,81 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
{
|
||||
if (f.isDirectory())
|
||||
{
|
||||
PathWatcher.Config config = new PathWatcher.Config(f.toPath());
|
||||
config.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
|
||||
scanner.watch(config);
|
||||
scanner.addDirectory(f.toPath());
|
||||
}
|
||||
else
|
||||
scanner.watch(f.toPath());
|
||||
scanner.addFile(f.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
scanner.addFile(project.getFile().toPath());
|
||||
|
||||
//handle the extra scan patterns
|
||||
if (scanTargetPatterns != null)
|
||||
{
|
||||
for (ScanTargetPattern p : scanTargetPatterns)
|
||||
{
|
||||
PathWatcher.Config config = new PathWatcher.Config(p.getDirectory().toPath());
|
||||
config.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
|
||||
for (String pattern : p.getExcludes())
|
||||
{
|
||||
config.addExcludeGlobRelative(pattern);
|
||||
}
|
||||
for (String pattern : p.getIncludes())
|
||||
{
|
||||
config.addIncludeGlobRelative(pattern);
|
||||
}
|
||||
scanner.watch(config);
|
||||
IncludeExcludeSet<PathMatcher, Path> includesExcludes = scanner.addDirectory(p.getDirectory().toPath());
|
||||
p.configureIncludesExcludeSet(includesExcludes);
|
||||
}
|
||||
}
|
||||
|
||||
scanner.watch(project.getFile().toPath());
|
||||
|
||||
if (webApp.getTestClasses() != null && webApp.getTestClasses().exists())
|
||||
{
|
||||
PathWatcher.Config config = new PathWatcher.Config(webApp.getTestClasses().toPath());
|
||||
config.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
|
||||
Path p = webApp.getTestClasses().toPath();
|
||||
IncludeExcludeSet<PathMatcher, Path> includeExcludeSet = scanner.addDirectory(p);
|
||||
|
||||
if (scanTestClassesPattern != null)
|
||||
{
|
||||
for (String p : scanTestClassesPattern.getExcludes())
|
||||
for (String s : scanTestClassesPattern.getExcludes())
|
||||
{
|
||||
config.addExcludeGlobRelative(p);
|
||||
if (!s.startsWith("glob:"))
|
||||
s = "glob:" + s;
|
||||
includeExcludeSet.exclude(p.getFileSystem().getPathMatcher(s));
|
||||
}
|
||||
for (String p : scanTestClassesPattern.getIncludes())
|
||||
for (String s : scanTestClassesPattern.getIncludes())
|
||||
{
|
||||
config.addIncludeGlobRelative(p);
|
||||
if (!s.startsWith("glob:"))
|
||||
s = "glob:" + s;
|
||||
includeExcludeSet.include(p.getFileSystem().getPathMatcher(s));
|
||||
}
|
||||
}
|
||||
scanner.watch(config);
|
||||
}
|
||||
|
||||
if (webApp.getClasses() != null && webApp.getClasses().exists())
|
||||
{
|
||||
PathWatcher.Config config = new PathWatcher.Config(webApp.getClasses().toPath());
|
||||
config.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
|
||||
Path p = webApp.getClasses().toPath();
|
||||
IncludeExcludeSet<PathMatcher, Path> includeExcludes = scanner.addDirectory(p);
|
||||
if (scanClassesPattern != null)
|
||||
{
|
||||
for (String p : scanClassesPattern.getExcludes())
|
||||
for (String s : scanClassesPattern.getExcludes())
|
||||
{
|
||||
config.addExcludeGlobRelative(p);
|
||||
if (!s.startsWith("glob:"))
|
||||
s = "glob:" + s;
|
||||
includeExcludes.exclude(p.getFileSystem().getPathMatcher(s));
|
||||
}
|
||||
|
||||
for (String p : scanClassesPattern.getIncludes())
|
||||
for (String s : scanClassesPattern.getIncludes())
|
||||
{
|
||||
config.addIncludeGlobRelative(p);
|
||||
if (!s.startsWith("glob:"))
|
||||
s = "glob:" + s;
|
||||
includeExcludes.include(p.getFileSystem().getPathMatcher(s));
|
||||
}
|
||||
}
|
||||
scanner.watch(config);
|
||||
}
|
||||
|
||||
if (webApp.getWebInfLib() != null)
|
||||
{
|
||||
for (File f : webApp.getWebInfLib())
|
||||
{
|
||||
PathWatcher.Config config = new PathWatcher.Config(f.toPath());
|
||||
config.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
|
||||
scanner.watch(config);
|
||||
if (f.isDirectory())
|
||||
scanner.addDirectory(f.toPath());
|
||||
else
|
||||
scanner.addFile(f.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#restartWebApp(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void restartWebApp(boolean reconfigureScanner) throws Exception
|
||||
{
|
||||
|
@ -665,9 +622,6 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
return Resource.newResource(dir.getCanonicalPath());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private List<Artifact> getWarArtifacts()
|
||||
{
|
||||
if (warArtifacts != null)
|
||||
|
@ -708,9 +662,6 @@ public class JettyRunMojo extends AbstractJettyMojo
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected String getJavaBin()
|
||||
{
|
||||
String[] javaexes = {"java", "java.exe"};
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
package org.eclipse.jetty.maven.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
|
@ -28,8 +28,6 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
|
|||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||
import org.eclipse.jetty.util.PathWatcher;
|
||||
import org.eclipse.jetty.util.PathWatcher.PathWatchEvent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
@ -55,9 +53,6 @@ public class JettyRunWarExplodedMojo extends AbstractJettyMojo
|
|||
@Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}", required = true)
|
||||
private File war;
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#execute()
|
||||
*/
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException, MojoFailureException
|
||||
{
|
||||
|
@ -71,70 +66,55 @@ public class JettyRunWarExplodedMojo extends AbstractJettyMojo
|
|||
super.finishConfigurationBeforeStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractJettyMojo#configureScanner()
|
||||
*/
|
||||
@Override
|
||||
public void configureScanner() throws MojoExecutionException
|
||||
{
|
||||
scanner.watch(project.getFile().toPath());
|
||||
File webInfDir = new File(war, "WEB-INF");
|
||||
File webXml = new File(webInfDir, "web.xml");
|
||||
if (webXml.exists())
|
||||
scanner.watch(webXml.toPath());
|
||||
File jettyWebXmlFile = findJettyWebXmlFile(webInfDir);
|
||||
if (jettyWebXmlFile != null)
|
||||
scanner.watch(jettyWebXmlFile.toPath());
|
||||
File jettyEnvXmlFile = new File(webInfDir, "jetty-env.xml");
|
||||
if (jettyEnvXmlFile.exists())
|
||||
scanner.watch(jettyEnvXmlFile.toPath());
|
||||
|
||||
File classes = new File(webInfDir, "classes");
|
||||
if (classes.exists())
|
||||
try
|
||||
{
|
||||
PathWatcher.Config classesConfig = new PathWatcher.Config(classes.toPath());
|
||||
classesConfig.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
|
||||
scanner.watch(classesConfig);
|
||||
}
|
||||
scanner.addFile(project.getFile().toPath());
|
||||
File webInfDir = new File(war, "WEB-INF");
|
||||
File webXml = new File(webInfDir, "web.xml");
|
||||
if (webXml.exists())
|
||||
scanner.addFile(webXml.toPath());
|
||||
File jettyWebXmlFile = findJettyWebXmlFile(webInfDir);
|
||||
if (jettyWebXmlFile != null)
|
||||
scanner.addFile(jettyWebXmlFile.toPath());
|
||||
File jettyEnvXmlFile = new File(webInfDir, "jetty-env.xml");
|
||||
if (jettyEnvXmlFile.exists())
|
||||
scanner.addFile(jettyEnvXmlFile.toPath());
|
||||
|
||||
File lib = new File(webInfDir, "lib");
|
||||
if (lib.exists())
|
||||
{
|
||||
PathWatcher.Config libConfig = new PathWatcher.Config(lib.toPath());
|
||||
libConfig.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
|
||||
scanner.watch(libConfig);
|
||||
}
|
||||
|
||||
scanner.addListener(new PathWatcher.EventListListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onPathWatchEvents(List<PathWatchEvent> events)
|
||||
File classes = new File(webInfDir, "classes");
|
||||
if (classes.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean reconfigure = false;
|
||||
for (PathWatchEvent e : events)
|
||||
{
|
||||
if (e.getPath().equals(project.getFile().toPath()))
|
||||
{
|
||||
reconfigure = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
restartWebApp(reconfigure);
|
||||
scanner.addDirectory(webApp.getClasses().toPath());
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (IOException e)
|
||||
{
|
||||
getLog().error("Error reconfiguring/restarting webapp after change in watched files", e);
|
||||
throw new MojoExecutionException("Error scanning classes", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
File lib = new File(webInfDir, "lib");
|
||||
if (lib.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
scanner.addDirectory(lib.toPath());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new MojoExecutionException("Error scanning lib", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new MojoExecutionException("Error configuring scanner", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#restartWebApp(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void restartWebApp(boolean reconfigureScanner) throws Exception
|
||||
{
|
||||
|
@ -161,9 +141,6 @@ public class JettyRunWarExplodedMojo extends AbstractJettyMojo
|
|||
getLog().info("Restart completed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureWebApplication()
|
||||
*/
|
||||
@Override
|
||||
public void configureWebApplication() throws Exception
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
package org.eclipse.jetty.maven.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
|
@ -28,8 +28,6 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
|
|||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||
import org.eclipse.jetty.util.PathWatcher;
|
||||
import org.eclipse.jetty.util.PathWatcher.PathWatchEvent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
@ -56,9 +54,6 @@ public class JettyRunWarMojo extends AbstractJettyMojo
|
|||
@Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.war", required = true)
|
||||
private File war;
|
||||
|
||||
/**
|
||||
* @see org.apache.maven.plugin.Mojo#execute()
|
||||
*/
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException, MojoFailureException
|
||||
{
|
||||
|
@ -80,45 +75,20 @@ public class JettyRunWarMojo extends AbstractJettyMojo
|
|||
webApp.setWar(war.getCanonicalPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractJettyMojo#configureScanner()
|
||||
*/
|
||||
@Override
|
||||
public void configureScanner() throws MojoExecutionException
|
||||
{
|
||||
scanner.watch(project.getFile().toPath());
|
||||
scanner.watch(war.toPath());
|
||||
|
||||
scanner.addListener(new PathWatcher.EventListListener()
|
||||
try
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onPathWatchEvents(List<PathWatchEvent> events)
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean reconfigure = false;
|
||||
for (PathWatchEvent e : events)
|
||||
{
|
||||
if (e.getPath().equals(project.getFile().toPath()))
|
||||
{
|
||||
reconfigure = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
restartWebApp(reconfigure);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
getLog().error("Error reconfiguring/restarting webapp after change in watched files", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
scanner.addFile(project.getFile().toPath());
|
||||
scanner.addFile(war.toPath());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new MojoExecutionException("Error configuring scanner", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#restartWebApp(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void restartWebApp(boolean reconfigureScanner) throws Exception
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.List;
|
|||
/**
|
||||
* ScanPattern
|
||||
*
|
||||
* A pattern of includes and excludes.
|
||||
* Ant-style pattern of includes and excludes.
|
||||
*/
|
||||
public class ScanPattern
|
||||
{
|
||||
|
|
|
@ -19,9 +19,13 @@
|
|||
package org.eclipse.jetty.maven.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.util.IncludeExcludeSet;
|
||||
|
||||
/**
|
||||
* ScanTargetPattern
|
||||
*
|
||||
|
@ -87,4 +91,21 @@ public class ScanTargetPattern
|
|||
{
|
||||
return (_pattern == null ? Collections.emptyList() : _pattern.getExcludes());
|
||||
}
|
||||
|
||||
public void configureIncludesExcludeSet(IncludeExcludeSet<PathMatcher, Path> includesExcludes)
|
||||
{
|
||||
for (String include:getIncludes())
|
||||
{
|
||||
if (!include.startsWith("glob:"))
|
||||
include = "glob:" + include;
|
||||
includesExcludes.include(_directory.toPath().getFileSystem().getPathMatcher(include));
|
||||
}
|
||||
|
||||
for (String exclude:getExcludes())
|
||||
{
|
||||
if (!exclude.startsWith("glob:"))
|
||||
exclude = "glob:" + exclude;
|
||||
includesExcludes.exclude(_directory.toPath().getFileSystem().getPathMatcher(exclude));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,12 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util-ajax</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -26,7 +26,12 @@ import java.nio.BufferOverflowException;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -442,23 +447,31 @@ public class ErrorHandler extends AbstractHandler
|
|||
|
||||
private void writeErrorJson(HttpServletRequest request, PrintWriter writer, int code, String message)
|
||||
{
|
||||
writer
|
||||
.append("{\n")
|
||||
.append(" url: \"").append(request.getRequestURI()).append("\",\n")
|
||||
.append(" status: \"").append(Integer.toString(code)).append("\",\n")
|
||||
.append(" message: ").append(QuotedStringTokenizer.quote(message)).append(",\n");
|
||||
Object servlet = request.getAttribute(Dispatcher.ERROR_SERVLET_NAME);
|
||||
if (servlet != null)
|
||||
writer.append("servlet: \"").append(servlet.toString()).append("\",\n");
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
Object servlet = request.getAttribute(Dispatcher.ERROR_SERVLET_NAME);
|
||||
Map<String,String> json = new HashMap<>();
|
||||
|
||||
json.put("url", request.getRequestURI());
|
||||
json.put("status", Integer.toString(code));
|
||||
json.put("message", message);
|
||||
if (servlet != null)
|
||||
{
|
||||
json.put("servlet", servlet.toString());
|
||||
}
|
||||
int c = 0;
|
||||
while (cause != null)
|
||||
{
|
||||
writer.append(" cause").append(Integer.toString(c++)).append(": ")
|
||||
.append(QuotedStringTokenizer.quote(cause.toString())).append(",\n");
|
||||
json.put("cause" + c++, cause.toString());
|
||||
cause = cause.getCause();
|
||||
}
|
||||
writer.append("}");
|
||||
|
||||
writer.append(json.entrySet().stream()
|
||||
.map(e -> QuotedStringTokenizer.quote(e.getKey()) +
|
||||
":" +
|
||||
QuotedStringTokenizer.quote((e.getValue())))
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}")));
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -30,6 +32,7 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.tools.HttpTester;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.ajax.JSON;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -302,4 +305,25 @@ public class ErrorHandlerTest
|
|||
|
||||
assertThat("Response status code", response.getStatus(), is(444));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonResponse() throws Exception
|
||||
{
|
||||
String rawResponse = connector.getResponse(
|
||||
"GET /badmessage/444 HTTP/1.1\r\n" +
|
||||
"Host: Localhost\r\n" +
|
||||
"Accept: text/json\r\n" +
|
||||
"\r\n");
|
||||
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
|
||||
|
||||
assertThat("Response status code", response.getStatus(), is(444));
|
||||
|
||||
System.out.println("response:" + response.getContent());
|
||||
|
||||
Map<Object,Object> jo = (Map) JSON.parse(response.getContent());
|
||||
|
||||
assertThat("url field null", jo.get("url"), is(notNullValue()));
|
||||
assertThat("status field null", jo.get("status"), is(notNullValue()));
|
||||
assertThat("message field null", jo.get("message"), is(notNullValue()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,16 @@ package org.eclipse.jetty.util;
|
|||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
@ -32,6 +40,7 @@ import java.util.Map.Entry;
|
|||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -45,6 +54,16 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*/
|
||||
public class Scanner extends AbstractLifeCycle
|
||||
{
|
||||
/**
|
||||
* When walking a directory, a depth of 1 ensures that
|
||||
* the directory's descendants are visited, not just the
|
||||
* directory itself (as a file).
|
||||
*
|
||||
* @see Visitor#preVisitDirectory
|
||||
*/
|
||||
public static final int DEFAULT_SCAN_DEPTH = 1;
|
||||
public static final int MAX_SCAN_DEPTH = Integer.MAX_VALUE;
|
||||
|
||||
private static final Logger LOG = Log.getLogger(Scanner.class);
|
||||
private static int __scannerId = 0;
|
||||
private int _scanInterval;
|
||||
|
@ -53,13 +72,13 @@ public class Scanner extends AbstractLifeCycle
|
|||
private final Map<String, TimeNSize> _prevScan = new HashMap<>();
|
||||
private final Map<String, TimeNSize> _currentScan = new HashMap<>();
|
||||
private FilenameFilter _filter;
|
||||
private final List<File> _scanDirs = new ArrayList<>();
|
||||
private final Map<Path, IncludeExcludeSet<PathMatcher, Path>> _scannables = new HashMap<>();
|
||||
private volatile boolean _running = false;
|
||||
private boolean _reportExisting = true;
|
||||
private boolean _reportDirs = true;
|
||||
private Timer _timer;
|
||||
private TimerTask _task;
|
||||
private int _scanDepth = 0;
|
||||
private int _scanDepth = DEFAULT_SCAN_DEPTH;
|
||||
|
||||
public enum Notification
|
||||
{
|
||||
|
@ -67,7 +86,32 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
private final Map<String, Notification> _notifications = new HashMap<>();
|
||||
|
||||
/**
|
||||
* PathMatcherSet
|
||||
*
|
||||
* A set of PathMatchers for testing Paths against path matching patterns via
|
||||
* @see IncludeExcludeSet
|
||||
*/
|
||||
static class PathMatcherSet extends HashSet<PathMatcher> implements Predicate<Path>
|
||||
{
|
||||
@Override
|
||||
public boolean test(Path p)
|
||||
{
|
||||
for (PathMatcher pm : this)
|
||||
{
|
||||
if (pm.matches(p))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TimeNSize
|
||||
*
|
||||
* Metadata about a file: Last modified time and file size.
|
||||
*/
|
||||
static class TimeNSize
|
||||
{
|
||||
final long _lastModified;
|
||||
|
@ -103,6 +147,105 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor
|
||||
*
|
||||
* A FileVisitor for walking a subtree of paths. The Scanner uses
|
||||
* this to examine the dirs and files it has been asked to scan.
|
||||
*/
|
||||
public class Visitor implements FileVisitor<Path>
|
||||
{
|
||||
Map<String, TimeNSize> scanInfoMap;
|
||||
IncludeExcludeSet<PathMatcher,Path> rootIncludesExcludes;
|
||||
Path root;
|
||||
|
||||
public Visitor(Path root, IncludeExcludeSet<PathMatcher,Path> rootIncludesExcludes, Map<String, TimeNSize> scanInfoMap)
|
||||
{
|
||||
this.root = root;
|
||||
this.rootIncludesExcludes = rootIncludesExcludes;
|
||||
this.scanInfoMap = scanInfoMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
|
||||
{
|
||||
if (!Files.exists(dir))
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
|
||||
File f = dir.toFile();
|
||||
|
||||
//if we want to report directories and we haven't already seen it
|
||||
if (_reportDirs && !scanInfoMap.containsKey(f.getCanonicalPath()))
|
||||
{
|
||||
boolean accepted = false;
|
||||
if (rootIncludesExcludes != null && !rootIncludesExcludes.isEmpty())
|
||||
{
|
||||
//accepted if not explicitly excluded and either is explicitly included or there are no explicit inclusions
|
||||
Boolean result = rootIncludesExcludes.test(dir);
|
||||
if (Boolean.TRUE == result)
|
||||
accepted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_filter == null || _filter.accept(f.getParentFile(), f.getName()))
|
||||
accepted = true;
|
||||
}
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
scanInfoMap.put(f.getCanonicalPath(), new TimeNSize(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||
if (LOG.isDebugEnabled()) LOG.debug("scan accepted dir {} mod={}", f, f.lastModified());
|
||||
}
|
||||
}
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
|
||||
{
|
||||
if (!Files.exists(file))
|
||||
return FileVisitResult.CONTINUE;
|
||||
|
||||
File f = file.toFile();
|
||||
boolean accepted = false;
|
||||
|
||||
if (f.isFile() || (f.isDirectory() && _reportDirs && !scanInfoMap.containsKey(f.getCanonicalPath())))
|
||||
{
|
||||
if (rootIncludesExcludes != null && !rootIncludesExcludes.isEmpty())
|
||||
{
|
||||
//accepted if not explicitly excluded and either is explicitly included or there are no explicit inclusions
|
||||
Boolean result = rootIncludesExcludes.test(file);
|
||||
if (Boolean.TRUE == result)
|
||||
accepted = true;
|
||||
}
|
||||
else if (_filter == null || _filter.accept(f.getParentFile(), f.getName()))
|
||||
accepted = true;
|
||||
}
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
scanInfoMap.put(f.getCanonicalPath(), new TimeNSize(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||
if (LOG.isDebugEnabled()) LOG.debug("scan accepted {} mod={}", f, f.lastModified());
|
||||
}
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException
|
||||
{
|
||||
LOG.warn(exc);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException
|
||||
{
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener
|
||||
*
|
||||
|
@ -171,36 +314,110 @@ public class Scanner extends AbstractLifeCycle
|
|||
|
||||
public void setScanDirs(List<File> dirs)
|
||||
{
|
||||
_scanDirs.clear();
|
||||
_scanDirs.addAll(dirs);
|
||||
_scannables.clear();
|
||||
if (dirs == null)
|
||||
return;
|
||||
|
||||
for (File f:dirs)
|
||||
{
|
||||
addScanDir(f);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public synchronized void addScanDir(File dir)
|
||||
{
|
||||
_scanDirs.add(dir);
|
||||
if (dir == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (dir.isDirectory())
|
||||
addDirectory(dir.toPath());
|
||||
else
|
||||
addFile(dir.toPath());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to be scanned. The file must not be null, and must exist.
|
||||
*
|
||||
* @param p the Path of the file to scan.
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized void addFile(Path p) throws IOException
|
||||
{
|
||||
if (p == null)
|
||||
throw new IllegalStateException("Null path");
|
||||
|
||||
File f = p.toFile();
|
||||
if (!f.exists() || f.isDirectory())
|
||||
throw new IllegalStateException("Not file or doesn't exist: " + f.getCanonicalPath());
|
||||
_scannables.put(p, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory to be scanned. The directory must not be null and must exist.
|
||||
*
|
||||
* @param p the directory to scan.
|
||||
* @return an IncludeExcludeSet to which the caller can add PathMatcher patterns to match
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized IncludeExcludeSet<PathMatcher, Path> addDirectory(Path p)
|
||||
throws IOException
|
||||
{
|
||||
if (p == null)
|
||||
throw new IllegalStateException("Null path");
|
||||
|
||||
File f = p.toFile();
|
||||
if (!f.exists() || !f.isDirectory())
|
||||
throw new IllegalStateException("Not directory or doesn't exist: " + f.getCanonicalPath());
|
||||
|
||||
IncludeExcludeSet<PathMatcher, Path> includesExcludes = _scannables.get(p);
|
||||
if (includesExcludes == null)
|
||||
{
|
||||
includesExcludes = new IncludeExcludeSet<>(PathMatcherSet.class);
|
||||
_scannables.put(p.toRealPath(), includesExcludes);
|
||||
}
|
||||
|
||||
return includesExcludes;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public List<File> getScanDirs()
|
||||
{
|
||||
return Collections.unmodifiableList(_scanDirs);
|
||||
ArrayList<File> files = new ArrayList<>();
|
||||
for (Path p : _scannables.keySet())
|
||||
files.add(p.toFile());
|
||||
return Collections.unmodifiableList(files);
|
||||
}
|
||||
|
||||
public Set<Path> getScannables()
|
||||
{
|
||||
return _scannables.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recursive True if scanning is recursive
|
||||
* @see #setScanDepth(int)
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRecursive(boolean recursive)
|
||||
{
|
||||
_scanDepth = recursive ? -1 : 0;
|
||||
_scanDepth = recursive ? Integer.MAX_VALUE : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if scanning is fully recursive (scandepth==-1)
|
||||
* @return True if scanning is recursive
|
||||
* @see #getScanDepth()
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean getRecursive()
|
||||
{
|
||||
return _scanDepth == -1;
|
||||
return _scanDepth > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -229,6 +446,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
*
|
||||
* @param filter the filename filter to use
|
||||
*/
|
||||
@Deprecated
|
||||
public void setFilenameFilter(FilenameFilter filter)
|
||||
{
|
||||
_filter = filter;
|
||||
|
@ -239,6 +457,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
*
|
||||
* @return the filename filter
|
||||
*/
|
||||
@Deprecated
|
||||
public FilenameFilter getFilenameFilter()
|
||||
{
|
||||
return _filter;
|
||||
|
@ -310,6 +529,9 @@ public class Scanner extends AbstractLifeCycle
|
|||
return;
|
||||
|
||||
_running = true;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Scanner start: rprtExists={}, depth={}, rprtDirs={}, interval={}, filter={}, scannables={}",
|
||||
_reportExisting, _scanDepth, _reportDirs, _scanInterval, _filter, _scannables);
|
||||
|
||||
if (_reportExisting)
|
||||
{
|
||||
|
@ -377,6 +599,23 @@ public class Scanner extends AbstractLifeCycle
|
|||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the list of scannables. The scanner must first
|
||||
* be in the stopped state.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
if (!isStopped())
|
||||
throw new IllegalStateException("Not stopped");
|
||||
|
||||
//clear the scannables
|
||||
_scannables.clear();
|
||||
|
||||
//clear the previous scans
|
||||
_currentScan.clear();
|
||||
_prevScan.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path tests if the path exists
|
||||
|
@ -384,9 +623,9 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
public boolean exists(String path)
|
||||
{
|
||||
for (File dir : _scanDirs)
|
||||
for (Path p : _scannables.keySet())
|
||||
{
|
||||
if (new File(dir, path).exists())
|
||||
if (p.resolve(path).toFile().exists())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -419,23 +658,20 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* Recursively scan all files in the designated directories.
|
||||
* Scan all of the given paths.
|
||||
*/
|
||||
public synchronized void scanFiles()
|
||||
{
|
||||
_currentScan.clear();
|
||||
for (File dir : _scanDirs)
|
||||
for (Path p : _scannables.keySet())
|
||||
{
|
||||
if ((dir != null) && (dir.exists()))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
scanFile(dir.getCanonicalFile(), _currentScan, 0);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn("Error scanning files.", e);
|
||||
}
|
||||
Files.walkFileTree(p, EnumSet.allOf(FileVisitOption.class),_scanDepth, new Visitor(p, _scannables.get(p), _currentScan));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn("Error scanning files.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -449,7 +685,6 @@ public class Scanner extends AbstractLifeCycle
|
|||
private synchronized void reportDifferences(Map<String, TimeNSize> currentScan, Map<String, TimeNSize> oldScan)
|
||||
{
|
||||
// scan the differences and add what was found to the map of notifications:
|
||||
|
||||
Set<String> oldScanKeys = new HashSet<>(oldScan.keySet());
|
||||
|
||||
// Look for new and changed files
|
||||
|
@ -492,16 +727,16 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("scanned " + _scanDirs + ": " + _notifications);
|
||||
LOG.debug("scanned " + _scannables.keySet() + ": " + _notifications);
|
||||
|
||||
// Process notifications
|
||||
// Only process notifications that are for stable files (ie same in old and current scan).
|
||||
List<String> bulkChanges = new ArrayList<>();
|
||||
for (Iterator<Entry<String, Notification>> iter = _notifications.entrySet().iterator(); iter.hasNext(); )
|
||||
{
|
||||
|
||||
Entry<String, Notification> entry = iter.next();
|
||||
String file = entry.getKey();
|
||||
|
||||
// Is the file stable?
|
||||
if (oldScan.containsKey(file))
|
||||
{
|
||||
|
@ -534,57 +769,6 @@ public class Scanner extends AbstractLifeCycle
|
|||
reportBulkChanges(bulkChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last modified time on a single file or recurse if
|
||||
* the file is a directory.
|
||||
*
|
||||
* @param f file or directory
|
||||
* @param scanInfoMap map of filenames to last modified times
|
||||
*/
|
||||
private void scanFile(File f, Map<String, TimeNSize> scanInfoMap, int depth)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!f.exists())
|
||||
return;
|
||||
|
||||
if (f.isFile() || depth > 0 && _reportDirs && f.isDirectory())
|
||||
{
|
||||
if (_filter == null || _filter.accept(f.getParentFile(), f.getName()))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("scan accepted {}", f);
|
||||
String name = f.getCanonicalPath();
|
||||
scanInfoMap.put(name, new TimeNSize(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("scan rejected {}", f);
|
||||
}
|
||||
}
|
||||
|
||||
// If it is a directory, scan if it is a known directory or the depth is OK.
|
||||
if (f.isDirectory() && (depth < _scanDepth || _scanDepth == -1 || _scanDirs.contains(f)))
|
||||
{
|
||||
File[] files = f.listFiles();
|
||||
if (files != null)
|
||||
{
|
||||
for (File file : files)
|
||||
{
|
||||
scanFile(file, scanInfoMap, depth + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG.warn("Error listing files in directory {}", f);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn("Error scanning watched files", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void warn(Object listener, String filename, Throwable th)
|
||||
{
|
||||
LOG.warn(listener + " failed on '" + filename, th);
|
||||
|
@ -653,6 +837,11 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the list of filenames for which changes were detected.
|
||||
*
|
||||
* @param filenames names of all files added/changed/removed
|
||||
*/
|
||||
private void reportBulkChanges(List<String> filenames)
|
||||
{
|
||||
for (Listener l : _listeners)
|
||||
|
@ -670,7 +859,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* signal any scan cycle listeners that a scan has started
|
||||
* Call ScanCycleListeners with start of scan
|
||||
*/
|
||||
private void reportScanStart(int cycle)
|
||||
{
|
||||
|
@ -691,7 +880,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* sign
|
||||
* Call ScanCycleListeners with end of scan.
|
||||
*/
|
||||
private void reportScanEnd(int cycle)
|
||||
{
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
@ -30,12 +32,14 @@ import java.util.concurrent.TimeUnit;
|
|||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.Scanner.Notification;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -61,6 +65,8 @@ public class ScannerTest
|
|||
_scanner = new Scanner();
|
||||
_scanner.addScanDir(_directory);
|
||||
_scanner.setScanInterval(0);
|
||||
_scanner.setReportDirs(false);
|
||||
_scanner.setReportExistingFilesOnStartup(false);
|
||||
_scanner.addListener(new Scanner.DiscreteListener()
|
||||
{
|
||||
@Override
|
||||
|
@ -89,8 +95,8 @@ public class ScannerTest
|
|||
_bulk.add(filenames);
|
||||
}
|
||||
});
|
||||
|
||||
_scanner.start();
|
||||
|
||||
_scanner.scan();
|
||||
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
@ -115,6 +121,155 @@ public class ScannerTest
|
|||
_notification = notification;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDepth() throws Exception
|
||||
{
|
||||
File root = new File (_directory, "root");
|
||||
FS.ensureDirExists(root);
|
||||
FS.touch(new File(root, "foo.foo"));
|
||||
FS.touch(new File(root, "foo2.foo"));
|
||||
File dir = new File(root, "xxx");
|
||||
FS.ensureDirExists(dir);
|
||||
File x1 = new File(dir, "xxx.foo");
|
||||
FS.touch(x1);
|
||||
File x2 = new File(dir, "xxx2.foo");
|
||||
FS.touch(x2);
|
||||
File dir2 = new File(dir, "yyy");
|
||||
FS.ensureDirExists(dir2);
|
||||
File y1 = new File(dir2, "yyy.foo");
|
||||
FS.touch(y1);
|
||||
File y2 = new File(dir2, "yyy2.foo");
|
||||
FS.touch(y2);
|
||||
|
||||
BlockingQueue<Event> queue = new LinkedBlockingQueue<Event>();
|
||||
Scanner scanner = new Scanner();
|
||||
scanner.setScanInterval(0);
|
||||
scanner.setScanDepth(0);
|
||||
scanner.setReportDirs(true);
|
||||
scanner.setReportExistingFilesOnStartup(true);
|
||||
scanner.addDirectory(root.toPath());
|
||||
scanner.addListener(new Scanner.DiscreteListener()
|
||||
{
|
||||
@Override
|
||||
public void fileRemoved(String filename) throws Exception
|
||||
{
|
||||
queue.add(new Event(filename, Notification.REMOVED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileChanged(String filename) throws Exception
|
||||
{
|
||||
queue.add(new Event(filename, Notification.CHANGED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileAdded(String filename) throws Exception
|
||||
{
|
||||
queue.add(new Event(filename, Notification.ADDED));
|
||||
}
|
||||
});
|
||||
|
||||
scanner.start();
|
||||
Event e = queue.take();
|
||||
assertNotNull(e);
|
||||
assertEquals(Notification.ADDED, e._notification);
|
||||
assertTrue(e._filename.endsWith(root.getName()));
|
||||
queue.clear();
|
||||
scanner.stop();
|
||||
scanner.reset();
|
||||
|
||||
//Depth one should report the dir itself and its file and dir direct children
|
||||
scanner.setScanDepth(1);
|
||||
scanner.addDirectory(root.toPath());
|
||||
scanner.start();
|
||||
assertEquals(4, queue.size());
|
||||
queue.clear();
|
||||
scanner.stop();
|
||||
scanner.reset();
|
||||
|
||||
//Depth 2 should report the dir itself, all file children, xxx and xxx's children
|
||||
scanner.setScanDepth(2);
|
||||
scanner.addDirectory(root.toPath());
|
||||
scanner.start();
|
||||
|
||||
assertEquals(7, queue.size());
|
||||
scanner.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatterns() throws Exception
|
||||
{
|
||||
//test include and exclude patterns
|
||||
File root = new File(_directory, "proot");
|
||||
FS.ensureDirExists(root);
|
||||
|
||||
File ttt = new File(root, "ttt.txt");
|
||||
FS.touch(ttt);
|
||||
FS.touch(new File(root, "ttt.foo"));
|
||||
File dir = new File(root, "xxx");
|
||||
FS.ensureDirExists(dir);
|
||||
|
||||
File x1 = new File(dir, "ttt.xxx");
|
||||
FS.touch(x1);
|
||||
File x2 = new File(dir, "xxx.txt");
|
||||
FS.touch(x2);
|
||||
|
||||
File dir2 = new File(dir, "yyy");
|
||||
FS.ensureDirExists(dir2);
|
||||
File y1 = new File(dir2, "ttt.yyy");
|
||||
FS.touch(y1);
|
||||
File y2 = new File(dir2, "yyy.txt");
|
||||
FS.touch(y2);
|
||||
|
||||
BlockingQueue<Event> queue = new LinkedBlockingQueue<Event>();
|
||||
//only scan the *.txt files for changes
|
||||
Scanner scanner = new Scanner();
|
||||
IncludeExcludeSet<PathMatcher, Path> pattern = scanner.addDirectory(root.toPath());
|
||||
pattern.exclude(root.toPath().getFileSystem().getPathMatcher("glob:**/*.foo"));
|
||||
pattern.exclude(root.toPath().getFileSystem().getPathMatcher("glob:**/ttt.xxx"));
|
||||
scanner.setScanInterval(0);
|
||||
scanner.setScanDepth(2); //should never see any files from subdir yyy
|
||||
scanner.setReportDirs(false);
|
||||
scanner.setReportExistingFilesOnStartup(false);
|
||||
scanner.addListener(new Scanner.DiscreteListener()
|
||||
{
|
||||
@Override
|
||||
public void fileRemoved(String filename) throws Exception
|
||||
{
|
||||
queue.add(new Event(filename, Notification.REMOVED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileChanged(String filename) throws Exception
|
||||
{
|
||||
queue.add(new Event(filename, Notification.CHANGED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileAdded(String filename) throws Exception
|
||||
{
|
||||
queue.add(new Event(filename, Notification.ADDED));
|
||||
}
|
||||
});
|
||||
|
||||
scanner.start();
|
||||
assertTrue(queue.isEmpty());
|
||||
|
||||
Thread.sleep(1100); // make sure time in seconds changes
|
||||
FS.touch(ttt);
|
||||
FS.touch(x2);
|
||||
FS.touch(x1);
|
||||
FS.touch(y2);
|
||||
scanner.scan();
|
||||
scanner.scan(); //2 scans for file to be considered settled
|
||||
|
||||
assertThat(queue.size(), Matchers.equalTo(2));
|
||||
for (Event e : queue)
|
||||
{
|
||||
assertTrue(e._filename.endsWith("ttt.txt") || e._filename.endsWith("xxx.txt"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledOnOs(WINDOWS) // TODO: needs review
|
||||
|
@ -126,6 +281,7 @@ public class ScannerTest
|
|||
// takes 2 scans to notice a0 and check that it is stable
|
||||
_scanner.scan();
|
||||
_scanner.scan();
|
||||
|
||||
Event event = _queue.poll();
|
||||
assertNotNull(event, "Event should not be null");
|
||||
assertEquals(_directory + "/a0", event._filename);
|
||||
|
|
Loading…
Reference in New Issue