Index: src/main/org/jboss/mq/SpyMessage.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/SpyMessage.java,v
retrieving revision 1.27.6.2
diff -b -B -U8 -r1.27.6.2 SpyMessage.java
--- src/main/org/jboss/mq/SpyMessage.java	19 Oct 2004 01:11:52 -0000	1.27.6.2
+++ src/main/org/jboss/mq/SpyMessage.java	24 Aug 2005 07:28:34 -0000
@@ -64,16 +64,31 @@
 
    /**
 	 * JBoss-vendor specific property specifying the limit of redelivery
 	 * attempts of a message. The message will be redelivered a given number of
 	 * times. If not set, the container default is used.
 	 */
    public static final String PROPERTY_REDELIVERY_LIMIT = "JMS_JBOSS_REDELIVERY_LIMIT";
 
+   /** 
+   * JBoss-vendor property name holding original destination.
+   */
+   public static final String PROPERTY_ORIG_DESTINATION = "JBOSS_ORIG_DESTINATION";
+
+   /** 
+   * JBoss-vendor property name holding original expiration value.
+   */
+   public static final String PROPERTY_ORIG_EXPIRATION = "JBOSS_ORIG_EXPIRATION";
+
+   /** 
+   * JBoss-vendor property name holding original message ID value.
+   */
+   public static final String PROPERTY_ORIG_MESSAGEID = "JBOSS_ORIG_MESSAGEID";
+
    /** An object message */
    protected static final byte OBJECT_MESS = 1;
    /** An bytes message */
    protected static final byte BYTES_MESS = 2;
    /** A map message */
    protected static final byte MAP_MESS = 3;
    /** A text message */
    protected static final byte TEXT_MESS = 4;
@@ -930,19 +945,16 @@
    void checkProperty(String name, Object value) throws JMSException
    {
       if (name == null)
          throw new IllegalArgumentException("The name of a property must not be null.");
 
       if (name.equals(""))
          throw new IllegalArgumentException("The name of a property must not be an empty String.");
 
-      if (Strings.isValidJavaIdentifier(name) == false)
-         throw new IllegalArgumentException("The property name '" + name + "' is not a valid java identifier.");
-
       if (reservedIdentifiers.contains(name))
          throw new IllegalArgumentException("The property name '" + name + "' is reserved due to selector syntax.");
 
       if (name.regionMatches(false, 0, "JMS_", 0, 4))
       {
          if (name.equals(PROPERTY_SCHEDULED_DELIVERY))
          {
             if (!(value instanceof Long))
@@ -958,30 +970,46 @@
             if (!(value instanceof Number))
                throw new JMSException(name + " must be Number: " + value);
          }
          else if (name.equals(PROPERTY_REDELIVERY_LIMIT))
          {
             if (!(value instanceof Number))
                throw new JMSException(name + " must be Number: " + value);
          }
+         else if (name.equals(PROPERTY_ORIG_EXPIRATION))
+         {
+            if (!(value instanceof Long))
+               throw new JMSException(name + " must be Long: " + value);
+         }
+         else if (name.equals(PROPERTY_ORIG_DESTINATION))
+         {
+            // no validation
+         }
+         else if (name.equals(PROPERTY_ORIG_MESSAGEID))
+         {
+            // no validation
+         }
          else
          {
             throw new JMSException("Illegal property name: " + name);
          }
       }
 
       if (name.regionMatches(false, 0, "JMSX", 0, 4))
       {
          if (name.equals("JMSXGroupID"))
             return;
          if (name.equals("JMSXGroupSeq"))
             return;
          throw new JMSException("Illegal property name: " + name);
       }
+
+      if (Strings.isValidJavaIdentifier(name) == false)
+         throw new IllegalArgumentException("The property name '" + name + "' is not a valid java identifier.");
    }
 
    /**
 	 * Clear a message
 	 * 
 	 * @throws JMSException for any error
 	 */
    void clearMessage() throws JMSException
