From d15dae1c0cbb6318e307232ccd5b527e0f2c6e69 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Wed, 19 Jul 2017 12:35:43 -0700 Subject: [PATCH] Add a cache around CloudConfigProvider --- .../bosh/BoshDeploymentManifestSchema.java | 7 +- .../ide/vscode/bosh/BoshLanguageServer.java | 4 +- .../springframework/ide/vscode/bosh/Main.java | 2 +- .../BoshCommandCloudConfigProvider.java | 2 +- .../bosh/models/CachingModelProvider.java | 73 +++++++++++++++++++ .../CloudConfigModel.java | 2 +- .../DynamicModelProvider.java | 2 +- .../BoshCommandCloudConfigProviderTest.java | 4 +- .../ide/vscode/bosh/BoshEditorTest.java | 23 ++++++ .../bosh/mocks/MockCloudConfigProvider.java | 9 ++- 10 files changed, 116 insertions(+), 12 deletions(-) rename headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/{cloudconfig => models}/BoshCommandCloudConfigProvider.java (98%) create mode 100644 headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java rename headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/{cloudconfig => models}/CloudConfigModel.java (92%) rename headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/{cloudconfig => models}/DynamicModelProvider.java (83%) 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 f791ba36e..af7150e9d 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 @@ -14,8 +14,9 @@ import java.util.Collection; import java.util.Map; import java.util.UUID; -import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigModel; -import org.springframework.ide.vscode.bosh.cloudconfig.DynamicModelProvider; +import org.springframework.ide.vscode.bosh.models.CachingModelProvider; +import org.springframework.ide.vscode.bosh.models.CloudConfigModel; +import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; @@ -83,7 +84,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema { public BoshDeploymentManifestSchema(ASTTypeCache astTypes, DynamicModelProvider cloudConfigProvider) { this.astTypes = astTypes; - this.cloudConfigProvider = cloudConfigProvider; + this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider); 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/BoshLanguageServer.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java index 71a90e822..2ae708e9f 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; -import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigModel; -import org.springframework.ide.vscode.bosh.cloudconfig.DynamicModelProvider; +import org.springframework.ide.vscode.bosh.models.CloudConfigModel; +import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java index 91b0c6119..7e1a1403d 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java @@ -12,7 +12,7 @@ package org.springframework.ide.vscode.bosh; import java.io.IOException; -import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; public class Main { diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java similarity index 98% rename from headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java rename to headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java index dffd92ace..cf75725d6 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/BoshCommandCloudConfigProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.bosh.cloudconfig; +package org.springframework.ide.vscode.bosh.models; import java.io.File; import java.time.Duration; 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 new file mode 100644 index 000000000..c843daed7 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.bosh.models; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +/** + * Wraps around a {@link DynamicModelProvider} to add a cache. + */ +public class CachingModelProvider implements DynamicModelProvider { + + /** + * Special key to use when the actual key is null (because guava cache doesn't + * like null keys). + */ + private static final Object NULL_KEY = new Object(); + + private long timeout = 15; + private TimeUnit timeoutUnit = TimeUnit.SECONDS; + private Cache cache = createCache(); + + private final DynamicModelProvider delegate; + + public CachingModelProvider(DynamicModelProvider delegate) { + this.delegate = delegate; + } + + /** + * Function used to determine the caching key, given the current {@link DynamicSchemaContext}. + *

+ * The default keyGetter ignores the context and just returns the same object all the time. This + * results in a cache that only keeps a single value (since there's only a single key ever used + * to store / find cache entries. + */ + private Function keyGetter = (dc) -> "WHATEVER"; + + protected Cache createCache() { + return CacheBuilder.newBuilder() + .expireAfterWrite(timeout, timeoutUnit) + .build(); + } + + public CachingModelProvider setTimeout(long timeout, TimeUnit unit) { + this.timeout = timeout; + this.timeoutUnit = unit; + return this; + } + + @Override + public T getModel(DynamicSchemaContext dc) throws Exception { + Object key = keyGetter.apply(dc); + if (key==null) { + //guava cache doesn't like null key + key = NULL_KEY; + } + return cache.get(key, () -> delegate.getModel(dc)); + } + +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigModel.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CloudConfigModel.java similarity index 92% rename from headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigModel.java rename to headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CloudConfigModel.java index ec3cae7a9..c90c2393e 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/CloudConfigModel.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CloudConfigModel.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.bosh.cloudconfig; +package org.springframework.ide.vscode.bosh.models; import java.util.Collection; diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/DynamicModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java similarity index 83% rename from headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/DynamicModelProvider.java rename to headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java index 69ee914f1..27c817576 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/cloudconfig/DynamicModelProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java @@ -1,4 +1,4 @@ -package org.springframework.ide.vscode.bosh.cloudconfig; +package org.springframework.ide.vscode.bosh.models; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java index 71d336777..ecc3c5667 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java @@ -14,9 +14,9 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; import org.mockito.Mockito; -import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider; -import org.springframework.ide.vscode.bosh.cloudconfig.CloudConfigModel; import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.CloudConfigModel; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; import com.google.common.collect.ImmutableMultiset; diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java index f025d5a9c..1a8660ca5 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; +import static org.junit.Assert.assertEquals; import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION; import java.io.IOException; @@ -849,5 +850,27 @@ public class BoshEditorTest { "default<*>", "large<*>" ); + + //Verify that the cache is working. Shouldn't read the cc provier more than once, evem for multiple CA requests. + editor.assertCompletionLabels("default", "large"); + editor.assertCompletionLabels("default", "large"); + assertEquals(1, cloudConfigProvider.getReadCount()); } + + @Test public void reconcileVMtype() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " vm_type: bogus-vm\n" + + "- name: other-server\n" + + " vm_type: large" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems( + "bogus-vm|unknown 'VMType'. Valid values are: [default, large]" + ); + assertEquals(1, cloudConfigProvider.getReadCount()); + } + } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java index 170570f58..542cf4fdf 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java @@ -13,7 +13,7 @@ package org.springframework.ide.vscode.bosh.mocks; import java.util.concurrent.Callable; import org.springframework.ide.vscode.bosh.BoshCommandCloudConfigProviderTest; -import org.springframework.ide.vscode.bosh.cloudconfig.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.commons.util.IOUtil; public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvider { @@ -22,12 +22,15 @@ public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvide private Callable cloudConfigReader = () -> IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC)); + private int readCount = 0; + /** * Override with a 'fake' which just returns some mock data. That way we can unit-test * without requiring a real bosh setup. */ @Override protected String executeBoshCloudConfigCommand() throws Exception { + readCount++; return cloudConfigReader.call(); } @@ -35,4 +38,8 @@ public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvide this.cloudConfigReader = reader; return this; } + + public int getReadCount() { + return readCount; + } } \ No newline at end of file