/******************************************************************************* * Copyright (c) 2014 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is 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: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.usage.event; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; import org.jboss.tools.usage.googleanalytics.IJBossToolsEclipseEnvironment; import org.jboss.tools.usage.googleanalytics.RequestType; import org.jboss.tools.usage.internal.JBossToolsUsageActivator; import org.jboss.tools.usage.internal.event.EventRegister; import org.jboss.tools.usage.internal.event.EventRegister.Result; import org.jboss.tools.usage.internal.preferences.GlobalUsageSettings; import org.jboss.tools.usage.internal.preferences.UsageReportPreferences; import org.jboss.tools.usage.internal.reporting.ReportingMessages; import org.jboss.tools.usage.internal.reporting.UsageReportEnablementDialog; import org.jboss.tools.usage.internal.reporting.UsageRequest; import org.osgi.service.prefs.BackingStoreException; /** * @author Alexey Kazakov * @author Andre Dietisheim */ public class UsageReporter { public static final String NOT_APPLICABLE_LABEL = "N/A"; private static final String PATH_PREFIX = "/tools/"; private static final UsageReporter INSTANCE = new UsageReporter(); private UsageRequest usageRequest; private Lock lockToAskUser = new ReentrantLock(); private GlobalUsageSettings settings; protected UsageReporter() { } public static UsageReporter getInstance() { return INSTANCE; } /** * Registers the event type * * @param type */ public void registerEvent(final UsageEventType type) { try { getEventRegister().registerEvent(type); if (isEnabled()) { ReportingJob job = new ReportingJob(new Reporter() { @Override public void report() { checkCountEventInternal(type); } }); job.schedule(); } } catch(Exception t) { // Catch all Exceptions to make sure, in case of bugs, Usage doesn't prevent JBT from working JBossToolsUsageActivator.getDefault().getLogger().error(t, true); } } /** * Tracks a user's event * * @param event */ public void trackEvent(final UsageEvent event) { try { if (isEnabled()) { ReportingJob job = new ReportingJob(new Reporter() { @Override public void report() { trackEventInternal(event); } }); job.schedule(); } } catch(Exception t) { // Catch all Exceptions to make sure, in case of bugs, Usage doesn't prevent JBT from working JBossToolsUsageActivator.getDefault().getLogger().error(t, true); } } /** * Tracks a user's event * * @param pagePath * @param title * @param event * @param type * @param startNewVisitSession */ public void trackEvent(final String pagePath, final String title, final UsageEvent event, final RequestType type, final boolean startNewVisitSession) { try { if (isEnabled()) { ReportingJob job = new ReportingJob(new Reporter() { @Override public void report() { trackEventInternal(pagePath, title, event, type, startNewVisitSession); } }); job.schedule(); } } catch(Exception t) { // Catch all Exceptions to make sure, in case of bugs, Usage doesn't prevent JBT from working JBossToolsUsageActivator.getDefault().getLogger().error(t, true); } } /** * Doesn't send a tracking request instantly but remembers the event's value for tracking events once a day. * If the type of this event was used for sending or counting events a day before then a new event with a sum (if bigger than 0) of all previously collected events is sent. * It's recommended to always set the value of the event = 1 (or null, since 1 is used by default) * because we actually send one real GA event every 24 hours with the sum of all the counted events. So, that sum will represents the number of user events. * If the value==o then the event won't be count. If the value>1 then it will represents a few events. Not just one. * Category, action names and labels are taken into account when values are being counted. * For events without labels and/or values the "N/A" is used as a label and "1" is used as the default value. * @param event */ public void countEvent(final UsageEvent event) { try { if (isEnabled()) { ReportingJob job = new ReportingJob(new Reporter() { @Override public void report() { countEventInternal(event); } }); job.schedule(); } } catch(Exception t) { // Catch all Exceptions to make sure, in case of bugs, Usage doesn't prevent JBT from working JBossToolsUsageActivator.getDefault().getLogger().error(t, true); } } /** * Sends a tracking request for all daily events if it's time to send them */ public void trackCountEvents() { try { if (isEnabled()) { ReportingJob job = new ReportingJob(new Reporter() { @Override public void report() { trackCountEventsInternal(); } }); job.schedule(); } } catch(Exception t) { // Catch all Exceptions to make sure, in case of bugs, Usage doesn't prevent JBT from working JBossToolsUsageActivator.getDefault().getLogger().error(t, true); } } private boolean isEnabled() { return !UsageReportPreferences.isEnablementSet() || isPreferencesEnabled(); } protected boolean trackEventInternal(String pagePath, String title, UsageEvent event, RequestType type, boolean startNewVisitSession) { boolean result = false; if (isPreferencesEnabled()) { result = sendRequest(pagePath, title, event, type, startNewVisitSession, false); } return result; } protected boolean trackEventInternal(UsageEvent event) { boolean result = false; if (isPreferencesEnabled()) { result = sendRequest(getPagePath(event), event.getType().getComponentName(), event, false); } return result; } protected boolean countEventInternal(UsageEvent event) { boolean result = false; if (isPreferencesEnabled()) { result = sendRequest(getPagePath(event), event.getType().getComponentName(), event, true); } return result; } /** * Checks if the corresponding daily event should be sent. If yes then that daily event(s) is sent. * @param type * @return the number of sent events */ protected int checkCountEventInternal(UsageEventType type) { int sent = 0; if (isPreferencesEnabled()) { Set<Result> results = getEventRegister().checkCountEvent(type, getGlobalUsageSettings()); for (Result result : results) { if(result.isOkToSend()) { int value = result.getPreviousSumOfValues(); String label = result.getCountEventLabel(); UsageEvent event = type.event(label, value); if(getUsageRequest().sendRequest(getPagePath(event), event.getType().getComponentName(), event, null, false)) { sent++; } } } } return sent; } /** * Returns number of sent daily events * @return */ protected int trackCountEventsInternal() { int result = 0; UsageEventType[] types = getEventRegister().getRegisteredEventTypes(); for (UsageEventType type : types) { result = result + checkCountEventInternal(type); } return result; } /** * Sends a page view tracking request with the given event in the current session */ private boolean sendRequest(String pagePath, String title, UsageEvent event, boolean onceADayOnly) { return sendRequest(pagePath, title, event, null, false, onceADayOnly); } /** * Sends a tracking request * @param environment * @param pagePath * @param title may be null * @param event may not be null if onceADayOnly==true * @param type if null, RequestType.PAGE is used * @param startNewVisitSession if false, the current session from environment is used * @param onceADayOnly if true, send a request only once a day * @return true if the request was sent successfully */ synchronized private boolean sendRequest(String pagePath, String title, UsageEvent event, RequestType type, boolean startNewVisitSession, boolean onceADayOnly) { boolean sent = false; if(onceADayOnly) { event = event.clone(); if(event.getLabel()==null) { event.setLabel(UsageReporter.NOT_APPLICABLE_LABEL); } if(event.getValue()==null) { event.setValue(1); } } EventRegister.Result result = getEventRegister().checkTrackData(event, getGlobalUsageSettings(), onceADayOnly); if(result.isOkToSend()) { int value = result.getPreviousSumOfValues(); if(onceADayOnly && value>0) { event.setValue(value); event.setLabel(result.getCountEventLabel()); } sent = getUsageRequest().sendRequest(pagePath, title, event, type, startNewVisitSession); } return sent; } protected EventRegister getEventRegister() { return EventRegister.getInstance(); } private String getPagePath(UsageEvent event) { return PATH_PREFIX + event.getType().getComponentName() + "/" + event.getType().getComponentVersion(); } protected boolean isPreferencesEnabled() { return UsageReportPreferences.isEnabled(); } protected UsageRequest getUsageRequest() { if(usageRequest==null) { usageRequest = new UsageRequest(getEnvironment()); } return usageRequest; } protected synchronized GlobalUsageSettings getGlobalUsageSettings() { if(settings==null) { settings = new GlobalUsageSettings(JBossToolsUsageActivator.getDefault()); } return settings; } protected IJBossToolsEclipseEnvironment getEnvironment() { return JBossToolsUsageActivator.getDefault().getJBossToolsEclipseEnvironment(); } /** * Checks reporting shall ask the user (which is the case if this was never * done before. Asking user shall only be done once per eclipse * installation) * * @return true, if the user should be asked */ protected boolean isAskUser() { return UsageReportPreferences.isAskUser(); } /** * Asks the user is he allows us to report usage. Opens a dialog for this sake. */ private void askUser() { Boolean isEnabled = askUserForEnablement(); if (isEnabled != null) { UsageReportPreferences.setEnabled(isEnabled); UsageReportPreferences.setAskUser(false); try { UsageReportPreferences.flush(); } catch (BackingStoreException e) { JBossToolsUsageActivator.getDefault().getLogger().error(e); } } } /** * Asks the user if he allows us to report. * * @return the boolean */ protected Boolean askUserForEnablement() { final CountDownLatch latch = new CountDownLatch(1); final Boolean[] userResponse = new Boolean[1]; Display.getDefault().asyncExec(new Runnable() { public void run() { try { userResponse[0] = false; Shell shell = PlatformUI.getWorkbench().getModalDialogShellProvider().getShell(); UsageReportEnablementDialog dialog = new UsageReportEnablementDialog( shell, JBossToolsUsageActivator.getDefault().getUsageBranding()); if (dialog.open() == Window.OK) { userResponse[0] = dialog.isReportEnabled(); } } finally { latch.countDown(); } } }); try { latch.await(); // main thread is waiting on CountDownLatch to finish } catch (InterruptedException ie) { // Error already logged by the asyncExec } return userResponse[0]; } private static interface Reporter { void report(); } private class ReportingJob extends Job { Reporter reporter; private ReportingJob(Reporter reporter) { super(ReportingMessages.UsageReport_Reporting_Usage); this.reporter = reporter; } @Override protected IStatus run(IProgressMonitor monitor) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } monitor.beginTask(ReportingMessages.UsageReport_Querying_Enablement, 2); if (getGlobalUsageSettings().isReportingEnabled()) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } monitor.worked(1); try { lockToAskUser.lock(); if (isAskUser()) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } askUser(); } } finally { lockToAskUser.unlock(); } reporter.report(); monitor.worked(2); monitor.done(); } return Status.OK_STATUS; } } }