From a92accde8632cb132c43e2a1699e8c24c0defa64 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Thu, 27 Jul 2017 13:09:12 -0700 Subject: [PATCH] Improve CachingModelProvider to wrap a cache around the models it provides --- .../bosh/BoshDeploymentManifestSchema.java | 6 ++-- .../bosh/models/CachingModelProvider.java | 35 ++++++++++++++++--- .../bosh/models/CachingModelProviderTest.java | 28 ++++++++++----- .../definition/SimpleDefinitionFinder.java | 2 +- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java index b5a9208d8..9d812ac57 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java @@ -108,9 +108,9 @@ public class BoshDeploymentManifestSchema implements YamlSchema { ) { this.asts = asts; this.astTypes = astTypes; - this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider); - this.stemcellsProvider = new CachingModelProvider<>(stemcellsProvider); - this.releasesProvider = new CachingModelProvider<>(releasesProvider); + this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider, CloudConfigModel.class); + this.stemcellsProvider = new CachingModelProvider<>(stemcellsProvider, StemcellsModel.class); + this.releasesProvider = new CachingModelProvider<>(releasesProvider, ReleasesModel.class); TYPE_UTIL = f.TYPE_UTIL; V2_TOPLEVEL_TYPE = createV2Schema(); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java index 964d93814..a1fbef3e6 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh.models; +import java.lang.reflect.Proxy; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -35,9 +36,11 @@ public class CachingModelProvider implements DynamicModelProvider { private Cache> cache = createCache(); private final DynamicModelProvider delegate; + private Class modelInterface; - public CachingModelProvider(DynamicModelProvider delegate) { + public CachingModelProvider(DynamicModelProvider delegate, Class modelInterface) { this.delegate = delegate; + this.modelInterface = modelInterface; } /** @@ -72,15 +75,37 @@ public class CachingModelProvider implements DynamicModelProvider { synchronized (this) { cached = cache.get(key, () -> { try { - return CompletableFuture.completedFuture(delegate.getModel(dc)); + return CompletableFuture.completedFuture(wrapWithCachingProxy(delegate.getModel(dc))); } catch (Throwable e) { - CompletableFuture failed = new CompletableFuture<>(); - failed.completeExceptionally(e); - return failed; + return failed(e); } }); } return cached.get(); } + private static CompletableFuture failed(Throwable e) { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(e); + return failed; + } + + @SuppressWarnings("unchecked") + private T wrapWithCachingProxy(T model) { + Cache> attributesCache = CacheBuilder.newBuilder().build(); + return (T) Proxy.newProxyInstance(modelInterface.getClassLoader(), new Class[] {modelInterface}, (o, m, a) -> { + //We only support caching results for methods that have no arguments (for now, its all we need). + if (m.getParameterTypes().length==0) { + return attributesCache.get(m.getName(), () -> { + try { + return CompletableFuture.completedFuture((T)m.invoke(model, a)); + } catch (Throwable e) { + return failed(e); + } + }).get(); + } + return m.invoke(model, a); + }); + } + } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java index 86d7ff2da..66b07df60 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/models/CachingModelProviderTest.java @@ -12,9 +12,12 @@ package org.springframework.ide.vscode.bosh.models; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static org.mockito.Mockito.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import org.junit.Test; @@ -23,24 +26,31 @@ import org.springframework.ide.vscode.commons.util.ExceptionUtil; @SuppressWarnings("unchecked") public class CachingModelProviderTest { + interface BoxModel { + String getContents(); + } + @Test public void goodValuesAreCached() throws Exception { - DynamicModelProvider modelProvider = mock(DynamicModelProvider.class); - when(modelProvider.getModel(any())).thenReturn("RESULT"); + DynamicModelProvider modelProvider = mock(DynamicModelProvider.class); + BoxModel model = mock(BoxModel.class); + when(modelProvider.getModel(any())).thenReturn(model); + when(model.getContents()).thenReturn("RESULT"); - DynamicModelProvider cached = new CachingModelProvider<>(modelProvider); + DynamicModelProvider cached = new CachingModelProvider<>(modelProvider, BoxModel.class); - assertEquals("RESULT", cached.getModel(null)); - assertEquals("RESULT", cached.getModel(null)); - assertEquals("RESULT", cached.getModel(null)); + assertEquals("RESULT", cached.getModel(null).getContents()); + assertEquals("RESULT", cached.getModel(null).getContents()); + assertEquals("RESULT", cached.getModel(null).getContents()); verify(modelProvider, times(1)).getModel(any()); + verify(model, times(1)).getContents(); //model itself is also wrapped in a cache! } @Test public void timeoutExceptionsAreCached() throws Exception { DynamicModelProvider modelProvider = mock(DynamicModelProvider.class); when(modelProvider.getModel(any())).thenThrow(new TimeoutException("timed out")); - DynamicModelProvider cached = new CachingModelProvider<>(modelProvider); + DynamicModelProvider cached = new CachingModelProvider<>(modelProvider, String.class); for (int i = 0; i < 3; i++) { try { cached.getModel(null); diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java index e7c8c1e37..7de697520 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java @@ -52,7 +52,7 @@ public class SimpleDefinitionFinder implements D * currently pointed at in the current document using String.indexOf. */ protected Flux findDefinitions(TextDocumentPositionParams params) { - try { + try { TextDocument doc = server.getTextDocumentService().get(params); if (doc != null) { int offset = doc.toOffset(params.getPosition());