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

com.datastax.oss.pulsar.jms.Utils Maven / Gradle / Ivy

There is a newer version: 7.0.2
Show newest version
/*
 * Copyright DataStax, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datastax.oss.pulsar.jms;

import jakarta.jms.IllegalStateException;
import jakarta.jms.IllegalStateRuntimeException;
import jakarta.jms.InvalidClientIDException;
import jakarta.jms.InvalidClientIDRuntimeException;
import jakarta.jms.InvalidDestinationException;
import jakarta.jms.InvalidDestinationRuntimeException;
import jakarta.jms.InvalidSelectorException;
import jakarta.jms.InvalidSelectorRuntimeException;
import jakarta.jms.JMSException;
import jakarta.jms.JMSRuntimeException;
import jakarta.jms.JMSSecurityException;
import jakarta.jms.JMSSecurityRuntimeException;
import jakarta.jms.MessageFormatException;
import jakarta.jms.MessageFormatRuntimeException;
import jakarta.jms.MessageNotWriteableException;
import jakarta.jms.MessageNotWriteableRuntimeException;
import jakarta.jms.TransactionRolledBackException;
import jakarta.jms.TransactionRolledBackRuntimeException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.pulsar.client.api.MessageId;
import org.apache.pulsar.client.api.MessageIdAdv;

@Slf4j
public final class Utils {
  private Utils() {}

  public static JMSException handleException(Throwable cause) {
    while (cause instanceof CompletionException) {
      cause = cause.getCause();
    }
    if (cause instanceof JMSException) {
      return (JMSException) cause;
    }
    if (cause instanceof InterruptedException) {
      Thread.currentThread().interrupt();
    }
    if (cause instanceof ClassCastException) {
      return (JMSException)
          new MessageFormatException("Invalid cast " + cause.getMessage()).initCause(cause);
    }
    if (cause instanceof NumberFormatException) {
      return (JMSException)
          new MessageFormatException("Invalid conversion " + cause.getMessage()).initCause(cause);
    }
    JMSException err = new JMSException(cause + "");
    err.initCause(cause);
    if (cause instanceof Exception) {
      err.setLinkedException((Exception) cause);
    } else {
      err.setLinkedException(new Exception(cause));
    }
    return err;
  }

  public static  T get(CompletableFuture future) throws JMSException {
    try {
      return future.get();
    } catch (ExecutionException err) {
      throw handleException(err.getCause());
    } catch (InterruptedException err) {
      throw handleException(err);
    }
  }

  public interface SupplierWithException {
    T run() throws Exception;
  }

  public interface RunnableWithException {
    void run() throws Exception;
  }

  public static  T invoke(SupplierWithException code) throws JMSException {
    try {
      return code.run();
    } catch (Throwable err) {
      throw handleException(err);
    }
  }

  public static void invoke(RunnableWithException code) throws JMSException {
    try {
      code.run();
    } catch (Throwable err) {
      throw handleException(err);
    }
  }

  public static boolean executeMessageListenerInSessionContext(
      PulsarSession session, PulsarMessageConsumer consumer, BooleanSupplier code) {
    currentSession.set(new CallbackContext(session, consumer, null));
    try {
      return session.executeCriticalOperation(
          () -> {
            return code.getAsBoolean();
          });
    } catch (IllegalStateException err) {
      log.debug("Ignore error in listener", err);
      return false;
    } catch (JMSException err) {
      log.error("Unexpected error in listener", err);
      return false;
    } finally {
      currentSession.remove();
    }
  }

  public static void executeCompletionListenerInSessionContext(
      PulsarSession session, PulsarMessageProducer producer, Runnable code) {
    currentSession.set(new CallbackContext(session, null, producer));
    try {
      session.executeCriticalOperation(
          () -> {
            code.run();
            return null;
          });
    } catch (IllegalStateException err) {
      log.debug("Ignore error in listener", err);
    } catch (JMSException err) {
      log.error("Unexpected error in listener", err);
    } finally {
      currentSession.remove();
    }
  }

  public static boolean isOnMessageListener(PulsarSession session, PulsarMessageConsumer consumer) {
    CallbackContext current = currentSession.get();
    return current != null && current.session == session && current.consumer == consumer;
  }

  public static void checkNotOnMessageListener(PulsarSession session) throws JMSException {
    CallbackContext current = currentSession.get();
    if (current != null && current.session == session && current.consumer != null) {
      throw new IllegalStateException("Cannot call this method inside a listener");
    }
  }

  public static void checkNotOnSessionCallback(PulsarSession session) throws JMSException {
    CallbackContext current = currentSession.get();
    if (current != null && current.session == session) {
      throw new IllegalStateException("Cannot call this method inside a callback");
    }
  }

  public static void checkNotOnMessageProducer(
      PulsarSession session, PulsarMessageProducer producer) throws JMSException {
    CallbackContext current = currentSession.get();
    if (current != null
        && current.session == session
        && ((producer != null && current.producer == producer)
            || // specific producer
            (producer == null && current.producer != null))) // any producer
    {
      throw new IllegalStateException("Cannot call this method inside a CompletionListener");
    }
  }

  private static class CallbackContext {
    final PulsarSession session;
    final PulsarMessageConsumer consumer;
    final PulsarMessageProducer producer;

    private CallbackContext(
        PulsarSession session, PulsarMessageConsumer consumer, PulsarMessageProducer producer) {
      this.session = session;
      this.consumer = consumer;
      this.producer = producer;
    }
  }

  private static ThreadLocal currentSession = new ThreadLocal();

  public static void noException(RunnableWithException run) {
    try {
      run.run();
    } catch (Exception err) {
      if (err instanceof InterruptedException) {
        Thread.currentThread().interrupt();
      }
      throw new RuntimeException(err);
    }
  }

  public static  T noException(SupplierWithException run) {
    try {
      return run.run();
    } catch (Exception err) {
      if (err instanceof InterruptedException) {
        Thread.currentThread().interrupt();
      }
      throw new RuntimeException(err);
    }
  }

  public static  T runtimeException(SupplierWithException run) {
    try {
      return run.run();
    } catch (Exception err) {
      if (err instanceof InterruptedException) {
        Thread.currentThread().interrupt();
      }
      throwAsRuntimeException(err);
      return null;
    }
  }

  public static void runtimeException(RunnableWithException run) {
    try {
      run.run();
    } catch (Exception err) {
      if (err instanceof InterruptedException) {
        Thread.currentThread().interrupt();
      }
      throwAsRuntimeException(err);
    }
  }

  private static void throwAsRuntimeException(Exception err) {
    if (err instanceof NumberFormatException) {
      throw (MessageFormatRuntimeException)
          new MessageFormatRuntimeException("Illegal value: " + err.getMessage()).initCause(err);
    }
    if (err instanceof IllegalStateException) {
      IllegalStateException jmsException = (IllegalStateException) err;
      throw new IllegalStateRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof TransactionRolledBackException) {
      TransactionRolledBackException jmsException = (TransactionRolledBackException) err;
      throw new TransactionRolledBackRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof InvalidDestinationException) {
      InvalidDestinationException jmsException = (InvalidDestinationException) err;
      throw new InvalidDestinationRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof InvalidClientIDException) {
      InvalidClientIDException jmsException = (InvalidClientIDException) err;
      throw new InvalidClientIDRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof InvalidSelectorException) {
      InvalidSelectorException jmsException = (InvalidSelectorException) err;
      throw new InvalidSelectorRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof MessageFormatException) {
      MessageFormatException jmsException = (MessageFormatException) err;
      throw new MessageFormatRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof MessageNotWriteableException) {
      MessageNotWriteableException jmsException = (MessageNotWriteableException) err;
      throw new MessageNotWriteableRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof JMSSecurityException) {
      JMSSecurityException jmsException = (JMSSecurityException) err;
      throw new JMSSecurityRuntimeException(
          jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    if (err instanceof JMSException) {
      JMSException jmsException = (JMSException) err;
      throw new JMSRuntimeException(jmsException.getMessage(), jmsException.getErrorCode(), err);
    }
    JMSRuntimeException jms = new JMSRuntimeException("Generic error " + err.getMessage());
    jms.initCause(err);
    throw jms;
  }

  private static List deepCopyList(List configuration) {
    return (List) configuration.stream().map(f -> deepCopyObject(f)).collect(Collectors.toList());
  }

  private static Set deepCopySet(Set configuration) {
    return (Set) configuration.stream().map(f -> deepCopyObject(f)).collect(Collectors.toSet());
  }

  public static Object deepCopyObject(Object value) {
    if (value == null) {
      return null;
    }
    if (value instanceof Map) {
      return deepCopyMap((Map) value);
    }
    if (value instanceof List) {
      return deepCopyList((List) value);
    }
    if (value instanceof Set) {
      return deepCopySet((Set) value);
    }
    return value;
  }

  public static Map deepCopyMap(Map configuration) {
    if (configuration == null) {
      return null;
    }
    Map copy = new HashMap<>();
    configuration.forEach(
        (key, value) -> {
          copy.put(key, deepCopyObject(value));
        });
    return copy;
  }

  public static String getAndRemoveString(
      String name, String defaultValue, Map properties) {
    Object value = (Object) properties.remove(name);
    return value != null ? value.toString() : defaultValue;
  }

  public static boolean sameEntryId(MessageId a, MessageId b) {
    // get rid of TopicMessageIdImpl
    MessageIdAdv a1 = (MessageIdAdv) a;
    MessageIdAdv b1 = (MessageIdAdv) b;
    return a1.getLedgerId() == b1.getLedgerId() && a1.getEntryId() == b1.getEntryId();
  }

  /**
   * Map the JMS Priority to a partition.
   *
   * @param priority
   * @param numPartitions
   * @return the partition id
   */
  public static int mapPriorityToPartition(int priority, int numPartitions, boolean linear) {
    if (linear) {
      return mapPriorityToPartitionLinearly(priority, numPartitions);
    } else {
      return mapPriorityToPartitionNonLinearly(priority, numPartitions);
    }
  }

  static int mapPriorityToPartitionLinearly(int priority, int numPartitions) {
    if (numPartitions <= 1) {
      return 0;
    }
    if (numPartitions == 2) {
      if (priority <= PulsarMessage.DEFAULT_PRIORITY) {
        return 0;
      } else {
        return 1;
      }
    }
    if (numPartitions == 3) {
      if (priority < PulsarMessage.DEFAULT_PRIORITY) {
        return 0;
      } else if (priority == PulsarMessage.DEFAULT_PRIORITY) {
        return 1;
      } else {
        return 2;
      }
    }
    if (priority < 0) {
      priority = 0;
    } else if (priority > 9) {
      priority = 9;
    }

    // from 0 to 9
    double bucketSize = numPartitions / 10.0;
    double start = Math.floor(bucketSize * priority);
    double value = (start + ThreadLocalRandom.current().nextDouble(bucketSize));
    int result = (int) Math.floor(value);
    if (result >= numPartitions) {
      return numPartitions - 1;
    }
    return result;
  }

  static int mapPriorityToPartitionNonLinearly(int priority, int numPartitions) {
    if (numPartitions <= 1) {
      return 0;
    }
    if (priority < 0) {
      priority = 0;
    } else if (priority > 9) {
      priority = 9;
    }
    double bucketSize = numPartitions / 4.0;
    if (bucketSize <= 0) {
      bucketSize = 1;
    }
    double bucketStart;
    int slots;

    switch (priority) {
      case 0:
      case 1:
      case 2:
      case 3:
        // low priority, 1/4 of the partitions
        bucketStart = 0;
        slots = 1;
        if (numPartitions <= 3) {
          return 0;
        }
        break;
      case 4:
        // mid-priority, 1/2 of the partitions
        bucketStart = Math.ceil(bucketSize);
        if (numPartitions <= 2) {
          return 0;
        } else if (numPartitions == 3) {
          return 1;
        } else {
          slots = 2;
        }
        break;
      case 5:
      case 6:
      case 7:
      case 8:
      case 9:
        // high priority, 1/4 of the partitions
        if (numPartitions <= 3) {
          return numPartitions - 1;
        }
        bucketStart = Math.ceil(bucketSize * 3);
        slots = 1;
        break;
      default:
        throw new java.lang.IllegalStateException();
    }

    double value = (bucketStart + ThreadLocalRandom.current().nextDouble(bucketSize * slots));
    int result = (int) Math.floor(value);
    if (result >= numPartitions) {
      return numPartitions - 1;
    }
    return result;
  }

  public static Map buildConfigurationOverride(PulsarDestination destination)
      throws InvalidDestinationException {
    String queryString = destination.getQueryString();
    if (queryString == null || queryString.isEmpty()) {
      return null;
    }
    try {
      String[] split = queryString.split("&");
      Map result = new HashMap<>();
      for (String s : split) {
        int equals = s.indexOf("=");
        String key;
        String value;
        if (equals >= 0) {
          key = URLDecoder.decode(s.substring(0, equals), "UTF-8");
          value = URLDecoder.decode(s.substring(equals + 1), "UTF-8");
        } else {
          key = URLDecoder.decode(s, "UTF-8");
          value = "";
        }
        String[] path = key.split("\\.");
        putHierichical(path, value, result, 0);
      }
      if (result.isEmpty()) {
        return null;
      }
      return result;
    } catch (UnsupportedEncodingException impossible) {
      throw new RuntimeException(impossible);
    }
  }

  private static void putHierichical(
      String[] path, String value, Map result, int pos)
      throws InvalidDestinationException {
    String key = path[pos];
    if (pos == path.length - 1) {
      result.put(key, value);
      return;
    }
    Object current = result.get(key);
    if (current == null) {
      current = new HashMap<>();
      result.put(key, current);
    } else if (!(current instanceof Map)) {
      throw new InvalidDestinationException(
          "Cannot build a configuration out of the destination query string");
    }
    Map subMap = (Map) current;
    putHierichical(path, value, subMap, pos + 1);
  }

  public static ConsumerConfiguration computeConsumerOverrideConfiguration(
      PulsarDestination destination) throws InvalidDestinationException {
    Map result = buildConfigurationOverride(destination);
    Map consumerConfig =
        result != null ? (Map) result.get("consumerConfig") : null;
    return ConsumerConfiguration.buildConsumerConfiguration(consumerConfig);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy