diff --git a/.vscode/launch.json b/.vscode/launch.json
index 4053025f1..024ad291d 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 4c3d32771..c6e2d421d 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 bfa541e9e..734cd1143 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,12 @@ 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 +98,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 +156,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 1d906cd94..28468efb6 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 000000000..85feda9df
--- /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 b3036eba6..a8a31cf26 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 28437b4e1..689bd43e5 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 000000000..b00699c8f
--- /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 000000000..d4110863c
--- /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