416371 implemented weekly usage statistics report

Signed-off-by: Igor Fedorenko <igor@ifedorenko.com>
diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/MavenPluginActivator.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/MavenPluginActivator.java
index 1301ca9..e7798cb 100644
--- a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/MavenPluginActivator.java
+++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/MavenPluginActivator.java
@@ -377,12 +377,7 @@
   }
 
   public static String getUserAgent() {
-    // cast is necessary for eclipse 3.6 compatibility
-    String osgiVersion = (String) Platform
-        .getBundle("org.eclipse.osgi").getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION); //$NON-NLS-1$
-    String m2eVersion = plugin.qualifiedVersion;
-    String javaVersion = System.getProperty("java.version", "unknown"); //$NON-NLS-1$ $NON-NLS-1$
-    return "m2e/" + osgiVersion + "/" + m2eVersion + "/" + javaVersion; //$NON-NLS-1$ $NON-NLS-1$
+    return "m2e/" + plugin.version;
   }
 
   public IRepositoryRegistry getRepositoryRegistry() {
diff --git a/org.eclipse.m2e.feature/feature.xml b/org.eclipse.m2e.feature/feature.xml
index a70f00b..b6ede89 100644
--- a/org.eclipse.m2e.feature/feature.xml
+++ b/org.eclipse.m2e.feature/feature.xml
@@ -127,15 +127,25 @@
          version="0.0.0"
          unpack="false"/>
 
-     <plugin id="com.ning.async-http-client"
-              download-size="0"
+   <plugin
+         id="com.ning.async-http-client"
+         download-size="0"
          install-size="0"
          version="0.0.0"
          unpack="false"/>
-         
-     <plugin id="org.jboss.netty"
-              download-size="0"
+
+   <plugin
+         id="org.jboss.netty"
+         download-size="0"
          install-size="0"
          version="0.0.0"
-         unpack="false"/>       
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.m2e.stats"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
 </feature>
diff --git a/org.eclipse.m2e.stats/META-INF/MANIFEST.MF b/org.eclipse.m2e.stats/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..cc28170
--- /dev/null
+++ b/org.eclipse.m2e.stats/META-INF/MANIFEST.MF
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Bundle-SymbolicName: org.eclipse.m2e.stats;singleton:=true
+Bundle-Version: 1.5.0.qualifier
+Bundle-Localization: plugin
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6,
+ JavaSE-1.7
+Bundle-Vendor: %Bundle-Vendor
+Require-Bundle: org.eclipse.osgi,
+ org.eclipse.equinox.preferences,
+ org.eclipse.core.runtime,
+ org.slf4j.api,
+ com.ning.async-http-client,
+ org.eclipse.m2e.core;bundle-version="1.5.0",
+ org.eclipse.m2e.maven.runtime;bundle-version="1.5.0"
+Bundle-Activator: org.eclipse.m2e.stats.internal.UsageStatsActivator
diff --git a/org.eclipse.m2e.stats/about.html b/org.eclipse.m2e.stats/about.html
new file mode 100644
index 0000000..70e4b67
--- /dev/null
+++ b/org.eclipse.m2e.stats/about.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+ 
+<p>October 29, 2010</p>	
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  Unless otherwise 
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available 
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is 
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor's license that was 
+provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+</body>
+</html>
diff --git a/org.eclipse.m2e.stats/build.properties b/org.eclipse.m2e.stats/build.properties
new file mode 100644
index 0000000..912e7e5
--- /dev/null
+++ b/org.eclipse.m2e.stats/build.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2013 Igor Fedorenko
+# 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:
+#    Igor Fedorenko - initial API and implementation
+#
+
+bin.includes = META-INF/,\
+               plugin.properties,\
+               .,\
+               about.html,\
+               plugin.xml
+jars.compile.order = .
+source.. = src/
+output.. = target/classes/
diff --git a/org.eclipse.m2e.stats/plugin.properties b/org.eclipse.m2e.stats/plugin.properties
new file mode 100644
index 0000000..220231f
--- /dev/null
+++ b/org.eclipse.m2e.stats/plugin.properties
@@ -0,0 +1,13 @@
+#
+# Copyright (c) 2013 Igor Fedorenko
+# 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:
+#    Igor Fedorenko - initial API and implementation
+#
+
+Bundle-Vendor = Eclipse.org - m2e
+Bundle-Name = Maven Integration for Eclipse Usage Statistics Reporter
diff --git a/org.eclipse.m2e.stats/plugin.xml b/org.eclipse.m2e.stats/plugin.xml
new file mode 100644
index 0000000..6f6778a
--- /dev/null
+++ b/org.eclipse.m2e.stats/plugin.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+   <extension
+         point="org.eclipse.m2e.core.mavenComponentContributors">
+      <configurator
+            class="org.eclipse.m2e.stats.internal.UsageStatsStartupHook">
+      </configurator>
+   </extension>
+</plugin>
diff --git a/org.eclipse.m2e.stats/pom.xml b/org.eclipse.m2e.stats/pom.xml
new file mode 100644
index 0000000..aa2c573
--- /dev/null
+++ b/org.eclipse.m2e.stats/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright (c) 2013 Igor Fedorenko
+  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:
+     Igor Fedorenko - initial API and implementation
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.eclipse.m2e</groupId>
+    <artifactId>m2e-core</artifactId>
+    <version>1.5.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.eclipse.m2e.stats</artifactId>
+  <packaging>eclipse-plugin</packaging>
+
+  <name>Maven Integration for Eclipse Usage Statistics Reporter</name>
+
+</project>
diff --git a/org.eclipse.m2e.stats/src/org/eclipse/m2e/stats/internal/UsageStatsActivator.java b/org.eclipse.m2e.stats/src/org/eclipse/m2e/stats/internal/UsageStatsActivator.java
new file mode 100644
index 0000000..08661ac
--- /dev/null
+++ b/org.eclipse.m2e.stats/src/org/eclipse/m2e/stats/internal/UsageStatsActivator.java
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Igor Fedorenko
+ * 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:
+ *      Igor Fedorenko - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.m2e.stats.internal;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+import java.util.concurrent.Future;
+
+import org.apache.maven.wagon.proxy.ProxyInfo;
+import org.apache.maven.wagon.proxy.ProxyUtils;
+import org.apache.maven.wagon.repository.Repository;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.m2e.core.MavenPlugin;
+import org.eclipse.m2e.core.internal.MavenPluginActivator;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.prefs.BackingStoreException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.http.client.ProxyServer;
+import com.ning.http.client.Response;
+import com.ning.http.client.SimpleAsyncHttpClient;
+import com.ning.http.client.SimpleAsyncHttpClient.ErrorDocumentBehaviour;
+
+
+/**
+ * Weekly usage statistics reporter.
+ * <p>
+ * The following information is reported
+ * <ul>
+ * <li>workspace id, generated as UUID.randomUUID and persisted in bundle preferences</li>
+ * <li>number of opened Maven workspace projects</li>
+ * <li>m2e fully qualified version</li>
+ * <li>equinox fully qualified version, as proxy for eclipse version</li>
+ * <li>java version</li>
+ * </ul>
+ * <p>
+ * Usages is reported weekly and report is delayed {@link #REPORT_MINIMAL_DELAY} milliseconds from bundle activation.
+ * <p>
+ * To disable usage reporting, this bundle needs to be stopped or remove from eclipse installation.
+ * 
+ * @see UsageStatsStartupHook
+ */
+@SuppressWarnings("restriction")
+public class UsageStatsActivator implements BundleActivator {
+
+  private final Logger log = LoggerFactory.getLogger(getClass());
+
+  private static final String BUNDLE_ID = "org.eclipse.m2e.stats";
+
+  private static final String PREF_INSTANCEID = "eclipse.m2.stats.instanceId";
+
+  private static final String PREF_NEXTREPORT = "eclipse.m2.stats.nextReport";
+
+  private static final long REPORT_MINIMAL_DELAY = 10 * 60 * 1000L; // 10 minutes
+
+  private static final long REPORT_PERIOD = 7 * 86400L; // 7 days
+
+  private static final String REPORT_URL = "http://localhost:8087/stats";
+
+  private Timer timer;
+
+  private IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(BUNDLE_ID);
+
+  private class UsageStatsReporter extends TimerTask {
+    @Override
+    public void run() {
+      // update next report time regardless if we report or not
+      prefs.putLong(PREF_NEXTREPORT, System.currentTimeMillis() + REPORT_PERIOD);
+      flushPreferences();
+
+      final int projectCount = MavenPlugin.getMavenProjectRegistry().getProjects().length;
+      if(projectCount > 0) {
+        String instanceId = prefs.get(PREF_INSTANCEID, null);
+        if(instanceId == null) {
+          instanceId = UUID.randomUUID().toString();
+          prefs.put(PREF_INSTANCEID, instanceId);
+          flushPreferences();
+        }
+
+        String m2eVersion = MavenPluginActivator.getQualifiedVersion();
+
+        String osgiVersion = Platform
+            .getBundle("org.eclipse.osgi").getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION); //$NON-NLS-1$
+
+        String javaVersion = System.getProperty("java.version", "unknown"); //$NON-NLS-1$ $NON-NLS-1$
+
+        Map<String, String> params = new LinkedHashMap<String, String>();
+        params.put("uid", instanceId);
+        params.put("pc", Integer.toString(projectCount));
+        params.put("m", m2eVersion);
+        params.put("e", osgiVersion);
+        params.put("j", javaVersion);
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("Reporting usage stats url=").append(REPORT_URL);
+        for(Map.Entry<String, String> param : params.entrySet()) {
+          sb.append(' ').append(param.getKey()).append('=').append(param.getValue());
+        }
+        log.info(sb.toString());
+
+        post(REPORT_URL, params);
+      }
+    }
+
+    void flushPreferences() {
+      try {
+        prefs.flush();
+      } catch(BackingStoreException e) {
+        log.debug("Could not update preferences", e);
+      }
+    }
+
+    private void post(String url, Map<String, String> params) {
+      SimpleAsyncHttpClient.Builder sahcBuilder = new SimpleAsyncHttpClient.Builder();
+      sahcBuilder.setUserAgent(MavenPluginActivator.getUserAgent());
+      sahcBuilder.setConnectionTimeoutInMs(15 * 1000);
+      sahcBuilder.setRequestTimeoutInMs(60 * 1000);
+      sahcBuilder.setCompressionEnabled(true);
+      sahcBuilder.setFollowRedirects(true);
+      sahcBuilder.setErrorDocumentBehaviour(ErrorDocumentBehaviour.OMIT);
+
+      ProxyInfo proxyInfo = null;
+      try {
+        proxyInfo = MavenPlugin.getMaven().getProxyInfo("http");
+      } catch(CoreException e1) {
+        log.debug("Could not read http proxy configuration", e1);
+      }
+      if(proxyInfo != null) {
+        Repository repo = new Repository("id", url); //$NON-NLS-1$
+        if(!ProxyUtils.validateNonProxyHosts(proxyInfo, repo.getHost())) {
+          if(proxyInfo != null) {
+            ProxyServer.Protocol protocol = "https".equalsIgnoreCase(proxyInfo.getType()) ? ProxyServer.Protocol.HTTPS //$NON-NLS-1$
+                : ProxyServer.Protocol.HTTP;
+
+            sahcBuilder.setProxyProtocol(protocol);
+            sahcBuilder.setProxyHost(proxyInfo.getHost());
+            sahcBuilder.setProxyPort(proxyInfo.getPort());
+            sahcBuilder.setProxyPrincipal(proxyInfo.getUserName());
+            sahcBuilder.setProxyPassword(proxyInfo.getPassword());
+          }
+        }
+      }
+
+      sahcBuilder.setUrl(url);
+      for(Map.Entry<String, String> param : params.entrySet()) {
+        sahcBuilder.addParameter(param.getKey(), param.getValue());
+      }
+
+      SimpleAsyncHttpClient ahc = sahcBuilder.build();
+      try {
+        Future<Response> report = ahc.post();
+        report.get();
+      } catch(Exception e) {
+        log.debug("Could not report usage statistics", e);
+      } finally {
+        ahc.close();
+      }
+    }
+  }
+
+  @Override
+  public void start(BundleContext context) throws Exception {
+    // daemon timer won't prevent JVM from shutting down.
+    timer = new Timer("m2e usage stats reporter", true);
+
+    long initialDelay = prefs.getLong(PREF_NEXTREPORT, 0) - System.currentTimeMillis();
+    if(initialDelay < REPORT_MINIMAL_DELAY) {
+      initialDelay = REPORT_MINIMAL_DELAY;
+    }
+
+    timer.scheduleAtFixedRate(new UsageStatsReporter(), initialDelay, REPORT_PERIOD);
+  }
+
+  @Override
+  public void stop(BundleContext context) throws Exception {
+    timer.cancel();
+    timer = null;
+  }
+
+}
diff --git a/org.eclipse.m2e.stats/src/org/eclipse/m2e/stats/internal/UsageStatsStartupHook.java b/org.eclipse.m2e.stats/src/org/eclipse/m2e/stats/internal/UsageStatsStartupHook.java
new file mode 100644
index 0000000..161a251
--- /dev/null
+++ b/org.eclipse.m2e.stats/src/org/eclipse/m2e/stats/internal/UsageStatsStartupHook.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Igor Fedorenko
+ * 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:
+ *      Igor Fedorenko - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.m2e.stats.internal;
+
+import org.eclipse.m2e.core.internal.embedder.IMavenComponentContributor;
+
+
+/**
+ * This class is registered with m2e core as {@code mavenComponentContributor} extension and provides a (hackish) way to
+ * trigger activation of this bundle whenever m2e core bundle is activated. Actual usage statistics reporting logic is
+ * implemented in {@link UsageStatsActivator}.
+ */
+@SuppressWarnings("restriction")
+public class UsageStatsStartupHook implements IMavenComponentContributor {
+
+  @Override
+  public void contribute(IMavenComponentBinder binder) {
+  }
+
+}
diff --git a/pom.xml b/pom.xml
index 43dc18b..a05a6a7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,7 @@
     <module>org.eclipse.m2e.lifecyclemapping.defaults</module>
     <module>org.eclipse.m2e.discovery</module>
     <module>org.eclipse.m2e.scm</module>
+    <module>org.eclipse.m2e.stats</module>
 
     <!-- common test helpers -->
     <module>org.eclipse.m2e.tests.common</module>