/*******************************************************************************
* Copyright (c) 2012-2015 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.analytics.impl;
import org.eclipse.che.api.analytics.AnalyticsService;
import org.eclipse.che.api.analytics.Constants;
import org.eclipse.che.api.analytics.MetricHandler;
import org.eclipse.che.api.analytics.shared.dto.MetricInfoDTO;
import org.eclipse.che.api.analytics.shared.dto.MetricInfoListDTO;
import org.eclipse.che.api.analytics.shared.dto.MetricValueDTO;
import org.eclipse.che.api.analytics.shared.dto.MetricValueListDTO;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.user.User;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.dto.server.JsonArrayImpl;
import org.eclipse.che.dto.server.JsonStringMapImpl;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
/**
* Implementation provides means to perform remote REST requests to receive analytics data from remote rest service.
*
* @author Dmitry Kuleshov
* @author Anatoliy Bazko
*/
public class RemoteMetricHandler implements MetricHandler {
private static final String PROXY_URL = "analytics.api.proxy_url";
private String proxyUrl;
public RemoteMetricHandler(Properties properties) {
this.proxyUrl = properties.getProperty(PROXY_URL);
if (this.proxyUrl == null) {
throw new IllegalArgumentException("Not defined mandatory property " + PROXY_URL);
}
}
@Override
@SuppressWarnings("unchecked")
public MetricValueDTO getValue(String metricName,
Map<String, String> executionContext,
UriInfo uriInfo) {
String proxyUrl = getProxyURL("getValue", metricName);
try {
List<Pair<String, String>> pairs = mapToParisList(executionContext);
return request(MetricValueDTO.class,
proxyUrl,
"GET",
null,
pairs.toArray(new Pair[pairs.size()]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public MetricValueListDTO getListValues(String metricName,
List<Map<String, String>> parameters,
Map<String, String> executionContext,
UriInfo uriInfo) throws Exception {
String proxyUrl = getProxyURL("getListValues", metricName);
try {
for (int i = 0; i < parameters.size(); i++) {
parameters.set(i, new JsonStringMapImpl<>(parameters.get(i)));
}
List<Pair<String, String>> pairs = mapToParisList(executionContext);
return request(MetricValueListDTO.class,
proxyUrl,
"POST",
new JsonArrayImpl<>(parameters),
pairs.toArray(new Pair[pairs.size()]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public MetricValueDTO getValueByJson(String metricName,
Map<String, String> parameters,
Map<String, String> executionContext,
UriInfo uriInfo) throws Exception {
String proxyUrl = getProxyURL("getValueByJson", metricName);
try {
List<Pair<String, String>> pairs = mapToParisList(executionContext);
return request(MetricValueDTO.class,
proxyUrl,
"POST",
parameters,
pairs.toArray(new Pair[pairs.size()]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
@SuppressWarnings("unchecked")
public MetricValueDTO getPublicValue(String metricName,
Map<String, String> executionContext,
UriInfo uriInfo) {
String proxyUrl = getProxyURL("getPublicValue", metricName);
try {
List<Pair<String, String>> pairs = mapToParisList(executionContext);
return request(MetricValueDTO.class,
proxyUrl,
"GET",
null,
pairs.toArray(new Pair[pairs.size()]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
@SuppressWarnings("unchecked")
public MetricValueListDTO getUserValues(List<String> metricNames,
Map<String, String> executionContext,
UriInfo uriInfo) {
String proxyUrl = getProxyURL("getUserValues", "");
try {
List<Pair<String, String>> pairs = mapToParisList(executionContext);
return request(MetricValueListDTO.class,
proxyUrl,
"POST",
metricNames,
pairs.toArray(new Pair[pairs.size()]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
@SuppressWarnings("unchecked")
public MetricInfoDTO getInfo(String metricName, UriInfo uriInfo) {
String proxyUrl = getProxyURL("getInfo", metricName);
try {
List<Pair<String, String>> pairs = mapToParisList(Collections.<String, String>emptyMap());
MetricInfoDTO metricInfoDTO = request(MetricInfoDTO.class,
proxyUrl,
"GET",
null,
pairs.toArray(new Pair[pairs.size()]));
updateLinks(uriInfo, metricInfoDTO);
return metricInfoDTO;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public MetricInfoListDTO getAllInfo(UriInfo uriInfo) {
String proxyUrl = getProxyURL("getAllInfo", "");
try {
List<Pair<String, String>> pairs = mapToParisList(Collections.<String, String>emptyMap());
@SuppressWarnings("unchecked")
MetricInfoListDTO metricInfoListDTO = request(MetricInfoListDTO.class,
proxyUrl,
"GET",
null,
pairs.toArray(new Pair[pairs.size()]));
updateLinks(uriInfo, metricInfoListDTO);
return metricInfoListDTO;
} catch (Exception e) {
throw new RuntimeException(
"We have received an error code from the server. For some reason, " +
"we are unable to generate the list of metrics.");
}
}
private void updateLinks(UriInfo uriInfo, MetricInfoDTO metricInfoDTO) {
metricInfoDTO.setLinks(getLinks(metricInfoDTO.getName(), uriInfo));
}
private void updateLinks(UriInfo uriInfo, MetricInfoListDTO metricInfoListDTO) {
for (MetricInfoDTO metricInfoDTO : metricInfoListDTO.getMetrics()) {
updateLinks(uriInfo, metricInfoDTO);
}
}
private List<Pair<String, String>> mapToParisList(Map<String, String> executionContext) {
List<Pair<String, String>> pairs = new ArrayList<>();
for (Map.Entry<String, String> entry : executionContext.entrySet()) {
pairs.add(new Pair<>(entry.getKey(), entry.getValue()));
}
putAuthenticationToken(pairs);
return pairs;
}
private void putAuthenticationToken(List<Pair<String, String>> pairs) {
User user = EnvironmentContext.getCurrent().getUser();
if (user != null) {
String authToken = user.getToken();
if (authToken != null) {
pairs.add(new Pair<>("token", authToken));
}
}
}
private String getProxyURL(String methodName, String metricName) {
String path = getMethod(methodName).getAnnotation(Path.class).value();
return proxyUrl + path.replace("{name}", metricName);
}
private <DTO> DTO request(Class<DTO> dtoInterface,
String proxyUrl,
String method,
Object body,
Pair<String, ?>... parameters) throws IOException {
if (parameters != null && parameters.length > 0) {
final StringBuilder sb = new StringBuilder();
sb.append(proxyUrl);
sb.append('?');
for (int i = 0, l = parameters.length; i < l; i++) {
String name = URLEncoder.encode(parameters[i].first, "UTF-8");
String value = parameters[i].second == null ? null : URLEncoder
.encode(String.valueOf(parameters[i].second), "UTF-8");
if (i > 0) {
sb.append('&');
}
sb.append(name);
if (value != null) {
sb.append('=');
sb.append(value);
}
}
proxyUrl = sb.toString();
}
final HttpURLConnection conn = (HttpURLConnection)new URL(proxyUrl).openConnection();
conn.setConnectTimeout(30 * 1000);
try {
conn.setRequestMethod(method);
if (body != null) {
conn.addRequestProperty("content-type", "application/json");
conn.setDoOutput(true);
try (OutputStream output = conn.getOutputStream()) {
output.write(DtoFactory.getInstance().toJson(body).getBytes());
}
}
final int responseCode = conn.getResponseCode();
if ((responseCode / 100) != 2) {
InputStream in = conn.getErrorStream();
if (in == null) {
in = conn.getInputStream();
}
throw new IOException(IoUtil.readAndCloseQuietly(in));
}
final String contentType = conn.getContentType();
if (!contentType.startsWith("application/json")) {
throw new IOException("Unsupported type of response from remote server. ");
}
try (InputStream input = conn.getInputStream()) {
return DtoFactory.getInstance().createDtoFromJson(input, dtoInterface);
}
} finally {
conn.disconnect();
}
}
private static List<Link> getLinks(String metricName, UriInfo uriInfo) {
final UriBuilder servicePathBuilder = uriInfo.getBaseUriBuilder();
List<Link> links = new ArrayList<>();
final Link statusLink = DtoFactory.getInstance().createDto(Link.class);
statusLink.setRel(Constants.LINK_REL_GET_METRIC_VALUE);
statusLink.setHref(servicePathBuilder
.clone()
.path("analytics")
.path(getMethod("getValue"))
.build(metricName, "name")
.toString());
statusLink.setMethod("GET");
statusLink.setProduces(MediaType.APPLICATION_JSON);
links.add(statusLink);
return links;
}
private static Method getMethod(String name) {
for (Method analyticsMethod : AnalyticsService.class.getMethods()) {
if (analyticsMethod.getName().equals(name)) {
return analyticsMethod;
}
}
throw new RuntimeException("No '" + name + "' method found in " + AnalyticsService.class + "class");
}
}