Index: src/main/org/jboss/mq/server/BasicQueue.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/BasicQueue.java,v
retrieving revision 1.42.2.4
diff -b -B -U8 -r1.42.2.4 BasicQueue.java
--- src/main/org/jboss/mq/server/BasicQueue.java	27 Jul 2005 21:40:15 -0000	1.42.2.4
+++ src/main/org/jboss/mq/server/BasicQueue.java	24 Aug 2005 07:28:34 -0000
@@ -18,16 +18,17 @@
 
 import javax.jms.IllegalStateException;
 import javax.jms.JMSException;
 
 import org.jboss.logging.Logger;
 import org.jboss.mq.AcknowledgementRequest;
 import org.jboss.mq.DestinationFullException;
 import org.jboss.mq.SpyJMSException;
+import org.jboss.mq.SpyDestination;
 import org.jboss.mq.SpyMessage;
 import org.jboss.mq.Subscription;
 import org.jboss.mq.pm.Tx;
 import org.jboss.mq.selectors.Selector;
 import org.jboss.util.timeout.Timeout;
 import org.jboss.util.timeout.TimeoutFactory;
 import org.jboss.util.timeout.TimeoutTarget;
 
@@ -207,19 +208,17 @@
             for (Iterator it = messages.iterator(); it.hasNext();)
             {
                MessageReference message = (MessageReference) it.next();
                try
                {
                   if (message.isExpired())
                   {
                      it.remove();
-                     if (trace)
-                        log.trace("message expired: " + message);
-                     dropMessage(message);
+                     expireMessage(message);
                   }
                   else if (sub.accepts(message.getHeaders()))
                   {
                      //queue message for sending to this sub
                      it.remove();
                      found = message;
                      break;
                   }
@@ -465,19 +464,17 @@
                // find a non-expired message
                while (messages.size() != 0)
                {
                   messageRef = (MessageReference) messages.first();
                   messages.remove(messageRef);
 
                   if (messageRef.isExpired())
                   {
-                     if (trace)
-                        log.trace("message expired: " + messageRef);
-                     dropMessage(messageRef);
+                     expireMessage(messageRef);
                      messageRef = null;
                   }
                   else
                      break;
                }
             }
          }
          else
@@ -487,19 +484,17 @@
             {
                Iterator i = messages.iterator();
                while (i.hasNext())
                {
                   MessageReference mr = (MessageReference) i.next();
                   if (mr.isExpired())
                   {
                      i.remove();
-                     if (trace)
-                        log.trace("message expired: " + mr);
-                     dropMessage(mr);
+                     expireMessage(mr);
                   }
                   else if (sub.accepts(mr.getHeaders()))
                   {
                      messageRef = mr;
                      i.remove();
                      break;
                   }
                }
@@ -843,19 +838,17 @@
             log.trace("scheduled message at " + new Date(ts) + ": " + message);
          // Can't deliver now
          return;
       }
 
       // Don't bother with expired messages
       if (message.isExpired())
       {
-         if (trace)
-            log.trace("message expired: " + message);
-         dropMessage(message);
+         expireMessage(message);
          return;
       }
 
       try
       {
          Subscription found = null;
          synchronized (receivers)
          {
@@ -1007,16 +1000,58 @@
          server.getMessageCache().remove(message);
       }
       catch (JMSException e)
       {
          log.warn("Error dropping message " + message, e);
       }
    }
 
