All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amplitude.Amplitude Maven / Gradle / Ivy

The newest version!
package com.amplitude;

import java.net.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;

public class Amplitude {
  private static Map instances = new HashMap<>();
  private final String instanceName;
  private String apiKey;
  private String serverUrl;

  private AmplitudeLog logger;

  private Queue eventsToSend;
  private boolean aboutToStartFlushing;

  private HttpCallMode httpCallMode;
  private HttpCall httpCall;
  private HttpTransport httpTransport;
  private int eventUploadThreshold = Constants.EVENT_BUF_COUNT;
  private int eventUploadPeriodMillis = Constants.EVENT_BUF_TIME_MILLIS;
  private Object eventQueueLock = new Object();
  private Plan plan;
  private IngestionMetadata ingestionMetadata;
  private long flushTimeout;

  /**
   * A dictionary of key-value pairs that represent additional instructions for server save
   * operation.
   */
  private Options options;

  /** Custom proxy for https requests */
  private Proxy proxy = Proxy.NO_PROXY;

  /** The runner for middleware */
  MiddlewareRunner middlewareRunner = new MiddlewareRunner();

  /**
   * Private internal constructor for Amplitude. Please use `getInstance(String name)` or
   * `getInstance()` to get a new instance.
   */
  private Amplitude(String name) {
    instanceName = name;
    logger = new AmplitudeLog();
    eventsToSend = new ConcurrentLinkedQueue<>();
    aboutToStartFlushing = false;
    httpTransport = new HttpTransport(httpCall, null, logger, flushTimeout);
  }

  /**
   * Return the default class instance of Amplitude that is associated with "" or no string (null).
   *
   * @return the Amplitude instance that should be used for instrumentation
   */
  public static Amplitude getInstance() {
    return getInstance("");
  }

  /**
   * Return the class instance of Amplitude that is associated with this name
   *
   * @param instanceName The key (unique identifier) that matches to the Amplitude instance
   * @return the Amplitude instance that should be used for instrumentation
   */
  public static Amplitude getInstance(String instanceName) {
    if (!instances.containsKey(instanceName)) {
      Amplitude ampInstance = new Amplitude(instanceName);
      instances.put(instanceName, ampInstance);
    }
    return instances.get(instanceName);
  }

  /**
   * Set the API key for this instance of Amplitude. API key is necessary to authorize and route
   * events to the current Amplitude project.
   *
   * @param key the API key from Amplitude website
   */
  public void init(String key) {
    apiKey = key;
    updateHttpCall(HttpCallMode.REGULAR);
  }

  /**
   * Sends events to a different URL other than Constants.API_URL or Constants.BATCH_API_URL. Used
   * for proxy server.
   *
   * @param url the server URL for sending event
   */
  public void setServerUrl(String url) {
    boolean isValidServerUrl = url.startsWith("http://") || url.startsWith("https://");
    if (!isValidServerUrl) return;
    serverUrl = url;
    updateHttpCall(httpCallMode);
  }

  public void setOptions(Options options) {
    this.options = options;
    updateHttpCall(httpCallMode);
  }

  /**
   * Set the Event Upload Mode. If isBatchMode is true, the events will log through the Amplitude
   * HTTP V2 Batch API.
   *
   * @param isBatchMode if using batch upload or not;
   */
  public void useBatchMode(Boolean isBatchMode) {
    updateHttpCall(isBatchMode ? HttpCallMode.BATCH : HttpCallMode.REGULAR);
  }

  /**
   * Set the level at which to filter out debug messages from the Java SDK.
   *
   * @param logMode Messages at this level and higher (more urgent) will be logged in the console.
   */
  public void setLogMode(AmplitudeLog.LogMode logMode) {
    this.logger.setLogMode(logMode);
  }

  /**
   * Sets event upload threshold. The SDK will attempt to batch upload unsent events every
   * eventUploadPeriodMillis milliseconds, or if the unsent event count exceeds the event upload
   * threshold.
   *
   * @param eventUploadThreshold the event upload threshold
   */
  public Amplitude setEventUploadThreshold(int eventUploadThreshold) {
    this.eventUploadThreshold = eventUploadThreshold;
    return this;
  }

  /**
   * Sets event upload period millis. The SDK will attempt to batch upload unsent events * every
   * eventUploadPeriodMillis milliseconds, or if the unsent event count exceeds the * event upload
   * threshold.
   *
   * @param eventUploadPeriodMillis the event upload period millis
   */
  public Amplitude setEventUploadPeriodMillis(int eventUploadPeriodMillis) {
    this.eventUploadPeriodMillis = eventUploadPeriodMillis;
    return this;
  }

  /**
   * Set event callback which are triggered after event sent
   *
   * @param callbacks AmplitudeCallbacks or null to clean up.
   */
  public void setCallbacks(AmplitudeCallbacks callbacks) {
    httpTransport.setCallbacks(callbacks);
  }

  /**
   * Set tracking plan information.
   *
   * @param plan Plan object
   */
  public Amplitude setPlan(Plan plan) {
    this.plan = plan;
    return this;
  }

  /**
   * Set ingestion metadata information.
   *
   * @param ingestionMetadata IngestionMetadata object
   */
  public Amplitude setIngestionMetadata(IngestionMetadata ingestionMetadata) {
    this.ingestionMetadata = ingestionMetadata;
    return this;
  }

  /**
   * Set custom proxy for https requests
   *
   * @param proxy Proxy object, default to Proxy.NO_PROXY for direct connection
   */
  public Amplitude setProxy(Proxy proxy) {
    this.proxy = proxy;
    updateHttpCall(httpCallMode);
    return this;
  }

