From 0abf22c3dc540864e384bf281e602e74669de41a Mon Sep 17 00:00:00 2001 From: Nikolas Komonen Date: Thu, 3 Jan 2019 15:48:07 -0500 Subject: [PATCH] Cache unavailable URI's Fixes #201 Signed-off-by: Nikolas Komonen --- .vscode/launch.json | 10 +- org.eclipse.lsp4xml/pom.xml | 17 ++++ .../uriresolver/CacheResourcesManager.java | 21 ++++- .../org/eclipse/lsp4xml/utils/FilesUtils.java | 28 +++++- .../lsp4xml/AbstractCacheBasedTest.java | 45 +++++++++ .../XMLSchemaPublishDiagnosticsTest.java | 7 +- .../CacheResourcesManagerTest.java | 81 +++++++++++++++- .../lsp4xml/uriresolver/FileServer.java | 93 +++++++++++++++++++ .../eclipse/lsp4xml/utils/ProjectUtils.java | 34 +++++++ 9 files changed, 320 insertions(+), 16 deletions(-) create mode 100644 org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/AbstractCacheBasedTest.java create mode 100644 org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/FileServer.java create mode 100644 org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/ProjectUtils.java diff --git a/.vscode/launch.json b/.vscode/launch.json index 4053025f1d..024ad291dd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,14 +3,12 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", - "configurations": [ - { + "configurations": [{ "type": "java", - "name": "Debug (Attach)", + "name": "Debug (Attach) - Remote", "request": "attach", "hostName": "localhost", "port": 1054, "projectName": "org.eclipse.lsp4xml" - } - ] - } \ No newline at end of file + }] +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/pom.xml b/org.eclipse.lsp4xml/pom.xml index 4c3d32771c..c6e2d421dd 100644 --- a/org.eclipse.lsp4xml/pom.xml +++ b/org.eclipse.lsp4xml/pom.xml @@ -69,6 +69,11 @@ + + com.google.guava + guava + 27.0.1-jre + com.google.code.gson gson @@ -76,6 +81,12 @@ org.eclipse.lsp4j org.eclipse.lsp4j + + + com.google.guava + guava + + org.eclipse.lsp4j @@ -102,5 +113,11 @@ junit test + + org.eclipse.jetty + jetty-server + 9.4.14.v20181114 + test + \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManager.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManager.java index bfa541e9e0..1f5dad1916 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManager.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManager.java @@ -27,9 +27,14 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + + import org.eclipse.lsp4xml.utils.FilesUtils; import org.eclipse.lsp4xml.utils.URIUtils; @@ -38,6 +43,7 @@ * */ public class CacheResourcesManager { + protected final Cache unavailableURICache; private static final String CACHE_PATH = "cache"; private static final Logger LOGGER = Logger.getLogger(CacheResourcesManager.class.getName()); @@ -79,7 +85,13 @@ public String getResourceFromClasspath() { } public CacheResourcesManager() { + this(CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(30, TimeUnit.SECONDS).build()); + + } + + public CacheResourcesManager(Cache cache) { resourcesLoading = new HashMap<>(); + unavailableURICache = cache; } public Path getResource(final String resourceURI) throws IOException { @@ -87,6 +99,12 @@ public Path getResource(final String resourceURI) throws IOException { if (Files.exists(resourceCachePath)) { return resourceCachePath; } + + if(unavailableURICache.getIfPresent(resourceURI) != null) { + LOGGER.info("Ignored unavailable schema URI: " + resourceURI + "\n"); + return null; + } + CompletableFuture f = null; synchronized (resourcesLoading) { if (resourcesLoading.containsKey(resourceURI)) { @@ -139,11 +157,12 @@ private CompletableFuture downloadResource(final String resourceURI, Path LOGGER.info("Downloaded " + resourceURI + " to " + resourceCachePath + " in " + elapsed + "ms"); } catch (Exception e) { // Do nothing + unavailableURICache.put(resourceURI, true); Throwable rootCause = getRootCause(e); String error = "[" + rootCause.getClass().getTypeName() + "] " + rootCause.getMessage(); LOGGER.log(Level.SEVERE, "Error while downloading " + resourceURI + " to " + resourceCachePath + " : " + error); - throw new CacheResourceDownloadedException("Error while downloading '" + resourceURI + "'.", e); + throw new CacheResourceDownloadedException("Error while downloading '" + resourceURI + "' to " + resourceCachePath + ".", e); } finally { synchronized (resourcesLoading) { resourcesLoading.remove(resourceURI); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/FilesUtils.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/FilesUtils.java index 1d906cd948..28468efb60 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/FilesUtils.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/FilesUtils.java @@ -19,18 +19,37 @@ import java.nio.file.Paths; import java.util.Scanner; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; + /** * Files utilities. * */ public class FilesUtils { - private FilesUtils(){} + public static final String LSP4XML_WORKDIR_KEY = "lsp4xml.workdir"; + + private FilesUtils() { + } - private static final Path DEPLOYED_BASE_PATH = getDeployedBasePath(); + public static Supplier DEPLOYED_BASE_PATH; + + static { + resetDeployPath(); + } + + /** Public for test purposes */ + public static void resetDeployPath() { + DEPLOYED_BASE_PATH = Suppliers.memoize(() -> getDeployedBasePath()); + } private static Path getDeployedBasePath() { - String dir = System.getProperty("user.home"); + String dir = System.getProperty(LSP4XML_WORKDIR_KEY); + if (dir != null) { + return Paths.get(dir); + } + dir = System.getProperty("user.home"); if (dir == null) { dir = System.getProperty("user.dir"); } @@ -48,7 +67,7 @@ private static Path getDeployedBasePath() { * @throws IOException */ public static Path getDeployedPath(Path path) throws IOException { - return DEPLOYED_BASE_PATH.resolve(path); + return DEPLOYED_BASE_PATH.get().resolve(path); } /** @@ -86,5 +105,4 @@ static String toString(InputStream is) { return s.hasNext() ? s.next() : ""; } } - } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/AbstractCacheBasedTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/AbstractCacheBasedTest.java new file mode 100644 index 0000000000..85feda9df2 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/AbstractCacheBasedTest.java @@ -0,0 +1,45 @@ +/******************************************************************************* +* Copyright (c) 2019 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lsp4xml; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.google.common.io.MoreFiles; +import com.google.common.io.RecursiveDeleteOption; + +import org.eclipse.lsp4xml.utils.FilesUtils; +import org.eclipse.lsp4xml.utils.ProjectUtils; +import org.junit.After; +import org.junit.Before; + +/** + * AbstractCacheBasedTest + */ +public abstract class AbstractCacheBasedTest { + + protected static Path TEST_WORK_DIRECTORY = ProjectUtils.getProjectDirectory().resolve("target/test-cache"); + + @Before + public final void setupCache() throws Exception { + clearCache(); + System.setProperty(FilesUtils.LSP4XML_WORKDIR_KEY, TEST_WORK_DIRECTORY.toAbsolutePath().toString()); + } + + @After + public final void clearCache() throws IOException { + if (Files.exists(TEST_WORK_DIRECTORY)) { + MoreFiles.deleteDirectoryContents(TEST_WORK_DIRECTORY,RecursiveDeleteOption.ALLOW_INSECURE); + } + System.clearProperty(FilesUtils.LSP4XML_WORKDIR_KEY); + FilesUtils.resetDeployPath(); + } +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaPublishDiagnosticsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaPublishDiagnosticsTest.java index b3036eba63..a8a31cf26c 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaPublishDiagnosticsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaPublishDiagnosticsTest.java @@ -17,6 +17,7 @@ import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4xml.AbstractCacheBasedTest; import org.eclipse.lsp4xml.XMLAssert; import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lsp4xml.extensions.contentmodel.participants.XMLSchemaErrorCode; @@ -27,7 +28,7 @@ * Test with published diagnostics. * */ -public class XMLSchemaPublishDiagnosticsTest { +public class XMLSchemaPublishDiagnosticsTest extends AbstractCacheBasedTest { @Test public void schemaWithUrlWithoutCache() throws Exception { @@ -77,11 +78,13 @@ public void schemaWithUrlWithCache() throws Exception { " xsi:noNamespaceSchemaLocation=\"http://invoice.xsd\">\r\n" + // " \r\n" + // ""; + + String expectedLocation = TEST_WORK_DIRECTORY.resolve("cache/http/invoice.xsd").toString(); XMLAssert.testPublishDiagnosticsFor(xml, fileURI, configuration, pd(fileURI, new Diagnostic(r(1, 1, 1, 8), "The resource 'http://invoice.xsd' is downloading.", DiagnosticSeverity.Information, "XML")), - pd(fileURI, new Diagnostic(r(1, 1, 1, 8), "Error while downloading 'http://invoice.xsd'.", + pd(fileURI, new Diagnostic(r(1, 1, 1, 8), "Error while downloading 'http://invoice.xsd' to "+expectedLocation+".", DiagnosticSeverity.Error, "XML"))); } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManagerTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManagerTest.java index 28437b4e15..689bd43e58 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManagerTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/CacheResourcesManagerTest.java @@ -12,10 +12,39 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import java.util.concurrent.TimeUnit; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import org.eclipse.lsp4xml.AbstractCacheBasedTest; +import org.junit.After; +import org.junit.Before; import org.junit.Test; -public class CacheResourcesManagerTest { +public class CacheResourcesManagerTest extends AbstractCacheBasedTest { + + private CacheResourcesManager cacheResourcesManager; + + private FileServer server; + + + @Before + public void setup() throws Exception { + cacheResourcesManager = new CacheResourcesManager(testingCache()); + cacheResourcesManager.setUseCache(true); + } + + @After + public void stopServer() throws Exception { + if (server != null) { + server.stop(); + } + } @Test public void testCanUseCache() { @@ -24,7 +53,6 @@ public void testCanUseCache() { } private void testCanUseCache(boolean useCacheEnabled) { - CacheResourcesManager cacheResourcesManager = new CacheResourcesManager(); cacheResourcesManager.setUseCache(useCacheEnabled); assertEquals(useCacheEnabled, cacheResourcesManager.canUseCache("http://foo")); assertEquals(useCacheEnabled, cacheResourcesManager.canUseCache("ftp://foo")); @@ -32,4 +60,53 @@ private void testCanUseCache(boolean useCacheEnabled) { assertFalse(cacheResourcesManager.canUseCache("file:///foo")); } + @Test + public void testUnavailableCache() throws Exception { + FileServer server = new FileServer(); + server.start(); + String uri = server.getUri("bad/url"); + try { + cacheResourcesManager.getResource(uri); + fail("cacheResourcesManager should be busy downloading the url"); + } catch (CacheResourceDownloadingException ignored) { + } + TimeUnit.MILLISECONDS.sleep(200); + //failed to download so returns null + assertNull(cacheResourcesManager.getResource(uri)); + + TimeUnit.SECONDS.sleep(1);//wait past the cache expiration date + + //Manager should retry downloading + try { + cacheResourcesManager.getResource(uri); + fail("cacheResourcesManager should be busy re-downloading the url"); + } catch (CacheResourceDownloadingException ignored) { + } + } + + @Test + public void testAvailableCache() throws Exception { + FileServer server = new FileServer(); + server.start(); + String uri = server.getUri("/dtd/web-app_2_3.dtd"); + try { + cacheResourcesManager.getResource(uri); + fail("cacheResourcesManager should be busy downloading the url"); + } catch (CacheResourceDownloadingException ignored) { + } + TimeUnit.MILLISECONDS.sleep(200); + + assertNotNull(cacheResourcesManager.getResource(uri)); + + server.stop(); + TimeUnit.SECONDS.sleep(1);//wait past the cache expiration date + + //Manager should return cached content, even if server is offline + assertNotNull(cacheResourcesManager.getResource(uri)); + } + + private Cache testingCache() { + return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).maximumSize(1).build(); + } + } \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/FileServer.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/FileServer.java new file mode 100644 index 0000000000..b00699c8f1 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/uriresolver/FileServer.java @@ -0,0 +1,93 @@ +/******************************************************************************* +* Copyright (c) 2019 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ + +package org.eclipse.lsp4xml.uriresolver; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.lsp4xml.utils.ProjectUtils; + +/** + * Test server + */ +public class FileServer { + + private static Logger LOG = Logger.getLogger(FileServer.class.getName()); + + private Server server; + + /** + * Creates an http server on a random port, serving the + * src/test/resources directory. + * + * @throws IOException + */ + public FileServer() throws IOException { + this("src/test/resources"); + } + + /** + * Creates an http server on a random port, serving the dir + * directory (relative to the current project). + * + * @param dir + * @throws IOException + */ + public FileServer(String dir) throws IOException { + server = new Server(0); + ResourceHandler resourceHandler = new ResourceHandler(); + Path base = ProjectUtils.getProjectDirectory().resolve(dir); + resourceHandler.setResourceBase(base.toUri().toString()); + resourceHandler.setDirectoriesListed(true); + HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { resourceHandler, new DefaultHandler() }); + server.setHandler(handlers); + } + + /** + * @return the port the server was started on. + * @throws Exception + */ + public int start() throws Exception { + if (!server.isStarting() && !server.isStarted() && !server.isRunning()) { + server.start(); + } + int port = getPort(); + LOG.info("http server started on port "+ port); + return port; + } + + public int getPort() { + return ((ServerConnector)server.getConnectors()[0]).getLocalPort(); + } + + public void stop() throws Exception { + server.stop(); + } + + public String getUri(String resourcePath) { + StringBuilder sb = new StringBuilder("http://localhost:") + .append(getPort()); + if (!resourcePath.startsWith("/")) { + sb.append("/"); + } + sb.append(resourcePath); + LOG.info("remote uri : "+sb.toString()); + return sb.toString(); + } +} \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/ProjectUtils.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/ProjectUtils.java new file mode 100644 index 0000000000..d4110863c0 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/utils/ProjectUtils.java @@ -0,0 +1,34 @@ +/******************************************************************************* +* Copyright (c) 2019 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ + +package org.eclipse.lsp4xml.utils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * ProjectUtils + */ +public class ProjectUtils { + + /** + * @return the current lsp4xml project directory + */ + public static Path getProjectDirectory() { + String currPath = ProjectUtils.class.getClassLoader().getResource("").getFile(); + Path dir = Paths.get(currPath); + while (!Files.exists(dir.resolve("pom.xml")) && dir.getParent() != null) { + dir = dir.getParent(); + } + return dir; + } + +} \ No newline at end of file