package nl.codecentric.jenkins.appd.rest;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.client.apache.ApacheHttpClient;
import com.sun.jersey.client.apache.config.DefaultApacheHttpClientConfig;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import nl.codecentric.jenkins.appd.rest.types.MetricData;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class providing only the connection to the AppDynamics REST interface.
* Checks all connection parameters and maintains the connection to the REST
* interface.
*/
public class RestConnection {
private static final String REST_SEGMENT_METRIC_DATA = "metric-data";
private static final String REST_PARAM_METRIC_PATH = "metric-path";
private static final String REST_PARAM_TIME_RANGE_TYPE = "time-range-type";
private static final String REST_PARAM_START_TIME = "start-time";
private static final String REST_PARAM_DURATION_IN_MINS = "duration-in-mins";
private static final String REST_PARAM_ROLLUP = "rollup";
private static final String REST_PARAM_OUTPUT = "output";
private static final String PARAM_TIME_RANGE_TYPE_AFTER_TIME = "AFTER_TIME";
private static final String PARAM_TIME_RANGE_TYPE_BEFORE_NOW = "BEFORE_NOW";
private static final String PARAM_DEFAULT_ROLLUP = "false";
private static final String PARAM_DEFAULT_OUTPUT = "JSON";
private static final Logger LOG = Logger.getLogger(RestConnection.class.getName());
private final ObjectMapper jsonMapper = new ObjectMapper();
private final WebResource restResource;
public RestConnection(final String restUri, final String username, final String password,
final String applicationName) {
final String parsedUsername = parseUsername(username);
final String parsedRestUri = parseRestUri(restUri);
final String parsedApplicationName = parseApplicationName(applicationName);
DefaultApacheHttpClientConfig config = new DefaultApacheHttpClientConfig();
config.getState().setCredentials(null, null, -1, parsedUsername, password);
config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
jsonMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Client restClient = ApacheHttpClient.create(config);
restClient.setFollowRedirects(true);
restResource = restClient.resource(parsedRestUri + parsedApplicationName);
}
public boolean validateConnection() {
boolean validationResult = false;
try {
ClientResponse response = restResource.path("business-transactions/").
queryParam("output", "JSON").
accept(MediaType.APPLICATION_JSON_TYPE).
get(ClientResponse.class);
if (response.getStatus() == 200) {
String output = response.getEntity(String.class);
LOG.fine(String.format("Response from AppDynamics server ==> code: %s | output: %s",
response.getStatus(), output));
validationResult = true;
}
} catch (Exception e) {
LOG.log(Level.INFO, "Some problem connecting to the AppDynamics REST interface, see stack-trace for " +
"more information", e);
}
return validationResult;
}
public MetricData fetchMetricData(final String metricPath, int durationInMinutes) {
return fetchMetricData(metricPath, durationInMinutes, -1);
}
public MetricData fetchMetricData(final String metricPath, int durationInMinutes, long buildStartTime) {
String encodedMetricPath = encodeRestSegment(metricPath);
MultivaluedMap<String, String> paramMap = new MultivaluedMapImpl();
paramMap.add(REST_PARAM_METRIC_PATH, encodedMetricPath);
if (buildStartTime > 0) {
paramMap.add(REST_PARAM_TIME_RANGE_TYPE, PARAM_TIME_RANGE_TYPE_AFTER_TIME);
paramMap.add(REST_PARAM_START_TIME, Long.toString(buildStartTime));
} else {
paramMap.add(REST_PARAM_TIME_RANGE_TYPE, PARAM_TIME_RANGE_TYPE_BEFORE_NOW);
}
paramMap.add(REST_PARAM_DURATION_IN_MINS, Integer.toString(durationInMinutes));
paramMap.add(REST_PARAM_ROLLUP, PARAM_DEFAULT_ROLLUP);
paramMap.add(REST_PARAM_OUTPUT, PARAM_DEFAULT_OUTPUT);
MetricData resultData = null;
try {
ClientResponse response = restResource.path(REST_SEGMENT_METRIC_DATA).
queryParams(paramMap).
accept(MediaType.APPLICATION_JSON_TYPE).
get(ClientResponse.class);
if (response.getStatus() == 200) {
String jsonOutput = response.getEntity(String.class);
LOG.fine(String.format("Response from AppDynamics server ==> code: %s | output: %s",
response.getStatus(), jsonOutput));
List<MetricData> metricList = jsonMapper.readValue(jsonOutput, new TypeReference<List<MetricData>>() {});
resultData = metricList.get(0); // Always expect only single 'MetricData' value
LOG.fine("Successfully fetched metrics for path: " + resultData.getMetricPath());
}
} catch (Exception e) {
LOG.log(Level.INFO, "Some problem fetching metrics from the AppDynamics REST interface, " +
"see stack-trace for more information", e);
}
return resultData;
}
public static boolean validateRestUri(final String restUri) {
if (isFieldEmpty(restUri)) {
return false;
}
if (restUri.startsWith("http://") || restUri.startsWith("https://")) {
return true;
} else {
return false;
}
}
public static boolean validateApplicationName(final String applicationName) {
return !isFieldEmpty(applicationName);
}
public static boolean validateUsername(final String username) {
return !isFieldEmpty(username);
}
public static boolean validatePassword(final String password) {
return !isFieldEmpty(password);
}
private String parseUsername(final String username) {
String parsedUsername = username;
if (!username.contains("@")) {
parsedUsername += "@customer1";
}
LOG.fine("Parsed username: " + parsedUsername);
return parsedUsername;
}
private String parseRestUri(final String restUri) {
StringBuilder parsedRestUri = new StringBuilder(parseRestSegment(restUri));
String[] uriOrderedSegments = {"controller", "rest", "applications"};
for (String segment : uriOrderedSegments) {
if (!restUri.contains(segment)) {
parsedRestUri.append(segment + "/");
}
}
LOG.fine("Parsed REST uri: " + parsedRestUri.toString());
return parsedRestUri.toString();
}
private String parseApplicationName(final String applicationName) {
String parsedApplicationName = encodeRestSegment(applicationName);
LOG.fine("Parsed Application Name: " + parsedApplicationName);
return parseRestSegment(parsedApplicationName);
}
private String encodeRestSegment(final String restSegment) {
String encodedSegment;
try {
encodedSegment = URLEncoder.encode(restSegment, "UTF-8");
// AppDynamics interface expects '%20' for spaces instead of '+'
encodedSegment = encodedSegment.replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
encodedSegment = restSegment;
}
return encodedSegment;
}
private String parseRestSegment(final String restSegment) {
String parsedSegment = restSegment;
if (!restSegment.endsWith("/")) {
parsedSegment += "/";
}
return parsedSegment;
}
private static boolean isFieldEmpty(final String field) {
if (field == null || field.isEmpty()) {
return true;
}
final String trimmedField = field.trim();
if (trimmedField.length() == 0) {
return true;
}
return false;
}
}