This is an automated email from the ASF dual-hosted git repository.

robbie pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/main by this push:
     new a7a70d6595 ARTEMIS-5142 support never expiring incoming messages
a7a70d6595 is described below

commit a7a70d6595c31ccfd1ef040916377daf4c67f344
Author: Justin Bertram <[email protected]>
AuthorDate: Fri Nov 1 11:19:22 2024 -0500

    ARTEMIS-5142 support never expiring incoming messages
---
 .../api/core/management/AddressSettingsInfo.java   |   9 +
 .../deployers/impl/FileConfigurationParser.java    |   4 +
 .../core/postoffice/impl/PostOfficeImpl.java       |  34 ++-
 .../core/settings/impl/AddressSettings.java        |  21 +-
 .../resources/schema/artemis-configuration.xsd     |   8 +
 .../core/config/impl/FileConfigurationTest.java    |   2 +
 .../core/postoffice/impl/PostOfficeImplTest.java   | 308 +++++++++++++++++++++
 .../artemis/core/settings/AddressSettingsTest.java |   7 +
 .../resources/ConfigurationTest-full-config.xml    |   1 +
 ...rationTest-xinclude-config-address-settings.xml |   1 +
 ...est-xinclude-schema-config-address-settings.xml |   1 +
 docs/user-manual/address-settings.adoc             |  27 +-
 docs/user-manual/message-expiry.adoc               |  62 +++--
 .../jms/multiprotocol/JMSMessageExpiryTest.java    | 275 ++++++++++++++++++
 14 files changed, 728 insertions(+), 32 deletions(-)

diff --git 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AddressSettingsInfo.java
 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AddressSettingsInfo.java
index 718fdb8551..ac2b164d9c 100644
--- 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AddressSettingsInfo.java
+++ 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/AddressSettingsInfo.java
@@ -304,6 +304,11 @@ public final class AddressSettingsInfo {
    }
    private long maxExpiryDelay;
 
+   static {
+      META_BEAN.add(Boolean.class, "noExpiry", (o, p) -> o.noExpiry = p, o -> 
o.noExpiry);
+   }
+   private boolean noExpiry;
+
    static {
       META_BEAN.add(Boolean.class, "enableMetrics", (o, p) -> o.enableMetrics 
= p, o -> o.enableMetrics);
    }
@@ -556,6 +561,10 @@ public final class AddressSettingsInfo {
       return maxExpiryDelay;
    }
 
