Merged branch 'jetty-10.0.x' into 'jetty-11.0.x'.
Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
commit
c11c8bbd5c
|
@ -48,6 +48,7 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
|
||||||
private final List<Resource> _monitored = new CopyOnWriteArrayList<>();
|
private final List<Resource> _monitored = new CopyOnWriteArrayList<>();
|
||||||
private int _scanInterval = 10;
|
private int _scanInterval = 10;
|
||||||
private Scanner _scanner;
|
private Scanner _scanner;
|
||||||
|
private boolean _useRealPaths;
|
||||||
|
|
||||||
private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener()
|
private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener()
|
||||||
{
|
{
|
||||||
|
@ -81,6 +82,22 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
|
||||||
addBean(_appMap);
|
addBean(_appMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if the real path of the scanned files should be used for deployment.
|
||||||
|
*/
|
||||||
|
public boolean isUseRealPaths()
|
||||||
|
{
|
||||||
|
return _useRealPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param useRealPaths True if the real path of the scanned files should be used for deployment.
|
||||||
|
*/
|
||||||
|
public void setUseRealPaths(boolean useRealPaths)
|
||||||
|
{
|
||||||
|
_useRealPaths = useRealPaths;
|
||||||
|
}
|
||||||
|
|
||||||
protected void setFilenameFilter(FilenameFilter filter)
|
protected void setFilenameFilter(FilenameFilter filter)
|
||||||
{
|
{
|
||||||
if (isRunning())
|
if (isRunning())
|
||||||
|
@ -128,7 +145,7 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
|
||||||
LOG.warn("Does not exist: {}", resource);
|
LOG.warn("Does not exist: {}", resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
_scanner = new Scanner();
|
_scanner = new Scanner(null, _useRealPaths);
|
||||||
_scanner.setScanDirs(files);
|
_scanner.setScanDirs(files);
|
||||||
_scanner.setScanInterval(_scanInterval);
|
_scanner.setScanInterval(_scanInterval);
|
||||||
_scanner.setFilenameFilter(_filenameFilter);
|
_scanner.setFilenameFilter(_filenameFilter);
|
||||||
|
|
|
@ -375,7 +375,7 @@ public class WebAppProvider extends ScanningAppProvider
|
||||||
{
|
{
|
||||||
//if a .xml file exists for it, then redeploy that instead
|
//if a .xml file exists for it, then redeploy that instead
|
||||||
File xml = new File(parent, xmlname);
|
File xml = new File(parent, xmlname);
|
||||||
super.fileChanged(xml.getCanonicalPath());
|
super.fileChanged(xml.getPath());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +384,7 @@ public class WebAppProvider extends ScanningAppProvider
|
||||||
{
|
{
|
||||||
//if a .XML file exists for it, then redeploy that instead
|
//if a .XML file exists for it, then redeploy that instead
|
||||||
File xml = new File(parent, xmlname);
|
File xml = new File(parent, xmlname);
|
||||||
super.fileChanged(xml.getCanonicalPath());
|
super.fileChanged(xml.getPath());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
package org.eclipse.jetty.deploy.providers;
|
package org.eclipse.jetty.deploy.providers;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.FileSystemException;
|
import java.nio.file.FileSystemException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -35,7 +34,6 @@ import org.eclipse.jetty.util.resource.Resource;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -45,7 +43,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||||
import static org.junit.jupiter.api.condition.OS.LINUX;
|
import static org.junit.jupiter.api.condition.OS.LINUX;
|
||||||
import static org.junit.jupiter.api.condition.OS.MAC;
|
|
||||||
|
|
||||||
@ExtendWith(WorkDirExtension.class)
|
@ExtendWith(WorkDirExtension.class)
|
||||||
public class WebAppProviderTest
|
public class WebAppProviderTest
|
||||||
|
@ -68,10 +65,13 @@ public class WebAppProviderTest
|
||||||
|
|
||||||
// Make symlink
|
// Make symlink
|
||||||
Path pathWar3 = MavenTestingUtils.getTestResourcePathFile("webapps/foo-webapp-3.war");
|
Path pathWar3 = MavenTestingUtils.getTestResourcePathFile("webapps/foo-webapp-3.war");
|
||||||
|
Path pathFoo = jetty.getJettyDir("webapps/foo.war").toPath();
|
||||||
Path pathBar = jetty.getJettyDir("webapps/bar.war").toPath();
|
Path pathBar = jetty.getJettyDir("webapps/bar.war").toPath();
|
||||||
|
Path pathBob = jetty.getJettyDir("webapps/bob.war").toPath();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Files.createSymbolicLink(pathBar, pathWar3);
|
Files.createSymbolicLink(pathBar, pathWar3);
|
||||||
|
Files.createSymbolicLink(pathBob, pathFoo);
|
||||||
symlinkSupported = true;
|
symlinkSupported = true;
|
||||||
}
|
}
|
||||||
catch (UnsupportedOperationException | FileSystemException e)
|
catch (UnsupportedOperationException | FileSystemException e)
|
||||||
|
@ -95,12 +95,11 @@ public class WebAppProviderTest
|
||||||
jetty.stop();
|
jetty.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("See issue #1200")
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartupContext()
|
public void testStartupContext()
|
||||||
{
|
{
|
||||||
// Check Server for Handlers
|
// Check Server for Handlers
|
||||||
jetty.assertWebAppContextsExists("/bar", "/foo");
|
jetty.assertWebAppContextsExists("/bar", "/foo", "/bob");
|
||||||
|
|
||||||
File workDir = jetty.getJettyDir("workish");
|
File workDir = jetty.getJettyDir("workish");
|
||||||
|
|
||||||
|
@ -109,10 +108,9 @@ public class WebAppProviderTest
|
||||||
assertDirNotExists("root of work directory", workDir, "jsp");
|
assertDirNotExists("root of work directory", workDir, "jsp");
|
||||||
|
|
||||||
// Test for correct behaviour
|
// Test for correct behaviour
|
||||||
assertTrue(hasJettyGeneratedPath(workDir, "foo.war"), "Should have generated directory in work directory: " + workDir);
|
assertTrue(hasJettyGeneratedPath(workDir, "foo_war"), "Should have generated directory in work directory: " + workDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("See issue #1200")
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartupSymlinkContext()
|
public void testStartupSymlinkContext()
|
||||||
{
|
{
|
||||||
|
@ -124,11 +122,11 @@ public class WebAppProviderTest
|
||||||
assertTrue(barLink.isFile(), "bar.war link isFile: " + barLink.toString());
|
assertTrue(barLink.isFile(), "bar.war link isFile: " + barLink.toString());
|
||||||
|
|
||||||
// Check Server for expected Handlers
|
// Check Server for expected Handlers
|
||||||
jetty.assertWebAppContextsExists("/bar", "/foo");
|
jetty.assertWebAppContextsExists("/bar", "/foo", "/bob");
|
||||||
|
|
||||||
// Test for expected work/temp directory behaviour
|
// Test for expected work/temp directory behaviour
|
||||||
File workDir = jetty.getJettyDir("workish");
|
File workDir = jetty.getJettyDir("workish");
|
||||||
assertTrue(hasJettyGeneratedPath(workDir, "bar.war"), "Should have generated directory in work directory: " + workDir);
|
assertTrue(hasJettyGeneratedPath(workDir, "bar_war"), "Should have generated directory in work directory: " + workDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<Set name="monitoredDirName"><SystemProperty name="jetty.home" />/webapps</Set>
|
<Set name="monitoredDirName"><SystemProperty name="jetty.home" />/webapps</Set>
|
||||||
<Set name="scanInterval">1</Set>
|
<Set name="scanInterval">1</Set>
|
||||||
<Set name="tempDir"><Property name="jetty.home" default="target" />/workish</Set>
|
<Set name="tempDir"><Property name="jetty.home" default="target" />/workish</Set>
|
||||||
|
<Set name="useRealPaths">false</Set>
|
||||||
</New>
|
</New>
|
||||||
</Item>
|
</Item>
|
||||||
</Array>
|
</Array>
|
||||||
|
|
|
@ -152,7 +152,9 @@ import static java.lang.invoke.MethodType.methodType;
|
||||||
*
|
*
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td valign="top">%H</td>
|
* <td valign="top">%H</td>
|
||||||
* <td>The request protocol.</td>
|
* <td>Returns the name and version of the protocol the request uses in the form
|
||||||
|
* protocol/majorVersion.minorVersion, for example, HTTP/1.1. For HTTP servlets,
|
||||||
|
* the value returned is the same as the value of the CGI variable SERVER_PROTOCOL.</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
*
|
*
|
||||||
* <tr>
|
* <tr>
|
||||||
|
|
|
@ -1013,75 +1013,6 @@ public class RequestTest
|
||||||
assertThat(response, containsString(" 200 OK"));
|
assertThat(response, containsString(" 200 OK"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Disabled("See issue #1175")
|
|
||||||
public void testMultiPartFormDataReadInputThenParams() throws Exception
|
|
||||||
{
|
|
||||||
final File tmpdir = MavenTestingUtils.getTargetTestingDir("multipart");
|
|
||||||
FS.ensureEmpty(tmpdir);
|
|
||||||
|
|
||||||
Handler handler = new AbstractHandler()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
if (baseRequest.getDispatcherType() != DispatcherType.REQUEST)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Fake a @MultiPartConfig'd servlet endpoint
|
|
||||||
MultipartConfigElement multipartConfig = new MultipartConfigElement(tmpdir.getAbsolutePath());
|
|
||||||
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, multipartConfig);
|
|
||||||
|
|
||||||
// Normal processing
|
|
||||||
baseRequest.setHandled(true);
|
|
||||||
|
|
||||||
// Fake the commons-fileupload behavior
|
|
||||||
int length = request.getContentLength();
|
|
||||||
InputStream in = request.getInputStream();
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
IO.copy(in, out, length); // KEY STEP (Don't Change!) commons-fileupload does not read to EOF
|
|
||||||
|
|
||||||
// Record what happened as servlet response headers
|
|
||||||
response.setIntHeader("x-request-content-length", request.getContentLength());
|
|
||||||
response.setIntHeader("x-request-content-read", out.size());
|
|
||||||
String foo = request.getParameter("foo"); // uri query parameter
|
|
||||||
String bar = request.getParameter("bar"); // form-data content parameter
|
|
||||||
response.setHeader("x-foo", foo == null ? "null" : foo);
|
|
||||||
response.setHeader("x-bar", bar == null ? "null" : bar);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_server.stop();
|
|
||||||
_server.setHandler(handler);
|
|
||||||
_server.start();
|
|
||||||
|
|
||||||
String multipart = "--AaBbCc\r\n" +
|
|
||||||
"content-disposition: form-data; name=\"bar\"\r\n" +
|
|
||||||
"\r\n" +
|
|
||||||
"BarContent\r\n" +
|
|
||||||
"--AaBbCc\r\n" +
|
|
||||||
"content-disposition: form-data; name=\"stuff\"\r\n" +
|
|
||||||
"Content-Type: text/plain;charset=ISO-8859-1\r\n" +
|
|
||||||
"\r\n" +
|
|
||||||
"000000000000000000000000000000000000000000000000000\r\n" +
|
|
||||||
"--AaBbCc--\r\n";
|
|
||||||
|
|
||||||
String request = "POST /?foo=FooUri HTTP/1.1\r\n" +
|
|
||||||
"Host: whatever\r\n" +
|
|
||||||
"Content-Type: multipart/form-data; boundary=\"AaBbCc\"\r\n" +
|
|
||||||
"Content-Length: " + multipart.getBytes().length + "\r\n" +
|
|
||||||
"Connection: close\r\n" +
|
|
||||||
"\r\n" +
|
|
||||||
multipart;
|
|
||||||
|
|
||||||
HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request));
|
|
||||||
|
|
||||||
// It should always be possible to read query string
|
|
||||||
assertThat("response.x-foo", response.get("x-foo"), is("FooUri"));
|
|
||||||
// Not possible to read request content parameters?
|
|
||||||
assertThat("response.x-bar", response.get("x-bar"), is("null")); // TODO: should this work?
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPartialRead() throws Exception
|
public void testPartialRead() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -1496,69 +1427,80 @@ public class RequestTest
|
||||||
assertEquals("value", cookies.get(0).getValue());
|
assertEquals("value", cookies.get(0).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("No longer relevant")
|
|
||||||
@Test
|
@Test
|
||||||
public void testCookieLeak() throws Exception
|
public void testCookieLeak() throws Exception
|
||||||
{
|
{
|
||||||
final String[] cookie = new String[10];
|
CookieRequestTester tester = new CookieRequestTester();
|
||||||
|
_handler._checker = tester;
|
||||||
|
|
||||||
_handler._checker = (request, response) ->
|
String[] cookies = new String[10];
|
||||||
{
|
tester.setCookieArray(cookies);
|
||||||
Arrays.fill(cookie, null);
|
LocalEndPoint endp = _connector.connect();
|
||||||
|
endp.addInput("POST / HTTP/1.1\r\n" +
|
||||||
Cookie[] cookies = request.getCookies();
|
|
||||||
for (int i = 0; cookies != null && i < cookies.length; i++)
|
|
||||||
{
|
|
||||||
cookie[i] = cookies[i].getValue();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
String request = "POST / HTTP/1.1\r\n" +
|
|
||||||
"Host: whatever\r\n" +
|
"Host: whatever\r\n" +
|
||||||
"Cookie: other=cookie\r\n" +
|
"Cookie: other=cookie\r\n" +
|
||||||
"\r\n" +
|
"\r\n");
|
||||||
"POST / HTTP/1.1\r\n" +
|
endp.getResponse();
|
||||||
|
assertEquals("cookie", cookies[0]);
|
||||||
|
assertNull(cookies[1]);
|
||||||
|
|
||||||
|
cookies = new String[10];
|
||||||
|
tester.setCookieArray(cookies);
|
||||||
|
endp.addInput("POST / HTTP/1.1\r\n" +
|
||||||
"Host: whatever\r\n" +
|
"Host: whatever\r\n" +
|
||||||
"Cookie: name=value\r\n" +
|
"Cookie: name=value\r\n" +
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n");
|
||||||
|
endp.getResponse();
|
||||||
|
assertEquals("value", cookies[0]);
|
||||||
|
assertNull(cookies[1]);
|
||||||
|
|
||||||
_connector.getResponse(request);
|
endp = _connector.connect();
|
||||||
|
cookies = new String[10];
|
||||||
assertEquals("value", cookie[0]);
|
tester.setCookieArray(cookies);
|
||||||
assertNull(cookie[1]);
|
endp.addInput("POST / HTTP/1.1\r\n" +
|
||||||
|
|
||||||
request = "POST / HTTP/1.1\r\n" +
|
|
||||||
"Host: whatever\r\n" +
|
"Host: whatever\r\n" +
|
||||||
"Cookie: name=value\r\n" +
|
"Cookie: name=value\r\n" +
|
||||||
"\r\n" +
|
"\r\n");
|
||||||
"POST / HTTP/1.1\r\n" +
|
endp.getResponse();
|
||||||
|
assertEquals("value", cookies[0]);
|
||||||
|
assertNull(cookies[1]);
|
||||||
|
|
||||||
|
cookies = new String[10];
|
||||||
|
tester.setCookieArray(cookies);
|
||||||
|
endp.addInput("POST / HTTP/1.1\r\n" +
|
||||||
"Host: whatever\r\n" +
|
"Host: whatever\r\n" +
|
||||||
"Cookie: \r\n" +
|
"Cookie: \r\n" +
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n");
|
||||||
|
endp.getResponse();
|
||||||
|
assertNull(cookies[0]);
|
||||||
|
assertNull(cookies[1]);
|
||||||
|
|
||||||
_connector.getResponse(request);
|
endp = _connector.connect();
|
||||||
assertNull(cookie[0]);
|
cookies = new String[10];
|
||||||
assertNull(cookie[1]);
|
tester.setCookieArray(cookies);
|
||||||
|
endp.addInput("POST / HTTP/1.1\r\n" +
|
||||||
request = "POST / HTTP/1.1\r\n" +
|
|
||||||
"Host: whatever\r\n" +
|
"Host: whatever\r\n" +
|
||||||
"Cookie: name=value\r\n" +
|
"Cookie: name=value\r\n" +
|
||||||
"Cookie: other=cookie\r\n" +
|
"Cookie: other=cookie\r\n" +
|
||||||
"\r\n" +
|
"\r\n");
|
||||||
"POST / HTTP/1.1\r\n" +
|
endp.getResponse();
|
||||||
|
assertEquals("value", cookies[0]);
|
||||||
|
assertEquals("cookie", cookies[1]);
|
||||||
|
assertNull(cookies[2]);
|
||||||
|
|
||||||
|
cookies = new String[10];
|
||||||
|
tester.setCookieArray(cookies);
|
||||||
|
endp.addInput("POST / HTTP/1.1\r\n" +
|
||||||
"Host: whatever\r\n" +
|
"Host: whatever\r\n" +
|
||||||
"Cookie: name=value\r\n" +
|
"Cookie: name=value\r\n" +
|
||||||
"Cookie:\r\n" +
|
"Cookie:\r\n" +
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n");
|
||||||
|
endp.getResponse();
|
||||||
_connector.getResponse(request);
|
assertEquals("value", cookies[0]);
|
||||||
|
assertNull(cookies[1]);
|
||||||
assertEquals("value", cookie[0]);
|
|
||||||
assertNull(cookie[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1947,6 +1889,29 @@ public class RequestTest
|
||||||
boolean check(HttpServletRequest request, HttpServletResponse response) throws IOException;
|
boolean check(HttpServletRequest request, HttpServletResponse response) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CookieRequestTester implements RequestTester
|
||||||
|
{
|
||||||
|
private String[] _cookieValues;
|
||||||
|
|
||||||
|
public void setCookieArray(String[] cookieValues)
|
||||||
|
{
|
||||||
|
_cookieValues = cookieValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
Arrays.fill(_cookieValues, null);
|
||||||
|
|
||||||
|
Cookie[] cookies = request.getCookies();
|
||||||
|
for (int i = 0; cookies != null && i < cookies.length; i++)
|
||||||
|
{
|
||||||
|
_cookieValues[i] = cookies[i].getValue();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class TestRequest extends Request
|
private static class TestRequest extends Request
|
||||||
{
|
{
|
||||||
public static final String TEST_SESSION_ID = "abc123";
|
public static final String TEST_SESSION_ID = "abc123";
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.nio.file.FileVisitOption;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.FileVisitor;
|
import java.nio.file.FileVisitor;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.PathMatcher;
|
import java.nio.file.PathMatcher;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
@ -35,6 +36,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||||
|
@ -47,6 +49,8 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
* Utility for scanning a directory for added, removed and changed
|
* Utility for scanning a directory for added, removed and changed
|
||||||
* files and reporting these events via registered Listeners.
|
* files and reporting these events via registered Listeners.
|
||||||
|
* The scanner operates on the {@link Path#toRealPath(LinkOption...)} of the files scanned and
|
||||||
|
* can be configured to follow symlinks.
|
||||||
*/
|
*/
|
||||||
public class Scanner extends ContainerLifeCycle
|
public class Scanner extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
|
@ -63,7 +67,7 @@ public class Scanner extends ContainerLifeCycle
|
||||||
private int _scanInterval;
|
private int _scanInterval;
|
||||||
private final AtomicInteger _scanCount = new AtomicInteger(0);
|
private final AtomicInteger _scanCount = new AtomicInteger(0);
|
||||||
private final List<Listener> _listeners = new CopyOnWriteArrayList<>();
|
private final List<Listener> _listeners = new CopyOnWriteArrayList<>();
|
||||||
private Map<String, MetaData> _prevScan;
|
private Map<Path, MetaData> _prevScan;
|
||||||
private FilenameFilter _filter;
|
private FilenameFilter _filter;
|
||||||
private final Map<Path, IncludeExcludeSet<PathMatcher, Path>> _scannables = new ConcurrentHashMap<>();
|
private final Map<Path, IncludeExcludeSet<PathMatcher, Path>> _scannables = new ConcurrentHashMap<>();
|
||||||
private boolean _reportExisting = true;
|
private boolean _reportExisting = true;
|
||||||
|
@ -71,6 +75,7 @@ public class Scanner extends ContainerLifeCycle
|
||||||
private Scheduler.Task _task;
|
private Scheduler.Task _task;
|
||||||
private final Scheduler _scheduler;
|
private final Scheduler _scheduler;
|
||||||
private int _scanDepth = DEFAULT_SCAN_DEPTH;
|
private int _scanDepth = DEFAULT_SCAN_DEPTH;
|
||||||
|
private final LinkOption[] _linkOptions;
|
||||||
|
|
||||||
private enum Status
|
private enum Status
|
||||||
{
|
{
|
||||||
|
@ -150,11 +155,11 @@ public class Scanner extends ContainerLifeCycle
|
||||||
*/
|
*/
|
||||||
private class Visitor implements FileVisitor<Path>
|
private class Visitor implements FileVisitor<Path>
|
||||||
{
|
{
|
||||||
Map<String, MetaData> scanInfoMap;
|
Map<Path, MetaData> scanInfoMap;
|
||||||
IncludeExcludeSet<PathMatcher, Path> rootIncludesExcludes;
|
IncludeExcludeSet<PathMatcher, Path> rootIncludesExcludes;
|
||||||
Path root;
|
Path root;
|
||||||
|
|
||||||
public Visitor(Path root, IncludeExcludeSet<PathMatcher, Path> rootIncludesExcludes, Map<String, MetaData> scanInfoMap)
|
private Visitor(Path root, IncludeExcludeSet<PathMatcher, Path> rootIncludesExcludes, Map<Path, MetaData> scanInfoMap)
|
||||||
{
|
{
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.rootIncludesExcludes = rootIncludesExcludes;
|
this.rootIncludesExcludes = rootIncludesExcludes;
|
||||||
|
@ -167,10 +172,11 @@ public class Scanner extends ContainerLifeCycle
|
||||||
if (!Files.exists(dir))
|
if (!Files.exists(dir))
|
||||||
return FileVisitResult.SKIP_SUBTREE;
|
return FileVisitResult.SKIP_SUBTREE;
|
||||||
|
|
||||||
|
dir = dir.toRealPath(_linkOptions);
|
||||||
File f = dir.toFile();
|
File f = dir.toFile();
|
||||||
|
|
||||||
//if we want to report directories and we haven't already seen it
|
//if we want to report directories and we haven't already seen it
|
||||||
if (_reportDirs && !scanInfoMap.containsKey(f.getCanonicalPath()))
|
if (_reportDirs && !scanInfoMap.containsKey(dir))
|
||||||
{
|
{
|
||||||
boolean accepted = false;
|
boolean accepted = false;
|
||||||
if (rootIncludesExcludes != null && !rootIncludesExcludes.isEmpty())
|
if (rootIncludesExcludes != null && !rootIncludesExcludes.isEmpty())
|
||||||
|
@ -186,7 +192,7 @@ public class Scanner extends ContainerLifeCycle
|
||||||
|
|
||||||
if (accepted)
|
if (accepted)
|
||||||
{
|
{
|
||||||
scanInfoMap.put(f.getCanonicalPath(), new MetaData(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
scanInfoMap.put(dir, new MetaData(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("scan accepted dir {} mod={}", f, f.lastModified());
|
if (LOG.isDebugEnabled()) LOG.debug("scan accepted dir {} mod={}", f, f.lastModified());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,20 +201,22 @@ public class Scanner extends ContainerLifeCycle
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
|
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException
|
||||||
{
|
{
|
||||||
if (!Files.exists(file))
|
path = path.toRealPath(_linkOptions);
|
||||||
|
|
||||||
|
if (!Files.exists(path))
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
|
|
||||||
File f = file.toFile();
|
File f = path.toFile();
|
||||||
boolean accepted = false;
|
boolean accepted = false;
|
||||||
|
|
||||||
if (f.isFile() || (f.isDirectory() && _reportDirs && !scanInfoMap.containsKey(f.getCanonicalPath())))
|
if (f.isFile() || (f.isDirectory() && _reportDirs && !scanInfoMap.containsKey(path)))
|
||||||
{
|
{
|
||||||
if (rootIncludesExcludes != null && !rootIncludesExcludes.isEmpty())
|
if (rootIncludesExcludes != null && !rootIncludesExcludes.isEmpty())
|
||||||
{
|
{
|
||||||
//accepted if not explicitly excluded and either is explicitly included or there are no explicit inclusions
|
//accepted if not explicitly excluded and either is explicitly included or there are no explicit inclusions
|
||||||
accepted = rootIncludesExcludes.test(file);
|
accepted = rootIncludesExcludes.test(path);
|
||||||
}
|
}
|
||||||
else if (_filter == null || _filter.accept(f.getParentFile(), f.getName()))
|
else if (_filter == null || _filter.accept(f.getParentFile(), f.getName()))
|
||||||
accepted = true;
|
accepted = true;
|
||||||
|
@ -216,7 +224,7 @@ public class Scanner extends ContainerLifeCycle
|
||||||
|
|
||||||
if (accepted)
|
if (accepted)
|
||||||
{
|
{
|
||||||
scanInfoMap.put(f.getCanonicalPath(), new MetaData(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
scanInfoMap.put(path, new MetaData(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("scan accepted {} mod={}", f, f.lastModified());
|
if (LOG.isDebugEnabled()) LOG.debug("scan accepted {} mod={}", f, f.lastModified());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,10 +259,62 @@ public class Scanner extends ContainerLifeCycle
|
||||||
*/
|
*/
|
||||||
public interface DiscreteListener extends Listener
|
public interface DiscreteListener extends Listener
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Called when a file is changed.
|
||||||
|
* Default implementation calls {@link #fileChanged(String)}.
|
||||||
|
* @param path the {@link Path#toRealPath(LinkOption...)} of the changed file
|
||||||
|
* @throws Exception May be thrown for handling errors
|
||||||
|
*/
|
||||||
|
default void pathChanged(Path path) throws Exception
|
||||||
|
{
|
||||||
|
path.toString();
|
||||||
|
fileChanged(path.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a file is added.
|
||||||
|
* Default implementation calls {@link #fileAdded(String)}.
|
||||||
|
* @param path the {@link Path#toRealPath(LinkOption...)} of the added file
|
||||||
|
* @throws Exception May be thrown for handling errors
|
||||||
|
*/
|
||||||
|
default void pathAdded(Path path) throws Exception
|
||||||
|
{
|
||||||
|
fileAdded(path.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a file is removed.
|
||||||
|
* Default implementation calls {@link #fileRemoved(String)}.
|
||||||
|
* @param path the {@link Path#toRealPath(LinkOption...)} of the removed file
|
||||||
|
* @throws Exception May be thrown for handling errors
|
||||||
|
*/
|
||||||
|
default void pathRemoved(Path path) throws Exception
|
||||||
|
{
|
||||||
|
fileRemoved(path.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a file is changed.
|
||||||
|
* May not be called if {@link #pathChanged(Path)} is overridden.
|
||||||
|
* @param filename the {@link Path#toRealPath(LinkOption...)} as a string of the changed file
|
||||||
|
* @throws Exception May be thrown for handling errors
|
||||||
|
*/
|
||||||
void fileChanged(String filename) throws Exception;
|
void fileChanged(String filename) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a file is added.
|
||||||
|
* May not be called if {@link #pathAdded(Path)} is overridden.
|
||||||
|
* @param filename the {@link Path#toRealPath(LinkOption...)} as a string of the added file
|
||||||
|
* @throws Exception May be thrown for handling errors
|
||||||
|
*/
|
||||||
void fileAdded(String filename) throws Exception;
|
void fileAdded(String filename) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a file is removed.
|
||||||
|
* May not be called if {@link #pathRemoved(Path)} is overridden.
|
||||||
|
* @param filename the {@link Path#toRealPath(LinkOption...)} as a string of the removed file
|
||||||
|
* @throws Exception May be thrown for handling errors
|
||||||
|
*/
|
||||||
void fileRemoved(String filename) throws Exception;
|
void fileRemoved(String filename) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +323,12 @@ public class Scanner extends ContainerLifeCycle
|
||||||
*/
|
*/
|
||||||
public interface BulkListener extends Listener
|
public interface BulkListener extends Listener
|
||||||
{
|
{
|
||||||
public void filesChanged(Set<String> filenames) throws Exception;
|
default void pathsChanged(Set<Path> paths) throws Exception
|
||||||
|
{
|
||||||
|
filesChanged(paths.stream().map(Path::toString).collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void filesChanged(Set<String> filenames) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,14 +347,24 @@ public class Scanner extends ContainerLifeCycle
|
||||||
|
|
||||||
public Scanner()
|
public Scanner()
|
||||||
{
|
{
|
||||||
this(new ScheduledExecutorScheduler("Scanner-" + SCANNER_IDS.getAndIncrement(), true, 1));
|
this(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scanner(Scheduler scheduler)
|
public Scanner(Scheduler scheduler)
|
||||||
|
{
|
||||||
|
this(scheduler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param scheduler The scheduler to use for scanning.
|
||||||
|
* @param reportRealPaths If true, the {@link Listener}s are called with the real path of scanned files.
|
||||||
|
*/
|
||||||
|
public Scanner(Scheduler scheduler, boolean reportRealPaths)
|
||||||
{
|
{
|
||||||
//Create the scheduler and start it
|
//Create the scheduler and start it
|
||||||
_scheduler = scheduler;
|
_scheduler = scheduler == null ? new ScheduledExecutorScheduler("Scanner-" + SCANNER_IDS.getAndIncrement(), true, 1) : scheduler;
|
||||||
addBean(_scheduler);
|
addBean(_scheduler);
|
||||||
|
_linkOptions = reportRealPaths ? new LinkOption[0] : new LinkOption[] {LinkOption.NOFOLLOW_LINKS};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -335,21 +410,24 @@ public class Scanner extends ContainerLifeCycle
|
||||||
/**
|
/**
|
||||||
* Add a file to be scanned. The file must not be null, and must exist.
|
* Add a file to be scanned. The file must not be null, and must exist.
|
||||||
*
|
*
|
||||||
* @param p the Path of the file to scan.
|
* @param path the Path of the file to scan.
|
||||||
*/
|
*/
|
||||||
public void addFile(Path p)
|
public void addFile(Path path)
|
||||||
{
|
{
|
||||||
if (isRunning())
|
if (isRunning())
|
||||||
throw new IllegalStateException("Scanner started");
|
throw new IllegalStateException("Scanner started");
|
||||||
|
|
||||||
if (p == null)
|
if (path == null)
|
||||||
throw new IllegalStateException("Null path");
|
throw new IllegalStateException("Null path");
|
||||||
|
|
||||||
if (!Files.exists(p) || Files.isDirectory(p))
|
|
||||||
throw new IllegalStateException("Not file or doesn't exist: " + p);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_scannables.putIfAbsent(p.toRealPath(), new IncludeExcludeSet<>(PathMatcherSet.class));
|
// Always follow links when check ultimate type of the path
|
||||||
|
Path real = path.toRealPath();
|
||||||
|
if (!Files.exists(real) || Files.isDirectory(real))
|
||||||
|
throw new IllegalStateException("Not file or doesn't exist: " + path);
|
||||||
|
|
||||||
|
_scannables.putIfAbsent(real, new IncludeExcludeSet<>(PathMatcherSet.class));
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
|
@ -371,13 +449,15 @@ public class Scanner extends ContainerLifeCycle
|
||||||
if (p == null)
|
if (p == null)
|
||||||
throw new IllegalStateException("Null path");
|
throw new IllegalStateException("Null path");
|
||||||
|
|
||||||
if (!Files.exists(p) || !Files.isDirectory(p))
|
|
||||||
throw new IllegalStateException("Not directory or doesn't exist: " + p);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Check status of the real path
|
||||||
|
Path real = p.toRealPath();
|
||||||
|
if (!Files.exists(real) || !Files.isDirectory(real))
|
||||||
|
throw new IllegalStateException("Not directory or doesn't exist: " + p);
|
||||||
|
|
||||||
IncludeExcludeSet<PathMatcher, Path> includesExcludes = new IncludeExcludeSet<>(PathMatcherSet.class);
|
IncludeExcludeSet<PathMatcher, Path> includesExcludes = new IncludeExcludeSet<>(PathMatcherSet.class);
|
||||||
IncludeExcludeSet<PathMatcher, Path> prev = _scannables.putIfAbsent(p.toRealPath(), includesExcludes);
|
IncludeExcludeSet<PathMatcher, Path> prev = _scannables.putIfAbsent(real, includesExcludes);
|
||||||
if (prev != null)
|
if (prev != null)
|
||||||
includesExcludes = prev;
|
includesExcludes = prev;
|
||||||
return includesExcludes;
|
return includesExcludes;
|
||||||
|
@ -625,7 +705,7 @@ public class Scanner extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
int cycle = _scanCount.incrementAndGet();
|
int cycle = _scanCount.incrementAndGet();
|
||||||
reportScanStart(cycle);
|
reportScanStart(cycle);
|
||||||
Map<String, MetaData> currentScan = scanFiles();
|
Map<Path, MetaData> currentScan = scanFiles();
|
||||||
reportDifferences(currentScan, _prevScan == null ? Collections.emptyMap() : Collections.unmodifiableMap(_prevScan));
|
reportDifferences(currentScan, _prevScan == null ? Collections.emptyMap() : Collections.unmodifiableMap(_prevScan));
|
||||||
_prevScan = currentScan;
|
_prevScan = currentScan;
|
||||||
reportScanEnd(cycle);
|
reportScanEnd(cycle);
|
||||||
|
@ -634,9 +714,9 @@ public class Scanner extends ContainerLifeCycle
|
||||||
/**
|
/**
|
||||||
* Scan all of the given paths.
|
* Scan all of the given paths.
|
||||||
*/
|
*/
|
||||||
private Map<String, MetaData> scanFiles()
|
private Map<Path, MetaData> scanFiles()
|
||||||
{
|
{
|
||||||
Map<String, MetaData> currentScan = new HashMap<>();
|
Map<Path, MetaData> currentScan = new HashMap<>();
|
||||||
for (Map.Entry<Path, IncludeExcludeSet<PathMatcher, Path>> entry : _scannables.entrySet())
|
for (Map.Entry<Path, IncludeExcludeSet<PathMatcher, Path>> entry : _scannables.entrySet())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -660,20 +740,20 @@ public class Scanner extends ContainerLifeCycle
|
||||||
* @param currentScan the info from the most recent pass
|
* @param currentScan the info from the most recent pass
|
||||||
* @param oldScan info from the previous pass
|
* @param oldScan info from the previous pass
|
||||||
*/
|
*/
|
||||||
private void reportDifferences(Map<String, MetaData> currentScan, Map<String, MetaData> oldScan)
|
private void reportDifferences(Map<Path, MetaData> currentScan, Map<Path, MetaData> oldScan)
|
||||||
{
|
{
|
||||||
Map<String, Notification> changes = new HashMap<>();
|
Map<Path, Notification> changes = new HashMap<>();
|
||||||
|
|
||||||
//Handle deleted files
|
//Handle deleted files
|
||||||
Set<String> oldScanKeys = new HashSet<>(oldScan.keySet());
|
Set<Path> oldScanKeys = new HashSet<>(oldScan.keySet());
|
||||||
oldScanKeys.removeAll(currentScan.keySet());
|
oldScanKeys.removeAll(currentScan.keySet());
|
||||||
for (String file : oldScanKeys)
|
for (Path path : oldScanKeys)
|
||||||
{
|
{
|
||||||
changes.put(file, Notification.REMOVED);
|
changes.put(path, Notification.REMOVED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new and changed files
|
// Handle new and changed files
|
||||||
for (Map.Entry<String, MetaData> entry : currentScan.entrySet())
|
for (Map.Entry<Path, MetaData> entry : currentScan.entrySet())
|
||||||
{
|
{
|
||||||
MetaData current = entry.getValue();
|
MetaData current = entry.getValue();
|
||||||
MetaData previous = oldScan.get(entry.getKey());
|
MetaData previous = oldScan.get(entry.getKey());
|
||||||
|
@ -714,7 +794,7 @@ public class Scanner extends ContainerLifeCycle
|
||||||
LOG.debug("scanned {}", _scannables.keySet());
|
LOG.debug("scanned {}", _scannables.keySet());
|
||||||
|
|
||||||
//Call the DiscreteListeners
|
//Call the DiscreteListeners
|
||||||
for (Map.Entry<String, Notification> entry : changes.entrySet())
|
for (Map.Entry<Path, Notification> entry : changes.entrySet())
|
||||||
{
|
{
|
||||||
switch (entry.getValue())
|
switch (entry.getValue())
|
||||||
{
|
{
|
||||||
|
@ -736,28 +816,28 @@ public class Scanner extends ContainerLifeCycle
|
||||||
reportBulkChanges(changes.keySet());
|
reportBulkChanges(changes.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void warn(Object listener, String filename, Throwable th)
|
private void warn(Object listener, Path path, Throwable th)
|
||||||
{
|
{
|
||||||
LOG.warn("{} failed on '{}'", listener, filename, th);
|
LOG.warn("{} failed on '{}'", listener, path, th);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Report a file addition to the registered FileAddedListeners
|
* Report a file addition to the registered FileAddedListeners
|
||||||
*
|
*
|
||||||
* @param filename the filename
|
* @param path the path
|
||||||
*/
|
*/
|
||||||
private void reportAddition(String filename)
|
private void reportAddition(Path path)
|
||||||
{
|
{
|
||||||
for (Listener l : _listeners)
|
for (Listener l : _listeners)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (l instanceof DiscreteListener)
|
if (l instanceof DiscreteListener)
|
||||||
((DiscreteListener)l).fileAdded(filename);
|
((DiscreteListener)l).pathAdded(path);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
warn(l, filename, e);
|
warn(l, path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -765,20 +845,20 @@ public class Scanner extends ContainerLifeCycle
|
||||||
/**
|
/**
|
||||||
* Report a file removal to the FileRemovedListeners
|
* Report a file removal to the FileRemovedListeners
|
||||||
*
|
*
|
||||||
* @param filename the filename
|
* @param path the path of the removed filename
|
||||||
*/
|
*/
|
||||||
private void reportRemoval(String filename)
|
private void reportRemoval(Path path)
|
||||||
{
|
{
|
||||||
for (Object l : _listeners)
|
for (Object l : _listeners)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (l instanceof DiscreteListener)
|
if (l instanceof DiscreteListener)
|
||||||
((DiscreteListener)l).fileRemoved(filename);
|
((DiscreteListener)l).pathRemoved(path);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
warn(l, filename, e);
|
warn(l, path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -786,11 +866,11 @@ public class Scanner extends ContainerLifeCycle
|
||||||
/**
|
/**
|
||||||
* Report a file change to the FileChangedListeners
|
* Report a file change to the FileChangedListeners
|
||||||
*
|
*
|
||||||
* @param filename the filename
|
* @param path the path of the changed file
|
||||||
*/
|
*/
|
||||||
private void reportChange(String filename)
|
private void reportChange(Path path)
|
||||||
{
|
{
|
||||||
if (filename == null)
|
if (path == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (Listener l : _listeners)
|
for (Listener l : _listeners)
|
||||||
|
@ -798,11 +878,11 @@ public class Scanner extends ContainerLifeCycle
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (l instanceof DiscreteListener)
|
if (l instanceof DiscreteListener)
|
||||||
((DiscreteListener)l).fileChanged(filename);
|
((DiscreteListener)l).pathChanged(path);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
warn(l, filename, e);
|
warn(l, path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -810,11 +890,11 @@ public class Scanner extends ContainerLifeCycle
|
||||||
/**
|
/**
|
||||||
* Report the list of filenames for which changes were detected.
|
* Report the list of filenames for which changes were detected.
|
||||||
*
|
*
|
||||||
* @param filenames names of all files added/changed/removed
|
* @param paths The paths of all files added/changed/removed
|
||||||
*/
|
*/
|
||||||
private void reportBulkChanges(Set<String> filenames)
|
private void reportBulkChanges(Set<Path> paths)
|
||||||
{
|
{
|
||||||
if (filenames == null || filenames.isEmpty())
|
if (paths == null || paths.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (Listener l : _listeners)
|
for (Listener l : _listeners)
|
||||||
|
@ -822,11 +902,11 @@ public class Scanner extends ContainerLifeCycle
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (l instanceof BulkListener)
|
if (l instanceof BulkListener)
|
||||||
((BulkListener)l).filesChanged(filenames);
|
((BulkListener)l).pathsChanged(paths);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
warn(l, filenames.toString(), e);
|
LOG.warn("{} failed on '{}'", l, paths, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue