Moved commons and concourse editor to 'headless-services'

This commit is contained in:
Kris De Volder
2017-04-06 16:27:12 -07:00
parent 3abf189512
commit bf8f8cffa9
608 changed files with 600 additions and 51 deletions

View File

@@ -0,0 +1,15 @@
/*******************************************************************************
* 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;
public interface CFBuildpack {
String getName();
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* 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;
/**
* Package-private.
* <p/>
* Use {@link CFEntities} public API to create instance
*
*/
class CFBuildpackImpl implements CFBuildpack {
private final String name;
public CFBuildpackImpl(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.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;
CFBuildpackImpl other = (CFBuildpackImpl) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

View File

@@ -0,0 +1,17 @@
/*******************************************************************************
* 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;
public interface CFDomain {
String getName();
}

View File

@@ -0,0 +1,52 @@
/*******************************************************************************
* 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;
class CFDomainImpl implements CFDomain {
private final String name;
public CFDomainImpl(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.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;
CFDomainImpl other = (CFDomainImpl) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* 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;
/**
* Factory to create CF "Entities" like CF services, buildpacks, etc..
*/
public class CFEntities {
public static CFBuildpack createBuildpack(String name) {
return new CFBuildpackImpl(name);
}
public static CFServiceInstance createServiceInstance(String name, String service, String plan,
String documentationUrl, String description, String dashboardUrl) {
return new CFServiceInstanceImpl(name, service, plan, documentationUrl, description, dashboardUrl);
}
public static CFServiceInstance createServiceInstance(String name, String service, String plan) {
return new CFServiceInstanceImpl(name, service, plan, /* doc url */ null, /* description */ null,
/* dasboard Url */ null);
}
public static CFDomain createDomain(String name) {
return new CFDomainImpl(name);
}
}

View File

@@ -0,0 +1,15 @@
/*******************************************************************************
* 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;
public interface CFEntity {
String getName();
}

View File

@@ -0,0 +1,27 @@
/*******************************************************************************
* 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;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
/**
* Static methods to recognize specific types of exceptions CF client
* may throw.
*
* @author Kris De Volder
*/
public class CFExceptions {
public static boolean isSSLCertificateFailure(Exception e) {
Throwable cause = ExceptionUtil.getDeepestCause(e);
return cause.getClass().getName().equals("sun.security.provider.certpath.SunCertPathBuilderException");
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* 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;
public interface CFServiceInstance extends CFEntity {
String getName();
String getService();
String getPlan();
String getDescription();
String getDocumentationUrl();
String getDashboardUrl();
//TODO: last operation info?
}

View File

@@ -0,0 +1,123 @@
/*******************************************************************************
* 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;
/**
* Package-private.
* <p/>
* Use {@link CFEntities} public API to create instance
*
*/
class CFServiceInstanceImpl implements CFServiceInstance {
private String service;
private String plan;
private String name;
private String documentationUrl;
private String description;
private String dashboardUrl;
public CFServiceInstanceImpl(String name, String service, String plan, String documentationUrl, String description,
String dashboardUrl) {
this.name = name;
this.service = service;
this.plan = plan;
this.documentationUrl = documentationUrl;
this.description = description;
this.dashboardUrl = dashboardUrl;
}
@Override
public String getService() {
return service;
}
@Override
public String getPlan() {
return plan;
}
@Override
public String getName() {
return name;
}
@Override
public String getDocumentationUrl() {
return documentationUrl;
}
@Override
public String getDescription() {
return description;
}
@Override
public String getDashboardUrl() {
return dashboardUrl;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dashboardUrl == null) ? 0 : dashboardUrl.hashCode());
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((documentationUrl == null) ? 0 : documentationUrl.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((plan == null) ? 0 : plan.hashCode());
result = prime * result + ((service == null) ? 0 : service.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;
CFServiceInstanceImpl other = (CFServiceInstanceImpl) obj;
if (dashboardUrl == null) {
if (other.dashboardUrl != null)
return false;
} else if (!dashboardUrl.equals(other.dashboardUrl))
return false;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (documentationUrl == null) {
if (other.documentationUrl != null)
return false;
} else if (!documentationUrl.equals(other.documentationUrl))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (plan == null) {
if (other.plan != null)
return false;
} else if (!plan.equals(other.plan))
return false;
if (service == null) {
if (other.service != null)
return false;
} else if (!service.equals(other.service))
return false;
return true;
}
}

View File

@@ -0,0 +1,22 @@
/*******************************************************************************
* Copyright (c) 2015, 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;
import java.util.List;
public interface ClientRequests {
List<CFBuildpack> getBuildpacks() throws Exception;
List<CFServiceInstance> getServices() throws Exception;
List<CFDomain> getDomains() throws Exception;
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* 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;
import java.time.Duration;
public class ClientTimeouts {
public static final ClientTimeouts DEFAULT_TIMEOUTS = new ClientTimeouts();
private static final Duration GET_SERVICES_TIMEOUT = Duration.ofSeconds(60);
public Duration getServicesTimeout() {
return GET_SERVICES_TIMEOUT;
}
public Duration getBuildpacksTimeout() {
return Duration.ofSeconds(10);
}
}

View File

@@ -0,0 +1,17 @@
/*******************************************************************************
* 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;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
public interface CloudFoundryClientFactory {
ClientRequests getClient(CFClientParams params, ClientTimeouts timeouts) throws Exception;
}

View File

@@ -0,0 +1,27 @@
/*******************************************************************************
* 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;
public enum LoginMethod {
PASSWORD,
TEMPORARY_CODE;
public String getLabel() {
String[] pieces = name().split("_");
StringBuilder label = new StringBuilder();
for (int i = 0; i < pieces.length; i++) {
if (i>0) {
label.append(" ");
}
label.append(pieces[i].toLowerCase());
}
return label.toString();
}
}

View File

@@ -0,0 +1,25 @@
/*******************************************************************************
* 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;
public class RequestErrorHandler {
/**
*
* @param e
* @return true if request error should be treated as an error and thrown. False error
* should be ignored.
*/
public boolean throwError(Throwable e) {
return true;
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* 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.cftarget;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
import org.cloudfoundry.uaa.UaaException;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import reactor.ipc.netty.channel.AbortedException;
/**
* This is a stateful callable context that is "aware" of CF errors, and is not
* suitable for reuse as it may cache errors
*
*/
public class CFCallableContext {
private final CFParamsProviderMessages paramsProviderMessages;
private Exception lastConnectionError;
public CFCallableContext(CFParamsProviderMessages paramsProviderMessages) {
this.paramsProviderMessages = paramsProviderMessages;
}
public <T> T checkConnection(Callable<T> callable) throws Exception {
this.lastConnectionError = null;
try {
return callable.call();
} catch (Exception e) {
throw convertToCfVscodeError(e);
}
}
protected Exception convertToCfVscodeError(Exception e) {
this.lastConnectionError = getConnectionError(e);
// return the "converted" error if it is available
if (this.lastConnectionError != null) {
return this.lastConnectionError;
}
return e;
}
protected Exception getConnectionError(Exception e) {
Throwable deepestCause = ExceptionUtil.getDeepestCause(e);
if (deepestCause instanceof UaaException || deepestCause instanceof AbortedException
|| deepestCause instanceof SocketException || deepestCause instanceof UnknownHostException) {
return new ConnectionException(this.paramsProviderMessages.noNetworkConnection());
}
return null;
}
public boolean hasConnectionError() {
return this.lastConnectionError != null;
}
}

View File

@@ -0,0 +1,164 @@
/*******************************************************************************
* 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.cftarget;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials.CFCredentialType;
import org.springframework.ide.vscode.commons.util.Assert;
/**
* All the parameters needed to create a CF client.
*
* @author Kris De Volder
*/
public class CFClientParams {
private static final Logger logger = Logger.getLogger(CFClientParams.class.getName());
private final String apiUrl;
private final String username;
private CFCredentials credentials;
private final boolean skipSslValidation;
private String orgName; // optional
private String spaceName; // optional
public CFClientParams(String apiUrl,
String username,
CFCredentials credentials,
String orgName,
String spaceName,
boolean skipSslValidation
) {
Assert.isNotNull(apiUrl);
Assert.isNotNull(credentials);
if (credentials.getType() == CFCredentialType.PASSWORD) {
Assert.isNotNull(username);
}
this.apiUrl = apiUrl;
this.username = username;
this.credentials = credentials;
this.skipSslValidation = skipSslValidation;
this.orgName = orgName;
this.spaceName = spaceName;
}
public CFClientParams(String apiUrl, String username, CFCredentials credentials, boolean skipSslValidation) {
this(apiUrl, username, credentials, null /* no org */,
null /* no space */, skipSslValidation);
}
public CFCredentials getCredentials() {
return credentials;
}
public String getUsername() {
return username;
}
public boolean skipSslValidation() {
return skipSslValidation;
}
public String getApiUrl() {
return apiUrl;
}
public String getOrgName() {
return orgName;
}
public void setOrgName(String orgName) {
this.orgName = orgName;
}
public String getSpaceName() {
return spaceName;
}
public void setSpaceName(String spaceName) {
this.spaceName = spaceName;
}
public String getHost() {
try {
URI uri = new URI(getApiUrl());
return uri.getHost();
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((apiUrl == null) ? 0 : apiUrl.hashCode());
result = prime * result + ((credentials == null) ? 0 : credentials.hashCode());
result = prime * result + ((orgName == null) ? 0 : orgName.hashCode());
result = prime * result + (skipSslValidation ? 1231 : 1237);
result = prime * result + ((spaceName == null) ? 0 : spaceName.hashCode());
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;
CFClientParams other = (CFClientParams) obj;
if (apiUrl == null) {
if (other.apiUrl != null)
return false;
} else if (!apiUrl.equals(other.apiUrl))
return false;
if (credentials == null) {
if (other.credentials != null)
return false;
} else if (!credentials.equals(other.credentials))
return false;
if (orgName == null) {
if (other.orgName != null)
return false;
} else if (!orgName.equals(other.orgName))
return false;
if (skipSslValidation != other.skipSslValidation)
return false;
if (spaceName == null) {
if (other.spaceName != null)
return false;
} else if (!spaceName.equals(other.spaceName))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
@Override
public String toString() {
return "CFClientParams [apiUrl=" + apiUrl + ", username=" + username + ", credentials=" + credentials
+ ", skipSslValidation=" + skipSslValidation + ", orgName=" + orgName + ", spaceName=" + spaceName
+ "]";
}
}

View File

@@ -0,0 +1,132 @@
/*******************************************************************************
* 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.cftarget;
import org.springframework.ide.vscode.commons.cloudfoundry.client.LoginMethod;
import org.springframework.ide.vscode.commons.util.Assert;
public class CFCredentials {
public enum CFCredentialType {
PASSWORD,
TEMPORARY_CODE,
REFRESH_TOKEN;
public LoginMethod toLoginMethod() {
switch (this) {
case PASSWORD:
return LoginMethod.PASSWORD;
case TEMPORARY_CODE:
return LoginMethod.TEMPORARY_CODE;
default:
return null;
}
}
}
private final CFCredentialType type;
private final String secret;
/**
* Deprecated, use fromLogin instead
*/
@Deprecated
public static CFCredentials fromPassword(String password) {
return fromLogin(LoginMethod.PASSWORD, password);
}
public static CFCredentials fromLogin(LoginMethod method, String secret) {
CFCredentialType type;
switch (method) {
case PASSWORD:
type = CFCredentialType.PASSWORD;
break;
case TEMPORARY_CODE:
type = CFCredentialType.TEMPORARY_CODE;
break;
default:
throw new IllegalStateException("Bug! Missing switch case?");
}
return new CFCredentials(type, secret);
}
public static CFCredentials fromRefreshToken(String refreshToken) {
Assert.isNotNull(refreshToken);
return new CFCredentials(CFCredentialType.REFRESH_TOKEN, refreshToken);
}
public String getSecret() {
return secret;
}
/////////////////////////////////////////////////////////////////////////
/**
* Private constuctor, use static `fromXXX` factory methods instead.
*/
private CFCredentials(CFCredentialType type, String secret) {
this.type = type;
this.secret = secret;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((secret == null) ? 0 : secret.hashCode());
result = prime * result + ((type == null) ? 0 : type.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;
CFCredentials other = (CFCredentials) obj;
if (secret == null) {
if (other.secret != null)
return false;
} else if (!secret.equals(other.secret))
return false;
if (type != other.type)
return false;
return true;
}
@Override
public String toString() {
return "CFCredentials [type=" + type + ", secret=" + hidePassword(type, secret) + "]";
}
private String hidePassword(CFCredentialType type, String password) {
if (password==null) {
return null;
}
return (type==CFCredentialType.PASSWORD || type==CFCredentialType.REFRESH_TOKEN)
? "****"
: password;
}
public CFCredentialType getType() {
return type;
}
public static CFCredentials fromSsoToken(String ssoToken) {
return CFCredentials.fromLogin(LoginMethod.TEMPORARY_CODE, ssoToken);
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* 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.cftarget;
/**
* Messages that are specific to a particular parameter provider
*
*/
public interface CFParamsProviderMessages {
String noTargetsFound();
String unauthorised();
String noNetworkConnection();
String noOrgSpace();
}

View File

@@ -0,0 +1,141 @@
/*******************************************************************************
* 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.cftarget;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/**
*
* Wrapper around a {@link ClientRequests} that may contain cached information
* like buildpacks
*
*/
public class CFTarget {
private final CFClientParams params;
private final ClientRequests requests;
private final String targetName;
/*
* Cached information
*/
private LoadingCache<String, List<CFBuildpack>> buildpacksCache;
private LoadingCache<String, List<CFServiceInstance>> servicesCache;
private LoadingCache<String, List<CFDomain>> domainCache;
private CFCallableContext callableContext;
public CFTarget(String targetName, CFClientParams params, ClientRequests requests,
CFCallableContext callableContext) {
this.params = params;
this.requests = requests;
this.targetName = targetName;
this.callableContext = callableContext;
initCache(requests);
}
private void initCache(ClientRequests requests) {
CacheLoader<String, List<CFServiceInstance>> servicesLoader = new CacheLoader<String, List<CFServiceInstance>>() {
@Override
public List<CFServiceInstance> load(String key) throws Exception {
/* Cache of services does not use keys, as the whole cache
* gets wiped clean on any new call to CF.
*/
return runAndCheckForFailure(() -> requests.getServices());
}
};
this.servicesCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.SERVICES_EXPIRATION, TimeUnit.SECONDS).build(servicesLoader);
CacheLoader<String, List<CFBuildpack>> buildpacksLoader = new CacheLoader<String, List<CFBuildpack>>() {
@Override
public List<CFBuildpack> load(String key) throws Exception {
/* Cache does not use keys, as the whole cache
* gets wiped clean on any new call to CF.
*/
return runAndCheckForFailure(() -> requests.getBuildpacks());
}
};
this.buildpacksCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.TARGET_EXPIRATION, TimeUnit.HOURS).build(buildpacksLoader);
CacheLoader<String, List<CFDomain>> domainLoader = new CacheLoader<String, List<CFDomain>>() {
@Override
public List<CFDomain> load(String key) throws Exception {
return runAndCheckForFailure(() -> requests.getDomains());
}
};
this.domainCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.TARGET_EXPIRATION, TimeUnit.HOURS).build(domainLoader);
}
protected <T> T runAndCheckForFailure(Callable<T> callable) throws Exception {
return callableContext.checkConnection(callable);
}
public boolean hasConnectionError() {
return callableContext.hasConnectionError();
}
public CFClientParams getParams() {
return params;
}
public List<CFBuildpack> getBuildpacks() throws Exception {
// Use the target name as the "key" , since Guava cache doesn't allow null keys
// However, the key is not really used when fetching buildpacks, as we are not caching
// buildpacks per target here. This class only represents ONE target, so it will only
// ever have one key
String key = getName();
return this.buildpacksCache.get(key);
}
public List<CFServiceInstance> getServices() throws Exception {
/* services don't use keys, as they get wiped clean on each refresh
* . That said, the cache doesn't allow a null key, so use the target name as the "key"
*
*/
String key = getName();
return this.servicesCache.get(key);
}
public List<CFDomain> getDomains() throws Exception {
String key = getName();
return this.domainCache.get(key);
}
public ClientRequests getClientRequests() {
return requests;
}
public String getName() {
return this.targetName;
}
@Override
public String toString() {
return "CFClientTarget [params=" + params + ", targetName=" + targetName + "]";
}
}

View File

@@ -0,0 +1,112 @@
/*******************************************************************************
* 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.cftarget;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
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 com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
public class CFTargetCache {
private final ClientParamsProvider paramsProvider;
private final CloudFoundryClientFactory clientFactory;
private final ClientTimeouts timeouts;
private final LoadingCache<ClientParamsCacheKey, CFTarget> cache;
private final CFCallableContext cacheCallableContext;
public static final long SERVICES_EXPIRATION = 10;
public static final long TARGET_EXPIRATION = 1;
public CFTargetCache(ClientParamsProvider paramsProvider, CloudFoundryClientFactory clientFactory,
ClientTimeouts timeouts) {
Assert.isLegal(paramsProvider != null,
"A Cloud Foundry client parameters provider must be set when creating a target cache.");
this.paramsProvider = paramsProvider;
this.clientFactory = clientFactory;
this.timeouts = timeouts;
this.cacheCallableContext = new CFCallableContext(paramsProvider.getMessages());
CacheLoader<ClientParamsCacheKey, CFTarget> loader = new CacheLoader<ClientParamsCacheKey, CFTarget>() {
@Override
public CFTarget load(ClientParamsCacheKey params) throws Exception {
return create(params.fullParams);
}
};
cache = CacheBuilder.newBuilder().maximumSize(1).expireAfterAccess(TARGET_EXPIRATION, TimeUnit.HOURS)
.build(loader);
}
/**
* @return non-null list of targets, or throws exception if no targets found
* @throws NoTargetsException
* if no targets found
* @throws Exception
* for any other error encountered
*/
public synchronized List<CFTarget> getOrCreate() throws NoTargetsException, Exception {
return cacheCallableContext.checkConnection(() -> doGetOrCreate());
}
protected synchronized List<CFTarget> doGetOrCreate() throws NoTargetsException, Exception {
List<CFClientParams> allParams = paramsProvider.getParams();
List<CFTarget> 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.hasConnectionError()) {
cache.refresh(key);
target = cache.get(key);
}
targets.add(target);
}
}
}
return targets;
}
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(paramsProvider.getMessages()));
}
protected static String getTargetName(CFClientParams params) {
return labelFromCfApi(params.getApiUrl());
}
protected static String labelFromCfApi(String cfApiUrl) {
if (cfApiUrl.startsWith("https://")) {
return cfApiUrl.substring("https://".length());
} else if (cfApiUrl.startsWith("http://")) {
return cfApiUrl.substring("http://".length());
} else {
return cfApiUrl;
}
}
}