+   public boolean isNoExpiry() {
+      return noExpiry;
+   }
+
    public boolean isEnableMetrics() {
       return enableMetrics;
    }
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
index 6819b307ad..dc7306d3b1 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
@@ -233,6 +233,8 @@ public final class FileConfigurationParser extends 
XMLConfigurationUtil {
 
    private static final String MAX_EXPIRY_DELAY_NODE_NAME = "max-expiry-delay";
 
+   private static final String NO_EXPIRY_NODE_NAME = "no-expiry";
+
    private static final String REDELIVERY_DELAY_NODE_NAME = "redelivery-delay";
 
    private static final String REDELIVERY_DELAY_MULTIPLIER_NODE_NAME = 
"redelivery-delay-multiplier";
@@ -1331,6 +1333,8 @@ public final class FileConfigurationParser extends 
XMLConfigurationUtil {
             addressSettings.setMinExpiryDelay(XMLUtil.parseLong(child));
          } else if (MAX_EXPIRY_DELAY_NODE_NAME.equalsIgnoreCase(name)) {
             addressSettings.setMaxExpiryDelay(XMLUtil.parseLong(child));
+         } else if (NO_EXPIRY_NODE_NAME.equalsIgnoreCase(name)) {
+            addressSettings.setNoExpiry(XMLUtil.parseBoolean(child));
          } else if (REDELIVERY_DELAY_NODE_NAME.equalsIgnoreCase(name)) {
             addressSettings.setRedeliveryDelay(XMLUtil.parseLong(child));
          } else if 
(REDELIVERY_DELAY_MULTIPLIER_NODE_NAME.equalsIgnoreCase(name)) {
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
index 8d38b131d4..ad66e7f194 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
@@ -1355,28 +1355,44 @@ public class PostOfficeImpl implements PostOffice, 
NotificationListener, Binding
       return status;
    }
 
-   // HORNETQ-1029
-   private static void applyExpiryDelay(Message message, AddressSettings 
settings) {
+   protected static void applyExpiryDelay(Message message, AddressSettings 
settings) {
       long expirationOverride = settings.getExpiryDelay();
 
-      // A -1 <expiry-delay> means don't do anything
-      if (expirationOverride >= 0) {
-         // only override the expiration on messages where the expiration 
hasn't been set by the user
+      if (settings.isNoExpiry()) {
+         if (message.getExpiration() != 0) {
+            message.setExpiration(0);
+            message.reencode();
+         }
+      } else if (expirationOverride >= 0) {
+         // A -1 <expiry-delay> means don't do anything
          if (message.getExpiration() == 0) {
-            message.setExpiration(System.currentTimeMillis() + 
expirationOverride);
+            // only override the expiration on messages where the expiration 
hasn't been set by the user
+            setExpiration(message, expirationOverride);
          }
       } else {
          long minExpiration = settings.getMinExpiryDelay();
          long maxExpiration = settings.getMaxExpiryDelay();
 
-         if (maxExpiration != AddressSettings.DEFAULT_MAX_EXPIRY_DELAY && 
(message.getExpiration() == 0 || message.getExpiration() > 
(System.currentTimeMillis() + maxExpiration))) {
-            message.setExpiration(System.currentTimeMillis() + maxExpiration);
+         if (message.getExpiration() == 0) {
+            // if the incoming message has NO expiration then apply the max if 
set and if not set then apply the min if set
+            if (maxExpiration != AddressSettings.DEFAULT_MAX_EXPIRY_DELAY) {
+               setExpiration(message, maxExpiration);
+            } else if (minExpiration != 
AddressSettings.DEFAULT_MIN_EXPIRY_DELAY) {
+               setExpiration(message, minExpiration);
+            }
+         } else if (maxExpiration != AddressSettings.DEFAULT_MAX_EXPIRY_DELAY 
&& message.getExpiration() > (System.currentTimeMillis() + maxExpiration)) {
+            setExpiration(message, maxExpiration);
          } else if (minExpiration != AddressSettings.DEFAULT_MIN_EXPIRY_DELAY 
&& message.getExpiration() < (System.currentTimeMillis() + minExpiration)) {
-            message.setExpiration(System.currentTimeMillis() + minExpiration);
+            setExpiration(message, minExpiration);
          }
       }
    }
 
+   private static void setExpiration(Message m, long expiration) {
+      m.setExpiration(System.currentTimeMillis() + expiration);
+      m.reencode();
+   }
+
    @Override
    public MessageReference reload(final Message message, final Queue queue, 
final Transaction tx) throws Exception {
 
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java
index 6056a239c8..3ca2924033 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java
@@ -117,6 +117,8 @@ public class AddressSettings implements 
Mergeable<AddressSettings>, Serializable
 
    public static final long DEFAULT_MAX_EXPIRY_DELAY = -1;
 
+   public static final boolean DEFAULT_NO_EXPIRY = false;
+
    public static final boolean DEFAULT_SEND_TO_DLA_ON_NO_ROUTE = false;
 
    public static final long DEFAULT_SLOW_CONSUMER_THRESHOLD = -1;
@@ -266,6 +268,11 @@ public class AddressSettings implements 
Mergeable<AddressSettings>, Serializable
    }
    private Long maxExpiryDelay = null;
 
+   static {
+      metaBean.add(Boolean.class, "noExpiry", (t, p) -> t.noExpiry = p, t -> 
t.noExpiry);
+   }
+   private Boolean noExpiry = null;
+
    static {
       metaBean.add(Boolean.class, "defaultLastValueQueue", (t, p) -> 
t.defaultLastValueQueue = p, t -> t.defaultLastValueQueue);
    }
@@ -1050,6 +1057,15 @@ public class AddressSettings implements 
Mergeable<AddressSettings>, Serializable
       return this;
    }
 
+   public Boolean isNoExpiry() {
+      return noExpiry != null ? noExpiry : AddressSettings.DEFAULT_NO_EXPIRY;
+   }
+
+   public AddressSettings setNoExpiry(final Boolean noExpiry) {
+      this.noExpiry = noExpiry;
+      return this;
+   }
+
    public boolean isSendToDLAOnNoRoute() {
       return sendToDLAOnNoRoute != null ? sendToDLAOnNoRoute : 
AddressSettings.DEFAULT_SEND_TO_DLA_ON_NO_ROUTE;
    }
@@ -1694,6 +1710,8 @@ public class AddressSettings implements 
Mergeable<AddressSettings>, Serializable
          return false;
       if (!Objects.equals(maxExpiryDelay, that.maxExpiryDelay))
          return false;
+      if (!Objects.equals(noExpiry, that.noExpiry))
+         return false;
       if (!Objects.equals(defaultLastValueQueue, that.defaultLastValueQueue))
          return false;
       if (!Objects.equals(defaultLastValueKey, that.defaultLastValueKey))
@@ -1830,6 +1848,7 @@ public class AddressSettings implements 
Mergeable<AddressSettings>, Serializable
       result = 31 * result + (expiryDelay != null ? expiryDelay.hashCode() : 
0);
       result = 31 * result + (minExpiryDelay != null ? 
minExpiryDelay.hashCode() : 0);
       result = 31 * result + (maxExpiryDelay != null ? 
maxExpiryDelay.hashCode() : 0);
+      result = 31 * result + (noExpiry != null ? noExpiry.hashCode() : 0);
       result = 31 * result + (defaultLastValueQueue != null ? 
defaultLastValueQueue.hashCode() : 0);
       result = 31 * result + (defaultLastValueKey != null ? 
defaultLastValueKey.hashCode() : 0);
       result = 31 * result + (defaultNonDestructive != null ? 
defaultNonDestructive.hashCode() : 0);
@@ -1889,7 +1908,7 @@ public class AddressSettings implements 
Mergeable<AddressSettings>, Serializable
 
    @Override
    public String toString() {
-      return "AddressSettings{" + "addressFullMessagePolicy=" + 
addressFullMessagePolicy + ", maxSizeBytes=" + maxSizeBytes + ", 
maxReadPageBytes=" + maxReadPageBytes + ", maxReadPageMessages=" + 
maxReadPageMessages + ", prefetchPageBytes=" + prefetchPageBytes + ", 
prefetchPageMessages=" + prefetchPageMessages + ", pageLimitBytes=" + 
pageLimitBytes + ", pageLimitMessages=" + pageLimitMessages + ", 
pageFullMessagePolicy=" + pageFullMessagePolicy + ", maxSizeMessages=" + 
maxSizeMessages +  [...]
+      return "AddressSettings{" + "addressFullMessagePolicy=" + 
addressFullMessagePolicy + ", maxSizeBytes=" + maxSizeBytes + ", 
maxReadPageBytes=" + maxReadPageBytes + ", maxReadPageMessages=" + 
maxReadPageMessages + ", prefetchPageBytes=" + prefetchPageBytes + ", 
prefetchPageMessages=" + prefetchPageMessages + ", pageLimitBytes=" + 
pageLimitBytes + ", pageLimitMessages=" + pageLimitMessages + ", 
pageFullMessagePolicy=" + pageFullMessagePolicy + ", maxSizeMessages=" + 
maxSizeMessages +  [...]
              + '}';
    }
 }
diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd 
b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
index afd809ad1a..db1e93eba0 100644
--- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd
+++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
@@ -3871,6 +3871,14 @@
             </xsd:annotation>
          </xsd:element>
 
+         <xsd:element name="no-expiry" type="xsd:boolean" default="false" 
maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  Overrides the expiration time for all messages so that they 
never expire.
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+
          <xsd:element name="expiry-delay" type="xsd:long" default="-1" 
maxOccurs="1" minOccurs="0">
             <xsd:annotation>
                <xsd:documentation>
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
index e32527ee31..de29b9ab8f 100644
--- 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
@@ -506,6 +506,7 @@ public class FileConfigurationTest extends 
AbstractConfigurationTestBase {
       assertEquals(1L, (long) 
conf.getAddressSettings().get("a1").getExpiryDelay());
       assertEquals(2L, (long) 
conf.getAddressSettings().get("a1").getMinExpiryDelay());
       assertEquals(3L, (long) 
conf.getAddressSettings().get("a1").getMaxExpiryDelay());
+      assertTrue(conf.getAddressSettings().get("a1").isNoExpiry());
       assertEquals(AddressSettings.DEFAULT_AUTO_CREATE_EXPIRY_RESOURCES, 
conf.getAddressSettings().get("a1").isAutoCreateExpiryResources());
       assertEquals(AddressSettings.DEFAULT_EXPIRY_QUEUE_PREFIX, 
conf.getAddressSettings().get("a1").getExpiryQueuePrefix());
       assertEquals(AddressSettings.DEFAULT_EXPIRY_QUEUE_SUFFIX, 
conf.getAddressSettings().get("a1").getExpiryQueueSuffix());
@@ -547,6 +548,7 @@ public class FileConfigurationTest extends 
AbstractConfigurationTestBase {
       assertEquals(-1L, (long) 
conf.getAddressSettings().get("a2").getExpiryDelay());
       assertEquals(-1L, (long) 
conf.getAddressSettings().get("a2").getMinExpiryDelay());
       assertEquals(-1L, (long) 
conf.getAddressSettings().get("a2").getMaxExpiryDelay());
+      assertFalse(conf.getAddressSettings().get("a2").isNoExpiry());
       
assertTrue(conf.getAddressSettings().get("a2").isAutoCreateDeadLetterResources());
       assertEquals("", 
conf.getAddressSettings().get("a2").getExpiryQueuePrefix().toString());
       assertEquals(".EXP", 
conf.getAddressSettings().get("a2").getExpiryQueueSuffix().toString());
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImplTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImplTest.java
new file mode 100644
index 0000000000..3730a1ee7e
--- /dev/null
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImplTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.core.postoffice.impl;
+
+import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class PostOfficeImplTest {
+
+   private static final int EXPIRATION_DELTA = 5000;
+
+   @Test
+   public void testNoExpiryWhenExpirationSetLow() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(1L);
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setNoExpiry(true));
+      Mockito.verify(mockMessage).setExpiration(0);
+   }
+
+   @Test
+   public void testNoExpiryWhenExpirationSetHigh() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(Long.MAX_VALUE);
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setNoExpiry(true));
+      Mockito.verify(mockMessage).setExpiration(0);
+   }
+
+   @Test
+   public void testNoExpiryWhenExpirationNotSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setNoExpiry(true));
+      Mockito.verify(mockMessage, 
Mockito.never()).setExpiration(Mockito.anyLong());
+   }
+
+   @Test
+   public void testExpiryDelayWhenExpirationNotSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      final long expiryDelay = 123456L;
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setExpiryDelay(expiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + expiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testExpiryDelayWhenExpirationSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(1L);
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setExpiryDelay(9999L));
+      Mockito.verify(mockMessage, 
Mockito.never()).setExpiration(Mockito.anyLong());
+   }
+
+   @Test
+   public void testMinExpiryDelayWhenExpirationNotSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      final long minExpiryDelay = 123456L;
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMinExpiryDelay(minExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + minExpiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testMinExpiryDelayWhenExpirationSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      long origExpiration = 1234L;
+      Mockito.when(mockMessage.getExpiration()).thenReturn(origExpiration);
+      final long minExpiryDelay = 123456L;
+      assertTrue(minExpiryDelay > origExpiration);
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMinExpiryDelay(minExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + minExpiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testMaxExpiryDelayWhenExpirationNotSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      final long maxExpiryDelay = 123456L;
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMaxExpiryDelay(maxExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + maxExpiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testMaxExpiryDelayWhenExpirationSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(Long.MAX_VALUE);
+      final long maxExpiryDelay = 123456L;
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMaxExpiryDelay(maxExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + maxExpiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testMinAndMaxExpiryDelayWhenExpirationNotSet() {
+      Message mockMessage = Mockito.mock(Message.class);
+      long origExpiration = 0L;
+      Mockito.when(mockMessage.getExpiration()).thenReturn(origExpiration);
+      final long minExpiryDelay = 100_000L;
+      final long maxExpiryDelay = 300_000L;
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMinExpiryDelay(minExpiryDelay).setMaxExpiryDelay(maxExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + maxExpiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testMinAndMaxExpiryDelayWhenExpirationSetInbetween() {
+      Message mockMessage = Mockito.mock(Message.class);
+      final long startTime = System.currentTimeMillis();
+      long origExpiration = startTime + 200_000L;
+      Mockito.when(mockMessage.getExpiration()).thenReturn(origExpiration);
+      final long minExpiryDelay = 100_000L;
+      final long maxExpiryDelay = 300_000L;
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMinExpiryDelay(minExpiryDelay).setMaxExpiryDelay(maxExpiryDelay));
+
+      Mockito.verify(mockMessage, 
Mockito.never()).setExpiration(Mockito.anyLong());
+   }
+
+   @Test
+   public void testMinAndMaxExpiryDelayWhenExpirationSetAbove() {
+      Message mockMessage = Mockito.mock(Message.class);
+      final long startTime = System.currentTimeMillis();
+      long origExpiration = startTime + 400_000L;
+      Mockito.when(mockMessage.getExpiration()).thenReturn(origExpiration);
+      final long minExpiryDelay = 100_000L;
+      final long maxExpiryDelay = 300_000L;
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMinExpiryDelay(minExpiryDelay).setMaxExpiryDelay(maxExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + maxExpiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testMinAndMaxExpiryDelayWhenExpirationSetBelow() {
+      Message mockMessage = Mockito.mock(Message.class);
+      final long startTime = System.currentTimeMillis();
+      long origExpiration = startTime + 50_000;
+      Mockito.when(mockMessage.getExpiration()).thenReturn(origExpiration);
+      final long minExpiryDelay = 100_000L;
+      final long maxExpiryDelay = 300_000L;
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setMinExpiryDelay(minExpiryDelay).setMaxExpiryDelay(maxExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + minExpiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   private void assertExpirationSetAsExpected(final long 
expectedExpirationLow, final long expectedExpirationHigh, final Long 
actualExpirationSet) {
+      assertNotNull(actualExpirationSet);
+
+      assertTrue(actualExpirationSet >= expectedExpirationLow, () -> "Expected 
set expiration of at least " + expectedExpirationLow + ", but was: " + 
actualExpirationSet);
+      assertTrue(actualExpirationSet < expectedExpirationHigh, "Expected set 
expiration less than " + expectedExpirationHigh + ", but was: " + 
actualExpirationSet);
+   }
+
+   @Test
+   public void testPrecedencNoExpiryOverExpiryDelay() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setNoExpiry(true).setExpiryDelay(10L));
+      Mockito.verify(mockMessage, 
Mockito.never()).setExpiration(Mockito.anyLong());
+   }
+
+   @Test
+   public void testPrecedencNoExpiryOverMaxExpiryDelay() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setNoExpiry(true).setMaxExpiryDelay(10L));
+      Mockito.verify(mockMessage, 
Mockito.never()).setExpiration(Mockito.anyLong());
+   }
+
+   @Test
+   public void testPrecedencNoExpiryOverMinExpiryDelay() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setNoExpiry(true).setMinExpiryDelay(10L));
+      Mockito.verify(mockMessage, 
Mockito.never()).setExpiration(Mockito.anyLong());
+   }
+
+   @Test
+   public void testPrecedencExpiryDelayOverMaxExpiryDelay() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      final long expiryDelay = 1000L;
+      final long maxExpiryDelay = 999999999L;
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setExpiryDelay(expiryDelay).setMaxExpiryDelay(maxExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + expiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+
+   @Test
+   public void testPrecedencExpiryDelayOverMinExpiryDelay() {
+      Message mockMessage = Mockito.mock(Message.class);
+      Mockito.when(mockMessage.getExpiration()).thenReturn(0L);
+      final long expiryDelay = 1000L;
+      final long minExpiryDelay = 999999999L;
+      final long startTime = System.currentTimeMillis();
+
+      PostOfficeImpl.applyExpiryDelay(mockMessage, new 
AddressSettings().setExpiryDelay(expiryDelay).setMinExpiryDelay(minExpiryDelay));
+
+      final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
+      Mockito.verify(mockMessage).setExpiration(captor.capture());
+
+      final long expectedExpirationLow = startTime + expiryDelay;
+      final long expectedExpirationHigh = expectedExpirationLow + 
EXPIRATION_DELTA; // Allowing a delta
+      final Long actualExpirationSet = captor.getValue();
+
+      assertExpirationSetAsExpected(expectedExpirationLow, 
expectedExpirationHigh, actualExpirationSet);
+   }
+}
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/settings/AddressSettingsTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/settings/AddressSettingsTest.java
index 02e08e3078..9b45770295 100644
--- 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/settings/AddressSettingsTest.java
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/settings/AddressSettingsTest.java
@@ -18,6 +18,7 @@ package org.apache.activemq.artemis.core.settings;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.lang.invoke.MethodHandles;
 
@@ -60,6 +61,7 @@ public class AddressSettingsTest extends ServerTestBase {
       assertEquals(AddressSettings.DEFAULT_AUTO_DELETE_ADDRESSES, 
addressSettings.isAutoDeleteAddresses());
       
assertEquals(ActiveMQDefaultConfiguration.getDefaultPurgeOnNoConsumers(), 
addressSettings.isDefaultPurgeOnNoConsumers());
       
assertEquals(Integer.valueOf(ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers()),
 addressSettings.getDefaultMaxConsumers());
+      assertEquals(AddressSettings.DEFAULT_NO_EXPIRY, 
addressSettings.isNoExpiry());
    }
 
    @Test
@@ -93,6 +95,7 @@ public class AddressSettingsTest extends ServerTestBase {
       addressSettingsToMerge.setMaxExpiryDelay(777L);
       addressSettingsToMerge.setIDCacheSize(5);
       addressSettingsToMerge.setInitialQueueBufferSize(256);
+      addressSettingsToMerge.setNoExpiry(true);
 
       if (copy) {
          addressSettings = addressSettings.mergeCopy(addressSettingsToMerge);
@@ -115,6 +118,7 @@ public class AddressSettingsTest extends ServerTestBase {
       assertEquals(Long.valueOf(777), addressSettings.getMaxExpiryDelay());
       assertEquals(Integer.valueOf(5), addressSettings.getIDCacheSize());
       assertEquals(Integer.valueOf(256), 
addressSettings.getInitialQueueBufferSize());
+      assertTrue(addressSettings.isNoExpiry());
    }
 
    @Test
@@ -139,6 +143,7 @@ public class AddressSettingsTest extends ServerTestBase {
       addressSettingsToMerge.setMessageCounterHistoryDayLimit(1002);
       
addressSettingsToMerge.setAddressFullMessagePolicy(AddressFullMessagePolicy.DROP);
       addressSettingsToMerge.setMaxSizeBytesRejectThreshold(10 * 1024);
+      addressSettingsToMerge.setNoExpiry(true);
       if (copy) {
          addressSettings = addressSettings.mergeCopy(addressSettingsToMerge);
       } else {
@@ -166,6 +171,7 @@ public class AddressSettingsTest extends ServerTestBase {
       assertEquals(addressSettings.getRedeliveryMultiplier(), 2.5, 0.000001);
       assertEquals(AddressFullMessagePolicy.DROP, 
addressSettings.getAddressFullMessagePolicy());
       assertEquals(addressSettings.getMaxSizeBytesRejectThreshold(), 10 * 
1024);
+      assertTrue(addressSettings.isNoExpiry());
    }
 
    @Test
@@ -236,6 +242,7 @@ public class AddressSettingsTest extends ServerTestBase {
       addressSettings.setRedeliveryDelay(1003);
       addressSettings.setRedeliveryMultiplier(1.0);
       
addressSettings.setAddressFullMessagePolicy(AddressFullMessagePolicy.DROP);
+      addressSettings.setNoExpiry(true);
 
       String json = addressSettings.toJSON();
       logger.info("Json:: {}", json);
diff --git 
a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml 
b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
index f53d5a4844..ad9e0b1a29 100644
--- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
+++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
@@ -577,6 +577,7 @@
             <expiry-delay>1</expiry-delay>
             <min-expiry-delay>2</min-expiry-delay>
             <max-expiry-delay>3</max-expiry-delay>
+            <no-expiry>true</no-expiry>
             <redelivery-delay>1</redelivery-delay>
             
<redelivery-collision-avoidance-factor>0.5</redelivery-collision-avoidance-factor>
             <max-size-bytes>817M</max-size-bytes>
diff --git 
a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config-address-settings.xml
 
b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config-address-settings.xml
index 4750e7a899..217e42dba2 100644
--- 
a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config-address-settings.xml
+++ 
b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config-address-settings.xml
@@ -21,6 +21,7 @@
       <expiry-delay>1</expiry-delay>
       <min-expiry-delay>2</min-expiry-delay>
       <max-expiry-delay>3</max-expiry-delay>
+      <no-expiry>true</no-expiry>
       <redelivery-delay>1</redelivery-delay>
       
<redelivery-collision-avoidance-factor>0.5</redelivery-collision-avoidance-factor>
       <max-size-bytes>817M</max-size-bytes>
diff --git 
a/artemis-server/src/test/resources/ConfigurationTest-xinclude-schema-config-address-settings.xml
 
b/artemis-server/src/test/resources/ConfigurationTest-xinclude-schema-config-address-settings.xml
index 4750e7a899..217e42dba2 100644
--- 
a/artemis-server/src/test/resources/ConfigurationTest-xinclude-schema-config-address-settings.xml
+++ 
b/artemis-server/src/test/resources/ConfigurationTest-xinclude-schema-config-address-settings.xml
@@ -21,6 +21,7 @@
       <expiry-delay>1</expiry-delay>
       <min-expiry-delay>2</min-expiry-delay>
       <max-expiry-delay>3</max-expiry-delay>
+      <no-expiry>true</no-expiry>
       <redelivery-delay>1</redelivery-delay>
       
<redelivery-collision-avoidance-factor>0.5</redelivery-collision-avoidance-factor>
       <max-size-bytes>817M</max-size-bytes>
diff --git a/docs/user-manual/address-settings.adoc 
b/docs/user-manual/address-settings.adoc
index 305f051208..ba197f8697 100644
--- a/docs/user-manual/address-settings.adoc
+++ b/docs/user-manual/address-settings.adoc
@@ -31,7 +31,10 @@ Here an example of an `address-setting` entry that might be 
found in the `broker
       <auto-create-expiry-resources>false</auto-create-expiry-resources>
       <expiry-queue-prefix></expiry-queue-prefix>
       <expiry-queue-suffix></expiry-queue-suffix>
-      <expiry-delay>123</expiry-delay>
+      <no-expiry>false</no-expiry>
+      <expiry-delay>-1</expiry-delay>
+      <min-expiry-delay>-1</min-expiry-delay>
+      <max-expiry-delay>-1</max-expiry-delay>
       <redelivery-delay>5000</redelivery-delay>
       <redelivery-delay-multiplier>1.0</redelivery-delay-multiplier>
       
<redelivery-collision-avoidance-factor>0.0</redelivery-collision-avoidance-factor>
@@ -123,12 +126,24 @@ The suffix used for automatically created expiry queues.
 Default is empty.
 Read more in the chapter about xref:message-expiry.adoc#message-expiry[message 
expiry].
 
+no-expiry::
+If `true` this overrides the expiration time for _all_ messages so that they 
never expire.
+The default is `false`.
+Read more about xref:message-expiry.adoc#configuring-expiry-delay[message 
expiry].
+
 expiry-delay::
-The expiration time that will be used for messages which are using the default 
expiration time (i.e. 0).
-For example, if `expiry-delay` is set to "10" and a message which is using the 
default expiration time (i.e. 0) arrives then its expiration time of "0" will 
be changed to "10." However, if a message which is using an expiration time of 
"20" arrives then its expiration time will remain unchanged.
-Setting `expiry-delay` to "-1" will disable this feature.
-The default is "-1".
-Read more about xref:message-expiry.adoc#configuring-expiry-addresses[message 
expiry].
+The expiration time that will be used for messages which are using the default 
expiration time (i.e. `0`).
+For example, if `expiry-delay` is set to `10` and a message which is using the 
default expiration time (i.e. `0`) arrives then its expiration time of `0` will 
be changed to `10`.
+However, if a message which is using an expiration time of `20` arrives then 
its expiration time will remain unchanged.
+Setting `expiry-delay` to `-1` will disable this feature.
+The default is `-1`.
+Read more about xref:message-expiry.adoc#configuring-expiry-delay[message 
expiry].
+
+min-expiry-delay::
+max-expiry-delay::
+These are applied if the aforementioned `expiry-delay` isn't set.
+Unlike `expiry-delay`, they can impact the expiration of a message even if 
that message is using a non-default expiration time.
+There are a xref:message-expiry.adoc#configuring-expiry-delay[handful of 
rules] which dictate the behavior of these settings.
 
 max-delivery-attempts::
 defines how many time a cancelled message can be redelivered before sending to 
the `dead-letter-address`.
diff --git a/docs/user-manual/message-expiry.adoc 
b/docs/user-manual/message-expiry.adoc
index 7906665096..09ab7de331 100644
--- a/docs/user-manual/message-expiry.adoc
+++ b/docs/user-manual/message-expiry.adoc
@@ -36,7 +36,40 @@ a Long property containing the _actual expiration time_ of 
the expired message
 
 == Configuring Expiry Delay
 
-Default expiry delay can be configured in the address-setting configuration:
+There are multiple address-settings which you can use to modify the expiry 
delay for incoming messages:
+
+. `no-expiry`
+. `expiry-delay`
+. `max-expiry-delay` & `min-expiry-delay`
+
+These settings are applied exclusively in this order of precedence. For 
example, if `no-expiry` is set and `expiry-delay` is also set then 
`expiry-delay` is ignored completely and `no-expiry` is enforced.
+
+[WARNING]
+====
+If you set any of these values for the `expiry-address` then messages which 
expire will have corresponding new expiry delays potentially causing the 
expired messages to themselves expire and be removed completely from the broker.
+====
+
+Let's look at each of these in turn.
+
+=== Never Expire
+
+If you want to force messages to _never_ expire regardless of their existing 
settings then set `no-expiry` to `true`, e.g.:
+
+[,xml]
+----
+<!-- messages will never expire -->
+<address-setting match="exampleQueue">
+   <no-expiry>true</no-expiry>
+</address-setting>
+----
+
+For example, if `no-expiry` is set to `true` and a message which is using an 
expiration of `10` arrives then its expiration time of `10` will be changed to 
`0`.
+
+The default is `false`.
+
+=== Modify Default Expiry
+
+To modify the expiry delay on a message using the _default expiration_ (i.e. 
`0`) set `expiry-delay`, e.g.
 
 [,xml]
 ----
@@ -47,14 +80,14 @@ Default expiry delay can be configured in the 
address-setting configuration:
 </address-setting>
 ----
 
-`expiry-delay` defines the expiration time in milliseconds that will be used 
for messages  which are using the default expiration time (i.e. 0).
+For example, if `expiry-delay` is set to `10` and a message which is using the 
default expiration time (i.e. `0`) arrives then its expiration time of `0` will 
be changed to `10`.
+However, if a message which is using an expiration time of `20` arrives then 
its expiration time will remain unchanged.
 
-For example, if `expiry-delay` is set to "10" and a message which is using the 
default  expiration time (i.e. 10) arrives then its expiration time of "0" will 
be changed to "10." However, if a message which is using an expiration time of 
"20" arrives then its expiration time will remain unchanged.
-Setting `expiry-delay` to "-1" will disable this feature.
+This value is measured in milliseconds. The default is `-1` (i.e. disabled).
 
-The default is `-1`.
+=== Enforce an Expiry Range
 
-If `expiry-delay` is _not set_ then minimum and maximum expiry delay values 
can be configured in the address-setting configuration.
+To enforce a range of expiry delay values
 
 [,xml]
 ----
@@ -67,20 +100,17 @@ If `expiry-delay` is _not set_ then minimum and maximum 
expiry delay values can
 Semantics are as follows:
 
 * Messages _without_ an expiration will be set to `max-expiry-delay`.
-If `max-expiry-delay` is not defined then the message will be set to 
`min-expiry-delay`.
-If `min-expiry-delay` is not defined then the message will not be changed.
-* Messages with an expiration _above_ `max-expiry-delay` will be set to 
`max-expiry-delay`
-* Messages with an expiration _below_ `min-expiry-delay` will be set to 
`min-expiry-delay`
-* Messages with an expiration _within_ `min-expiry-delay` and 
`max-expiry-delay` range will not be changed
-* Any value set for `expiry-delay` other than the default (i.e. `-1`) will 
override the aforementioned min/max settings.
+** If `max-expiry-delay` is not defined then the message will be set to 
`min-expiry-delay`.
+** If `min-expiry-delay` is not defined then the message will not be changed.
+* Messages with an expiration _above_ `max-expiry-delay` will be set to 
`max-expiry-delay`.
+* Messages with an expiration _below_ `min-expiry-delay` will be set to 
`min-expiry-delay`.
+* Messages with an expiration _within_ `min-expiry-delay` and 
`max-expiry-delay` range will not be changed.
 
-The default for both `min-expiry-delay` and `max-expiry-delay` is `-1` (i.e. 
disabled).
+These values are measured in milliseconds. The default for both is `-1` (i.e. 
disabled).
 
 [WARNING]
 ====
-**If you set expiry-delay, or min/max-expiry-delay, on the expiration target 
address beware of the following:**
-
-* Messages will get a new expiration when moved to the expiry queue, rather 
than being set to 0 as usual, and so may disappear after the new expiration.
+Setting a value of `0` for `max-expiry-delay` will cause messages to expire 
_immediately_.
 ====
 
 == Configuring Expiry Addresses
diff --git 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/multiprotocol/JMSMessageExpiryTest.java
 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/multiprotocol/JMSMessageExpiryTest.java
new file mode 100644
index 0000000000..3bf8a859f7
--- /dev/null
+++ 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/multiprotocol/JMSMessageExpiryTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.tests.integration.jms.multiprotocol;
+
+import javax.jms.BytesMessage;
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.lang.invoke.MethodHandles;
+
+import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
+import org.apache.activemq.artemis.utils.RandomUtil;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class JMSMessageExpiryTest extends MultiprotocolJMSClientTestSupport {
+
+   protected static final Logger logger = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+   private final long EXPIRY_DELAY = 10_000_000L;
+
+   @Test
+   @Timeout(30)
+   public void testCoreMessageExpiryDelay() throws Exception {
+      testExpiry(CoreConnection, DelayType.NORMAL, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpMessageExpiryDelay() throws Exception {
+      testExpiry(AMQPConnection, DelayType.NORMAL, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireMessageExpiryDelay() throws Exception {
+      testExpiry(OpenWireConnection, DelayType.NORMAL, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreLargeMessageExpiryDelay() throws Exception {
+      testExpiry(CoreConnection, DelayType.NORMAL, false, true, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpLargeMessageExpiryDelay() throws Exception {
+      testExpiry(AMQPConnection, DelayType.NORMAL, false, true, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireLargeMessageExpiryDelay() throws Exception {
+      testExpiry(OpenWireConnection, DelayType.NORMAL, false, true, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreLargeMessageExpiryDelayWithBrokerRestart() throws 
Exception {
+      testExpiry(CoreConnection, DelayType.NORMAL, false, true, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpLargeMessageExpiryDelayWithBrokerRestart() throws 
Exception {
+      testExpiry(AMQPConnection, DelayType.NORMAL, false, true, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireLargeMessageExpiryDelayWithBrokerRestart() throws 
Exception {
+      testExpiry(OpenWireConnection, DelayType.NORMAL, false, true, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreMessageExpiryDelayWithBrokerRestart() throws Exception {
+      testExpiry(CoreConnection, DelayType.NORMAL, false, false, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpMessageExpiryDelayWithBrokerRestart() throws Exception {
+      testExpiry(AMQPConnection, DelayType.NORMAL, false, false, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireMessageExpiryDelayWithBrokerRestart() throws 
Exception {
+      testExpiry(OpenWireConnection, DelayType.NORMAL, false, false, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreMaxExpiryDelayNoExpiration() throws Exception {
+      testExpiry(CoreConnection, DelayType.MAX, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpMaxExpiryDelayNoExpiration() throws Exception {
+      testExpiry(AMQPConnection, DelayType.MAX, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireMaxExpiryDelayNoExpiration() throws Exception {
+      testExpiry(OpenWireConnection, DelayType.MAX, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreMinExpiryDelayNoExpiration() throws Exception {
+      testExpiry(CoreConnection, DelayType.MIN, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpMinExpiryDelayNoExpiration() throws Exception {
+      testExpiry(AMQPConnection, DelayType.MIN, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireMinExpiryDelayNoExpiration() throws Exception {
+      testExpiry(OpenWireConnection, DelayType.MIN, false);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreMaxExpiryDelayWithExpiration() throws Exception {
+      testExpiry(CoreConnection, DelayType.MAX, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpMaxExpiryDelayWithExpiration() throws Exception {
+      testExpiry(AMQPConnection, DelayType.MAX, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireMaxExpiryDelayWithExpiration() throws Exception {
+      testExpiry(OpenWireConnection, DelayType.MAX, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreMinExpiryDelayWithExpiration() throws Exception {
+      testExpiry(CoreConnection, DelayType.MIN, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpMinExpiryDelayWithExpiration() throws Exception {
+      testExpiry(AMQPConnection, DelayType.MIN, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireMinExpiryDelayWithExpiration() throws Exception {
+      testExpiry(OpenWireConnection, DelayType.MIN, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testCoreMessageNoExpiry() throws Exception {
+      testExpiry(CoreConnection, DelayType.NEVER, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testAmqpMessageNoExpiry() throws Exception {
+      testExpiry(AMQPConnection, DelayType.NEVER, true);
+   }
+
+   @Test
+   @Timeout(30)
+   public void testOpenWireMessageNoExpiry() throws Exception {
+      testExpiry(OpenWireConnection, DelayType.NEVER, true);
+   }
+
+   private void testExpiry(ConnectionSupplier supplier, DelayType delayType, 
boolean setTimeToLive) throws Exception {
+      testExpiry(supplier, delayType, setTimeToLive, false, false);
+   }
+
+   private void testExpiry(ConnectionSupplier supplier, DelayType delayType, 
boolean setTimeToLive, boolean useLargeMessage, boolean restartBroker) throws 
Exception {
+      AddressSettings addressSettings = new AddressSettings();
+      if (delayType == DelayType.NORMAL) {
+         addressSettings.setExpiryDelay(EXPIRY_DELAY);
+      } else if (delayType == DelayType.MIN) {
+         addressSettings.setMinExpiryDelay(EXPIRY_DELAY);
+      } else if (delayType == DelayType.MAX) {
+         addressSettings.setMaxExpiryDelay(EXPIRY_DELAY);
+      } else if (delayType == DelayType.NEVER) {
+         addressSettings.setNoExpiry(true);
+      }
+      server.getAddressSettingsRepository().addMatch(getQueueName(), 
addressSettings);
+
+      Connection producerConnection = supplier.createConnection();
+      Session session = producerConnection.createSession(false, 
Session.AUTO_ACKNOWLEDGE);
+      Queue q = session.createQueue(getQueueName());
+      MessageProducer producer = session.createProducer(q);
+      if (setTimeToLive) {
+         if (delayType == DelayType.MIN) {
+            producer.setTimeToLive(EXPIRY_DELAY / 2);
+         } else if (delayType == DelayType.MAX) {
+            producer.setTimeToLive(EXPIRY_DELAY * 2);
+         } else if (delayType == DelayType.NEVER) {
+            producer.setTimeToLive(EXPIRY_DELAY);
+         }
+      }
+      BytesMessage m = session.createBytesMessage();
+      if (useLargeMessage) {
+         
m.writeBytes(RandomUtil.randomBytes(server.getConfiguration().getJournalBufferSize_NIO()
 * 2));
+      }
+      long start = System.currentTimeMillis();
+      producer.send(m);
+      producerConnection.close();
+      if (useLargeMessage) {
+         validateNoFilesOnLargeDir(getLargeMessagesDir(), 1);
+      }
+      if (restartBroker) {
+         server.stop();
+         server.start();
+      }
+      Connection consumerConnection = supplier.createConnection();
+      session = consumerConnection.createSession(false, 
Session.AUTO_ACKNOWLEDGE);
+      q = session.createQueue(getQueueName());
+      MessageConsumer consumer = session.createConsumer(q);
+      consumerConnection.start();
+      m = (BytesMessage) consumer.receive(1500);
+      long stop = System.currentTimeMillis();
+      assertNotNull(m);
+      consumerConnection.close();
+      if (delayType == DelayType.NEVER) {
+         assertEquals(0, m.getJMSExpiration());
+      } else {
+         long duration = stop - start;
+         long delayOnMessage = m.getJMSExpiration() - stop;
+         assertTrue(delayOnMessage >= (EXPIRY_DELAY - duration));
+         assertTrue(delayOnMessage <= EXPIRY_DELAY);
+      }
+      if (useLargeMessage) {
+         validateNoFilesOnLargeDir();
+      }
+   }
+
+   enum DelayType {
+      NORMAL, MIN, MAX, NEVER;
+   }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information, visit: https://activemq.apache.org/contact


Reply via email to