  /**
   * Set custom logger instance for amplitude client
   *
   * @param logger AmplitudeLog object
   */
  public Amplitude setLogger(AmplitudeLog logger) {
    this.logger = logger;
    this.httpTransport.setLogger(logger);
    return this;
  }

  /**
   * Set flush events threads execution timeout in milliseconds
   *
   * @param timeout the flush events threads timeout millis
   */
  public Amplitude setFlushTimeout(long timeout) {
    flushTimeout = timeout;
    this.httpTransport.setFlushTimeout(timeout);
    return this;
  }

  /**
   * Set the thread pool for sending events via {@link HttpTransport}
   *
   * @param sendThreadPool the thread pool for sending events
   */
  public Amplitude setSendThreadPool(ExecutorService sendThreadPool) {
    this.httpTransport.setSendThreadPool(sendThreadPool);
    return this;
  }

  /**
   * Set the thread pool for retrying events via {@link HttpTransport}
   *
   * @param retryThreadPool the thread pool for retrying events
   */
  public Amplitude setRetryThreadPool(ExecutorService retryThreadPool) {
    this.httpTransport.setRetryThreadPool(retryThreadPool);
    return this;
  }

  /** Add middleware to the middleware runner */
  public synchronized void addEventMiddleware(Middleware middleware) {
    middlewareRunner.add(middleware);
  }

  /**
   * Log an event to the Amplitude HTTP V2 API through the Java SDK
   *
   * @param event The event to be sent
   */
  public void logEvent(Event event) {
    logEvent(event, null, null);
  }

  /**
   * Log an event to the Amplitude HTTP V2 API through the Java SDK
   *
   * @param event The event to be sent
   * @param extra The extra unstructured data for middleware
   */
  public void logEvent(Event event, MiddlewareExtra extra) {
    logEvent(event, null, extra);
  }

  /**
   * Log an event and set a callback for this event.
   *
   * @param event The event to be sent
   * @param callbacks The callback for the event, this will run in addition to client level callback
   */
  public void logEvent(Event event, AmplitudeCallbacks callbacks) {
    logEvent(event, callbacks, null);
  }

  /**
   * Log an event and set a callback for this event.
   *
   * @param event The event to be sent
   * @param callbacks The callback for the event, this will run in addition to client level callback
   * @param extra The extra unstructured data for middleware
   */
  public void logEvent(Event event, AmplitudeCallbacks callbacks, MiddlewareExtra extra) {
    if (event.plan == null) {
      event.plan = this.plan;
    }

    if (event.ingestionMetadata == null) {
      event.ingestionMetadata = this.ingestionMetadata;
    }

    if (!middlewareRunner.run(new MiddlewarePayload(event, extra))) {
      return;
    }

    if (callbacks != null) {
      event.callback = callbacks;
    }
    int queueSize = 0;
    synchronized (eventQueueLock) {
      eventsToSend.add(event);
      queueSize = eventsToSend.size();
    }
    if (queueSize >= this.eventUploadThreshold) {
      flushEvents();
    } else {
      scheduleFlushEvents();
    }
  }

  /**
   * Forces events currently in the event buffer to be sent to Amplitude API endpoint. Only one
   * thread may flush at a time. Next flushes will happen immediately after.
   */
  public void flushEvents() {
    List eventsInTransit = new ArrayList<>();
    synchronized (eventQueueLock) {
      if (eventsToSend.size() > 0) {
        eventsInTransit.addAll(eventsToSend);
        eventsToSend.clear();
      }
    }
    if (!eventsInTransit.isEmpty()) {
      httpTransport.sendEventsWithRetry(eventsInTransit);
    }
  }

  /**
   * Check the status of the client, return true if any of the following situation:
   * 1. Events sit in memory waiting for flush are more than flush threshold
   * 2. Events in retry buffer are more than 16,000
   * 3. When setRecordThrottledId(true), the userId or deviceId of the input event was throttled and retry attempt for the userId/deviceId not finished.
   * Can be called before logEvent(event) and add some wait time if return true.
   *
   * @param event The event to be sent.
   * @return true if client is busy or event may be throttled.
   */
  public boolean shouldWait(Event event) {
    return eventsToSend.size() > eventUploadThreshold || httpTransport.shouldWait(event);
  }

  /**
   * Config whether the client record the recent throttled userId/deviceId and make suggestion base
   * on it.
   *
   * @param record true to record
   */
  public void setRecordThrottledId(boolean record) {
    httpTransport.setRecordThrottledId(record);
  }

  /**
   * Release resource by:
   * Shutdown threadspool for executing flush events task.
   * All events hold by these threads will trigger callbacks if amplitude client have configured callbacks method.
   * Shutdown client instance stop sending further events.
   */
  public void shutdown() throws InterruptedException {
    httpTransport.shutdown();
    instances.remove(instanceName);
  }

  private void updateHttpCall(HttpCallMode updatedHttpCallMode) {
    httpCallMode = updatedHttpCallMode;

    if (updatedHttpCallMode == HttpCallMode.BATCH) {
      httpCall =
          new HttpCall(
              apiKey, serverUrl != null ? serverUrl : Constants.BATCH_API_URL, options, proxy);
    } else {
      httpCall =
          new HttpCall(apiKey, serverUrl != null ? serverUrl : Constants.API_URL, options, proxy);
    }
    httpTransport.setHttpCall(httpCall);
  }

  private synchronized void scheduleFlushEvents() {
    if (!aboutToStartFlushing) {
      aboutToStartFlushing = true;
      Thread flushThread =
          new Thread(
              () -> {
                try {
                  Thread.sleep(this.eventUploadPeriodMillis);
                } catch (InterruptedException e) {
                  logger.warn("Error schedule flush events.", e.getMessage());
                }
                flushEvents();
                aboutToStartFlushing = false;
              });
      flushThread.start();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy