getUserName();
+}
diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java
new file mode 100644
index 000000000..e12b171b8
--- /dev/null
+++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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.commons.cloudfoundry.client.v2;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.cloudfoundry.client.CloudFoundryClient;
+import org.cloudfoundry.reactor.ConnectionContext;
+import org.cloudfoundry.reactor.DefaultConnectionContext;
+import org.cloudfoundry.reactor.ProxyConfiguration;
+import org.cloudfoundry.reactor.TokenProvider;
+import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient;
+import org.cloudfoundry.reactor.doppler.ReactorDopplerClient;
+import org.cloudfoundry.reactor.tokenprovider.OneTimePasscodeTokenProvider;
+import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider;
+import org.cloudfoundry.reactor.tokenprovider.RefreshTokenGrantTokenProvider;
+import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFCredentials;
+
+/**
+ * TODO: Remove this class when the 'thread leak bug' in V2 client is fixed.
+ *
+ * At the moment each time {@link SpringCloudFoundryClient} is create a threadpool
+ * is created by the client and it is never cleaned up. The only way we have
+ * to mitigate this leak is to try and create as few clients as possible.
+ *
+ * So we have a permanent cache of clients here that is reused.
+ *
+ * When the bug is fixed then this should no longer be necessary and we can removed this cache
+ * and just create the client as needed.
+ *
+ * @author Kris De Volder
+ */
+public class CloudFoundryClientCache {
+
+ public class CFClientProvider {
+
+ final ConnectionContext connection;
+ final TokenProvider tokenProvider;
+
+ //Note the three client objects below are 'stateless' wrappers and it would be
+ // fine to recreate as needed instead of store them
+
+ final CloudFoundryClient client;
+ final ReactorUaaClient uaaClient;
+ final ReactorDopplerClient doppler;
+
+ private ProxyConfiguration getProxy(String host) {
+ // TODO: enable proxy support for vscode. The code below retrieves proxy via Eclipse proxy service thus
+ // not applicable when this is used outside of Eclipse
+// try {
+// if (StringUtils.hasText(host)) {
+// URL url = new URL("https://"+host);
+// // In certain cases, the activator would have stopped and the plugin may
+// // no longer be available. Usually onl happens on shutdown.
+// BootDashActivator plugin = BootDashActivator.getDefault();
+// if (plugin != null) {
+// IProxyService proxyService = plugin.getProxyService();
+// if (proxyService != null) {
+// IProxyData[] selectedProxies = proxyService.select(url.toURI());
+//
+// // No proxy configured or not found
+// if (selectedProxies == null || selectedProxies.length == 0) {
+// return null;
+// }
+//
+// IProxyData data = selectedProxies[0];
+// int proxyPort = data.getPort();
+// String proxyHost = data.getHost();
+// String user = data.getUserId();
+// String password = data.getPassword();
+// if (proxyHost!=null) {
+// return ProxyConfiguration.builder()
+// .host(proxyHost)
+// .port(proxyPort==-1?Optional.empty():Optional.of(proxyPort))
+// .username(Optional.ofNullable(user))
+// .password(Optional.ofNullable(password))
+// .build();
+//// return proxyHost != null ? new HttpProxyConfiguration(proxyHost, proxyPort,
+//// data.isRequiresAuthentication(), user, password) : null;
+// }
+// }
+// }
+// }
+// } catch (Exception e) {
+// Log.log(e);
+// }
+ return null;
+ }
+
+ public CFClientProvider(Params params) {
+ long sslTimeout = Long.getLong("sts.bootdash.cf.client.ssl.handshake.timeout", 60); //TODO: make a preference for this?
+ Optional keepAlive = getBooleanSystemProp("http.keepAlive");
+ debug("cf client keepAlive = "+keepAlive);
+ connection = DefaultConnectionContext.builder()
+ .proxyConfiguration(Optional.ofNullable(getProxy(params.host)))
+ .apiHost(params.host)
+ .sslHandshakeTimeout(Duration.ofSeconds(sslTimeout))
+ .keepAlive(keepAlive)
+ .skipSslValidation(params.skipSsl)
+ .build();
+
+ tokenProvider = createTokenProvider(params);
+
+ client = ReactorCloudFoundryClient.builder()
+ .connectionContext(connection)
+ .tokenProvider(tokenProvider)
+ .build();
+
+ uaaClient = ReactorUaaClient.builder()
+ .connectionContext(connection)
+ .tokenProvider(tokenProvider)
+ .build();
+
+ doppler = ReactorDopplerClient.builder()
+ .connectionContext(connection)
+ .tokenProvider(tokenProvider)
+ .build();
+ }
+
+ private TokenProvider createTokenProvider(Params params) {
+ CFCredentials creds = params.credentials;
+ switch (creds.getType()) {
+ case PASSWORD:
+ return PasswordGrantTokenProvider.builder()
+ .username(params.username)
+ .password(creds.getSecret())
+ .build();
+ case REFRESH_TOKEN:
+ return RefreshTokenGrantTokenProvider.builder()
+ .token(creds.getSecret())
+ .build();
+ case TEMPORARY_CODE:
+ return OneTimePasscodeTokenProvider.builder()
+ .passcode(creds.getSecret())
+ .build();
+ default:
+ throw new IllegalStateException("BUG! Missing switch case?");
+ }
+ }
+
+ private Optional getBooleanSystemProp(String name) {
+ String str = System.getProperty(name);
+ if (str!=null) {
+ return Optional.of(Boolean.valueOf(str));
+ }
+ return Optional.empty();
+ }
+ }
+
+ private static final boolean DEBUG = true;
+
+ private static void debug(String string) {
+ if (DEBUG) {
+ System.out.println(string);
+ }
+ }
+
+ public static class Params {
+ public final String username;
+ public final CFCredentials credentials;
+ public final String host;
+ public final boolean skipSsl;
+ public Params(String username, CFCredentials credentials, String host, boolean skipSsl) {
+ super();
+ this.username = username;
+ this.credentials = credentials;
+ this.host = host;
+ this.skipSsl = skipSsl;
+ }
+
+ @Override
+ public String toString() {
+ return "Params [username=" + username + ", host=" + host + ", skipSsl=" + skipSsl
+ + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((host == null) ? 0 : host.hashCode());
+ result = prime * result + ((credentials == null) ? 0 : credentials.hashCode());
+ result = prime * result + (skipSsl ? 1231 : 1237);
+ result = prime * result + ((username == null) ? 0 : username.hashCode());
+ return result;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Params other = (Params) obj;
+ if (host == null) {
+ if (other.host != null)
+ return false;
+ } else if (!host.equals(other.host))
+ return false;
+ if (credentials == null) {
+ if (other.credentials != null)
+ return false;
+ } else if (!credentials.equals(other.credentials))
+ return false;
+ if (skipSsl != other.skipSsl)
+ return false;
+ if (username == null) {
+ if (other.username != null)
+ return false;
+ } else if (!username.equals(other.username))
+ return false;
+ return true;
+ }
+ }
+
+ private Map cache = new HashMap<>();
+
+ private int clientCount = 0;
+
+ public synchronized CFClientProvider getOrCreate(String username, CFCredentials credentials, String host, boolean skipSsl) {
+ Params params = new Params(username, credentials, host, skipSsl);
+ CFClientProvider client = cache.get(params);
+ if (client==null) {
+ clientCount++;
+ debug("Creating client ["+clientCount+"]: "+params);
+ cache.put(params, client = create(params));
+ } else {
+ debug("Reusing client ["+clientCount+"]: "+params);
+ }
+ return client;
+ }
+
+ protected CFClientProvider create(Params params) {
+ return new CFClientProvider(params);
+ }
+
+}
diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientFactory.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientFactory.java
new file mode 100644
index 000000000..f54a993ae
--- /dev/null
+++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientFactory.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * 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.commons.cloudfoundry.client.v2;
+
+import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams;
+
+public interface CloudFoundryClientFactory {
+
+ ClientRequests getClient(CFClientParams params) throws Exception;
+
+}
\ No newline at end of file
diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java
new file mode 100644
index 000000000..57c7474db
--- /dev/null
+++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java
@@ -0,0 +1,1330 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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.commons.cloudfoundry.client.v2;
+
+import static org.cloudfoundry.util.tuple.TupleUtils.function;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.cloudfoundry.client.CloudFoundryClient;
+import org.cloudfoundry.client.v2.applications.ApplicationEntity;
+import org.cloudfoundry.client.v2.applications.CreateApplicationRequest;
+import org.cloudfoundry.client.v2.applications.GetApplicationResponse;
+import org.cloudfoundry.client.v2.applications.UpdateApplicationRequest;
+import org.cloudfoundry.client.v2.applications.UpdateApplicationResponse;
+import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksRequest;
+import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksResponse;
+import org.cloudfoundry.client.v2.domains.DomainResource;
+import org.cloudfoundry.client.v2.domains.ListDomainsRequest;
+import org.cloudfoundry.client.v2.domains.ListDomainsResponse;
+import org.cloudfoundry.client.v2.info.GetInfoRequest;
+import org.cloudfoundry.client.v2.info.GetInfoResponse;
+import org.cloudfoundry.client.v2.serviceinstances.DeleteServiceInstanceRequest;
+import org.cloudfoundry.client.v2.stacks.GetStackRequest;
+import org.cloudfoundry.client.v2.stacks.GetStackResponse;
+import org.cloudfoundry.client.v2.userprovidedserviceinstances.DeleteUserProvidedServiceInstanceRequest;
+import org.cloudfoundry.operations.CloudFoundryOperations;
+import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
+import org.cloudfoundry.operations.applications.ApplicationDetail;
+import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
+import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
+import org.cloudfoundry.operations.applications.GetApplicationRequest;
+import org.cloudfoundry.operations.applications.RestartApplicationRequest;
+import org.cloudfoundry.operations.applications.StartApplicationRequest;
+import org.cloudfoundry.operations.applications.StopApplicationRequest;
+import org.cloudfoundry.operations.organizations.OrganizationDetail;
+import org.cloudfoundry.operations.organizations.OrganizationInfoRequest;
+import org.cloudfoundry.operations.organizations.OrganizationSummary;
+import org.cloudfoundry.operations.routes.ListRoutesRequest;
+import org.cloudfoundry.operations.routes.MapRouteRequest;
+import org.cloudfoundry.operations.routes.UnmapRouteRequest;
+import org.cloudfoundry.operations.services.BindServiceInstanceRequest;
+import org.cloudfoundry.operations.services.CreateServiceInstanceRequest;
+import org.cloudfoundry.operations.services.CreateUserProvidedServiceInstanceRequest;
+import org.cloudfoundry.operations.services.GetServiceInstanceRequest;
+import org.cloudfoundry.operations.services.ServiceInstance;
+import org.cloudfoundry.operations.services.UnbindServiceInstanceRequest;
+import org.cloudfoundry.operations.spaces.GetSpaceRequest;
+import org.cloudfoundry.operations.spaces.SpaceDetail;
+import org.cloudfoundry.reactor.tokenprovider.AbstractUaaTokenProvider;
+import org.cloudfoundry.uaa.UaaClient;
+import org.cloudfoundry.util.PaginationUtils;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplication;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplicationDetail;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.CFCloudDomain;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.CFSpace;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.SshClientSupport;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.SshHost;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CancelationTokens.CancelationToken;
+import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientCache.CFClientProvider;
+import org.springframework.ide.vscode.commons.util.ExceptionUtil;
+import org.springframework.util.StringUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author Kris De Volder
+ * @author Nieraj Singh
+ */
+public class DefaultClientRequestsV2 implements ClientRequests {
+
+ private static final Duration APP_START_TIMEOUT = Duration.ofMillis(60*10);
+ private static final Duration GET_SERVICES_TIMEOUT = Duration.ofSeconds(60);
+ private static final Duration GET_SPACES_TIMEOUT = Duration.ofSeconds(20);
+ private static final Duration GET_USERNAME_TIMEOUT = Duration.ofSeconds(5);
+ private static final Logger logger = Logger.getLogger(DefaultClientRequestsV2.class.getName());
+ private static final boolean DEBUG = false;
+
+
+
+// static {
+// if (DEBUG_REACTOR) {
+// Loggers.enableExtension(new Extension() {
+// @Override
+// public void log(String category, java.util.logging.Level level, String msg, Object... arguments) {
+// debug(category +"["+level + "] : "+MessageFormatter.format(msg, arguments).getMessage());
+// }
+// });
+// }
+// }
+
+
+// TODO: it would be good not to create another 'threadpool' and use something like the below code
+// instead so that eclipse job scheduler is used for reactor 'tasks'. However... the code below
+// may not be 100% correct.
+// private static final Callable extends Consumer> SCHEDULER_GROUP = () -> {
+// return (Runnable task) -> {
+// Job job = new Job("CF Client background task") {
+// @Override
+// protected IStatus run(IProgressMonitor monitor) {
+// if (task!=null) {
+// task.run();
+// }
+// return Status.OK_STATUS;
+// }
+// };
+// job.setRule(JobUtil.lightRule("reactor-job-rule"));
+// job.setSystem(true);
+// job.schedule();
+// };
+// };
+
+
+ private CFClientParams params;
+ private CloudFoundryClient _client ;
+ private UaaClient _uaa;
+ private CloudFoundryOperations _operations;
+
+ private Mono orgId;
+ private Mono info;
+ private Mono spaceId;
+ private AbstractUaaTokenProvider _tokenProvider;
+
+ public DefaultClientRequestsV2(CloudFoundryClientCache clients, CFClientParams params) {
+ this.params = params;
+ CFClientProvider provider = clients.getOrCreate(params.getUsername(), params.getCredentials(), params.getHost(), params.skipSslValidation());
+ this._client = provider.client;
+ this._uaa = provider.uaaClient;
+ this._tokenProvider = (AbstractUaaTokenProvider) provider.tokenProvider;
+ this._operations = DefaultCloudFoundryOperations.builder()
+ .cloudFoundryClient(_client)
+ .dopplerClient(provider.doppler)
+ .uaaClient(provider.uaaClient)
+ .organization(params.getOrgName())
+ .space(params.getSpaceName())
+ .build();
+ this.orgId = getOrgId();
+ this.spaceId = getSpaceId();
+ this.info = client_getInfo().cache();
+ }
+
+ private Mono client_createOperations(OrganizationSummary org) {
+ return log("client.createOperations(org="+org.getName()+")",
+ Mono.fromCallable(() -> DefaultCloudFoundryOperations.builder()
+ .cloudFoundryClient(_client)
+ .organization(org.getName())
+ .build()
+ )
+ );
+ }
+
+ private Mono getOrgId() {
+ String orgName = params.getOrgName();
+ if (orgName==null) {
+ return Mono.error(new IOException("No organization targetted"));
+ } else {
+ return operations_getOrgId().cache();
+ }
+ }
+
+ private Mono getSpaceId() {
+ String spaceName = params.getSpaceName();
+ if (spaceName==null) {
+ return Mono.error(new IOException("No space targetted"));
+ } else {
+ return _operations.spaces().get(GetSpaceRequest.builder()
+ .name(params.getSpaceName())
+ .build()
+ )
+ .map(SpaceDetail::getId)
+ .cache();
+ }
+ }
+
+ @Override
+ public List getApplicationsWithBasicInfo() throws Exception {
+ return ReactorUtils.get(operations_listApps());
+ }
+
+ private ApplicationExtras getApplicationExtras(String appName) {
+ //Stuff used in computing the 'extras'...
+ Mono appIdMono = getApplicationId(appName);
+ Mono entity = appIdMono
+ .then((appId) ->
+ client_getApplication(appId)
+ )
+ .map((appResource) -> appResource.getEntity())
+ .cache();
+
+ //The stuff returned from the getters of 'extras'...
+ Mono> services = prefetch("services", getBoundServicesList(appName));
+ Mono