content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements. See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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 org.apache.activemq.artemis.jms.client;
  
  import javax.jms.Destination;
  import javax.jms.JMSException;
  import javax.jms.JMSRuntimeException;
  import java.io.Serializable;
  import java.util.Objects;
  import java.util.Properties;
  import java.util.UUID;
  
  import org.apache.activemq.artemis.api.core.Pair;
  import org.apache.activemq.artemis.api.core.ParameterisedAddress;
  import org.apache.activemq.artemis.api.core.QueueAttributes;
  import org.apache.activemq.artemis.api.core.QueueConfiguration;
  import org.apache.activemq.artemis.api.core.RoutingType;
  import org.apache.activemq.artemis.api.core.SimpleString;
  import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
  import org.apache.activemq.artemis.jndi.JNDIStorable;
  import org.apache.activemq.artemis.utils.DestinationUtil;
  
  /**
   * ActiveMQ Artemis implementation of a JMS Destination.
   */
  public class ActiveMQDestination extends JNDIStorable implements Destination, Serializable {
  
  
     private static final long serialVersionUID = 5027962425462382883L;
  
     public static final String QUEUE_QUALIFIED_PREFIX = DestinationUtil.QUEUE_QUALIFIED_PREFIX;
     public static final String TOPIC_QUALIFIED_PREFIX = DestinationUtil.TOPIC_QUALIFIED_PREFIX;
     public static final String TEMP_QUEUE_QUALIFED_PREFIX = DestinationUtil.TEMP_QUEUE_QUALIFED_PREFIX;
     public static final String TEMP_TOPIC_QUALIFED_PREFIX = DestinationUtil.TEMP_TOPIC_QUALIFED_PREFIX;
  
     /**
      * {@code createQueue} and {@code createTopic} from {@link ActiveMQSession} may change the name in case Prefix usage
      */
     void setName(String name) {
        this.name = name;
     }
  
     public static ActiveMQDestination createDestination(RoutingType routingType, SimpleString address) {
        if (address == null) {
           return null;
        } else if (RoutingType.ANYCAST.equals(routingType)) {
           return ActiveMQDestination.createQueue(address);
        } else if (RoutingType.MULTICAST.equals(routingType)) {
           return ActiveMQDestination.createTopic(address);
        } else {
           return ActiveMQDestination.fromPrefixedName(address.toString());
        }
     }
  
     /**
      * Static helper method for working with destinations.
      */
     public static ActiveMQDestination createDestination(String name, TYPE defaultType) {
        if (name.startsWith(QUEUE_QUALIFIED_PREFIX)) {
           return new ActiveMQQueue(name.substring(QUEUE_QUALIFIED_PREFIX.length()));
        } else if (name.startsWith(TOPIC_QUALIFIED_PREFIX)) {
           return new ActiveMQTopic(name.substring(TOPIC_QUALIFIED_PREFIX.length()));
        } else if (name.startsWith(TEMP_QUEUE_QUALIFED_PREFIX)) {
           return new ActiveMQQueue(name.substring(TEMP_QUEUE_QUALIFED_PREFIX.length()), true);
        } else if (name.startsWith(TEMP_TOPIC_QUALIFED_PREFIX)) {
           return new ActiveMQTopic(name.substring(TEMP_TOPIC_QUALIFED_PREFIX.length()), true);
        }
  
        return switch (defaultType) {
           case QUEUE -> new ActiveMQQueue(name);
           case TOPIC -> new ActiveMQTopic(name);
           case TEMP_QUEUE -> new ActiveMQQueue(name, true);
           case TEMP_TOPIC -> new ActiveMQTopic(name, true);
           case DESTINATION -> new ActiveMQDestination(name, TYPE.DESTINATION, null);
           default -> throw new IllegalArgumentException("Invalid default destination type: " + defaultType);
        };
     }
  
     public static ActiveMQDestination fromPrefixedName(final String name) {
        return fromPrefixedName(name, name);
     }
  
     public static ActiveMQDestination fromPrefixedName(final String addr, final String name) {
  
        ActiveMQDestination destination;
        if (addr.startsWith(ActiveMQDestination.QUEUE_QUALIFIED_PREFIX)) {
           String address = addr.substring(ActiveMQDestination.QUEUE_QUALIFIED_PREFIX.length());
           destination = createQueue(address);
        } else if (addr.startsWith(ActiveMQDestination.TOPIC_QUALIFIED_PREFIX)) {
           String address = addr.substring(ActiveMQDestination.TOPIC_QUALIFIED_PREFIX.length());
           destination = createTopic(address);
        } else if (addr.startsWith(ActiveMQDestination.TEMP_QUEUE_QUALIFED_PREFIX)) {
           String address = addr.substring(ActiveMQDestination.TEMP_QUEUE_QUALIFED_PREFIX.length());
           destination = new ActiveMQTemporaryQueue(address, null);
        } else if (addr.startsWith(ActiveMQDestination.TEMP_TOPIC_QUALIFED_PREFIX)) {
           String address = addr.substring(ActiveMQDestination.TEMP_TOPIC_QUALIFED_PREFIX.length());
           destination = new ActiveMQTemporaryTopic(address, null);
        } else {
           destination = new ActiveMQDestination(addr, TYPE.DESTINATION, null);
        }
  
        return destination;
     }
  
     public static Destination fromPrefixed1XName(final String addr, final String name) {
        ActiveMQDestination destination;
        if (addr.startsWith(PacketImpl.OLD_QUEUE_PREFIX.toString())) {
           destination = createQueue(addr);
        } else if (addr.startsWith(PacketImpl.OLD_TOPIC_PREFIX.toString())) {
           destination = createTopic(addr);
        } else if (addr.startsWith(PacketImpl.OLD_TEMP_QUEUE_PREFIX.toString())) {
           destination = new ActiveMQTemporaryQueue(addr, null);
        } else if (addr.startsWith(PacketImpl.OLD_TEMP_TOPIC_PREFIX.toString())) {
           destination = new ActiveMQTemporaryTopic(addr, null);
        } else {
           destination = new ActiveMQDestination(addr, TYPE.DESTINATION, null);
        }
  
        String unprefixedName = name;
  
        if (name.startsWith(PacketImpl.OLD_QUEUE_PREFIX.toString())) {
           unprefixedName = name.substring(PacketImpl.OLD_QUEUE_PREFIX.length());
        } else if (name.startsWith(PacketImpl.OLD_TOPIC_PREFIX.toString())) {
           unprefixedName = name.substring(PacketImpl.OLD_TOPIC_PREFIX.length());
        } else if (name.startsWith(PacketImpl.OLD_TEMP_QUEUE_PREFIX.toString())) {
           unprefixedName = name.substring(PacketImpl.OLD_TEMP_QUEUE_PREFIX.length());
        } else if (name.startsWith(PacketImpl.OLD_TEMP_TOPIC_PREFIX.toString())) {
           unprefixedName = name.substring(PacketImpl.OLD_TEMP_TOPIC_PREFIX.length());
        }
  
        destination.setName(unprefixedName);
  
        return destination;
     }
  
  
  
  
     public static SimpleString createQueueNameForSubscription(final boolean isDurable,
                                                         final String clientID,
                                                         final String subscriptionName) {
        return DestinationUtil.createQueueNameForSubscription(isDurable, clientID, subscriptionName);
     }
  
     public static String createQueueNameForSharedSubscription(final boolean isDurable,
                                                               final String clientID,
                                                               final String subscriptionName) {
        return DestinationUtil.createQueueNameForSharedSubscription(isDurable, clientID, subscriptionName);
     }
  
     public static Pair<String, String> decomposeQueueNameForDurableSubscription(final String queueName) {
        StringBuilder[] parts = new StringBuilder[2];
        int currentPart = 0;
  
        parts[0] = new StringBuilder();
        parts[1] = new StringBuilder();
  
        int pos = 0;
        while (pos < queueName.length()) {
           char ch = queueName.charAt(pos);
           pos++;
  
           if (ch == DestinationUtil.SEPARATOR) {
              currentPart++;
              if (currentPart >= parts.length) {
                 throw new JMSRuntimeException("Invalid message queue name: " + queueName);
              }
  
              continue;
           }
  
           if (ch == '\\') {
              if (pos >= queueName.length()) {
                 throw new JMSRuntimeException("Invalid message queue name: " + queueName);
              }
              ch = queueName.charAt(pos);
              pos++;
           }
  
           parts[currentPart].append(ch);
        }
  
        if (currentPart != 1) {
           /*
            * JMS 2.0 introduced the ability to create "shared" subscriptions which do not require a clientID.
            * In this case the subscription name will be the same as the queue name, but the above algorithm will put that
            * in the wrong position in the array so we need to move it.
            */
           parts[1] = parts[0];
           parts[0] = new StringBuilder();
        }
  
        Pair<String, String> pair = new Pair<>(parts[0].toString(), parts[1].toString());
  
        return pair;
     }
  
     public static SimpleString createQueueAddressFromName(final String name) {
        return SimpleString.of(QUEUE_QUALIFIED_PREFIX + name);
     }
  
     public static SimpleString createTopicAddressFromName(final String name) {
        return SimpleString.of(TOPIC_QUALIFIED_PREFIX + name);
     }
  
     public static ActiveMQQueue createQueue(final String address) {
        return new ActiveMQQueue(address);
     }
  
     public static ActiveMQQueue createQueue(final SimpleString address) {
        return new ActiveMQQueue(address);
     }
  
     public static ActiveMQQueue createQueue(final String address, final String name) {
        return new ActiveMQQueue(address, name);
     }
  
     public static ActiveMQTopic createTopic(final String address) {
        return new ActiveMQTopic(address);
     }
  
     public static ActiveMQTopic createTopic(final SimpleString address) {
        return new ActiveMQTopic(address);
     }
  
     public static ActiveMQTopic createTopic(final String address, final String name) {
        return new ActiveMQTopic(address, name);
     }
  
     public static ActiveMQTemporaryQueue createTemporaryQueue(final String address, final ActiveMQSession session) {
        return new ActiveMQTemporaryQueue(address, session);
     }
  
     public static ActiveMQTemporaryQueue createTemporaryQueue(final String address) {
        return createTemporaryQueue(address, null);
     }
  
     public static ActiveMQTemporaryQueue createTemporaryQueue(final ActiveMQSession session) {
        String address = UUID.randomUUID().toString();
  
        return createTemporaryQueue(address, session);
     }
  
     public static ActiveMQTemporaryTopic createTemporaryTopic(final ActiveMQSession session) {
        String address = UUID.randomUUID().toString();
  
        return createTemporaryTopic(address, session);
     }
  
     public static ActiveMQTemporaryQueue createTemporaryQueue(final ActiveMQSession session, final String prefix) {
        String name = UUID.randomUUID().toString();
        String address = prefix + name;
  
        ActiveMQTemporaryQueue queue = createTemporaryQueue(address, session);
        queue.setName(name);
        return queue;
     }
  
     public static ActiveMQTemporaryTopic createTemporaryTopic(final ActiveMQSession session, final String prefix) {
        String name = UUID.randomUUID().toString();
        String address = prefix + name;
  
        ActiveMQTemporaryTopic topic  = createTemporaryTopic(address, session);
        topic.setName(name);
        return topic;
     }
  
     public static ActiveMQTemporaryTopic createTemporaryTopic(String address, final ActiveMQSession session) {
        return new ActiveMQTemporaryTopic(address, session);
     }
  
     public static ActiveMQTemporaryTopic createTemporaryTopic(String address) {
        return createTemporaryTopic(address, null);
     }
  
  
     /**
      * The core address
      */
     private SimpleString simpleAddress;
  
     private QueueConfiguration queueConfiguration;
  
     /**
      * Needed for serialization backwards compatibility.
      */
     @Deprecated
     private String address;
  
     private String name;
  
     private final boolean temporary;
  
     private final boolean queue;
  
     private transient TYPE thetype;
  
     private final transient ActiveMQSession session;
  
     private transient boolean created;
  
  
     protected ActiveMQDestination(final String address,
                                   final TYPE type,
                                   final ActiveMQSession session) {
        this(SimpleString.of(address), type, session);
     }
  
     protected ActiveMQDestination(final SimpleString address,
                                   final TYPE type,
                                   final ActiveMQSession session) {
  
        if (address != null) {
           setSimpleAddress(address);
        }
  
        this.thetype = type;
  
        this.session = session;
  
        this.temporary = TYPE.isTemporary(type);
  
        this.queue = TYPE.isQueue(type);
     }
  
     protected ActiveMQDestination(final String address,
                                   final String name,
                                   final TYPE type,
                                   final ActiveMQSession session) {
        this(SimpleString.of(address), name, type, session);
     }
  
     @Deprecated
     protected ActiveMQDestination(final SimpleString address,
                                   final String name,
                                   final TYPE type,
                                   final ActiveMQSession session) {
        this(address, type, session);
  
        this.name = name;
  
        this.address = Objects.toString(simpleAddress, null);
     }
  
     public void setAddress(String address) {
        setSimpleAddress(SimpleString.of(address));
     }
  
     @Override
     public String toString() {
        return "ActiveMQDestination [address=" + simpleAddress.toString() +
           ", name=" +
           name +
           ", type =" +
           thetype +
           "]";
     }
  
     public void setSimpleAddress(SimpleString address) {
        if (address == null) {
           throw new IllegalArgumentException("address cannot be null");
        }
        if (ParameterisedAddress.isParameterised(address)) {
           ParameterisedAddress parameteredAddress = new ParameterisedAddress(address);
           this.simpleAddress = parameteredAddress.getAddress();
           this.address = parameteredAddress.getAddress().toString();
           this.queueConfiguration = parameteredAddress.getQueueConfiguration();
        } else {
           this.simpleAddress = address;
           this.address = address.toString();
           this.queueConfiguration = null;
        }
     }
  
     public void setSimpleAddress(String address) {
        setSimpleAddress(SimpleString.of(address));
     }
  
     public void delete() throws JMSException {
        if (session != null) {
           boolean openedHere = false;
           ActiveMQSession sessionToUse = session;
  
           if (session.getCoreSession().isClosed()) {
              sessionToUse = (ActiveMQSession)session.getConnection().createSession();
              openedHere = true;
           }
  
           try {
              /*
               * The status of the session used to create the temporary destination is uncertain, but the JMS spec states
               * that the lifetime of the temporary destination is tied to the connection so even if the originating
               * session is closed the temporary destination should still be deleted. Therefore, just create a new one
               * and close it after the temporary destination is deleted. This is necessary because the Core API is
               * predicated on having a Core ClientSession which is encapsulated by the JMS session implementation.
               */
              if (isQueue()) {
                 sessionToUse.deleteTemporaryQueue(this);
              } else {
                 sessionToUse.deleteTemporaryTopic(this);
              }
              setCreated(false);
           } finally {
              if (openedHere) {
                 sessionToUse.close();
              }
           }
        }
     }
  
     public boolean isQueue() {
        return queue;
     }
  
  
     public String getAddress() {
        return Objects.toString(simpleAddress, null);
     }
  
     public SimpleString getSimpleAddress() {
        return simpleAddress;
     }
  
     @Deprecated
     public QueueAttributes getQueueAttributes() {
        return QueueAttributes.fromQueueConfiguration(queueConfiguration);
     }
  
     public QueueConfiguration getQueueConfiguration() {
        return queueConfiguration;
     }
  
     public String getName() {
        return name != null ? name : getAddress();
     }
  
     public boolean isTemporary() {
        return temporary;
     }
  
     public boolean getCreated() {
        return created;
     }
  
     public boolean isCreated() {
        return created;
     }
  
     public void setCreated(boolean created) {
        this.created = created;
     }
  
     public void setCreated(String created) {
        this.created = Boolean.parseBoolean(created);
     }
  
     public TYPE getType() {
        if (thetype == null) {
           if (temporary) {
              if (isQueue()) {
                 thetype = TYPE.TEMP_QUEUE;
              } else {
                 thetype = TYPE.TEMP_TOPIC;
              }
           } else {
              if (isQueue()) {
                 thetype = TYPE.QUEUE;
              } else {
                 thetype = TYPE.TOPIC;
              }
           }
        }
        return thetype;
     }
  
     @Override
     public boolean equals(final Object obj) {
        if (this == obj) {
           return true;
        }
        if (!(obj instanceof ActiveMQDestination other)) {
           return false;
        }
  
        return Objects.equals(simpleAddress, other.simpleAddress);
     }
  
     @Override
     public int hashCode() {
        return simpleAddress.hashCode();
     }
  
     @Override
     protected void buildFromProperties(Properties props) {
        setAddress(props.getProperty("address"));
     }
  
     @Override
     protected void populateProperties(Properties props) {
        props.put("address", getAddress());
     }
  
     public enum TYPE {
        QUEUE,
        TOPIC,
        TEMP_QUEUE,
        TEMP_TOPIC,
        DESTINATION; // unknown
  
        public byte getType() {
           return switch (this) {
              case QUEUE -> 0;
              case TOPIC -> 1;
              case TEMP_QUEUE -> 2;
              case TEMP_TOPIC -> 3;
              case DESTINATION -> 4;
              default -> -1;
           };
        }
  
        public static TYPE getType(byte type) {
           return switch (type) {
              case 0 -> QUEUE;
              case 1 -> TOPIC;
              case 2 -> TEMP_QUEUE;
              case 3 -> TEMP_TOPIC;
              case 4 -> DESTINATION;
              default -> null;
           };
        }
  
        public static boolean isQueue(TYPE type) {
           boolean result = false;
  
           if (type != null && (type.equals(QUEUE) || type.equals(TEMP_QUEUE))) {
              result = true;
           }
  
           return result;
        }
  
        public static boolean isTemporary(TYPE type) {
           boolean result = false;
  
           if (type != null && (type.equals(TEMP_TOPIC) || type.equals(TEMP_QUEUE))) {
              result = true;
           }
  
           return result;
        }
     }
  }
  