+   /**
+    * Expire a message
+    *
+    * @param message the message to remove
+    * @param txid the transaction context for the removal
+    */
+   protected void expireMessage(MessageReference messageRef)
+   {
+      boolean trace = log.isTraceEnabled();
+      if (trace)
+         log.trace("message expired: " + messageRef);
+
+      SpyDestination ed = parameters.expiryDestination;
+      if (ed == null)
+      {
+         dropMessage(messageRef);
+         return;
+      }
+
+      if (trace)
+         log.trace("sending to: " + ed);
+
+      try
+      {
+         SpyMessage orig = messageRef.getMessage();
+         SpyMessage copy = orig.myClone();
+         copy.header.jmsPropertiesReadWrite = true;
+         copy.setJMSExpiration(0);
+         copy.setJMSDestination(ed);
+         copy.setLongProperty(SpyMessage.PROPERTY_ORIG_EXPIRATION, orig.getJMSExpiration());
+         copy.setStringProperty(SpyMessage.PROPERTY_ORIG_DESTINATION, orig.getJMSDestination().toString());
+         server.addMessage(null, copy, null);
+         // If add fails, don't drop
+         // Ideally, we would drop and add as a single transaction
+         dropMessage(messageRef);
+      }
+      catch (JMSException e)
+      {
+         log.error("Could not move expired message: " + messageRef, e);
+      }
+   }
+
    // Package Private -----------------------------------------------
 
    // Private -------------------------------------------------------
 
    /**
     * Check whether a removed subscription can be permenantly removed.
     * This method is private because it assumes external synchronization
     *
@@ -1183,17 +1218,17 @@
          {
             // If the message was already sent, then do nothing
             // (This probably happens more than not)
             if (messages.remove(messageRef) == false)
                return;
          }
          if (log.isTraceEnabled())
             log.trace("message expired: " + messageRef);
-         dropMessage(messageRef);
+         expireMessage(messageRef);
       }
    }
 
    /**
     * Information about unacknowledged messages
     */
    private static class UnackedMessageInfo
    {
Index: src/main/org/jboss/mq/server/BasicQueueParameters.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/BasicQueueParameters.java,v
retrieving revision 1.4.4.2
diff -b -B -U8 -r1.4.4.2 BasicQueueParameters.java
--- src/main/org/jboss/mq/server/BasicQueueParameters.java	12 Jul 2005 17:12:02 -0000	1.4.4.2
+++ src/main/org/jboss/mq/server/BasicQueueParameters.java	24 Aug 2005 07:28:34 -0000
@@ -1,16 +1,18 @@
 /*
  * JBossMQ, the OpenSource JMS implementation
  *
  * Distributable under LGPL license.
  * See terms of license at gnu.org.
  */
 package org.jboss.mq.server;
 
+import org.jboss.mq.SpyDestination;
+
 /**
  * Parameters controlling a basic queue
  *
  * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
  * @version $Revision: 1.4.4.2 $
  */
 public class BasicQueueParameters
 {
@@ -35,9 +37,12 @@
    /** The receivers implementation class */
    public Class receiversImpl;
    
    /** The message counter history */
    public int messageCounterHistoryDayLimit = -1;
    
    /** The number of retries when recovering a queue/durable subscription */
    public int recoveryRetries = 0;
+   
+   /** The destination that receives expired messages */
+   public SpyDestination expiryDestination = null;
 }
Index: src/main/org/jboss/mq/server/MessageCache.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/MessageCache.java,v
retrieving revision 1.31.2.1
diff -b -B -U8 -r1.31.2.1 MessageCache.java
--- src/main/org/jboss/mq/server/MessageCache.java	13 Apr 2005 17:58:26 -0000	1.31.2.1
+++ src/main/org/jboss/mq/server/MessageCache.java	24 Aug 2005 07:28:35 -0000
@@ -884,15 +884,15 @@
          return mostRecent;
       }
       public Node getLeastRecent()
       {
          return leastRecent;
       }
    }
 
-   class Node
+   static class Node
    {
       Node moreRecent = null;
       Node lessRecent = null;
       Object data = null;
    }
 }
Index: src/main/org/jboss/mq/server/MessageReference.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/MessageReference.java,v
retrieving revision 1.20.2.2
diff -b -B -U8 -r1.20.2.2 MessageReference.java
--- src/main/org/jboss/mq/server/MessageReference.java	12 Jul 2005 00:42:50 -0000	1.20.2.2
+++ src/main/org/jboss/mq/server/MessageReference.java	24 Aug 2005 07:28:35 -0000
@@ -172,17 +172,17 @@
       if (redeliveryDelay != 0)
       {
          log.trace("message has redelivery delay");
          messageScheduledDelivery = System.currentTimeMillis() + redeliveryDelay;
       }
 
       ++redeliveryCount;
       
