From 1097abe65cf14bc5425068541051ded46bca3694 Mon Sep 17 00:00:00 2001 From: azerr Date: Tue, 28 May 2019 02:48:34 +0200 Subject: [PATCH] Use DTD/XML Schema cache manager for completion This PR uses like validation the cache manager to load the DTD/XML Schema content model used for completion based on grammar, hover. It should fix the performance problem of #397 Signed-off-by: azerr --- .../model/ContentModelManager.java | 20 +++++- .../XMLCacheResolverExtension.java | 42 +++++++++-- .../DTDCompletionWithCacheExtensionsTest.java | 72 +++++++++++++++++++ 3 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/DTDCompletionWithCacheExtensionsTest.java diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java index 1dc1a7355..af8f1bc92 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java @@ -10,6 +10,7 @@ */ package org.eclipse.lsp4xml.extensions.contentmodel.model; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -22,6 +23,7 @@ import org.eclipse.lsp4xml.extensions.contentmodel.uriresolver.XMLCacheResolverExtension; import org.eclipse.lsp4xml.extensions.contentmodel.uriresolver.XMLCatalogResolverExtension; import org.eclipse.lsp4xml.extensions.contentmodel.uriresolver.XMLFileAssociationResolverExtension; +import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadingException; import org.eclipse.lsp4xml.uriresolver.URIResolverExtensionManager; import org.eclipse.lsp4xml.utils.URIUtils; @@ -114,7 +116,23 @@ private CMDocument findCMDocument(String uri, String publicId, String systemId, cmDocument = cmDocumentCache.get(key); } if (cmDocument == null) { - cmDocument = modelProvider.createCMDocument(key); + if (isCacheable && cacheResolverExtension.isUseCache()) { + // Try to load the DTD/XML Schema with the cache manager + try { + Path file = cacheResolverExtension.getCachedResource(key); + if (file != null) { + cmDocument = modelProvider.createCMDocument(file.toFile().getPath()); + } + } catch (CacheResourceDownloadingException e) { + // the DTD/XML Schema is downloading + return null; + } catch (Exception e) { + // other error like network which is not available + cmDocument = modelProvider.createCMDocument(key); + } + } else { + cmDocument = modelProvider.createCMDocument(key); + } if (isCacheable && cmDocument != null) { cmDocumentCache.put(key, cmDocument); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/uriresolver/XMLCacheResolverExtension.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/uriresolver/XMLCacheResolverExtension.java index f05d87f04..2e6431170 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/uriresolver/XMLCacheResolverExtension.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/uriresolver/XMLCacheResolverExtension.java @@ -17,6 +17,7 @@ import org.apache.xerces.xni.XMLResourceIdentifier; import org.apache.xerces.xni.XNIException; import org.apache.xerces.xni.parser.XMLInputSource; +import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadedException; import org.eclipse.lsp4xml.uriresolver.CacheResourcesManager; import org.eclipse.lsp4xml.uriresolver.URIResolverExtension; @@ -44,18 +45,34 @@ public String resolve(String baseLocation, String publicId, String systemId) { @Override public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws XNIException, IOException { String url = resourceIdentifier.getExpandedSystemId(); + // Try to get the downloaded resource. In the case where the resource is + // downloading but takes too long, a CacheResourceDownloadingException is + // thrown. + Path file = getCachedResource(url); + if (file != null) { + // The resource was downloaded locally, use it. + XMLInputSource source = new XMLInputSource(resourceIdentifier); + source.setByteStream(Files.newInputStream(file)); + return source; + } + return null; + } + + /** + * Returns the cached resource path from the given url and null otherwise. + * + * @param url the url + * @return the cached resource path from the given url and null otherwise. + * @throws IOException + * @throws CacheResourceDownloadedException throws when resource is downloading. + */ + public Path getCachedResource(String url) throws IOException, CacheResourceDownloadedException { // Cache is used only for resource coming from "http(s)" or "ftp". if (cacheResourcesManager.canUseCache(url)) { // Try to get the downloaded resource. In the case where the resource is // downloading but takes too long, a CacheResourceDownloadingException is // thrown. - Path file = cacheResourcesManager.getResource(url); - if (file != null) { - // The resource was downloaded locally, use it. - XMLInputSource source = new XMLInputSource(resourceIdentifier); - source.setByteStream(Files.newInputStream(file)); - return source; - } + return cacheResourcesManager.getResource(url); } return null; } @@ -70,4 +87,15 @@ public void setUseCache(boolean useCache) { cacheResourcesManager.setUseCache(useCache); } + /** + * Returns true if cache must be used, false + * otherwise. + * + * @return true if cache must be used, false + * otherwise. + */ + public boolean isUseCache() { + return cacheResourcesManager.isUseCache(); + } + } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/DTDCompletionWithCacheExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/DTDCompletionWithCacheExtensionsTest.java new file mode 100644 index 000000000..4b195ecde --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/DTDCompletionWithCacheExtensionsTest.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * 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.extensions.contentmodel; + +import static org.eclipse.lsp4xml.XMLAssert.c; +import static org.eclipse.lsp4xml.XMLAssert.te; + +import java.io.FileOutputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Consumer; + +import org.eclipse.lsp4xml.AbstractCacheBasedTest; +import org.eclipse.lsp4xml.XMLAssert; +import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lsp4xml.services.XMLLanguageService; +import org.junit.Test; + +/** + * Test with DTD completion and cache. + * + * @author Angelo ZERR + * + */ +public class DTDCompletionWithCacheExtensionsTest extends AbstractCacheBasedTest { + + @Test + public void dtdCache() throws Exception { + Consumer configuration = ls -> { + ContentModelManager contentModelManager = ls.getComponent(ContentModelManager.class); + // Use cache on file system + contentModelManager.setUseCache(true); + }; + + // Copy the svg.dtd in the cache folder + Path expectedLocation = TEST_WORK_DIRECTORY + .resolve("cache/http/www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"); + // Download resource in a temporary file + Files.createDirectories(expectedLocation.getParent()); + Path path = Files.createFile(expectedLocation); + + try (ReadableByteChannel rbc = Channels + .newChannel(DTDCompletionWithCacheExtensionsTest.class.getResourceAsStream("/dtd/svg.dtd")); + FileOutputStream fos = new FileOutputStream(path.toFile())) { + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + } + + // Execute completion based on svg.dtd by using the cache manager + String fileURI = "test.xml"; + String xml = "\r\n" + // + "\r\n" + + // + "\r\n" + // + " \r\n" + // + " |\r\n"; // <- completion"; + + XMLLanguageService ls = new XMLLanguageService(); + XMLAssert.testCompletionFor(ls, xml, null, configuration, fileURI, null, true, + c("desc", te(4, 8, 4, 8, ""), "desc")); + } + +}