/**
   * Synchronously sends all queued events for the given project. This method will immediately
   * publish the events to the Keen server in the current thread.
   *
   * @param project The project for which to send queued events. If a default project has been set
   *     on the client this parameter may be null, in which case the default project will be used.
   * @param callback An optional callback to receive notification of success or failure.
   */
  public synchronized void sendQueuedEvents(KeenProject project, KeenCallback callback) {

    if (!isActive) {
      handleLibraryInactive(callback);
      return;
    }

    if (project == null && defaultProject == null) {
      handleFailure(
          null, new IllegalStateException("No project specified, but no default project found"));
      return;
    }
    KeenProject useProject = (project == null ? defaultProject : project);

    try {
      String projectId = useProject.getProjectId();
      Map<String, List<Object>> eventHandles = eventStore.getHandles(projectId);
      Map<String, List<Map<String, Object>>> events = buildEventMap(eventHandles);
      String response = publishAll(useProject, events);
      if (response != null) {
        try {
          handleAddEventsResponse(eventHandles, response);
        } catch (Exception e) {
          // Errors handling the response are non-fatal; just log them.
          KeenLogging.log("Error handling response to batch publish: " + e.getMessage());
        }
      }
      handleSuccess(callback);
    } catch (Exception e) {
      handleFailure(callback, e);
    }
  }
 /**
  * Publishes a batch of events to the Keen service.
  *
  * @param project The project in which to publish the event.
  * @param events A map from collection name to a list of event maps.
  * @return The response from the server.
  * @throws IOException If there was an error communicating with the server.
  */
 private String publishAll(KeenProject project, Map<String, List<Map<String, Object>>> events)
     throws IOException {
   // just using basic JDK HTTP library
   String urlString =
       String.format(
           Locale.US,
           "%s/%s/projects/%s/events",
           getBaseUrl(),
           KeenConstants.API_VERSION,
           project.getProjectId());
   URL url = new URL(urlString);
   return publishObject(project, url, events);
 }
  /**
   * Posts a request to the server in the specified project, using the given URL and request data.
   * The request data will be serialized into JSON using the client's {@link
   * io.keen.client.java.KeenJsonHandler}.
   *
   * @param project The project in which the event(s) will be published; this is used to determine
   *     the write key to use for authentication.
   * @param url The URL to which the POST should be sent.
   * @param requestData The request data, which will be serialized into JSON and sent in the request
   *     body.
   * @return The response from the server.
   * @throws IOException If there was an error communicating with the server.
   */
  private synchronized String publishObject(
      KeenProject project, URL url, final Map<String, ?> requestData) throws IOException {
    if (requestData == null || requestData.size() == 0) {
      KeenLogging.log("No API calls were made because there were no events to upload");
      return null;
    }

    // Build an output source which simply writes the serialized JSON to the output.
    OutputSource source =
        new OutputSource() {
          @Override
          public void writeTo(OutputStream out) throws IOException {
            OutputStreamWriter writer = new OutputStreamWriter(out, ENCODING);
            jsonHandler.writeJson(writer, requestData);
          }
        };

    // If logging is enabled, log the request being sent.
    if (KeenLogging.isLoggingEnabled()) {
      try {
        StringWriter writer = new StringWriter();
        jsonHandler.writeJson(writer, requestData);
        String request = writer.toString();
        KeenLogging.log(
            String.format(Locale.US, "Sent request '%s' to URL '%s'", request, url.toString()));
      } catch (IOException e) {
        KeenLogging.log("Couldn't log event written to file: ");
        e.printStackTrace();
      }
    }

    // Send the request.
    String writeKey = project.getWriteKey();
    Request request = new Request(url, "POST", writeKey, source);
    Response response = httpHandler.execute(request);

    // If logging is enabled, log the response.
    if (KeenLogging.isLoggingEnabled()) {
      KeenLogging.log(
          String.format(
              Locale.US, "Received response: '%s' (%d)", response.body, response.statusCode));
    }

    // If the request succeeded, return the response body. Otherwise throw an exception.
    if (response.isSuccess()) {
      return response.body;
    } else {
      throw new ServerException(response.body);
    }
  }
  /**
   * Synchronously queues an event for publishing. The event will be cached in the client's {@link
   * io.keen.client.java.KeenEventStore} until the next call to either {@link #sendQueuedEvents()}
   * or {@link #sendQueuedEventsAsync()}.
   *
   * @param project The project in which to publish the event. If a default project has been set on
   *     the client this parameter may be null, in which case the default project will be used.
   * @param eventCollection The name of the collection in which to publish the event.
   * @param event A Map that consists of key/value pairs. Keen naming conventions apply (see docs).
   *     Nested Maps and lists are acceptable (and encouraged!).
   * @param keenProperties A Map that consists of key/value pairs to override default properties.
   *     ex: "timestamp" -> Calendar.getInstance()
   * @param callback An optional callback to receive notification of success or failure.
   */
  public void queueEvent(
      KeenProject project,
      String eventCollection,
      Map<String, Object> event,
      Map<String, Object> keenProperties,
      final KeenCallback callback) {

    if (!isActive) {
      handleLibraryInactive(callback);
      return;
    }

    if (project == null && defaultProject == null) {
      handleFailure(
          null, new IllegalStateException("No project specified, but no default project found"));
      return;
    }
    KeenProject useProject = (project == null ? defaultProject : project);

    try {
      // Build the event
      Map<String, Object> newEvent =
          validateAndBuildEvent(useProject, eventCollection, event, keenProperties);

      // Serialize the event into JSON.
      StringWriter writer = new StringWriter();
      jsonHandler.writeJson(writer, newEvent);
      String jsonEvent = writer.toString();
      KeenUtils.closeQuietly(writer);

      // Save the JSON event out to the event store.
      eventStore.store(useProject.getProjectId(), eventCollection, jsonEvent);
      handleSuccess(callback);
    } catch (Exception e) {
      handleFailure(callback, e);
    }
  }
  /**
   * Validates an event and inserts global properties, producing a new event object which is ready
   * to be published to the Keen service.
   *
   * @param project The project in which the event will be published.
   * @param eventCollection The name of the collection in which the event will be published.
   * @param event A Map that consists of key/value pairs.
   * @param keenProperties A Map that consists of key/value pairs to override default properties.
   * @return A new event Map containing Keen properties and global properties.
   */
  protected Map<String, Object> validateAndBuildEvent(
      KeenProject project,
      String eventCollection,
      Map<String, Object> event,
      Map<String, Object> keenProperties) {

    if (project.getWriteKey() == null) {
      throw new NoWriteKeyException(
          "You can't send events to Keen IO if you haven't set a write key.");
    }

    validateEventCollection(eventCollection);
    validateEvent(event);

    KeenLogging.log(String.format(Locale.US, "Adding event to collection: %s", eventCollection));

    // build the event
    Map<String, Object> newEvent = new HashMap<String, Object>();
    // handle keen properties
    Calendar currentTime = Calendar.getInstance();
    String timestamp = ISO_8601_FORMAT.format(currentTime.getTime());
    if (keenProperties == null) {
      keenProperties = new HashMap<String, Object>();
      keenProperties.put("timestamp", timestamp);
    } else {
      if (!keenProperties.containsKey("timestamp")) {
        keenProperties.put("timestamp", timestamp);
      }
    }
    newEvent.put("keen", keenProperties);

    // handle global properties
    Map<String, Object> globalProperties = getGlobalProperties();
    if (globalProperties != null) {
      newEvent.putAll(globalProperties);
    }
    GlobalPropertiesEvaluator globalPropertiesEvaluator = getGlobalPropertiesEvaluator();
    if (globalPropertiesEvaluator != null) {
      Map<String, Object> props = globalPropertiesEvaluator.getGlobalProperties(eventCollection);
      if (props != null) {
        newEvent.putAll(props);
      }
    }

    // now handle user-defined properties
    newEvent.putAll(event);
    return newEvent;
  }