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

com.metamx.emitter.core.ParametrizedUriEmitter Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
package com.metamx.emitter.core;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.metamx.common.lifecycle.Lifecycle;
import com.metamx.common.lifecycle.LifecycleStart;
import com.metamx.common.lifecycle.LifecycleStop;
import com.metamx.common.logger.Logger;
import org.asynchttpclient.AsyncHttpClient;

import javax.annotation.concurrent.GuardedBy;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

public class ParametrizedUriEmitter implements Flushable, Closeable, Emitter
{
  private static final Logger log = new Logger(ParametrizedUriEmitter.class);
  private static final Set ONLY_FEED_PARAM = ImmutableSet.of("feed");

  private static UriExtractor makeUriExtractor(ParametrizedUriEmitterConfig config)
  {
    final String baseUri = config.getRecipientBaseUrlPattern();
    final ParametrizedUriExtractor parametrizedUriExtractor = new ParametrizedUriExtractor(baseUri);
    UriExtractor uriExtractor = parametrizedUriExtractor;
    if (ONLY_FEED_PARAM.equals(parametrizedUriExtractor.getParams())) {
      uriExtractor = new FeedUriExtractor(baseUri.replace("{feed}", "%s"));
    }
    return uriExtractor;
  }

  /**
   * Type should be ConcurrentHashMap, not {@link java.util.concurrent.ConcurrentMap}, because the latter _doesn't_
   * guarantee that the lambda passed to {@link java.util.Map#computeIfAbsent} is executed at most once.
   */
  private final ConcurrentHashMap emitters = new ConcurrentHashMap<>();
  private final UriExtractor uriExtractor;
  private final Object startCloseLock = new Object();
  @GuardedBy("startCloseLock")
  private boolean started = false;
  @GuardedBy("startCloseLock")
  private boolean closed = false;
  private final Lifecycle innerLifecycle = new Lifecycle();
  private final AsyncHttpClient client;
  private final ObjectMapper jsonMapper;
  private final ParametrizedUriEmitterConfig config;

  public ParametrizedUriEmitter(
      ParametrizedUriEmitterConfig config,
      AsyncHttpClient client,
      ObjectMapper jsonMapper
  )
  {
    this(config, client, jsonMapper, makeUriExtractor(config));
  }

  public ParametrizedUriEmitter(
      ParametrizedUriEmitterConfig config,
      AsyncHttpClient client,
      ObjectMapper jsonMapper,
      UriExtractor uriExtractor
  )
  {
    this.config = config;
    this.client = client;
    this.jsonMapper = jsonMapper;
    this.uriExtractor = uriExtractor;
  }

  @Override
  @LifecycleStart
  public void start()
  {
    // Use full synchronized instead of atomic flag, because otherwise some thread may think that the emitter is already
    // started while it's in the process of starting by another thread.
    synchronized (startCloseLock) {
      if (started) {
        return;
      }
      started = true;
      try {
        innerLifecycle.start();
      }
      catch (RuntimeException e) {
        throw e;
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }

  @Override
  public void emit(Event event)
  {
    try {
      URI uri = uriExtractor.apply(event);
      HttpPostEmitter emitter = emitters.get(uri);
      if (emitter == null) {
        try {
          emitter = emitters.computeIfAbsent(uri, u -> {
            try {
              return innerLifecycle.addMaybeStartManagedInstance(
                  new HttpPostEmitter(
                      config.buildHttpEmitterConfig(u.toString()),
                      client,
                      jsonMapper
                  )
              );
            }
            catch (Exception e) {
              throw Throwables.propagate(e);
            }
          });
        }
        catch (RuntimeException e) {
          log.error(e, "Error while creating or starting an HttpPostEmitter for URI[%s]", uri);
          return;
        }
      }
      emitter.emit(event);
    }
    catch (URISyntaxException e) {
      log.error(e, "Failed to extract URI for event[%s]", event.toMap());
    }
  }

  @Override
  @LifecycleStop
  public void close() throws IOException
  {
    // Use full synchronized instead of atomic flag, because otherwise some thread may think that the emitter is already
    // closed while it's in the process of closing by another thread.
    synchronized (startCloseLock) {
      if (closed) {
        return;
      }
      closed = true;
      innerLifecycle.stop();
    }
  }

  @Override
  public void flush() throws IOException
  {
    Exception thrown = null;
    for (HttpPostEmitter httpPostEmitter : emitters.values()) {
      try {
        httpPostEmitter.flush();
      }
      catch (Exception e) {
        // If flush was interrupted, exit the loop
        if (Thread.currentThread().isInterrupted()) {
          if (thrown != null) {
            e.addSuppressed(thrown);
          }
          throw Throwables.propagate(e);
        }
        if (thrown == null) {
          thrown = e;
        } else {
          if (thrown != e) {
            thrown.addSuppressed(e);
          }
        }
      }
    }
    if (thrown != null) {
      throw Throwables.propagate(thrown);
    }
  }

  public void forEachEmitter(BiConsumer action)
  {
    emitters.forEach(action);
  }

  @Override
  public String toString()
  {
    return "ParametrizedUriEmitter{" +
           "emitters=" + emitters.keySet() +
           ", uriExtractor=" + uriExtractor +
           ", config=" + config +
           '}';
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy