diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java index afba8f087..4042ad550 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFCallableContext.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -15,6 +15,7 @@ import java.net.UnknownHostException; import java.util.concurrent.Callable; import org.cloudfoundry.uaa.UaaException; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo.TargetDiagnosticMessages; import org.springframework.ide.vscode.commons.util.ExceptionUtil; import reactor.ipc.netty.channel.AbortedException; @@ -25,15 +26,15 @@ import reactor.ipc.netty.channel.AbortedException; */ public class CFCallableContext { - private final CFParamsProviderMessages paramsProviderMessages; + private final TargetDiagnosticMessages diagnosticMessages; private Exception lastConnectionError; private long lastErrorTime = 0; - public CFCallableContext(CFParamsProviderMessages paramsProviderMessages) { - this.paramsProviderMessages = paramsProviderMessages; + public CFCallableContext(TargetDiagnosticMessages diagnosticMessages) { + this.diagnosticMessages = diagnosticMessages; } - public T checkConnection(Callable callable) throws Exception { + public T run(Callable callable) throws Exception { this.lastConnectionError = null; try { return callable.call(); @@ -56,9 +57,12 @@ public class CFCallableContext { Throwable deepestCause = ExceptionUtil.getDeepestCause(e); if (deepestCause instanceof UaaException || deepestCause instanceof AbortedException - || deepestCause instanceof SocketException || deepestCause instanceof UnknownHostException - && this.paramsProviderMessages != null) { - return new ConnectionException(this.paramsProviderMessages.noNetworkConnection()); + || deepestCause instanceof SocketException || deepestCause instanceof UnknownHostException) { + if (this.diagnosticMessages != null) { + return new ConnectionException(this.diagnosticMessages.getConnectionError()); + } else { + return new ConnectionException(CfTargetsInfoProvder.DEFAULT_MESSAGES.getConnectionError()); + } } return null; } diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java index 54bca7a0a..a6e3a993f 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTarget.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -44,6 +44,7 @@ public class CFTarget { private LoadingCache> domainCache; private LoadingCache> stacksCache; private CFCallableContext callableContext; + public CFTarget(String targetName, CFClientParams params, ClientRequests requests, CFCallableContext callableContext) { this.params = params; @@ -107,7 +108,7 @@ public class CFTarget { } protected T runAndCheckForFailure(Callable callable) throws Exception { - return callableContext.checkConnection(callable); + return callableContext.run(callable); } public boolean hasExpiredConnectionError() { diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java index 926d82349..6c62b7551 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CFTargetCache.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -12,57 +12,64 @@ package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts; import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory; -import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo.TargetDiagnosticMessages; +import org.springframework.ide.vscode.commons.util.StringUtil; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; public class CFTargetCache { - private final CfClientConfig cfClientConfig; + private Logger logger = LoggerFactory.getLogger(CFTargetCache.class); private final CloudFoundryClientFactory clientFactory; private final ClientTimeouts timeouts; private LoadingCache cache; - private CFCallableContext cacheCallableContext; + private List _providers; public static final Duration SERVICES_EXPIRATION = Duration.ofSeconds(10); public static final Duration TARGET_EXPIRATION = Duration.ofHours(1); public static final Duration ERROR_EXPIRATION = Duration.ofSeconds(10); - public CFTargetCache(CfClientConfig cfClientConfig, CloudFoundryClientFactory clientFactory, + public CFTargetCache(List providers, CloudFoundryClientFactory clientFactory, ClientTimeouts timeouts) { - Assert.isLegal(cfClientConfig != null, - "A Cloud Foundry client parameters provider must be set when creating a target cache."); - this.cfClientConfig = cfClientConfig; this.clientFactory = clientFactory; this.timeouts = timeouts; - //TODO: I suspect that addClientParamsProviderChangedListener below is not necessary. - // I think it results in unnessary refreshes of the cache, any time the providers are - // changed. The cached results doesn't really depend on the providers, only on the targets. So I think, - // it shouldn't need to refresh when the providers are changed. - cfClientConfig.addClientParamsProviderChangedListener((newProvider, oldProvider) -> initCache()); + this._providers = providers; initCache(); } + private void initCache() { CacheLoader loader = new CacheLoader() { @Override - public CFTarget load(ClientParamsCacheKey params) throws Exception { - return create(params.fullParams); + public CFTarget load(ClientParamsCacheKey key) throws Exception { + return create(key.fullParams, key.getProvider()); } }; cache = CacheBuilder.newBuilder()./*maximumSize(1).*/expireAfterAccess(TARGET_EXPIRATION.toMillis(), TimeUnit.MILLISECONDS) .build(loader); - this.cacheCallableContext = new CFCallableContext(cfClientConfig.getClientParamsProvider().getMessages()); + } + + /** + * + * @param providers list of providers that will be called in order. + */ + public synchronized void setProviders(ClientParamsProvider... providers) { + this._providers = providers != null ? Arrays.asList(providers) : ImmutableList.of(); } /** @@ -73,39 +80,67 @@ public class CFTargetCache { * for any other error encountered */ public synchronized List getOrCreate() throws NoTargetsException, Exception { - return cacheCallableContext.checkConnection(() -> doGetOrCreate()); - } - - protected synchronized List doGetOrCreate() throws NoTargetsException, Exception { - - Collection allParams = cfClientConfig.getClientParamsProvider().getParams(); + // Obtain an uptodate list of params from the providers and refresh the list of targets. List targets = new ArrayList<>(); - if (allParams != null) { - for (CFClientParams params : allParams) { - ClientParamsCacheKey key = ClientParamsCacheKey.from(params); - CFTarget target = cache.get(key); - if (target != null) { - // If any CF errors occurred in the target, refresh once - if (target.hasExpiredConnectionError()) { - cache.refresh(key); - target = cache.get(key); - } - targets.add(target); - } + + Exception lastError = null; + for (ClientParamsProvider provider : this._providers) { + + // IMPORTANT: do not let errors stop iterating through all the providers. + // If one provider cannot provide targets, try the next one. + try { + Collection providerParams = provider.getParams(); + getTargets(targets, provider, providerParams); + } catch (Exception e) { + lastError = e; } } - + + if (targets.isEmpty() && lastError != null) { + throw lastError; + } return targets; } + + + private void getTargets(List targets, ClientParamsProvider provider, + Collection providerParams) throws ExecutionException { + for (CFClientParams params : providerParams) { + ClientParamsCacheKey key = ClientParamsCacheKey.from(params, provider); + CFTarget target = cache.get(key); + if (target != null) { + // If any CF errors occurred in the target, refresh once + if (target.hasExpiredConnectionError()) { + cache.refresh(key); + target = cache.get(key); + } + targets.add(target); + } + } + } - protected CFTarget create(CFClientParams params) throws Exception { - /* - * Must pass a NEW callable context. Cannot be - * the same as the target cache callable context, as - * contexts may contain error state - */ - return new CFTarget(getTargetName(params), params, clientFactory.getClient(params, timeouts), - new CFCallableContext(cfClientConfig.getClientParamsProvider().getMessages())); + public synchronized List getParamsProviders() { + return this._providers; + } + + + protected CFTarget create(CFClientParams params, ClientParamsProvider provider) throws Exception { + TargetDiagnosticMessages messages = provider.getMessages(); + CFCallableContext context = createCallingContext(provider); + + CFTarget target = new CFTarget(getTargetName(params), params, clientFactory.getClient(params, timeouts), + context); + if (messages != null && StringUtil.hasText(messages.getTargetSource())) { + logger.info("Created CF target for [{}/{}], from {}", params.getOrgName(), params.getSpaceName(), + messages.getTargetSource()); + } else { + logger.info("Created CF target for [{}/{}]", params.getOrgName(), params.getSpaceName()); + } + return target; + } + + private CFCallableContext createCallingContext(ClientParamsProvider provider) { + return new CFCallableContext(provider.getMessages()); } protected static String getTargetName(CFClientParams params) { @@ -121,8 +156,4 @@ public class CFTargetCache { return cfApiUrl; } } - - public CfClientConfig getCfClientConfig() { - return cfClientConfig; - } } diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java index 8626e51cd..a89edca71 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfCliParamsProvider.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo.TargetDiagnosticMessages; import org.springframework.ide.vscode.commons.util.StringUtil; import com.google.gson.Gson; @@ -37,7 +38,32 @@ public class CfCliParamsProvider implements ClientParamsProvider { public static final String SPACE_FIELDS = "SpaceFields"; public static final String NAME = "Name"; public static final String SSL_DISABLED = "SSLDisabled"; - private CfCliProviderMessages cfCliProviderMessages = new CfCliProviderMessages(); + + public static final TargetDiagnosticMessages CLI_PROVIDER_MESSAGES = new TargetDiagnosticMessages() { + + @Override + public String getNoTargetsFound() { + // Make this a "generic" message, instead of using "cf CLI" prefix as it shows general instructions when there are not targets + return "No Cloud Foundry targets found: Use 'cf' CLI to login"; + } + + @Override + public String getConnectionError() { + return "cf CLI - Connection error: Verify connection or use 'cf' CLI to login again"; + } + + @Override + public String getNoOrgSpace() { + return "cf CLI - No org/space selected: Use 'cf' CLI to login"; + } + + @Override + public String getTargetSource() { + return "cf CLI"; + } + + }; + private Gson gson = new GsonBuilder().disableHtmlEscaping().create(); private static CfCliParamsProvider instance; @@ -81,7 +107,7 @@ public class CfCliParamsProvider implements ClientParamsProvider { String orgName = (String) orgFields.get(NAME); String spaceName = (String) spaceFields.get(NAME); if (!StringUtil.hasText(orgName) || !StringUtil.hasText(spaceName)) { - throw new NoTargetsException(getMessages().noOrgSpace()); + throw new NoTargetsException(getMessages().getNoOrgSpace()); } params.add(new CFClientParams(target, null, credentials, orgName, spaceName, sslDisabled)); } @@ -93,7 +119,7 @@ public class CfCliParamsProvider implements ClientParamsProvider { } if (params.isEmpty()) { - throw new NoTargetsException(getMessages().noTargetsFound()); + throw new NoTargetsException(getMessages().getNoTargetsFound()); } else { return params; } @@ -123,8 +149,7 @@ public class CfCliParamsProvider implements ClientParamsProvider { } @Override - public CFParamsProviderMessages getMessages() { - return cfCliProviderMessages; + public TargetDiagnosticMessages getMessages() { + return CLI_PROVIDER_MESSAGES; } - } diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfo.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfo.java index b35fe53dd..1ee1534f8 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfo.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfo.java @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget; import java.util.List; -import java.util.Map; /** * JSON-friendly representation of Cloud Foundry targets, used for integrations that need @@ -25,7 +24,7 @@ import java.util.Map; public class CfTargetsInfo { private List cfTargets; - private Map cfDiagnosticMessages; + private TargetDiagnosticMessages diagnosticMessages; public List getCfTargets() { return cfTargets; @@ -35,12 +34,61 @@ public class CfTargetsInfo { this.cfTargets = cfTargets; } - public Map getCfDiagnosticMessages() { - return this.cfDiagnosticMessages; + public TargetDiagnosticMessages getDiagnosticMessages() { + return diagnosticMessages; } - public void setCfDiagnosticMessages(Map cfDiagnosticMessages) { - this.cfDiagnosticMessages = cfDiagnosticMessages; + public void setDiagnosticMessages(TargetDiagnosticMessages diagnosticMessages) { + this.diagnosticMessages = diagnosticMessages; + } + + public static class TargetDiagnosticMessages { + + + private String noTargetsFound; + private String connectionError; + private String noOrgSpace; + private String targetSource; + + /** + * + * @return only when there are no targets available (e.g. cf CLI is not connected or no boot dash targets) + */ + public String getNoTargetsFound() { + return noTargetsFound; + } + + public void setNoTargetsFound(String noTargetsFound) { + this.noTargetsFound = noTargetsFound; + } + + /** + * + * @return error if any existing target cannot connect. + */ + public String getConnectionError() { + return connectionError; + } + + public void setConnectionError(String connectionError) { + this.connectionError = connectionError; + } + + public String getNoOrgSpace() { + return noOrgSpace; + } + + public void setNoOrgSpace(String noOrgSpace) { + this.noOrgSpace = noOrgSpace; + } + + public String getTargetSource() { + return targetSource; + } + + public void setTargetSource(String targetSource) { + this.targetSource = targetSource; + } } public static class Target { @@ -66,7 +114,7 @@ public class CfTargetsInfo { public void setOrg(String org) { this.org = org; } - + public void setSpace(String space) { this.space = space; } @@ -90,5 +138,10 @@ public class CfTargetsInfo { public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } + + @Override + public String toString() { + return "Target [api=" + api + ", org=" + org + ", space=" + space + "]"; + } } } diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfoProvder.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfoProvder.java index 5049d0ba8..93659d160 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfoProvder.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/CfTargetsInfoProvder.java @@ -11,11 +11,11 @@ package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget; import java.util.Collection; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo.TargetDiagnosticMessages; import org.springframework.ide.vscode.commons.util.StringUtil; import com.google.common.base.Supplier; @@ -33,19 +33,31 @@ import com.google.common.base.Suppliers; public class CfTargetsInfoProvder implements ClientParamsProvider { private static final String NO_TARGETS_FOUND_MESSAGE = "No targets found"; - private static final String NO_NETWORK_CONNECTION = "No connection to Cloud Foundry"; + public static final String NO_NETWORK_CONNECTION = "No connection to Cloud Foundry"; private static final String NO_ORG_SPACE = "No org/space selected"; - - private static final String PROP_NO_TARGETS_FOUND = "noTargetsFound"; - private static final String PROP_UNAUTHORISED = "unauthorised"; - private static final String PROP_NO_NETWORK_CONNECTION = "noNetworkConnection"; - private static final String PROP_NO_ORG_SPACE = "noOrgSpace"; - + public static final TargetDiagnosticMessages DEFAULT_MESSAGES = new TargetDiagnosticMessages() { + + @Override + public String getNoTargetsFound() { + return NO_TARGETS_FOUND_MESSAGE; + } + + @Override + public String getConnectionError() { + return NO_NETWORK_CONNECTION; + } + + @Override + public String getNoOrgSpace() { + return NO_ORG_SPACE; + } + }; + private Supplier> paramsSupplier; - private Map messages; - + private TargetDiagnosticMessages messages; + public CfTargetsInfoProvder(CfTargetsInfo targetsInfo) { - this.messages = targetsInfo.getCfDiagnosticMessages(); + this.messages = targetsInfo.getDiagnosticMessages(); this.paramsSupplier = Suppliers.memoize(() -> targetsInfo.getCfTargets() .stream() .map(t -> parseCfClientParams(t)) @@ -57,7 +69,7 @@ public class CfTargetsInfoProvder implements ClientParamsProvider { public Collection getParams() throws NoTargetsException, ExecutionException { Collection params = paramsSupplier.get(); if (params == null || params.isEmpty()) { - throw new NoTargetsException(getMessages().noTargetsFound()); + throw new NoTargetsException(getMessages().getNoTargetsFound()); } return params; } @@ -79,42 +91,7 @@ public class CfTargetsInfoProvder implements ClientParamsProvider { } @Override - public CFParamsProviderMessages getMessages() { - return new CFParamsProviderMessages() { - - @Override - public String noTargetsFound() { - if (messages != null && messages.containsKey(PROP_NO_TARGETS_FOUND)) { - return messages.get(PROP_NO_TARGETS_FOUND); - } - return NO_TARGETS_FOUND_MESSAGE; - } - - @Override - public String unauthorised() { - if (messages != null && messages.containsKey(PROP_UNAUTHORISED)) { - return messages.get(PROP_UNAUTHORISED); - } - return NO_NETWORK_CONNECTION; - } - - @Override - public String noNetworkConnection() { - if (messages != null && messages.containsKey(PROP_NO_NETWORK_CONNECTION)) { - return messages.get(PROP_NO_NETWORK_CONNECTION); - } - return NO_NETWORK_CONNECTION; - } - - @Override - public String noOrgSpace() { - if (messages != null && messages.containsKey(PROP_NO_ORG_SPACE)) { - return messages.get(PROP_NO_ORG_SPACE); - } - return NO_ORG_SPACE; - } - - }; + public TargetDiagnosticMessages getMessages() { + return messages != null ? messages : DEFAULT_MESSAGES; } - } diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsCacheKey.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsCacheKey.java index fc0ddbd68..b139c6503 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsCacheKey.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsCacheKey.java @@ -19,15 +19,23 @@ package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget; public class ClientParamsCacheKey { public final CFClientParams fullParams; + private final ClientParamsProvider provider; /** * Use static API to create: {@link #from(CFClientParams)} * @param fullParams */ - private ClientParamsCacheKey(CFClientParams fullParams) { + private ClientParamsCacheKey(CFClientParams fullParams, ClientParamsProvider provider) { this.fullParams = fullParams; + + // Not used in evaluating key equality. It's passed into the key because when a + // target is created from this key, it requires a provider context. See the CFTargetCache + this.provider = provider; } + public ClientParamsProvider getProvider() { + return this.provider; + } // @Override @@ -100,11 +108,9 @@ public class ClientParamsCacheKey { return false; return true; } - - - - public static ClientParamsCacheKey from(CFClientParams params) { - return new ClientParamsCacheKey(params); + + public static ClientParamsCacheKey from(CFClientParams params, ClientParamsProvider provider) { + return new ClientParamsCacheKey(params, provider); } } diff --git a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java index 8a80a0926..d4c4cf756 100644 --- a/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java +++ b/headless-services/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/cftarget/ClientParamsProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -13,6 +13,8 @@ package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget; import java.util.Collection; import java.util.concurrent.ExecutionException; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo.TargetDiagnosticMessages; + public interface ClientParamsProvider { /** @@ -23,6 +25,7 @@ public interface ClientParamsProvider { */ Collection getParams() throws NoTargetsException, ExecutionException; - CFParamsProviderMessages getMessages(); + TargetDiagnosticMessages getMessages(); + } \ No newline at end of file diff --git a/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java b/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java index d53606c26..ea9058fae 100644 --- a/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java +++ b/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -18,13 +18,14 @@ import static org.mockito.Mockito.when; import java.net.UnknownHostException; import java.util.concurrent.Callable; +import org.cloudfoundry.uaa.UaaException; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFParamsProviderMessages; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliProviderMessages; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliParamsProvider; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo.TargetDiagnosticMessages; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ConnectionException; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException; import org.springframework.ide.vscode.commons.util.ExceptionUtil; @@ -36,19 +37,19 @@ public class CFClientTest { MockCfCli cloudfoundry = new MockCfCli(); ClientTimeouts timeouts = new ClientTimeouts(); CFTargetCache targetCache; - CFParamsProviderMessages expectedMessages = new CfCliProviderMessages(); + TargetDiagnosticMessages expectedMessages = CfCliParamsProvider.CLI_PROVIDER_MESSAGES; @Before public void setup() throws Exception { - targetCache = new CFTargetCache(cloudfoundry.cfClientConfig, cloudfoundry.factory, timeouts); + targetCache = new CFTargetCache(ImmutableList.of(cloudfoundry.paramsProvider), cloudfoundry.factory, timeouts); } @Test public void testNoTarget() throws Exception { when(cloudfoundry.paramsProvider.getParams()) - .thenThrow(new NoTargetsException(expectedMessages.noTargetsFound())); - assertError(() -> targetCache.getOrCreate(), NoTargetsException.class, expectedMessages.noTargetsFound()); + .thenThrow(new NoTargetsException(expectedMessages.getNoTargetsFound())); + assertError(() -> targetCache.getOrCreate(), NoTargetsException.class, expectedMessages.getNoTargetsFound()); } @Test @@ -63,7 +64,7 @@ public class CFClientTest { ClientRequests client = cloudfoundry.client; when(client.getServices()).thenThrow(new UnknownHostException("api.run.pivotal.io")); CFTarget target = targetCache.getOrCreate().get(0); - assertError(() -> target.getServices(), ConnectionException.class, expectedMessages.noNetworkConnection()); + assertError(() -> target.getServices(), ConnectionException.class, expectedMessages.getConnectionError()); } @Test @@ -71,7 +72,7 @@ public class CFClientTest { ClientRequests client = cloudfoundry.client; when(client.getBuildpacks()).thenThrow(new UnknownHostException("api.run.pivotal.io")); CFTarget target = targetCache.getOrCreate().get(0); - assertError(() -> target.getBuildpacks(), ConnectionException.class, expectedMessages.noNetworkConnection()); + assertError(() -> target.getBuildpacks(), ConnectionException.class, expectedMessages.getConnectionError()); } @Test @@ -79,7 +80,16 @@ public class CFClientTest { ClientRequests client = cloudfoundry.client; when(client.getDomains()).thenThrow(new UnknownHostException("api.run.pivotal.io")); CFTarget target = targetCache.getOrCreate().get(0); - assertError(() -> target.getDomains(), ConnectionException.class, expectedMessages.noNetworkConnection()); + assertError(() -> target.getDomains(), ConnectionException.class, expectedMessages.getConnectionError()); + } + + @Test + public void testInvalidRefreshToken() throws Exception { + ClientRequests client = cloudfoundry.client; + String mockedError = "org.cloudfoundry.uaa.UaaException: invalid_token: Invalid refresh token expired at Wed Mar 28 18:58:20 UTC 2018"; + when(client.getDomains()).thenThrow(new UaaException(1111, mockedError, mockedError)); + CFTarget target = targetCache.getOrCreate().get(0); + assertError(() -> target.getDomains(), ConnectionException.class, expectedMessages.getConnectionError()); } @Test diff --git a/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java b/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java index b8bde4aae..65a79d63a 100644 --- a/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java +++ b/headless-services/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/MockCfCli.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -15,12 +15,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.mockito.Mockito; -import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests; -import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliProviderMessages; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfClientConfig; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliParamsProvider; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider; import org.springframework.ide.vscode.commons.util.ExceptionUtil; @@ -33,18 +30,16 @@ public class MockCfCli { public final CloudFoundryClientFactory factory = mock(CloudFoundryClientFactory.class); public final ClientRequests client = mock(ClientRequests.class); - public final CfClientConfig cfClientConfig = CfClientConfig.createDefault(); public final ClientParamsProvider paramsProvider = mock(ClientParamsProvider.class); - public final CfCliProviderMessages actualCfCliMessages = new CfCliProviderMessages(); + public MockCfCli() { try { - cfClientConfig.setClientParamsProvider(paramsProvider); //program some default behavior into mocks... most tests will use this. //other tests should 'reset' the mocks and reprogram them as needed. when(factory.getClient(any(), any())).thenReturn(client); when(paramsProvider.getParams()).thenReturn(ImmutableList.of(DEFAULT_PARAMS)); - when(paramsProvider.getMessages()).thenReturn(actualCfCliMessages); + when(paramsProvider.getMessages()).thenReturn(CfCliParamsProvider.CLI_PROVIDER_MESSAGES); } catch (Exception e) { throw ExceptionUtil.unchecked(e); diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java index 0d72f8665..3c730e413 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/AbstractCFHintsProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -15,6 +15,8 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ConnectionException; @@ -29,7 +31,10 @@ import com.google.common.collect.ImmutableList; public abstract class AbstractCFHintsProvider implements Callable> { public static final String EMPTY_VALUE = ""; + public static final String PROBLEM_RESOLVING_FROM_TARGETS = "Unable to resolve hints from: "; + protected final CFTargetCache targetCache; + private Logger logger = LoggerFactory.getLogger(AbstractCFHintsProvider.class); public AbstractCFHintsProvider(CFTargetCache targetCache) { Assert.isNotNull(targetCache); @@ -59,12 +64,10 @@ public abstract class AbstractCFHintsProvider implements Callable> toLookFor) { return ExceptionUtil.findThrowable(e, - ImmutableList.of(NoTargetsException.class, ConnectionException.class)); + toLookFor); } /** @@ -97,20 +100,31 @@ public abstract class AbstractCFHintsProvider implements Callable hints = new ArrayList<>(); boolean validTargetsPresent = false; + Exception lastErrorEncountered = null; + for (CFTarget cfTarget : targets) { try { // TODO: check if duplicate proposals can be the list of all hints. Duplicates don't seem to cause duplicate proposals. Verify this! getHints(cfTarget).stream().filter(hint -> !hints.contains(hint)).forEach(hint -> hints.add(hint)); validTargetsPresent = true; } catch (Exception e) { - // Drop individual target error + // PT 156579665 - Log the Connection exceptions, as it means there are existing targets that have connection errors + // and this information could be useful to the user, + // but don't log the "NoTarget" errors, as they may be logged frequently and dont necessarily indicate an issue (e.g. cf CLI is not installed). + Throwable connectionError = getErrorOfType(e, ImmutableList.of(ConnectionException.class)); + if (connectionError != null) { + logger.error("{}", ExceptionUtil.getMessageNoAppendedInformation(connectionError)); + } + lastErrorEncountered = e; } } + if (validTargetsPresent) { return hints; + } else if (lastErrorEncountered != null){ + throw lastErrorEncountered; } else { - throw new ConnectionException( - targetCache.getCfClientConfig().getClientParamsProvider().getMessages().noNetworkConnection()); + throw new ConnectionException(PROBLEM_RESOLVING_FROM_TARGETS + " " + targets.toString()); } } diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java index 60e600739..b84b6291f 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2017 Pivotal, Inc. + * Copyright (c) 2016, 2018 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 @@ -10,26 +10,18 @@ *******************************************************************************/ package org.springframework.ide.vscode.manifest.yaml; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts; import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFParamsProviderMessages; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfCliParamsProvider; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfClientConfig; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfoProvder; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo.Target; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfoProvder; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException; import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; @@ -38,7 +30,6 @@ import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProv import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler; -import org.springframework.ide.vscode.commons.languageserver.util.Settings; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService; @@ -61,9 +52,6 @@ import org.yaml.snakeyaml.Yaml; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonSyntaxException; public class ManifestYamlLanguageServer extends SimpleLanguageServer { @@ -71,9 +59,6 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer { private CfJson cfJson = new CfJson(); private ManifestYmlSchema schema; private CFTargetCache cfTargetCache; - private final CloudFoundryClientFactory cfClientFactory; - private final CfClientConfig cfClientConfig; - private final Logger log = LoggerFactory.getLogger(ManifestYamlLanguageServer.class); private final ImmutableSet FALLBACK_YML_IDS = ImmutableSet.of(LanguageId.of("yml"), LanguageId.of("yaml")); final private ClientParamsProvider defaultClientParamsProvider; @@ -84,9 +69,10 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer { public ManifestYamlLanguageServer(CloudFoundryClientFactory cfClientFactory, ClientParamsProvider defaultClientParamsProvider) { super("vscode-manifest-yaml"); - this.cfClientFactory = cfClientFactory; - this.cfClientConfig=CfClientConfig.createDefault(); this.defaultClientParamsProvider = defaultClientParamsProvider; + + this.cfTargetCache = new CFTargetCache(ImmutableList.of(this.defaultClientParamsProvider), cfClientFactory, new ClientTimeouts()); + SimpleTextDocumentService documents = getTextDocumentService(); SimpleWorkspaceService workspace = getWorkspaceService(); @@ -144,52 +130,11 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer { }); } - public CfClientConfig getCfClientConfig() { - return cfClientConfig; - } - - - - @SuppressWarnings("unchecked") private void applyCfLoginParameterSettings(CfTargetsInfo info) { - List cfTargets = info.getCfTargets(); - if (cfTargets != null) { - - CfTargetsInfoProvder cfClientParamsProvider = new CfTargetsInfoProvder(info); - - cfClientConfig.setClientParamsProvider(new ClientParamsProvider() { - - @Override - public Collection getParams() throws NoTargetsException, ExecutionException { - List providers = ImmutableList.of(defaultClientParamsProvider, cfClientParamsProvider); - - List params = new ArrayList<>(); - - for (ClientParamsProvider provider : providers) { - try { - params.addAll(provider.getParams()); - } catch (Exception e) { - // ignore - } - } - - if (params.isEmpty()) { - throw new NoTargetsException(getMessages().noTargetsFound()); - } - - return params; - } - - @Override - public CFParamsProviderMessages getMessages() { - return cfTargets.isEmpty() ? defaultClientParamsProvider.getMessages() - : cfClientParamsProvider.getMessages(); - } - - }); - - } - + // Refresh the list of providers + CfTargetsInfoProvder cfClientParamsProvider = new CfTargetsInfoProvder(info); + // set providers in the order that they should be called + cfTargetCache.setProviders(defaultClientParamsProvider, cfClientParamsProvider); } private void validateOnDocumentChange(IReconcileEngine engine, TextDocument doc) { @@ -235,15 +180,11 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer { } private CFTargetCache getCfTargetCache() { - if (cfTargetCache == null) { - // Init CF client params provider if it's initilized - if (cfClientConfig.getClientParamsProvider() == null) { - cfClientConfig.setClientParamsProvider(defaultClientParamsProvider ); - } - CloudFoundryClientFactory clientFactory = cfClientFactory; - cfTargetCache = new CFTargetCache(cfClientConfig, clientFactory, new ClientTimeouts()); - } - return cfTargetCache; + return this.cfTargetCache; + } + + public List getParamsProvider() { + return cfTargetCache.getParamsProviders(); } /** diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlActualCfClientTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlActualCfClientTest.java index dd442de21..23cac2ec0 100644 --- a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlActualCfClientTest.java +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlActualCfClientTest.java @@ -22,11 +22,12 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache; -import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfClientConfig; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfo; import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CfTargetsInfoProvder; import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; +import com.google.common.collect.ImmutableList; + public class ManifestYamlActualCfClientTest { private CFTargetCache cfTargetCache; @@ -37,9 +38,8 @@ public class ManifestYamlActualCfClientTest { cfJson = new CfJson(); CfTargetsInfo info = getTargetsInfoFromEnv(); CfTargetsInfoProvder provider = new CfTargetsInfoProvder(info); - CfClientConfig cfClientConfig = CfClientConfig.createDefault(provider); CloudFoundryClientFactory clientFactory = DefaultCloudFoundryClientFactoryV2.INSTANCE; - cfTargetCache = new CFTargetCache(cfClientConfig, clientFactory, new ClientTimeouts()); + cfTargetCache = new CFTargetCache(ImmutableList.of(provider), clientFactory, new ClientTimeouts()); } private CfTargetsInfo getTargetsInfoFromEnv() { diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java index 607f5ac1c..bb534b85b 100644 --- a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServerTest.java @@ -18,19 +18,21 @@ import java.io.File; import java.io.InputStreamReader; import java.net.URISyntaxException; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.List; import org.eclipse.lsp4j.DidChangeConfigurationParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.junit.Test; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams; +import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; -import com.google.gson.Gson; -import com.google.gson.JsonElement; import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; public class ManifestYamlLanguageServerTest { @@ -99,20 +101,30 @@ public class ManifestYamlLanguageServerTest { ); harness.intialize(null); - assertEquals(1, manifestYamlLanguageServer.getCfClientConfig().getClientParamsProvider().getParams().size()); + // This is an initial target, for example from cf CLI + assertEquals(1, getAllParams(manifestYamlLanguageServer.getParamsProvider()).size()); assertEquals(Arrays.asList("test.io"), manifestYamlLanguageServer.getCfTargets()); + // This tests a change in workspace (e.g. boot dash) that results in two more targets created. DidChangeConfigurationParams params = new DidChangeConfigurationParams(); JsonParser parser = new JsonParser(); - params.setSettings(parser.parse(new InputStreamReader(getClass().getResourceAsStream("/cf-targets1.json"))) -); + params.setSettings(parser.parse(new InputStreamReader(getClass().getResourceAsStream("/cf-targets1.json")))); manifestYamlLanguageServer.getWorkspaceService().didChangeConfiguration(params); - assertEquals(3, manifestYamlLanguageServer.getCfClientConfig().getClientParamsProvider().getParams().size()); + assertEquals(3, getAllParams(manifestYamlLanguageServer.getParamsProvider()).size()); + + // End result should have the initial target as well as the two additional targets obtained on workspace change assertEquals(Arrays.asList("test.io", "api.system.demo-gcp.springapps.io", "api.run.pivotal.io"), manifestYamlLanguageServer.getCfTargets()); } - + private List getAllParams(List providers) throws Exception { + List all = new ArrayList<>(); + for (ClientParamsProvider clientParamsProvider : providers) { + Collection params = clientParamsProvider.getParams(); + all.addAll(params); + } + return all; + } }