package org.springframework.roo.addon.cloud.foundry;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.json.simple.JSONObject;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.uaa.client.TransmissionEventListener;
import org.springframework.uaa.client.UaaService;
import org.springframework.uaa.client.VersionHelper;
import org.springframework.uaa.client.protobuf.UaaClient.FeatureUse;
import org.springframework.uaa.client.protobuf.UaaClient.Product;
import org.springframework.uaa.client.util.HexUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import com.vmware.appcloud.client.AppCloudClient;
import com.vmware.appcloud.client.AppCloudException;
import com.vmware.appcloud.client.ApplicationStats;
import com.vmware.appcloud.client.CloudApplication;
import com.vmware.appcloud.client.CloudInfo;
import com.vmware.appcloud.client.CloudService;
import com.vmware.appcloud.client.CrashesInfo;
import com.vmware.appcloud.client.InstancesInfo;
import com.vmware.appcloud.client.ServiceConfiguration;
import com.vmware.appcloud.client.UploadStatusCallback;
public class UaaAwareAppCloudClient extends AppCloudClient implements
TransmissionEventListener {
public static final String CLOUD_FOUNDRY_URL = "http://api.cloudfoundry.com";
private static final int CLOUD_MAJOR_VERSION = 0;
private static final int CLOUD_MINOR_VERSION = 0;
private static final int CLOUD_PATCH_VERSION = 0;
private static final Product DEFAULT_PRODUCT = VersionHelper.getProduct(
"Cloud Foundry Java API", "0.0.0.RELEASE");
private static final int HTTP_SUCCESS_CODE = 200;
private final Set<String> discoveredAppNames;
// key = method name; value = sorted map of HTTP response code keys to count
// of that response code
private final Map<String, SortedMap<Integer, Integer>> methodToResponses = new HashMap<String, SortedMap<Integer, Integer>>();
private final Product product;
private final UaaService uaaService;
/**
* Constructor; consider using
* {@link AppCloudClientFactory#getUaaAwareInstance(CloudCredentials)}
* instead.
*
* @param product can be <code>null</code> to use the default
* {@link Product}
* @param uaaService the UAA service (required)
* @param credentials the cloud login credentials (required)
* @param requestFactory
*/
public UaaAwareAppCloudClient(final Product product,
final UaaService uaaService, final CloudCredentials credentials,
final ClientHttpRequestFactory requestFactory) {
super(credentials.getEmail(), credentials.getPassword(), null,
credentials.getUrlObject(), requestFactory);
Validate.notNull(uaaService, "UAA Service required");
discoveredAppNames = new HashSet<String>();
this.product = ObjectUtils.defaultIfNull(product, DEFAULT_PRODUCT);
this.uaaService = uaaService;
}
public void afterTransmission(final TransmissionType type,
final boolean successful) {
if (type == TransmissionType.UPLOAD && successful) {
discoveredAppNames.clear();
methodToResponses.clear();
}
}
public void beforeTransmission(final TransmissionType type) {
if (type == TransmissionType.UPLOAD) {
flushToUaa();
}
}
@Override
public void bindService(final String appName, final String serviceName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.bindService(appName, serviceName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("bindService", resultCode, appName);
}
}
@Override
public void createAndUploadAndStartApplication(final String appName,
final String framework, final int memory, final File warFile,
final List<String> uris, final List<String> serviceNames)
throws IOException {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.createAndUploadAndStartApplication(appName, framework,
memory, warFile, uris, serviceNames);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("createAndUploadAndStartApplication", resultCode,
appName);
}
}
@Override
public void createApplication(final String appName, final String framework,
final int memory, final List<String> uris,
final List<String> serviceNames) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.createApplication(appName, framework, memory, uris,
serviceNames);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("createApplication", resultCode, appName);
}
}
@Override
public void createApplication(final String appName, final String framework,
final int memory, final List<String> uris,
final List<String> serviceNames, final boolean checkExists) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.createApplication(appName, framework, memory, uris,
serviceNames, checkExists);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.getMessage(), he);
}
finally {
recordHttpResult("createApplication", resultCode, appName);
}
}
@Override
public void createService(final CloudService service) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.createService(service);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("createService", resultCode);
}
}
public void deactivate() {
flushToUaa();
}
@Override
public void deleteAllApplications() {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.deleteAllApplications();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("deleteAllApplications", resultCode);
}
}
@Override
public void deleteAllServices() {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.deleteAllServices();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("deleteAllServices", resultCode);
}
}
@Override
public void deleteApplication(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.deleteApplication(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("deleteApplication", resultCode, appName);
}
}
@Override
public void deleteService(final String service) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.deleteService(service);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("deleteService", resultCode);
}
}
private void flushToUaa() {
// Store the app names being used
for (final String appName : discoveredAppNames) {
uaaService.registerProductUsage(product, appName);
}
// Store the cloud controller URL being used
String ccType = "Cloud Controller: Custom";
final String cloudHost = getCloudControllerUrl().getHost();
if (CLOUD_FOUNDRY_URL.equals(getCloudControllerUrl().toExternalForm())) {
ccType = "Cloud Controller: Public Cloud";
}
else if (cloudHost.equals("localhost")) {
ccType = "Cloud Controller: Localhost";
}
else if (cloudHost.equals("127.0.0.1")) {
ccType = "Cloud Controller: Localhost";
}
// Store the cloud controller hostname SHA 256
final String ccUrlHashed = sha256(cloudHost);
// Create a feature use record for the cloud controller
final Map<String, Object> ccJson = new HashMap<String, Object>();
ccJson.put("type", "cc_info");
ccJson.put("cc_hostname_sha256", JSONObject.escape(ccUrlHashed));
registerFeatureUse(ccType, ccJson);
// Crate feature uses for each method name
for (final String methodName : methodToResponses.keySet()) {
final SortedMap<Integer, Integer> resultCounts = methodToResponses
.get(methodName);
final Map<String, Object> methodCallInfo = new HashMap<String, Object>();
methodCallInfo.put("type", "method_call_info");
methodCallInfo.put("cc_hostname_sha256",
JSONObject.escape(ccUrlHashed));
methodCallInfo.put("http_results_to_counts", resultCounts);
registerFeatureUse(methodName, methodCallInfo);
}
}
@Override
public CloudApplication getApplication(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getApplication(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getApplication", resultCode, appName);
}
}
@Override
public InstancesInfo getApplicationInstances(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getApplicationInstances(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getApplicationInstances", resultCode, appName);
}
}
@Override
public int[] getApplicationMemoryChoices() {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getApplicationMemoryChoices();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getApplicationMemoryChoices", resultCode);
}
}
@Override
public List<CloudApplication> getApplications() {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getApplications();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getApplications", resultCode);
}
}
@Override
public ApplicationStats getApplicationStats(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getApplicationStats(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getApplicationStats", resultCode, appName);
}
}
@Override
public URL getCloudControllerUrl() {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getCloudControllerUrl();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getCloudControllerUrl", resultCode);
}
}
@Override
public CloudInfo getCloudInfo() {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getCloudInfo();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getCloudInfo", resultCode);
}
}
@Override
public CrashesInfo getCrashes(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getCrashes(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getCrashes", resultCode, appName);
}
}
@Override
public int getDefaultApplicationMemory(final String framework) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getDefaultApplicationMemory(framework);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getDefaultApplicationMemory", resultCode);
}
}
@Override
public String getFile(final String appName, final int instanceIndex,
final String filePath) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getFile(appName, instanceIndex, filePath);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getFile", resultCode, appName);
}
}
@Override
public <T> T getFile(final String appName, final int instanceIndex,
final String filePath, final RequestCallback requestCallback,
final ResponseExtractor<T> responseHandler) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getFile(appName, instanceIndex, filePath,
requestCallback, responseHandler);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getFile", resultCode, appName);
}
}
@Override
public CloudService getService(final String service) {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getService(service);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getService", resultCode);
}
}
@Override
public List<ServiceConfiguration> getServiceConfigurations() {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getServiceConfigurations();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getServiceConfigurations", resultCode);
}
}
@Override
public List<CloudService> getServices() {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.getServices();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("getServices", resultCode);
}
}
@Override
public String login() {
int resultCode = HTTP_SUCCESS_CODE;
try {
return super.login();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("login", resultCode);
}
}
@Override
public String loginIfNeeded() {
int resultCode = 200;
try {
return super.loginIfNeeded();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("loginIfNeeded", resultCode);
}
}
private void recordHttpResult(final String methodName, final int resultCode) {
recordHttpResult(methodName, resultCode, null);
}
private void recordHttpResult(final String methodName,
final int resultCode, final String appName) {
if (appName != null) {
discoveredAppNames.add(appName);
}
SortedMap<Integer, Integer> results = methodToResponses.get(methodName);
if (results == null) {
results = new TreeMap<Integer, Integer>();
methodToResponses.put(methodName, results);
}
Integer countSoFar = results.get(resultCode);
if (countSoFar == null) {
countSoFar = 0;
}
results.put(resultCode, countSoFar + 1);
}
@Override
public void register(final String email, final String password) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.register(email, password);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("register", resultCode);
}
}
private void registerFeatureUse(final String featureName,
final Map<String, Object> jsonPayload) {
jsonPayload.put("version",
product.getMajorVersion() + "." + product.getMinorVersion()
+ "." + product.getPatchVersion());
final String jsonAsString = JSONObject.toJSONString(jsonPayload);
final FeatureUse featureToRegister = FeatureUse.newBuilder()
.setName(featureName)
.setDateLastUsed(System.currentTimeMillis())
.setMajorVersion(CLOUD_MAJOR_VERSION)
.setMinorVersion(CLOUD_MINOR_VERSION)
.setPatchVersion(CLOUD_PATCH_VERSION).build();
try {
uaaService.registerFeatureUsage(product, featureToRegister,
jsonAsString.getBytes("UTF-8"));
}
catch (final UnsupportedEncodingException ignore) {
}
}
@Override
public void rename(final String appName, final String newName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.rename(appName, newName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("rename", resultCode, appName);
}
}
@Override
public void restartApplication(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.restartApplication(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("restartApplication", resultCode, appName);
}
}
private String sha256(final String input) {
try {
final MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
final byte[] digest = sha1.digest(input.getBytes("UTF-8"));
return HexUtils.toHex(digest);
}
catch (final NoSuchAlgorithmException e) {
// This can't happen as we know that there is an SHA-256 algorithm
}
catch (final UnsupportedEncodingException e) {
// This can't happen as we know that there is an UTF-8 encoding
}
return null;
}
@Override
public void startApplication(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.startApplication(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("startApplication", resultCode, appName);
}
}
@Override
public void stopApplication(final String appName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.stopApplication(appName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("stopApplication", resultCode, appName);
}
}
@Override
public void unbindService(final String appName, final String serviceName) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.unbindService(appName, serviceName);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("unbindService", resultCode, appName);
}
}
@Override
public void unregister() {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.unregister();
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("unregister", resultCode);
}
}
@Override
public void updateApplicationInstances(final String appName,
final int instances) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.updateApplicationInstances(appName, instances);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("updateApplicationInstances", resultCode, appName);
}
}
@Override
public void updateApplicationMemory(final String appName, final int memory) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.updateApplicationMemory(appName, memory);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("updateApplicationMemory", resultCode, appName);
}
}
@Override
public void updateApplicationServices(final String appName,
final List<String> services) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.updateApplicationServices(appName, services);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("updateApplicationServices", resultCode, appName);
}
}
@Override
public void updateApplicationUris(final String appName,
final List<String> uris) {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.updateApplicationUris(appName, uris);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("updateApplicationUris", resultCode, appName);
}
}
@Override
public void uploadApplication(final String appName, final File warFile)
throws IOException {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.uploadApplication(appName, warFile);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("uploadApplication", resultCode, appName);
}
}
@Override
public void uploadApplication(final String appName, final File warFile,
final UploadStatusCallback callback) throws IOException {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.uploadApplication(appName, warFile, callback);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("uploadApplication", resultCode, appName);
}
}
@Override
public void uploadApplication(final String appName, final String warFilePath)
throws IOException {
int resultCode = HTTP_SUCCESS_CODE;
try {
super.uploadApplication(appName, warFilePath);
}
catch (final AppCloudException he) {
resultCode = he.getStatusCode().value();
throw new IllegalStateException(
"Operation could not be completed: " + he.toString(), he);
}
finally {
recordHttpResult("uploadApplication", resultCode, appName);
}
}
}