/* * Copyright (C) 2011 Everit Kft. (http://everit.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 org.everit.osgi.dev.maven.analytics; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.NetworkInterface; import java.net.SocketException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.codec.binary.Base64; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.maven.plugin.logging.Log; /** * Implementation of {@link GoogleAnalyticsTrackingService}. */ public class GoogleAnalyticsTrackingServiceImpl implements GoogleAnalyticsTrackingService { /** * Simple implementation of the {@link Runnable}. Responsible to send event the Google Analytics * server. */ private class EventSender implements Runnable { private final String analyticsReferer; private final String executedGoalName; private final HttpClient httpClient; private final String macAddressHash; /** * Constructor. * * @param analyticsReferer * the name of the referer that means who execute goal (example: eosgi-maven-plugin or * eclipse-e4-plugin). * @param executedGoalName * the name of the executed goal. * @param macAddressHash * the MAC address hash to anonym_user_id dimension. */ EventSender(final String analyticsReferer, final String executedGoalName, final String macAddressHash) { this.analyticsReferer = analyticsReferer; this.executedGoalName = executedGoalName; this.macAddressHash = macAddressHash; httpClient = new DefaultHttpClient(); } @Override public void run() { HttpPost post = new HttpPost(GA_ENDPOINT); List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("v", "1")); // version params.add(new BasicNameValuePair("tid", trackingId)); // tracking id params.add(new BasicNameValuePair("cid", macAddressHash)); // client id params.add(new BasicNameValuePair("t", "event")); // hit type params.add(new BasicNameValuePair("ec", analyticsReferer)); // event category params.add(new BasicNameValuePair("ea", executedGoalName)); // event action setCustomDimensionToParams(params, customDimensionMacAddressHash, macAddressHash); setCustomDimensionToParams(params, customDimensionPluginVersion, pluginVersion); try { UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8.name()); post.setEntity(entity); httpClient.execute(post); } catch (IOException e) { log.warn("Sending anonymous usage statistics failed: " + e.getMessage()); if (log.isDebugEnabled()) { log.debug(e); } } } private void setCustomDimensionToParams(final List<NameValuePair> params, final String parameterName, final String parameterValue) { if (parameterName != null) { params.add(new BasicNameValuePair(parameterName, parameterValue)); } } } private static final String GA_ENDPOINT = "http://www.google-analytics.com/collect?payload_data"; private static final String PROP_KEY_GA_CD_MAC_ADDRESS_HASH = "ga.cd.mac.address.hash"; private static final String PROP_KEY_GA_CD_PLUGIN_VERSION = "ga.cd.plugin.version"; private static final String PROP_KEY_GA_UA = "ga.ua"; private static final String UNKNOWN_MAC_ADDRESS = "UNKNOWN_MAC_ADDRESS"; private final long analyticsWaitingTimeInMs; private final String customDimensionMacAddressHash; private final String customDimensionPluginVersion; private final Log log; private final String pluginVersion; private final ConcurrentMap<Long, Thread> sendingEvents = new ConcurrentHashMap<>(); private final boolean skipAnalytics; private final String trackingId; /** * Constructor. Initialize values. * * @param analyticsWaitingTimeInMs * the waiting time to send the analytics to Google Analytics server. * @param skipAnalytics * skip analytics tracking or not. * @param pluginVersion * the version of the plugin. */ public GoogleAnalyticsTrackingServiceImpl(final long analyticsWaitingTimeInMs, final boolean skipAnalytics, final String pluginVersion, final Log log) { this.analyticsWaitingTimeInMs = analyticsWaitingTimeInMs; this.skipAnalytics = skipAnalytics; this.pluginVersion = pluginVersion; this.log = log; Properties properties = loadProperties(); trackingId = getProperty(properties, PROP_KEY_GA_UA); customDimensionMacAddressHash = getProperty(properties, PROP_KEY_GA_CD_MAC_ADDRESS_HASH); customDimensionPluginVersion = getProperty(properties, PROP_KEY_GA_CD_PLUGIN_VERSION); } private String getMacAddressHash() { try { Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); while (networkInterfaces.hasMoreElements()) { NetworkInterface network = networkInterfaces.nextElement(); byte[] macAddress = network.getHardwareAddress(); if (macAddress != null) { bout.write(macAddress, 0, macAddress.length); } } if (bout.size() == 0) { return UNKNOWN_MAC_ADDRESS; } MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); messageDigest.update(bout.toByteArray()); byte[] macAddressHash = messageDigest.digest(); byte[] encodedMacAddressHash = Base64.encodeBase64(macAddressHash); return new String(encodedMacAddressHash, StandardCharsets.UTF_8); } catch (SocketException | NoSuchAlgorithmException e) { return UNKNOWN_MAC_ADDRESS; } } private String getProperty(final Properties properties, final String propertyKey) { String propValue = (String) properties.get(propertyKey); if (!("${" + propertyKey + "}").equals(propValue)) { return propValue; } else { return null; } } private Properties loadProperties() { try (InputStream gaPropertiesInputStream = this.getClass().getResourceAsStream("/META-INF/ga.properties")) { Properties properties = new Properties(); properties.load(gaPropertiesInputStream); return properties; } catch (IOException e) { throw new RuntimeException(e); } } @Override public long sendEvent(final String analyticsReferer, final String executedGoalName) { if (skipAnalytics || (trackingId == null) || EnvironmentUtil.isCi()) { log.info("The eosgi-maven-plugin anonym usage statistics collection is turned off."); return DEFAULT_EVENT_ID; } log.info("The eosgi-maven-plugin collects anonym usage statistics. See more information on " + "http://www.everit.org/eosgi-maven-plugin/#google_analytics_tracking."); EventSender sendingEvent = new EventSender(analyticsReferer, executedGoalName, getMacAddressHash()); Thread thread = new Thread(sendingEvent); long eventId = thread.getId(); sendingEvents.put(eventId, thread); thread.start(); return eventId; } @Override public void waitForEventSending(final long eventId) { if (skipAnalytics || (trackingId == null) || EnvironmentUtil.isCi()) { return; } Thread thread = sendingEvents.get(eventId); if ((thread == null) || !thread.isAlive()) { return; } try { thread.join(analyticsWaitingTimeInMs); if (thread.isAlive()) { thread.interrupt(); } } catch (InterruptedException e) { throw new RuntimeException(e); } } }