View File

@@ -0,0 +1,116 @@
/*******************************************************************************
* 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.cftarget;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.springframework.ide.vscode.commons.util.StringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Provides existing Cloud Foundry client params, like target and credentials,
* from the CLI config.json in the file system.
*
*
*/
public class CfCliParamsProvider implements ClientParamsProvider {
public static final String TARGET = "Target";
public static final String REFRESH_TOKEN = "RefreshToken";
public static final String ORGANIZATION_FIELDS = "OrganizationFields";
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();
/*
* (non-Javadoc)
*
* @see org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.
* ClientParamsProvider#getParams()
*/
@Override
public List<CFClientParams> getParams() throws NoTargetsException, ExecutionException {
List<CFClientParams> params = new ArrayList<>();
try {
File file = getConfigJsonFile();
if (file != null) {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> userData = mapper.readValue(file, Map.class);
if (userData != null) {
String refreshToken = (String) userData.get(REFRESH_TOKEN);
// Only support connecting to CF via refresh token for now
if (isRefreshTokenSet(refreshToken)) {
CFCredentials credentials = CFCredentials.fromRefreshToken(refreshToken);
boolean sslDisabled = (Boolean) userData.get(SSL_DISABLED);
String target = (String) userData.get(TARGET);
Map<String, Object> orgFields = (Map<String, Object>) userData.get(ORGANIZATION_FIELDS);
Map<String, Object> spaceFields = (Map<String, Object>) userData.get(SPACE_FIELDS);
if (target != null && orgFields != null && spaceFields != null) {
String orgName = (String) orgFields.get(NAME);
String spaceName = (String) spaceFields.get(NAME);
if (!StringUtil.hasText(orgName) || !StringUtil.hasText(spaceName)) {
throw new NoTargetsException(getMessages().noOrgSpace());
}
params.add(new CFClientParams(target, null, credentials, orgName, spaceName, sslDisabled));
}
}
}
}
} catch (IOException | InterruptedException e) {
throw new ExecutionException(e);
}
if (params.isEmpty()) {
throw new NoTargetsException(getMessages().noTargetsFound());
} else {
return params;
}
}
private boolean isRefreshTokenSet(String token) {
return StringUtil.hasText(token);
}
private File getConfigJsonFile() throws IOException, InterruptedException {
String homeDir = getHomeDir();
if (homeDir != null) {
if (!homeDir.endsWith("/")) {
homeDir += '/';
}
String filePath = homeDir + ".cf/config.json";
File file = new File(filePath);
if (file.exists() && file.canRead()) {
return file;
}
}
return null;
}
private String getHomeDir() throws IOException, InterruptedException {
return System.getProperty("user.home");
}
@Override
public CFParamsProviderMessages getMessages() {
return cfCliProviderMessages;
}
}

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* 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.cftarget;
public final class CfCliProviderMessages implements CFParamsProviderMessages {
/*
* Important: be sure to use ':' to separate the initial part of the message
* with a longer portion. The vscode content assist will parse around the
* first ':' and the second segment will appear as a doc string that can be
* longer
*/
public static final String NO_CLI_TARGETS_FOUND_MESSAGE = "No Cloud Foundry targets found: Use cf CLI to login";
public static final String NO_NETWORK_CONNECTION = "No connection to Cloud Foundry: Use cf CLI to login or verify network connection";
public static final String NO_ORG_SPACE = "No org/space selected: Use CF CLI to login";
@Override
public String noTargetsFound() {
return NO_CLI_TARGETS_FOUND_MESSAGE;
}
@Override
public String unauthorised() {
return NO_NETWORK_CONNECTION;
}
@Override
public String noNetworkConnection() {
return NO_NETWORK_CONNECTION;
}
@Override
public String noOrgSpace() {
return NO_ORG_SPACE;
}
}

View File

@@ -0,0 +1,81 @@
/*******************************************************************************
* 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.cftarget;
/**
*
* A key for CF targets that uses {@link CFClientParams} values, EXCEPT for
* credentials, as the key value.
*
*/
public class ClientParamsCacheKey {
public final CFClientParams fullParams;
/**
* Use static API to create: {@link #from(CFClientParams)}
* @param fullParams
*/
private ClientParamsCacheKey(CFClientParams fullParams) {
this.fullParams = fullParams;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fullParams.getApiUrl() == null) ? 0 : fullParams.getApiUrl().hashCode());
result = prime * result + ((fullParams.getOrgName() == null) ? 0 : fullParams.getOrgName().hashCode());
result = prime * result + (fullParams.skipSslValidation() ? 1231 : 1237);
result = prime * result + ((fullParams.getSpaceName() == null) ? 0 : fullParams.getSpaceName().hashCode());
result = prime * result + ((fullParams.getUsername() == null) ? 0 : fullParams.getUsername().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;
ClientParamsCacheKey other = (ClientParamsCacheKey) obj;
if (fullParams.getApiUrl() == null) {
if (other.fullParams.getApiUrl() != null)
return false;
} else if (!fullParams.getApiUrl().equals(other.fullParams.getApiUrl()))
return false;
if (fullParams.getOrgName() == null) {
if (other.fullParams.getOrgName() != null)
return false;
} else if (!fullParams.getOrgName().equals(other.fullParams.getOrgName()))
return false;
if (fullParams.skipSslValidation() != other.fullParams.skipSslValidation())
return false;
if (fullParams.getSpaceName() == null) {
if (other.fullParams.getSpaceName() != null)
return false;
} else if (!fullParams.getSpaceName().equals(other.fullParams.getSpaceName()))
return false;
if (fullParams.getUsername() == null) {
if (other.fullParams.getUsername() != null)
return false;
} else if (!fullParams.getUsername().equals(other.fullParams.getUsername()))
return false;
return true;
}
public static ClientParamsCacheKey from(CFClientParams params) {
return new ClientParamsCacheKey(params);
}
}

View File

@@ -0,0 +1,27 @@
/*******************************************************************************
* 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.cftarget;
import java.util.List;
import java.util.concurrent.ExecutionException;
public interface ClientParamsProvider {
/**
*
* @return non-null list of VALID params to connect to Cloud Foundry
* @throws NoTargetsException if failure to resolve any params for Cloud Foundry
* @throws ExecutionException if failure occurs while resolving params
*/
List<CFClientParams> getParams() throws NoTargetsException, ExecutionException;
CFParamsProviderMessages getMessages();
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* 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.cftarget;
public class ConnectionException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
public ConnectionException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* 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.cftarget;
public class NoTargetsException extends Exception {
public NoTargetsException(String message) {
super(message);
}
/**
*
*/
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* 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 java.time.Duration;
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.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFCredentials;
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(CFClientParams params) {
long sslTimeout = Long.getLong("sts.bootdash.cf.client.ssl.handshake.timeout", 60); //TODO: make a preference for this?
Optional<Boolean> keepAlive = getBooleanSystemProp("http.keepAlive");
CloudFoundryClientCache.debug("cf client keepAlive = "+keepAlive);
connection = DefaultConnectionContext.builder()
.proxyConfiguration(Optional.ofNullable(getProxy(params.getHost())))
.apiHost(params.getHost())
.sslHandshakeTimeout(Duration.ofSeconds(sslTimeout))
.keepAlive(keepAlive)
.skipSslValidation(params.skipSslValidation())
.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(CFClientParams params) {
CFCredentials creds = params.getCredentials();
switch (creds.getType()) {
case PASSWORD:
return PasswordGrantTokenProvider.builder()
.username(params.getUsername())
.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<Boolean> getBooleanSystemProp(String name) {
String str = System.getProperty(name);
if (str!=null) {
return Optional.of(Boolean.valueOf(str));
}
return Optional.empty();
}
}

View File

@@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2016, 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.cloudfoundry.operations.buildpacks.Buildpack;
import org.cloudfoundry.operations.domains.Domain;
import org.cloudfoundry.operations.services.ServiceInstanceSummary;
import org.cloudfoundry.operations.services.ServiceInstanceType;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFEntities;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
/**
* Various helper methods to 'wrap' objects returned by CF client into our own
* types, so that we do not directly expose library types to our code.
*
* @author Kris De Volder
*/
public class CFWrappingV2 {
public static CFBuildpack wrap(Buildpack buildpack) {
String name = buildpack.getName();
return CFEntities.createBuildpack(name);
}
public static CFDomain wrap(Domain domain) {
String name = domain.getName();
return CFEntities.createDomain(name);
}
public static CFServiceInstance wrap(ServiceInstanceSummary serviceInstance) {
String name = serviceInstance.getName();
String plan = serviceInstance.getPlan();
String service = serviceInstance.getType() == ServiceInstanceType.USER_PROVIDED ? "user-provided"
: serviceInstance.getService();
return CFEntities.createServiceInstance(name, service, plan);
}
}

View File

@@ -0,0 +1,94 @@
/*******************************************************************************
* 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;
/**
* Manages a set of CancelationTokens.
*
* @author Kris De Volder
*/
public class CancelationTokens {
private String DEBUG = null;
//Note: we don't actually have to keep a set of tokens explicitly.
// The tokens use a 'id' which is incremented on each new token.
//So it is easy to cancel all existing tokens based on a their
//id simply by remembering the 'id' where the cancelation
//occurred. All ids 'older' than the current id are 'canceled'.
/**
* An uncancelable token that can be used by operations that don't
* need cancelation support.
*/
public static final CancelationToken NULL = new CancelationToken() {
@Override
public boolean isCanceled() {
return false;
}
};
private final Object SYNC = CancelationTokens.this;
private int canceledAllBefore = 0;
private int nextId = 0;
public CancelationTokens() {
}
public CancelationTokens(String debug) {
this.DEBUG = debug;
}
public interface CancelationToken {
boolean isCanceled();
}
public synchronized CancelationToken create() {
CancelationToken token = new ManagedToken();
debug("creating cancelation token: "+token);
return token;
}
private class ManagedToken implements CancelationToken {
private int id;
private ManagedToken() {
synchronized (SYNC) {
this.id = nextId++;
}
}
public boolean isCanceled() {
synchronized (SYNC) {
boolean isCanceled = id < canceledAllBefore;
debug("isCanceled? ["+id+"] => "+isCanceled);
return isCanceled;
}
}
@Override
public String toString() {
return "CancelToken("+id+")";
}
}
public synchronized void cancelAll() {
canceledAllBefore = nextId;
debug("CancelationTokens < "+canceledAllBefore+" are Canceled");
}
private void debug(String string) {
if (DEBUG!=null) {
System.out.println(DEBUG+": "+string);
}
}
}

View File

@@ -0,0 +1,77 @@
/*******************************************************************************
* 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.util.concurrent.TimeUnit;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsCacheKey;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/**
* 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.
* <p>
* So we have a permanent cache of clients here that is reused.
* <p>
* 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 {
private static final boolean DEBUG = false;
public static final long EXPIRATION = 1;
static void debug(String string) {
if (DEBUG) {
System.out.println(string);
}
}
private final LoadingCache<ClientParamsCacheKey, CFClientProvider> cache;
private int clientCount = 0;
public CloudFoundryClientCache() {
CacheLoader<ClientParamsCacheKey, CFClientProvider> loader = new CacheLoader<ClientParamsCacheKey, CFClientProvider>() {
@Override
public CFClientProvider load(ClientParamsCacheKey params) throws Exception {
clientCount++;
debug("Creating client [" + clientCount + "]: " + params);
return create(params.fullParams);
}
};
cache = CacheBuilder.newBuilder().maximumSize(1).expireAfterAccess(EXPIRATION, TimeUnit.HOURS)
.build(loader);
}
public synchronized CFClientProvider getOrCreate(CFClientParams params) throws Exception {
return create(params);
// Disable cache as corrupted clients may be kept due to connection or auth errors
// return cache.get(ClientParamsCacheKey.from(params));
}
protected CFClientProvider create(CFClientParams params) {
return new CFClientProvider(params);
}
}

View File

@@ -0,0 +1,156 @@
/*******************************************************************************
* Copyright (c) 2016, 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 java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import com.google.common.collect.ImmutableList;
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 Logger logger = Logger.getLogger(DefaultClientRequestsV2.class.getName());
private static final boolean DEBUG = false;
private CloudFoundryClient _client ;
private CloudFoundryOperations _operations;
private final ClientTimeouts timeouts;
public DefaultClientRequestsV2(CloudFoundryClientCache clients, CFClientParams params, ClientTimeouts timeouts) {
CFClientProvider provider = getFromCache(clients, params);
this._client = provider.client;
this._operations = DefaultCloudFoundryOperations.builder()
.cloudFoundryClient(_client)
.dopplerClient(provider.doppler)
.uaaClient(provider.uaaClient)
.organization(params.getOrgName())
.space(params.getSpaceName())
.build();
// timeouts must never be null
this.timeouts = timeouts != null ? timeouts : ClientTimeouts.DEFAULT_TIMEOUTS;
}
private CFClientProvider getFromCache(CloudFoundryClientCache clients, CFClientParams params) {
CFClientProvider provider = null;
try {
provider = clients.getOrCreate(params);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to create a v2 CF Java client using params: " + params, e);
}
return provider;
}
@Override
public List<CFServiceInstance> getServices() throws Exception {
return ReactorUtils.get(timeouts.getServicesTimeout(), CancelationTokens.NULL,
log("operations.services.listIntances",
_operations
.services()
.listInstances()
.map(CFWrappingV2::wrap)
.collectList()
.map(ImmutableList::copyOf)
)
);
}
@Override
public List<CFDomain> getDomains() throws Exception {
return ReactorUtils.get(timeouts.getBuildpacksTimeout(), CancelationTokens.NULL,
log("operations.domains.list",
_operations
.domains()
.list()
.map(CFWrappingV2::wrap)
.collectList()
.map(ImmutableList::copyOf)
)
);
}
@Override
public List<CFBuildpack> getBuildpacks() throws Exception {
return ReactorUtils.get(timeouts.getBuildpacksTimeout(), CancelationTokens.NULL,
log("operations.buildpacks.list",
_operations
.buildpacks()
.list()
.map(CFWrappingV2::wrap)
.collectList()
.map(ImmutableList::copyOf)
)
);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//// calls to client and operations with 'logging'.
private <T> Flux<T> log(String msg, Flux<T> flux) {
if (DEBUG) {
return flux
.doOnSubscribe((sub) -> debug(">>> "+msg))
.doOnComplete(() -> {
debug("<<< "+msg+" OK");
})
.doOnCancel(() -> {
debug("<<< "+msg+" CANCEL");
})
.doOnError((error) -> {
debug("<<< "+msg+" ERROR: "+ExceptionUtil.getMessage(error));
});
} else {
return flux;
}
}
private <T> Mono<T> log(String msg, Mono<T> mono) {
if (DEBUG) {
return mono
.doOnSubscribe((sub) -> debug(">>> "+msg))
.doOnCancel(() -> debug("<<< "+msg+" CANCEL"))
.doOnSuccess((data) -> {
debug("<<< "+msg+" OK");
})
.doOnError((error) -> {
debug("<<< "+msg+" ERROR: "+ExceptionUtil.getMessage(error));
});
} else {
return mono;
}
}
private void debug(String msg) {
logger.log(Level.INFO, msg);
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* 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 org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
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;
public class DefaultCloudFoundryClientFactoryV2 implements CloudFoundryClientFactory {
public static final CloudFoundryClientFactory INSTANCE = new DefaultCloudFoundryClientFactoryV2();
/**
* Use 'INSTANCE' constant instead. This class is a singleton.
*/
private DefaultCloudFoundryClientFactoryV2() {
}
private CloudFoundryClientCache cache = new CloudFoundryClientCache();
/*
* (non-Javadoc)
*
* @see org.springframework.ide.vscode.commons.cloudfoundry.client.v2.
* CloudFoundryClientFactory#getClient(org.springframework.ide.vscode.
* commons.cloudfoundry.client.cftarget.CFClientParams,
* org.springframework.ide.vscode.commons.cloudfoundry.client.v2.
* RequestTimeouts)
*/
@Override
public ClientRequests getClient(CFClientParams params, ClientTimeouts timeouts) throws Exception {
return new DefaultClientRequestsV2(cache, params, timeouts);
}
}

View File

@@ -0,0 +1,295 @@
/*******************************************************************************
* 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.io.IOException;
import java.time.Duration;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CancelationTokens.CancelationToken;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
/**
* @author Kris De Volder
*/
public class ReactorUtils {
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(45); // reflects default timeout of Mono.block in reactor 2.x.
public static boolean DUMP_STACK_ON_TIMEOUT = false;
// TODO: uncommented when cancellation is handled in vscode
// /**
// * Convert a {@link CancelationToken} into a Mono that raises
// * an {@link OperationCanceledException} when the token is canceled.
// */
// public static <T> Mono<T> toMono(CancelationToken cancelToken) {
// return Mono.delay(Duration.ofSeconds(1))
// .then((ping) ->
// cancelToken.isCanceled()
// ? Mono.<T>error(new OperationCanceledException())
// : Mono.empty()
// )
// .repeatWhenEmpty((x) -> x);
// }
/**
* Similar to Mono.get but logs a more traceable version of the exception to Eclipse's error
* log before 'rethrowing' it.
* <p>
* This is useful because the actual exception is pretty hard to trace. It doesn't even 'point'
* to the line where 'get' was called.
*/
public static <T> T get(Mono<T> mono) throws Exception {
try {
return mono.block(DEFAULT_TIMEOUT);
} catch (Exception e) {
dumpStacks();
throw new IOException(e);
}
}
/**
* Similar to Mono.get but logs a more traceable version of the exception to Eclipse's error
* log before 'rethrowing' it.
* <p>
* This is useful because the actual exception is pretty hard to trace. It doesn't even 'point'
* to the line where 'get' was called.
*/
public static <T> T get(Duration timeout, CancelationToken cancelationToken, Mono<T> mono) throws Exception {
try {
return mono
// TODO: uncomment when cancellation properly supported in vscode
// Mono
// .first(mono,
// toMono(cancelationToken))
// .otherwise(errorFilter(cancelationToken))
.block(timeout);
} catch (Exception e) {
dumpStacks();
throw new IOException(e);
}
}
public static <T> List<T> get(Duration t, Mono<List<T>> m) throws IOException {
try {
return m.block(t);
} catch (Exception e) {
dumpStacks();
throw new IOException(e);
}
}
private static void dumpStacks() {
if (DUMP_STACK_ON_TIMEOUT) {
System.out.println(getStackDumps().toString());
}
}
/**
* A 'filter' to use as a Mono.otherwise hanlder. It transforms any exception into {@link OperationCanceledException}
* when cancelationToken has been canceled.
*/
private static <T> Function<Throwable, Mono<T>> errorFilter(CancelationToken cancelationToken) {
// return (Throwable e) -> cancelationToken.isCanceled()?Mono.error(new OperationCanceledException()):Mono.error(e);
return (Throwable e) -> Mono.error(e);
}
/**
* Deprecated because this is really the same as Mono.justOrEmpty, so use that instead.
*/
@Deprecated
public static <T> Mono<T> just(T it) {
return it == null ? Mono.empty() : Mono.just(it);
}
/**
* @return A function that can be passed to Mono.otherwise to convert a specific exception type into
* Mono.empty().
*/
public static <T> Function<Throwable, Mono<T>> suppressException(Class<? extends Throwable> exceptionType) {
return (Throwable caught) -> {
if (exceptionType.isAssignableFrom(caught.getClass())) {
return Mono.empty();
} else {
return Mono.error(caught);
}
};
}
/**
* Build a Mono<Void> that executes a given number of Mono<Void> one after the
* other.
*/
@SafeVarargs
public static Mono<Void> sequence(Mono<Void>... tasks) {
Mono<Void> seq = Mono.empty();
for (Mono<Void> t : tasks) {
seq = seq.then(t);
}
return seq;
}
/**
* Execute a bunch of mono in parallel. All monos are executed to completion (rather than canceled early
* when one of them fails)
* <p>
* When at least one operation has failed then, upon completion or failure of the last Mono we guarantee that at least
* one of the exceptions is propagated.
*/
public static Mono<Void> safeMerge(Flux<Mono<Void>> operations, int concurrency) {
AtomicReference<Throwable> failure = new AtomicReference<>(null);
return Flux.merge(
operations
.map((Mono<Void> op) -> {
return op.otherwise((e) -> {
failure.compareAndSet(null, e);
return Mono.empty();
});
}),
concurrency //limit concurrency otherwise troubles (flooding/choking request broker?)
)
.then(() -> {
Throwable error = failure.get();
if (error!=null) {
return Mono.error(error);
} else {
return Mono.empty();
}
});
}
/**
* Attach a timestamp to each element in a Stream
*/
public static <T> Flux<Tuple2<T,Long>> timestamp(Flux<T> stream) {
return stream.map((e) -> Tuples.of(e, System.currentTimeMillis()));
}
/**
* Sorts the elements in a flux in a moving time window. I.e. this assumes element order may be
* scrambled but the scrambling has a certain 'time localilty' to it. So we only need to consider
* sorting of elements that arrive 'close to eachother'.
* <p>
* WARNING: The returned flux is intended for a single subscriber. It only maintains a
* single buffer for sorting stream elements. This buffer is consumed when elements
* are released to any subscriber. Therefore if one subscriber received a element it is gone
* from the buffer and will not be delivered to the other subscribers.
*
* @param stream The stream to be sorted
* @param comparator Compare function to sort with
* @param bufferTime The 'window' of time beyond which we don't need to compare elements.
*/
public static <T> Flux<T> sort(Flux<T> stream, Comparator<T> comparator, Duration bufferTime) {
class SorterAccumulator {
final PriorityQueue<Tuple2<T, Long>> holdingPen = new PriorityQueue<>((Tuple2<T, Long> o1, Tuple2<T, Long> o2) -> {
return comparator.compare(o1.getT1(), o2.getT1());
});
final Flux<T> released = Flux.fromIterable(() -> new Iterator<T>() {
@Override
public boolean hasNext() {
Tuple2<T, Long> nxt;
synchronized (holdingPen) {
nxt = holdingPen.peek();
}
return nxt!=null && isOldEnough(nxt);
}
private boolean isOldEnough(Tuple2<T, Long> nxt) {
long age = System.currentTimeMillis() - nxt.getT2();
return age > bufferTime.toMillis();
}
@Override
public T next() {
synchronized (holdingPen) {
return holdingPen.remove().getT1();
}
}
});
public SorterAccumulator next(Flux<Tuple2<T, Long>> window) {
window.subscribe((e) -> {
synchronized (holdingPen) {
holdingPen.add(e);
}
});
return this;
}
public Flux<T> getReleased() {
return released;
}
public Publisher<? extends T> drain() {
return Flux.fromIterable(holdingPen)
.map(Tuple2::getT1);
}
}
SorterAccumulator sorter = new SorterAccumulator();
return timestamp(stream)
.window(bufferTime)
.scan(sorter, SorterAccumulator::next)
.concatMap(SorterAccumulator::getReleased)
.concatWith(sorter.drain());
}
protected static StringBuffer getStackDumps() {
StringBuffer sb = new StringBuffer();
Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> entry : traces.entrySet()) {
sb.append(entry.getKey().toString());
sb.append("\n");
for (StackTraceElement element : entry.getValue()) {
sb.append(" ");
sb.append(element.toString());
sb.append("\n");
}
sb.append("\n");
}
return sb;
}
/**
* Connect a mono to a CompletableFuture so that the result of the mono
* can be retrieved from the {@link CompletableFuture} by calling it's 'get'
* method.
*/
public static <T> void completeWith(CompletableFuture<T> future, Mono<T> mono) {
mono.doOnNext((T v) -> {
future.complete(v);
})
.doOnError((Throwable e) -> {
future.completeExceptionally(e);
})
.subscribeOn(Schedulers.elastic())
.subscribe();
}
}

View File

@@ -0,0 +1,146 @@
/*******************************************************************************
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
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.ConnectionException;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import com.google.common.collect.ImmutableList;
public class CFClientTest {
MockCfCli cloudfoundry = new MockCfCli();
ClientTimeouts timeouts = new ClientTimeouts();
CFTargetCache targetCache;
CFParamsProviderMessages expectedMessages = new CfCliProviderMessages();
@Before
public void setup() throws Exception {
targetCache = new CFTargetCache(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());
}
@Test
public void testOneTarget() throws Exception {
CFTarget target = targetCache.getOrCreate().get(0);
assertNotNull(target);
assertEquals(MockCfCli.DEFAULT_PARAMS, target.getParams());
}
@Test
public void testUnknownHostServices() throws Exception {
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());
}
@Test
public void testUnknownHostBuildpacks() throws Exception {
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());
}
@Test
public void testUnknownHostDomains() throws Exception {
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());
}
@Test
public void testBuildpacksFromTarget() throws Exception {
ClientRequests client = cloudfoundry.client;
CFBuildpack buildpack = Mockito.mock(CFBuildpack.class);
when(buildpack.getName()).thenReturn("java_buildpack");
when(client.getBuildpacks()).thenReturn(ImmutableList.of(buildpack));
CFTarget target = targetCache.getOrCreate().get(0);
assertEquals("java_buildpack", target.getBuildpacks().get(0).getName());
}
@Test
public void testDomainsFromTarget() throws Exception {
ClientRequests client = cloudfoundry.client;
CFDomain domain = Mockito.mock(CFDomain.class);
when(domain.getName()).thenReturn("cfapps.io");
when(client.getDomains()).thenReturn(ImmutableList.of(domain));
CFTarget target = targetCache.getOrCreate().get(0);
assertEquals("cfapps.io", target.getDomains().get(0).getName());
}
@Test
public void testServicesFromTarget() throws Exception {
ClientRequests client = cloudfoundry.client;
CFServiceInstance service = Mockito.mock(CFServiceInstance.class);
when(service.getName()).thenReturn("appdb");
when(service.getPlan()).thenReturn("spark");
when(service.getService()).thenReturn("cleardb");
when(client.getServices()).thenReturn(ImmutableList.of(service));
CFTarget target = targetCache.getOrCreate().get(0);
assertEquals("appdb", target.getServices().get(0).getName());
assertEquals("spark", target.getServices().get(0).getPlan());
assertEquals("cleardb", target.getServices().get(0).getService());
}
@Test
public void testNoServicesFromTarget() throws Exception {
ClientRequests client = cloudfoundry.client;
when(client.getServices()).thenReturn(ImmutableList.of());
CFTarget target = targetCache.getOrCreate().get(0);
assertTrue(target.getServices().isEmpty());
}
protected void assertError(Callable<?> callable, Class<? extends Throwable> expected, String expectedMessage)
throws Exception {
Throwable error = null;
try {
callable.call();
} catch (Exception e) {
error = ExceptionUtil.getDeepestCause(e);
}
assertEquals(expected, error.getClass());
assertTrue(ExceptionUtil.getMessageNoAppendedInformation(error).contains(expectedMessage));
}
}

View File

@@ -0,0 +1,61 @@
/*******************************************************************************
* 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;
import static org.mockito.Matchers.any;
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.ClientParamsProvider;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import com.google.common.collect.ImmutableList;
public class MockCfCli {
public final static CFClientParams DEFAULT_PARAMS = new CFClientParams("test.io", "testuser",
CFCredentials.fromRefreshToken("refreshtoken"), false);
public final CloudFoundryClientFactory factory = mock(CloudFoundryClientFactory.class);
public final ClientRequests client = mock(ClientRequests.class);
public final ClientParamsProvider paramsProvider = mock(ClientParamsProvider.class);
public final CfCliProviderMessages actualCfCliMessages = new CfCliProviderMessages();
public MockCfCli() {
try {
//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);
} catch (Exception e) {
throw ExceptionUtil.unchecked(e);
}
}
/**
* Reset the mocks. Use this if the default's programmed into the mocks don't suite your test case.
* <p>
* Note: you may also choose to call {@link Mockito}.mock directly if you do not want to
* reset all of the mocks.
*/
public void reset() throws Exception {
Mockito.reset(factory, client, paramsProvider);
}
}