-      if (queue.parameters.lateClone == false)
+      if (isLateClone() == false)
       {
          SpyMessage message = getMessage();
          message.setJMSRedelivered(redelivered);
 
          message.header.jmsProperties.put(SpyMessage.PROPERTY_REDELIVERY_COUNT, new Integer(redeliveryCount));
       }
    }
 
Index: src/main/org/jboss/mq/server/jmx/DestinationMBean.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/jmx/DestinationMBean.java,v
retrieving revision 1.7.4.2
diff -b -B -U8 -r1.7.4.2 DestinationMBean.java
--- src/main/org/jboss/mq/server/jmx/DestinationMBean.java	12 Jul 2005 17:12:02 -0000	1.7.4.2
+++ src/main/org/jboss/mq/server/jmx/DestinationMBean.java	24 Aug 2005 07:28:35 -0000
@@ -168,9 +168,20 @@
     * Returns the recovery retries
     */
    public int getRecoveryRetries();
 
    /**
     * Sets the class implementating the receivers
     */
    public void setRecoveryRetries(int retries);
+
+   /**
+    * Returns the expiry destination.
+    */
+   public ObjectName getExpiryDestination();
+
+   /**
+    * Sets the expiry destination.
+    */
+   public void setExpiryDestination(ObjectName destination);
+
 }
Index: src/main/org/jboss/mq/server/jmx/DestinationMBeanSupport.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/jmx/DestinationMBeanSupport.java,v
retrieving revision 1.10.4.5
diff -b -B -U8 -r1.10.4.5 DestinationMBeanSupport.java
--- src/main/org/jboss/mq/server/jmx/DestinationMBeanSupport.java	1 Aug 2005 16:42:17 -0000	1.10.4.5
+++ src/main/org/jboss/mq/server/jmx/DestinationMBeanSupport.java	24 Aug 2005 07:28:35 -0000
@@ -41,16 +41,18 @@
    String destinationName;
    String jndiName;
    boolean jndiBound;
    ObjectName jbossMQService;
    //String securityConf;
    Element securityConf;
    /** The basic parameters */
    BasicQueueParameters parameters = new BasicQueueParameters();
+   /** Destination JNDI name */
+   ObjectName expiryDestination;
 
    /**
     * A optional security manager. Must be set to use security conf.
     */
    ObjectName securityManager;
 
    /**
     * Get the value of JBossMQService.
@@ -100,27 +102,43 @@
    
    public void createService() throws Exception
    {
       // Copy default values from the server when null or zero
       if (parameters.receiversImpl == null)
          parameters.receiversImpl = (Class) server.getAttribute(jbossMQService, "ReceiversImpl");
       if (parameters.recoveryRetries == 0)
          parameters.recoveryRetries = ((Integer) server.getAttribute(jbossMQService, "RecoveryRetries")).intValue();
+      if (expiryDestination == null)
+         expiryDestination = (ObjectName) server.getAttribute(jbossMQService, "ExpiryDestination");
    }
    
    public void startService() throws Exception
    {
 
       if (securityManager != null)
       {
          // Set securityConf at manager
          getServer().invoke(securityManager, "addDestination", new Object[]{spyDest.getName(), securityConf}, new String[]{"java.lang.String", "org.w3c.dom.Element"});
       }
 
+      if (expiryDestination != null)
+      {
+         String jndi = (String)getServer().getAttribute(expiryDestination, "JNDIName");
+         InitialContext ctx = new InitialContext();
+         try
+         {
+            parameters.expiryDestination = (SpyDestination)ctx.lookup(jndi);
+         }
+         finally
+         {
+            ctx.close();
+         }
+      }
+
    }
 
    public void stopService() throws Exception
    {
       // unbind from JNDI
       if (jndiBound)
       {
          InitialContext ctx = new InitialContext();
@@ -501,9 +519,20 @@
    {
       return parameters.recoveryRetries;
    }
 
    public void setRecoveryRetries(int retries)
    {
       parameters.recoveryRetries = retries;
    }
+
+   public ObjectName getExpiryDestination() 
+   {
+      return expiryDestination;
+   }
+
+   public void setExpiryDestination(ObjectName expiryDestination)
+   {
+      this.expiryDestination = expiryDestination;
+   }
+
 }
Index: src/main/org/jboss/mq/server/jmx/DestinationManager.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/jmx/DestinationManager.java,v
retrieving revision 1.13.4.3
diff -b -B -U8 -r1.13.4.3 DestinationManager.java
--- src/main/org/jboss/mq/server/jmx/DestinationManager.java	12 Jul 2005 18:04:36 -0000	1.13.4.3
+++ src/main/org/jboss/mq/server/jmx/DestinationManager.java	24 Aug 2005 07:28:35 -0000
@@ -52,16 +52,18 @@
    /** The JMX ObjectName of the configured persistence manager */
    private ObjectName persistenceManager;
    /** The JMX ObjectName of the configured state manager */
    private ObjectName stateManager;
    /** The JMX ObjectName of the message cache */
    private ObjectName messageCache;
    /** The temporary topic/queue parameters */
    private BasicQueueParameters tempParameters = new BasicQueueParameters();
+   /** Default expiry destination */
+   private ObjectName expiryDestination;
 
    /**
     * @jmx:managed-attribute
     * @return the count of active clients
     */
    public int getClientCount()
    {
       if (jmsServer == null)
@@ -214,16 +216,26 @@
       return tempParameters.recoveryRetries;
    }
 
    public void setRecoveryRetries(int retries)
    {
       tempParameters.recoveryRetries = retries;
    }
    
+   public ObjectName getExpiryDestination() 
+   {
+      return expiryDestination;
+   }
+
+   public void setExpiryDestination(ObjectName expiryDestination)
+   {
+      this.expiryDestination = expiryDestination;
+   }
+
    /**
     * @jmx:managed-operation
     */
    public void createQueue(String name) throws Exception
    {
       createDestination("org.jboss.mq.server.jmx.Queue", getQueueObjectName(name), null);
    }
 
Index: src/main/org/jboss/mq/server/jmx/DestinationManagerMBean.java
===================================================================
RCS file: /cvsroot/jboss/jbossmq/src/main/org/jboss/mq/server/jmx/DestinationManagerMBean.java,v
retrieving revision 1.3.6.3
diff -b -B -U8 -r1.3.6.3 DestinationManagerMBean.java
--- src/main/org/jboss/mq/server/jmx/DestinationManagerMBean.java	12 Jul 2005 17:12:02 -0000	1.3.6.3
+++ src/main/org/jboss/mq/server/jmx/DestinationManagerMBean.java	24 Aug 2005 07:28:35 -0000
@@ -1,16 +1,18 @@
 /*
  * JBoss, the OpenSource J2EE webOS
  *
  * Distributable under LGPL license.
  * See terms of license at gnu.org.
  */
 package org.jboss.mq.server.jmx;
 
+import javax.management.ObjectName;
+
 /**
  * MBean interface.
  */
 public interface DestinationManagerMBean extends org.jboss.mq.server.jmx.InterceptorMBean
 {
 
    int getClientCount();
 
@@ -87,16 +89,26 @@
     */
    public int getRecoveryRetries();
 
    /**
     * Sets the class implementating the receivers
     */
    public void setRecoveryRetries(int retries);
 
+   /**
+    * Returns the expiry destination.
+    */
+   public ObjectName getExpiryDestination();
+
+   /**
+    * Sets the expiry destination.
+    */
+   public void setExpiryDestination(ObjectName destination);
+
    void createQueue(java.lang.String name) throws java.lang.Exception;
 
    void createTopic(java.lang.String name) throws java.lang.Exception;
 
    void createQueue(java.lang.String name, java.lang.String jndiLocation) throws java.lang.Exception;
 
    void createTopic(java.lang.String name, java.lang.String jndiLocation) throws java.lang.Exception;
 
