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

com.rabbitmq.stream.impl.Utils Maven / Gradle / Ivy

Go to download

The RabbitMQ Stream Java client library allows Java applications to interface with RabbitMQ Stream.

The newest version!
// Copyright (c) 2020-2023 Broadcom. All Rights Reserved.
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
//
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
// Mozilla Public License 2.0 ("MPL"), and the Apache License version 2 ("ASL").
// For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].
package com.rabbitmq.stream.impl;

import static java.lang.String.format;

import com.rabbitmq.stream.*;
import com.rabbitmq.stream.impl.Client.ClientParameters;
import io.netty.channel.ConnectTimeoutException;
import java.net.UnknownHostException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class Utils {

  @SuppressWarnings("rawtypes")
  private static final Consumer NO_OP_CONSUMER = o -> {};

  static final LongConsumer NO_OP_LONG_CONSUMER = someLong -> {};
  static final LongSupplier NO_OP_LONG_SUPPLIER = () -> 0;
  static final X509TrustManager TRUST_EVERYTHING_TRUST_MANAGER = new TrustEverythingTrustManager();
  private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
  private static final Map CONSTANT_LABELS;

  static final String SUBSCRIPTION_PROPERTY_SAC = "single-active-consumer";
  static final String SUBSCRIPTION_PROPERTY_SUPER_STREAM = "super-stream";
  static final String SUBSCRIPTION_PROPERTY_FILTER_PREFIX = "filter.";
  static final String SUBSCRIPTION_PROPERTY_MATCH_UNFILTERED = "match-unfiltered";

  static {
    Map labels = new HashMap<>();
    Arrays.stream(Constants.class.getDeclaredFields())
        .filter(f -> f.getName().startsWith("RESPONSE_CODE_") || f.getName().startsWith("CODE_"))
        .forEach(
            field -> {
              try {
                labels.put(
                    field.getShort(null),
                    field.getName().replace("RESPONSE_CODE_", "").replace("CODE_", ""));
              } catch (IllegalAccessException e) {
                LOGGER.info("Error while trying to access field Constants." + field.getName());
              }
            });
    CONSTANT_LABELS = Collections.unmodifiableMap(labels);
  }

  static final AddressResolver DEFAULT_ADDRESS_RESOLVER = address -> address;
  static final String DEFAULT_USERNAME = "guest";

  private Utils() {}

  @SuppressWarnings("unchecked")
  static  Consumer noOpConsumer() {
    return (Consumer) NO_OP_CONSUMER;
  }

  static Runnable makeIdempotent(Runnable action) {
    AtomicBoolean executed = new AtomicBoolean(false);
    return () -> {
      if (executed.compareAndSet(false, true)) {
        action.run();
      }
    };
  }

  static  Consumer makeIdempotent(Consumer action) {
    AtomicBoolean executed = new AtomicBoolean(false);
    return t -> {
      if (executed.compareAndSet(false, true)) {
        action.accept(t);
      }
    };
  }

  static String formatConstant(short value) {
    return value + " (" + CONSTANT_LABELS.getOrDefault(value, "UNKNOWN") + ")";
  }

  static boolean isSac(Map properties) {
    if (properties == null || properties.isEmpty()) {
      return false;
    } else {
      return "true".equals(properties.get("single-active-consumer"));
    }
  }

  static boolean filteringEnabled(Map properties) {
    if (properties == null || properties.isEmpty()) {
      return false;
    } else {
      return properties.keySet().stream()
          .anyMatch(k -> k.startsWith(SUBSCRIPTION_PROPERTY_FILTER_PREFIX));
    }
  }

  static short encodeRequestCode(Short code) {
    return code;
  }

  static short extractResponseCode(Short code) {
    return (short) (code & 0B0111_1111_1111_1111);
  }

  static short encodeResponseCode(Short code) {
    return (short) (code | 0B1000_0000_0000_0000);
  }

  static ClientFactory coordinatorClientFactory(StreamEnvironment environment) {
    String messageFormat =
        "%s. %s. "
            + "This may be due to the usage of a load balancer that makes topology discovery fail. "
            + "Use a custom AddressResolver or the --load-balancer flag if using StreamPerfTest. "
            + "See https://rabbitmq.github.io/rabbitmq-stream-java-client/stable/htmlsingle/#understanding-connection-logic "
            + "and https://blog.rabbitmq.com/posts/2021/07/connecting-to-streams/#with-a-load-balancer.";
    return context -> {
      ClientParameters parametersCopy = context.parameters().duplicate();
      Address address = new Address(parametersCopy.host(), parametersCopy.port());
      address = environment.addressResolver().resolve(address);
      parametersCopy.host(address.host()).port(address.port());

      if (context.key() == null) {
        throw new IllegalArgumentException("A key is necessary to create the client connection");
      }

      try {
        return Utils.connectToAdvertisedNodeClientFactory(
                context.key(), context1 -> new Client(context1.parameters()))
            .client(Utils.ClientFactoryContext.fromParameters(parametersCopy).key(context.key()));
      } catch (TimeoutStreamException e) {
        throw new TimeoutStreamException(
            format(messageFormat, e.getMessage(), e.getCause().getMessage(), e.getCause()));
      } catch (StreamException e) {
        if (e.getCause() != null
            && (e.getCause() instanceof UnknownHostException
                || e.getCause() instanceof ConnectTimeoutException)) {
          throw new StreamException(
              format(messageFormat, e.getMessage(), e.getCause().getMessage()), e.getCause());
        } else {
          throw e;
        }
      }
    };
  }

  static ClientFactory connectToAdvertisedNodeClientFactory(
      String expectedAdvertisedHostPort, ClientFactory clientFactory) {
    return connectToAdvertisedNodeClientFactory(
        expectedAdvertisedHostPort, clientFactory, ExactNodeRetryClientFactory.RETRY_INTERVAL);
  }

  static ClientFactory connectToAdvertisedNodeClientFactory(
      String expectedAdvertisedHostPort, ClientFactory clientFactory, Duration retryInterval) {
    return new ExactNodeRetryClientFactory(
        clientFactory,
        client -> {
          String currentKey = client.serverAdvertisedHost() + ":" + client.serverAdvertisedPort();
          boolean success = expectedAdvertisedHostPort.equals(currentKey);
          LOGGER.debug(
              "Expected client {}, got {}: {}",
              expectedAdvertisedHostPort,
              currentKey,
              success ? "success" : "failure");
          return success;
        },
        retryInterval);
  }

  static Runnable namedRunnable(Runnable task, String format, Object... args) {
    return new NamedRunnable(format(format, args), task);
  }

  static  Function namedFunction(Function task, String format, Object... args) {
    return new NamedFunction<>(format(format, args), task);
  }

  static  T callAndMaybeRetry(
      Callable operation, Predicate retryCondition, String format, Object... args) {
    return callAndMaybeRetry(
        operation,
        retryCondition,
        i -> i >= 3 ? BackOffDelayPolicy.TIMEOUT : Duration.ZERO,
        format,
        args);
  }

  static  T callAndMaybeRetry(
      Callable operation,
      Predicate retryCondition,
      BackOffDelayPolicy delayPolicy,
      String format,
      Object... args) {
    String description = format(format, args);
    int attempt = 0;
    Exception lastException = null;
    long startTime = System.nanoTime();
    boolean keepTrying = true;
    while (keepTrying) {
      try {
        attempt++;
        LOGGER.debug("Starting attempt #{} for operation '{}'", attempt, description);
        T result = operation.call();
        Duration operationDuration = Duration.ofNanos(System.nanoTime() - startTime);
        LOGGER.debug(
            "Operation '{}' completed in {} ms after {} attempt(s)",
            description,
            operationDuration.toMillis(),
            attempt);
        return result;
      } catch (Exception e) {
        lastException = e;
        if (retryCondition.test(e)) {
          LOGGER.debug("Operation '{}' failed, retrying...", description);
          Duration delay = delayPolicy.delay(attempt);
          if (BackOffDelayPolicy.TIMEOUT.equals(delay)) {
            keepTrying = false;
          } else if (!delay.isZero()) {
            try {
              Thread.sleep(delay.toMillis());
            } catch (InterruptedException ex) {
              Thread.currentThread().interrupt();
              lastException = ex;
              keepTrying = false;
            }
          }
        } else {
          keepTrying = false;
        }
      }
    }
    String message =
        format(
            "Could not complete task '%s' after %d attempt(s) (reason: %s)",
            description, attempt, exceptionMessage(lastException));
    LOGGER.debug(message);
    if (lastException == null) {
      throw new StreamException(message);
    } else if (lastException instanceof RuntimeException) {
      throw (RuntimeException) lastException;
    } else {
      throw new StreamException(message, lastException);
    }
  }

  static String exceptionMessage(Exception e) {
    if (e == null) {
      return "unknown";
    } else if (e.getMessage() == null) {
      return e.getClass().getSimpleName();
    } else {
      return e.getMessage() + " [" + e.getClass().getSimpleName() + "]";
    }
  }

  interface ClientFactory {

    Client client(ClientFactoryContext context);
  }

  static class ExactNodeRetryClientFactory implements ClientFactory {

    private static final Duration RETRY_INTERVAL = Duration.ofSeconds(1);

    private final ClientFactory delegate;
    private final Predicate condition;
    private final Duration retryInterval;

    ExactNodeRetryClientFactory(
        ClientFactory delegate, Predicate condition, Duration retryInterval) {
      this.delegate = delegate;
      this.condition = condition;
      this.retryInterval = retryInterval;
    }

    @Override
    public Client client(ClientFactoryContext context) {
      while (true) {
        Client client = this.delegate.client(context);
        if (condition.test(client)) {
          return client;
        } else {
          try {
            client.close();
          } catch (Exception e) {
            LOGGER.warn("Error while trying to close client", e);
          }
        }
        try {
          Thread.sleep(this.retryInterval.toMillis());
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          return null;
        }
      }
    }
  }

  static class ClientFactoryContext {

    private ClientParameters parameters;
    private String key;

    static ClientFactoryContext fromParameters(ClientParameters parameters) {
      return new ClientFactoryContext().parameters(parameters);
    }

    ClientParameters parameters() {
      return parameters;
    }

    ClientFactoryContext parameters(ClientParameters parameters) {
      this.parameters = parameters;
      return this;
    }

    String key() {
      return key;
    }

    ClientFactoryContext key(String key) {
      this.key = key;
      return this;
    }
  }

  private static class TrustEverythingTrustManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {}

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) {}

    @Override
    public X509Certificate[] getAcceptedIssuers() {
      return new X509Certificate[0];
    }
  }

  enum ClientConnectionType {
    CONSUMER,
    PRODUCER,
    LOCATOR
  }

  static Function defaultConnectionNamingStrategy(String prefix) {
    Map sequences =
        new ConcurrentHashMap<>(ClientConnectionType.values().length);
    Map prefixes =
        new ConcurrentHashMap<>(ClientConnectionType.values().length);
    for (ClientConnectionType type : ClientConnectionType.values()) {
      sequences.put(type, new AtomicLong(0));
      prefixes.put(type, prefix + type.name().toLowerCase(Locale.ENGLISH) + "-");
    }
    return clientConnectionType ->
        prefixes.get(clientConnectionType) + sequences.get(clientConnectionType).getAndIncrement();
  }

  /*
  class to help testing SAC on super streams
   */
  static class CompositeConsumerUpdateListener implements ConsumerUpdateListener {

    private final List delegates = new CopyOnWriteArrayList<>();

    @Override
    public OffsetSpecification update(Context context) {
      OffsetSpecification result = null;
      for (ConsumerUpdateListener delegate : delegates) {
        OffsetSpecification offsetSpecification = delegate.update(context);
        if (offsetSpecification != null) {
          result = offsetSpecification;
        }
      }
      return result;
    }

    void add(ConsumerUpdateListener delegate) {
      this.delegates.add(delegate);
    }

    CompositeConsumerUpdateListener duplicate() {
      CompositeConsumerUpdateListener duplica = new CompositeConsumerUpdateListener();
      for (ConsumerUpdateListener delegate : this.delegates) {
        duplica.add(delegate);
      }
      return duplica;
    }
  }

  static boolean offsetBefore(long x, long y) {
    return Long.compareUnsigned(x, y) < 0;
  }

  private static String currentVersion(String currentVersion) {
    // versions built from source: 3.7.0+rc.1.4.gedc5d96
    if (currentVersion.contains("+")) {
      currentVersion = currentVersion.substring(0, currentVersion.indexOf("+"));
    }
    // alpha (snapshot) versions: 3.7.0~alpha.449-1
    if (currentVersion.contains("~")) {
      currentVersion = currentVersion.substring(0, currentVersion.indexOf("~"));
    }
    // alpha (snapshot) versions: 3.7.1-alpha.40
    if (currentVersion.contains("-")) {
      currentVersion = currentVersion.substring(0, currentVersion.indexOf("-"));
    }
    return currentVersion;
  }

  /**
   * https://stackoverflow.com/questions/6701948/efficient-way-to-compare-version-strings-in-java
   */
  static int versionCompare(String str1, String str2) {
    String[] vals1 = str1.split("\\.");
    String[] vals2 = str2.split("\\.");
    int i = 0;
    // set index to first non-equal ordinal or length of shortest version string
    while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) {
      i++;
    }
    // compare first non-equal ordinal number
    if (i < vals1.length && i < vals2.length) {
      int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i]));
      return Integer.signum(diff);
    }
    // the strings are equal or one string is a substring of the other
    // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4"
    return Integer.signum(vals1.length - vals2.length);
  }

  static boolean is3_11_OrMore(String brokerVersion) {
    return versionCompare(currentVersion(brokerVersion), "3.11.0") >= 0;
  }

  static StreamException convertCodeToException(
      short responseCode, String stream, Supplier fallbackMessage) {
    if (responseCode == Constants.RESPONSE_CODE_STREAM_DOES_NOT_EXIST) {
      return new StreamDoesNotExistException(stream);
    } else if (responseCode == Constants.RESPONSE_CODE_STREAM_NOT_AVAILABLE) {
      return new StreamNotAvailableException(stream);
    } else {
      return new StreamException(fallbackMessage.get(), responseCode);
    }
  }

  private static class NamedRunnable implements Runnable {

    private final String name;
    private final Runnable delegate;

    private NamedRunnable(String name, Runnable delegate) {
      this.name = name;
      this.delegate = delegate;
    }

    @Override
    public void run() {
      this.delegate.run();
    }

    @Override
    public String toString() {
      return this.name;
    }
  }

  private static class NamedFunction implements Function {

    private final String name;
    private final Function delegate;

    private NamedFunction(String name, Function delegate) {
      this.name = name;
      this.delegate = delegate;
    }

    @Override
    public R apply(T t) {
      return this.delegate.apply(t);
    }

    @Override
    public String toString() {
      return this.name;
    }
  }

  static String quote(String value) {
    if (value == null) {
      return "null";
    } else {
      return "\"" + value + "\"";
    }
  }

  static String jsonField(String name, Number value) {
    return quote(name) + " : " + value.longValue();
  }

  static String jsonField(String name, String value) {
    return quote(name) + " : " + quote(value);
  }

  static class NamedThreadFactory implements ThreadFactory {

    private final ThreadFactory backingThreaFactory;

    private final String prefix;

    private final AtomicLong count = new AtomicLong(0);

    public NamedThreadFactory(String prefix) {
      this(Executors.defaultThreadFactory(), prefix);
    }

    public NamedThreadFactory(ThreadFactory backingThreadFactory, String prefix) {
      this.backingThreaFactory = backingThreadFactory;
      this.prefix = prefix;
    }

    @Override
    public Thread newThread(Runnable r) {
      Thread thread = this.backingThreaFactory.newThread(r);
      thread.setName(prefix + count.getAndIncrement());
      return thread;
    }
  }

  static final ExecutorServiceFactory NO_OP_EXECUTOR_SERVICE_FACTORY =
      new NoOpExecutorServiceFactory();

  static class NoOpExecutorServiceFactory implements ExecutorServiceFactory {

    private final ExecutorService executorService = new NoOpExecutorService();

    @Override
    public ExecutorService get() {
      return executorService;
    }

    @Override
    public void clientClosed(ExecutorService executorService) {}

    @Override
    public void close() {}
  }

  private static class NoOpExecutorService implements ExecutorService {

    @Override
    public void shutdown() {}

    @Override
    public List shutdownNow() {
      return null;
    }

    @Override
    public boolean isShutdown() {
      return false;
    }

    @Override
    public boolean isTerminated() {
      return false;
    }

    @Override
    public boolean awaitTermination(long timeout, TimeUnit unit) {
      return false;
    }

    @Override
    public  Future submit(Callable task) {
      return null;
    }

    @Override
    public  Future submit(Runnable task, T result) {
      return null;
    }

    @Override
    public Future submit(Runnable task) {
      return null;
    }

    @Override
    public  List> invokeAll(Collection> tasks) {
      return null;
    }

    @Override
    public  List> invokeAll(
        Collection> tasks, long timeout, TimeUnit unit) {
      return null;
    }

    @Override
    public  T invokeAny(Collection> tasks) {
      return null;
    }

    @Override
    public  T invokeAny(Collection> tasks, long timeout, TimeUnit unit) {
      return null;
    }

    @Override
    public void execute(Runnable command) {}
  }

  static class MutableBoolean {

    private boolean value;

    MutableBoolean(boolean initialValue) {
      this.value = initialValue;
    }

    void set(boolean value) {
      this.value = value;
    }

    boolean get() {
      return this.value;
    }
  }

  static  T lock(Lock lock, Supplier action) {
    lock.lock();
    try {
      return action.get();
    } finally {
      lock.unlock();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy