/*******************************************************************************
* Copyright 2013 Google Inc. All Rights Reserved.
*
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.google.gdt.eclipse.suite.update.usage;
import com.google.api.client.util.escape.PercentEscaper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gdt.eclipse.suite.GdtPlugin;
import com.google.gdt.eclipse.suite.update.GdtExtPlugin;
import com.google.gdt.eclipse.suite.update.GdtExtPlugin.GwtMaxSdkVersionComputer;
import com.google.gdt.eclipse.suite.update.UpdateSiteURLGenerator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.update.internal.configurator.VersionedIdentifier;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Provides methods that report plugin-specific events to Analytics. The events reported to
* Analytics all have a category that is the name of the reporting plugin, an action that is the
* name of one of the values of the enum {@link Action}, a label that varies with the event being
* reported, and optionally a numeric value that varies with the event being reported.
*/
@SuppressWarnings("restriction") // VersionedIdentifier
public class AnalyticsPingManager implements PingManager {
public enum Action {
GWT_COMPILATION
}
private static final String ANALYTICS_COLLECTION_URL = "http://www.google-analytics.com/collect";
private static final String GWT_ANALYTICS_ID = "UA-62291716-1";
private static final String APPLICATION_NAME = "GWT Eclipse Plugin";
// Fixed-value query parameters present in every ping, and their fixed values:
private static final ImmutableMap<String, String> STANDARD_PARAMETERS =
ImmutableMap.<String, String>builder()
.put("v", "1") // Google Analytics Measurement Protocol version
.put("tid", GWT_ANALYTICS_ID) // tracking ID
.put("t", "event") // hit type
.put("an", APPLICATION_NAME)
.build();
private static final PercentEscaper ESCAPER = new PercentEscaper(".-_", false);
private final UpdateSiteURLGenerator urlGenerator;
public AnalyticsPingManager(UpdateSiteURLGenerator urlGenerator) {
this.urlGenerator = urlGenerator;
}
@Override
public void sendCompilationPing() {
sendPing(GdtExtPlugin.PLUGIN_ID, Action.GWT_COMPILATION, null, null);
}
private static void sendPing(
String pluginName, Action action, @Nullable String label, @Nullable Integer value,
CustomDimensionAssignment... customDimensions) {
Map<String, String> parametersMap = Maps.newHashMap(STANDARD_PARAMETERS);
String anonymizedClientId = GdtPlugin.getInstallationId();
parametersMap.put("cid", anonymizedClientId);
parametersMap.put("ec", pluginName); // category
parametersMap.put("ea", action.toString());
if (label != null) {
parametersMap.put("el", label);
}
if (value != null) {
parametersMap.put("ev", value.toString());
}
setCustomDimension(parametersMap, CustomDimensionName.ANONYMIZED_CLIENT_ID, anonymizedClientId);
@SuppressWarnings("deprecation") // PluginVersionIdentifier.toString()
String gwtVersion =
new VersionedIdentifier(
GdtExtPlugin.FEATURE_ID, GdtExtPlugin.FEATURE_VERSION.toString()).toString();
setCustomDimension(parametersMap, CustomDimensionName.GWT_VERSION, gwtVersion);
setCustomDimension(
parametersMap, CustomDimensionName.ECLIPSE_VERSION, GdtPlugin.getEclipseVersion());
if (Platform.getProduct() != null) {
setCustomDimension(
parametersMap, CustomDimensionName.ECLIPSE_PRODUCT_ID, Platform.getProduct().getId());
} else {
setCustomDimension(
parametersMap, CustomDimensionName.ECLIPSE_PRODUCT_ID, "null");
}
setCustomDimension(parametersMap, CustomDimensionName.SDK_VERSIONS, getSdkVersions());
for (CustomDimensionAssignment nameValuePair : customDimensions) {
setCustomDimension(parametersMap, nameValuePair.getName(), nameValuePair.getValue());
}
sendPostRequest(parametersMap);
}
private static String getSdkVersions() {
IJavaProject[] projects = GdtExtPlugin.getJavaProjects();
String maxGwtSdkVersion = new GwtMaxSdkVersionComputer().computeMaxSdkVersion(projects);
if (maxGwtSdkVersion == null || maxGwtSdkVersion.isEmpty()) {
return "none";
} else {
return "GWT " + maxGwtSdkVersion;
}
}
private static void setCustomDimension(
Map<String, String> map, CustomDimensionName dimension, String value) {
map.put("cd" + dimension.getIndex(), value);
}
private static void sendPostRequest(Map<String, String> parametersMap) {
GdtExtPlugin.getLogger().logInfo("Sending POST request to Analytics: " + parametersMap);
String parametersString = getParametersString(parametersMap);
try {
URL url = new URL(ANALYTICS_COLLECTION_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty(
"Content-Length", Integer.toString(parametersString.length()));
GdtExtPlugin.getLogger().logInfo(
"Analytics ping request parameters: " + parametersString);
try {
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
try {
out.writeBytes(parametersString);
out.flush();
} finally {
out.close();
}
} finally {
logPostResponse(connection);
connection.disconnect();
}
} catch (IOException e) {
GdtExtPlugin.getLogger().logError(e, "Error trying to ping Analytics");
}
}
private static String getParametersString(Map<String, String> parametersMap) {
StringBuilder resultBuilder = new StringBuilder();
boolean ampersandNeeded = false;
for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
if (ampersandNeeded) {
resultBuilder.append('&');
} else {
ampersandNeeded = true;
}
resultBuilder.append(entry.getKey());
resultBuilder.append('=');
resultBuilder.append(ESCAPER.escape(entry.getValue()));
}
return resultBuilder.toString();
}
private static void logPostResponse(HttpURLConnection connection) throws IOException {
int responseCode = connection.getResponseCode();
StringBuilder responseBuilder = new StringBuilder();
responseBuilder.append("Analytics ping response: ");
responseBuilder.append(responseCode);
responseBuilder.append(": ");
responseBuilder.append(connection.getResponseMessage());
if (responseCode == 200) {
responseBuilder.append('\n');
BufferedReader responseContentReader =
new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine = responseContentReader.readLine();
while (inputLine != null) {
responseBuilder.append(inputLine);
inputLine = responseContentReader.readLine();
}
responseContentReader.close();
}
GdtExtPlugin.getLogger().logInfo(responseBuilder.toString());
}
// Each element of this enum corresponds to a custom dimensions defined in the Analytics web UI,
// The constructor argument specifies the index of the custom dimension.
private enum CustomDimensionName {
ECLIPSE_PRODUCT_ID(1),
ECLIPSE_VERSION(3),
GWT_VERSION(4),
SDK_VERSIONS(6),
ANONYMIZED_CLIENT_ID(10);
private int index;
private CustomDimensionName(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
private static final class CustomDimensionAssignment {
private final CustomDimensionName name;
private final String value;
public CustomDimensionAssignment(CustomDimensionName name, String value) {
this.name = name;
this.value = value;
}
public CustomDimensionName getName() {
return name;
}
public String getValue() {
return value;
}
}
}