You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ba...@apache.org on 2008/09/26 23:01:44 UTC

svn commit: r699500 - /james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java

Author: bago
Date: Fri Sep 26 14:01:44 2008
New Revision: 699500

URL: http://svn.apache.org/viewvc?rev=699500&view=rev
Log:
Introduced a Retry mailet (JAMES-874)
Code provided by Ajay Chitre. I just renamed it to Retry (we never have the Mailet postfix in our mailets) and removed generics and java5 "for" loop because we're still on java4 compliance.

Added:
    james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java   (with props)

Added: james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java
URL: http://svn.apache.org/viewvc/james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java?rev=699500&view=auto
==============================================================================
--- james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java (added)
+++ james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java Fri Sep 26 14:01:44 2008
@@ -0,0 +1,735 @@
+/****************************************************************
+ * 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.james.transport.mailets;
+
+import org.apache.avalon.cornerstone.services.store.Store;
+import org.apache.avalon.framework.configuration.DefaultConfiguration;
+import org.apache.avalon.framework.container.ContainerUtil;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.james.Constants;
+import org.apache.james.services.SpoolRepository;
+import org.apache.mailet.GenericMailet;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailetContext;
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.MatchResult;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.Perl5Compiler;
+import org.apache.oro.text.regex.Perl5Matcher;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Vector;
+
+import javax.mail.MessagingException;
+
+/**
+ * This Mailet retries delivery of a mail based on schedule specified in the
+ * James configuration file by the 'delayTime' attribute. The format of the
+ * 'delayTime' attribute is: [attempts*]delay[units]
+ * <p>
+ * For example, if the delay times were specified as follows:<br>
+ * <delayTime> 4*15 minutes </delayTime> <delayTime> 3*1 hour </delayTime>
+ * <delayTime> 3*4 hours </delayTime>
+ * 
+ * <maxRetries> 10 </maxRetries>
+ * 
+ * after the initial failure, the message will be retried by sending it to the
+ * processor specified by the 'retryProcessor' attribute, as per the following
+ * schedule: 1) 4 attempts will be made every 15 minutes. 2) 3 attempts will be
+ * made every hour. 3) 3 attempts will be made every 4 hours.
+ * 
+ * If the message still fails, it will be sent for error processing to the
+ * processor specified by the 'errorProcessor' attribute.
+ * 
+ * <p>
+ * Following list summarizes all the attributes of this Mailet that can be
+ * configured:
+ * <ul>
+ * <li><b>retryRepository</b> - Spool repository where mails are stored.
+ * <li><b>delayTime</b> - Delay time (See description above).
+ * <li><b>maxRetries</b> - Maximum no. of retry attempts.
+ * <li><b>retryThreads</b> - No. of Threads used for retrying.
+ * <li><b>retryProcessor</b> - Processor used for retrying.
+ * <li><b>errorProcessor</b> - Error processor that will be used when all retry
+ * attempts fail.
+ * <li><b>isDebug</b> - Can be set to 'true' for debugging.
+ * </ul>
+ * 
+ */
+public class Retry extends GenericMailet implements Runnable {
+    // Mail attribute that keeps track of # of retries.
+    private static final String RETRY_COUNT = "RETRY_COUNT";
+
+    // Mail attribute that keeps track of original error message.
+    public static final String ORIGINAL_ERROR = "originalError";
+
+    // Default Delay Time (Default is 6*60*60*1000 Milliseconds (6 hours)).
+    private static final long DEFAULT_DELAY_TIME = 21600000;
+
+    // Pattern to match [attempts*]delay[units].
+    private static final String PATTERN_STRING = "\\s*([0-9]*\\s*[\\*])?\\s*([0-9]+)\\s*([a-z,A-Z]*)\\s*";
+
+    // Compiled pattern of the above String.
+    private static Pattern PATTERN = null;
+
+    // Holds allowed units for delayTime together with factor to turn it into
+    // the
+    // equivalent time in milliseconds.
+    private static final HashMap MULTIPLIERS = new HashMap(10);
+
+    /*
+     * Compiles pattern for processing delayTime entries. <p>Initializes
+     * MULTIPLIERS with the supported unit quantifiers.
+     */
+    static {
+        try {
+            Perl5Compiler compiler = new Perl5Compiler();
+            PATTERN = compiler.compile(PATTERN_STRING,
+                    Perl5Compiler.READ_ONLY_MASK);
+        } catch (MalformedPatternException mpe) {
+            // This should never happen as the pattern string is hard coded.
+            System.err.println("Malformed pattern: " + PATTERN_STRING);
+            mpe.printStackTrace(System.err);
+        }
+
+        // Add allowed units and their respective multiplier.
+        MULTIPLIERS.put("msec", new Integer(1));
+        MULTIPLIERS.put("msecs", new Integer(1));
+        MULTIPLIERS.put("sec", new Integer(1000));
+        MULTIPLIERS.put("secs", new Integer(1000));
+        MULTIPLIERS.put("minute", new Integer(1000 * 60));
+        MULTIPLIERS.put("minutes", new Integer(1000 * 60));
+        MULTIPLIERS.put("hour", new Integer(1000 * 60 * 60));
+        MULTIPLIERS.put("hours", new Integer(1000 * 60 * 60));
+        MULTIPLIERS.put("day", new Integer(1000 * 60 * 60 * 24));
+        MULTIPLIERS.put("days", new Integer(1000 * 60 * 60 * 24));
+    }
+
+    /**
+     * Used in the accept call to the spool. It will select the next mail ready
+     * for processing according to the mails 'retrycount' and 'lastUpdated'
+     * time.
+     **/
+    private class MultipleDelayFilter implements SpoolRepository.AcceptFilter {
+        /**
+         * Holds the time to wait for the youngest mail to get ready for
+         * processing.
+         **/
+        long youngest = 0;
+
+        /**
+         * Uses the getNextDelay to determine if a mail is ready for processing
+         * based on the delivered parameters errorMessage (which holds the
+         * retrycount), lastUpdated and state.
+         * 
+         * @param key
+         *            the name/key of the message
+         * @param state
+         *            the mails state
+         * @param lastUpdated
+         *            the mail was last written to the spool at this time.
+         * @param errorMessage
+         *            actually holds the retrycount as a string
+         * @return {@code true} if message is ready for processing else {@code
+         *         false}
+         **/
+        public boolean accept(String key, String state, long lastUpdated,
+                String errorMessage) {
+            int retries = Integer.parseInt(errorMessage);
+
+            long delay = getNextDelay(retries);
+            long timeToProcess = delay + lastUpdated;
+
+            if (System.currentTimeMillis() > timeToProcess) {
+                // We're ready to process this again
+                return true;
+            } else {
+                // We're not ready to process this.
+                if (youngest == 0 || youngest > timeToProcess) {
+                    // Mark this as the next most likely possible mail to
+                    // process
+                    youngest = timeToProcess;
+                }
+                return false;
+            }
+        }
+
+        /**
+         * Returns the optimal time the SpoolRepository.accept(AcceptFilter)
+         * method should wait before trying to find a mail ready for processing
+         * again.
+         **/
+        public long getWaitTime() {
+            if (youngest == 0) {
+                return 0;
+            } else {
+                long duration = youngest - System.currentTimeMillis();
+                youngest = 0;
+                return duration <= 0 ? 1 : duration;
+            }
+        }
+    }
+    
+    // Flag used by James for logging messages.
+    private boolean isDebug = false;
+
+    // Repository used to store messages that will be retried.
+    private SpoolRepository retryRepository;
+
+    // List of Delay Times. Controls frequency of retry attempts.
+    private long[] delayTimes;
+
+    // Maximum no. of retries (Defaults to 5).
+    private int maxRetries = 5;
+
+    // No. of threads used to process messages that should be retried.
+    private int retryThreadCount = 1;
+
+    // Collection that stores all Retry threads.
+    private Collection retryThreads = new Vector();
+
+    // Processor that will be called for retrying. Defaults to "root" processor.
+    private String retryProcessor = Mail.DEFAULT;
+
+    // Processor that will be called if retrying fails after trying maximum no.
+    // of
+    // times. Defaults to "error" processor.
+    private String errorProcessor = Mail.ERROR;
+
+    // Flag used by 'run' method to end itself.
+    private volatile boolean destroyed = false;
+
+    // Matcher used in 'init' method to parse delayTimes specified in config
+    // file.
+    private Perl5Matcher delayTimeMatcher;
+
+    // Filter used by 'accept' to check if message is ready for retrying.
+    private MultipleDelayFilter delayFilter = new MultipleDelayFilter();
+
+    // Path of the retry repository
+    private String retryRepositoryPath = null;
+
+    /**
+     * Initializes all arguments based on configuration values specified in the
+     * James configuration file.
+     * 
+     * @throws MessagingException
+     *             on failure to initialize attributes.
+     */
+    public void init() throws MessagingException {
+        // Set isDebug flag.
+        String debug = getInitParameter("debug");
+        isDebug = (debug == null || debug.equalsIgnoreCase("false")) ? false
+                : true;
+
+        // Create list of Delay Times.
+        ArrayList delayTimesList = new ArrayList();
+        if (getInitParameter("delayTime") != null) {
+            delayTimeMatcher = new Perl5Matcher();
+            String delayTimesParm = getInitParameter("delayTime");
+
+            // Split on commas
+            String[] tokens = delayTimesParm.split(",");
+            for (int i = 0; i < tokens.length; i++) {
+                delayTimesList.add(new Delay(tokens[i]));
+            }
+        } else {
+            // Use default delayTime.
+            delayTimesList.add(new Delay());
+        }
+
+        // Get No. of Max Retries.
+        if (getInitParameter("maxRetries") != null) {
+            maxRetries = Integer.parseInt(getInitParameter("maxRetries"));
+        }
+
+        // Check consistency of 'maxRetries' with delay_times_list attempts.
+        int totalAttempts = calcTotalAttempts(delayTimesList);
+
+        // If inconsistency found, fix it.
+        if (totalAttempts > maxRetries) {
+            log("Total number of delayTime attempts exceeds maxRetries specified. "
+                    + " Increasing maxRetries from "
+                    + maxRetries
+                    + " to "
+                    + totalAttempts);
+            maxRetries = totalAttempts;
+        } else {
+            int extra = maxRetries - totalAttempts;
+            if (extra != 0) {
+                log("maxRetries is larger than total number of attempts specified.  "
+                        + "Increasing last delayTime with "
+                        + extra
+                        + " attempts ");
+
+                // Add extra attempts to the last delayTime.
+                if (delayTimesList.size() != 0) {
+                    // Get the last delayTime.
+                    Delay delay = (Delay) delayTimesList.get(delayTimesList
+                            .size() - 1);
+
+                    // Increase no. of attempts.
+                    delay.setAttempts(delay.getAttempts() + extra);
+                    log("Delay of " + delay.getDelayTime()
+                            + " msecs is now attempted: " + delay.getAttempts()
+                            + " times");
+                } else {
+                    throw new MessagingException(
+                            "No delaytimes, cannot continue");
+                }
+            }
+        }
+        delayTimes = expandDelays(delayTimesList);
+
+        ServiceManager compMgr = (ServiceManager) getMailetContext()
+                .getAttribute(Constants.AVALON_COMPONENT_MANAGER);
+
+        // Get the path for the 'Retry' repository. This is the place on the
+        // file system where Mail objects will be saved during the 'retry'
+        // processing. This can be changed to a repository on a database (e.g.
+        // db://maildb/spool/retry).
+        retryRepositoryPath = getInitParameter("retryRepository");
+        if (retryRepositoryPath == null) {
+            retryRepositoryPath = "file://var/mail/retry/";
+        }
+
+        try {
+            // Instantiate a MailRepository for mails that should be retried.
+            Store mailstore = (Store) compMgr.lookup(Store.ROLE);
+
+            DefaultConfiguration spoolConf = new DefaultConfiguration(
+                    "repository", "generated:Retry");
+            spoolConf.setAttribute("destinationURL", retryRepositoryPath);
+            spoolConf.setAttribute("type", "SPOOL");
+            retryRepository = (SpoolRepository) mailstore.select(spoolConf);
+        } catch (ServiceException cnfe) {
+            log("Failed to retrieve Store component:" + cnfe.getMessage());
+            throw new MessagingException("Failed to retrieve Store component",
+                    cnfe);
+        }
+
+        // Start Retry Threads.
+        retryThreadCount = Integer.parseInt(getInitParameter("retryThreads"));
+        for (int i = 0; i < retryThreadCount; i++) {
+            String threadName = "Retry thread (" + i + ")";
+            Thread t = new Thread(this, threadName);
+            t.start();
+            retryThreads.add(t);
+        }
+
+        // Get Retry Processor
+        String processor = getInitParameter("retryProcessor");
+        retryProcessor = (processor == null) ? Mail.DEFAULT : processor;
+
+        // Get Error Processor
+        processor = getInitParameter("errorProcessor");
+        errorProcessor = (processor == null) ? Mail.ERROR : processor;
+    }
+
+    /**
+     * Calculates Total no. of attempts for the specified delayList.
+     * 
+     * @param delayList
+     *            list of 'Delay' objects
+     * @return total no. of retry attempts
+     */
+    private int calcTotalAttempts(ArrayList delayList) {
+        int sum = 0;
+        if (delayList != null)
+            for (int i = 0; i < delayList.size(); i++) {
+                sum += ((Delay) delayList.get(i)).getAttempts();
+            }
+        return sum;
+    }
+
+    /**
+     * Expands an ArrayList containing Delay objects into an array holding the
+     * only delaytime in the order.
+     * <p>
+     * 
+     * For example, if the list has 2 Delay objects : First having attempts=2
+     * and delaytime 4000 Second having attempts=1 and delaytime=300000
+     * 
+     * This will be expanded into this array:
+     * <p>
+     * 
+     * long[0] = 4000
+     * <p>
+     * long[1] = 4000
+     * <p>
+     * long[2] = 300000
+     * <p>
+     * 
+     * @param delayList
+     *            the list to expand
+     * @return the expanded list
+     **/
+    private long[] expandDelays(ArrayList delayList) {
+        long[] delays = new long[calcTotalAttempts(delayList)];
+        int idx = 0;
+        for (int i = 0; i < delayList.size(); i++) {
+            for (int j = 0; j < ((Delay) delayList.get(i)).getAttempts(); j++) {
+                delays[idx++] = ((Delay) delayList.get(i)).getDelayTime();
+            }
+        }
+        return delays;
+    }
+
+    /**
+     * Returns, given a retry count, the next delay time to use.
+     * 
+     * @param retryCount
+     *            the current retry count.
+     * @return the next delay time to use
+     **/
+    private long getNextDelay(int retryCount) {
+        if (retryCount > delayTimes.length) {
+            return DEFAULT_DELAY_TIME;
+        }
+        return delayTimes[retryCount];
+    }
+
+    public String getMailetInfo() {
+        return "Retry Mailet";
+    }
+
+    /**
+     * Checks if maximum retry count has been reached. If it is, then it
+     * forwards the message to the error processor; otherwise writes it to the
+     * retry repository.
+     * 
+     * @param mail
+     *            the mail to be retried.
+     * @throws MessagingException
+     *             on failure to send it to the error processor.
+     * 
+     * @see org.apache.mailet.Mailet#service(org.apache.mailet.Mail)
+     */
+    public void service(Mail mail) throws MessagingException {
+        if (isDebug) {
+            log("Retrying mail " + mail.getName());
+        }
+
+        // Save the original error message.
+        mail.setAttribute(ORIGINAL_ERROR, mail.getErrorMessage());
+
+        // Get retry count and put it in the error message.
+        // Note: 'errorMessage' is the only argument of 'accept' method in
+        // SpoolRepository.AcceptFilter that can be used to pass the retry
+        // count.
+        String retryCount = (String) mail.getAttribute(RETRY_COUNT);
+        if (retryCount == null) {
+            retryCount = "0";
+        }
+        mail.setErrorMessage(retryCount);
+
+        int retries = Integer.parseInt(retryCount);
+        String message = "";
+
+        // If maximum retries number hasn't reached, store message in retrying
+        // repository.
+        if (retries < maxRetries) {
+            message = "Storing " + mail.getMessage().getMessageID()
+                    + " to retry repository " + retryRepositoryPath
+                    + ", retry " + retries;
+            log(message);
+
+            mail.setAttribute(RETRY_COUNT, retryCount);
+            retryRepository.store(mail);
+            mail.setState(Mail.GHOST);
+        } else {
+            // Forward message to 'errorProcessor'.
+            message = "Sending " + mail.getMessage().getMessageID()
+                    + " to error processor after retrying " + retries
+                    + " times.";
+            log(message);
+            mail.setState(errorProcessor);
+            MailetContext mc = getMailetContext();
+            try {
+                message = "Message failed after " + retries
+                        + " retries with error " + "message: "
+                        + mail.getAttribute(ORIGINAL_ERROR);
+                mail.setErrorMessage(message);
+                mc.sendMail(mail);
+            } catch (MessagingException e) {
+                // We shouldn't get an exception, because the mail was already
+                // processed.
+                log("Exception re-inserting failed mail: ", e);
+                throw new MessagingException(
+                        "Exception encountered while bouncing "
+                                + "mail in Retry process.", e);
+            }
+        }
+    }
+
+    /**
+     * Stops all the retry threads that are waiting for messages. This method is
+     * called by the Mailet container before taking this Mailet out of service.
+     */
+    public synchronized void destroy() {
+        // Mark flag so threads from this Mailet stop themselves
+        destroyed = true;
+
+        // Wake up all threads from waiting for an accept
+        for (int i = 0; i < retryThreads.size(); i++) {
+            ((Thread) ((ArrayList) retryThreads).get(i)).interrupt();
+        }
+        notifyAll();
+    }
+
+    /**
+     * Handles checking the retrying spool for new mail and retrying them if
+     * there are ready for retrying.
+     */
+    public void run() {
+        try {
+            while (!Thread.interrupted() && !destroyed) {
+                try {
+                    // Get the 'mail' object that is ready for retrying. If no
+                    // message is
+                    // ready, the 'accept' will block until message is ready.
+                    // The amount
+                    // of time to block is determined by the 'getWaitTime'
+                    // method of the
+                    // MultipleDelayFilter.
+                    Mail mail = retryRepository.accept(delayFilter);
+                    String key = mail.getName();
+                    try {
+                        if (isDebug) {
+                            String message = Thread.currentThread().getName()
+                                    + " will " + "process mail " + key;
+                            log(message);
+                        }
+
+                        // Retry message
+                        if (retry(mail)) {
+                            // If retry attempt was successful, remove message.
+                            // ContainerUtil.dispose(mail);
+                            retryRepository.remove(key);
+                        } else {
+                            // Something happened that will delay delivery.
+                            // Store it back in
+                            // the retry repository.
+                            retryRepository.store(mail);
+
+                            // This is an update, so we have to unlock and
+                            // notify or this mail
+                            // is kept locked by this thread.
+                            // Note: We do not notify because we updated an
+                            // already existing
+                            // mail and we are now free to handle more mails.
+                            // Furthermore this mail should not be processed now
+                            // because we
+                            // have a retry time scheduling.
+                            retryRepository.unlock(key);
+
+                            // Clear the object handle to make sure it recycles
+                            // this object.
+                            ContainerUtil.dispose(mail);
+                            mail = null;
+                        }
+                    } catch (Exception e) {
+                        // Prevent unexpected exceptions from causing looping by
+                        // removing
+                        // message from outgoing.
+                        // DO NOT CHANGE THIS to catch Error! For example, if
+                        // there were an
+                        // OutOfMemory condition caused because something else
+                        // in the server
+                        // was abusing memory, we would not want to start
+                        // purging the
+                        // retrying spool!
+                        ContainerUtil.dispose(mail);
+                        retryRepository.remove(key);
+                        throw e;
+                    }
+                } catch (Throwable e) {
+                    if (!destroyed) {
+                        log("Exception caught in Retry.run()", e);
+                    }
+                }
+            }
+        } finally {
+            // Restore the thread state to non-interrupted.
+            Thread.interrupted();
+        }
+    }
+
+    /**
+     * Retries delivery of a {@link Mail}.
+     * 
+     * @param mail
+     *            mail to be retried.
+     * @return {@code true} if message was resent successfully else {@code
+     *         false}
+     */
+    private boolean retry(Mail mail) {
+        if (isDebug) {
+            log("Attempting to deliver " + mail.getName());
+        }
+
+        // Update retry count
+        int retries = Integer.parseInt((String) mail.getAttribute(RETRY_COUNT));
+        ++retries;
+        mail.setErrorMessage(retries + "");
+        mail.setAttribute(RETRY_COUNT, String.valueOf(retries));
+        mail.setLastUpdated(new Date());
+
+        // Call preprocessor
+        preprocess(mail);
+
+        // Send it to 'retry' processor
+        mail.setState(retryProcessor);
+        MailetContext mc = getMailetContext();
+        try {
+            String message = "Retrying message "
+                    + mail.getMessage().getMessageID() + ".  Attempt #: "
+                    + retries;
+            log(message);
+            mc.sendMail(mail);
+        } catch (MessagingException e) {
+            // We shouldn't get an exception, because the mail was already
+            // processed
+            log("Exception while retrying message. ", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Pre-processes the {@link Mail} object before resending.
+     * <p>
+     * This method can be used by subclasses to perform application specific
+     * processing on the Mail object, such as, adding and/or removing
+     * application specific Mail attributes etc. The default implementation
+     * leaves the Mail object intact.
+     * 
+     * @param mail
+     *            mail object that can be customized before resending.
+     */
+    protected void preprocess(Mail mail) {
+    }
+
+    /**
+     * This class is used to hold a delay time and its corresponding number of
+     * retries.
+     **/
+    private class Delay {
+        private int attempts = 1;
+
+        private long delayTime = DEFAULT_DELAY_TIME;
+
+        /**
+         * This constructor expects Strings of the form
+         * "[attempt\*]delaytime[unit]".
+         * <p>
+         * The optional attempt is the number of tries this delay should be used
+         * (default = 1). The unit, if present, must be one of
+         * (msec,sec,minute,hour,day). The default value of unit is 'msec'.
+         * <p>
+         * The constructor multiplies the delaytime by the relevant multiplier
+         * for the unit, so the delayTime instance variable is always in msec.
+         * 
+         * @param initString
+         *            the string to initialize this Delay object from
+         **/
+        public Delay(String initString) throws MessagingException {
+            // Default unit value to 'msec'.
+            String unit = "msec";
+
+            if (delayTimeMatcher.matches(initString, PATTERN)) {
+                MatchResult res = delayTimeMatcher.getMatch();
+
+                // The capturing groups will now hold:
+                // at 1: attempts * (if present)
+                // at 2: delaytime
+                // at 3: unit (if present)
+                if (res.group(1) != null && !res.group(1).equals("")) {
+                    // We have an attempt *
+                    String attemptMatch = res.group(1);
+
+                    // Strip the * and whitespace.
+                    attemptMatch = attemptMatch.substring(0,
+                            attemptMatch.length() - 1).trim();
+                    attempts = Integer.parseInt(attemptMatch);
+                }
+
+                delayTime = Long.parseLong(res.group(2));
+
+                if (!res.group(3).equals("")) {
+                    // We have a value for 'unit'.
+                    unit = res.group(3).toLowerCase(Locale.US);
+                }
+            } else {
+                throw new MessagingException(initString + " does not match "
+                        + PATTERN_STRING);
+            }
+
+            // Look for unit in the MULTIPLIERS Hashmap & calculate delayTime.
+            if (MULTIPLIERS.get(unit) != null) {
+                int multiplier = ((Integer) MULTIPLIERS.get(unit)).intValue();
+                delayTime *= multiplier;
+            } else {
+                throw new MessagingException("Unknown unit: " + unit);
+            }
+        }
+
+        /**
+         * This constructor makes a default Delay object with attempts = 1 and
+         * delayTime = DEFAULT_DELAY_TIME.
+         **/
+        public Delay() {
+        }
+
+        /**
+         * @return the delayTime for this Delay
+         **/
+        public long getDelayTime() {
+            return delayTime;
+        }
+
+        /**
+         * @return the number attempts this Delay should be used.
+         **/
+        public int getAttempts() {
+            return attempts;
+        }
+
+        /**
+         * Set the number attempts this Delay should be used.
+         **/
+        public void setAttempts(int value) {
+            attempts = value;
+        }
+
+        /**
+         * Pretty prints this Delay
+         **/
+        public String toString() {
+            String message = getAttempts() + "*" + getDelayTime() + "msecs";
+            return message;
+        }
+    }
+}

Propchange: james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/server/trunk/mailets-function/src/main/java/org/apache/james/transport/mailets/Retry.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org