001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.fetchmail;
021    
022    import java.util.ArrayList;
023    import java.util.Collections;
024    import java.util.Enumeration;
025    import java.util.HashMap;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Properties;
030    
031    import javax.mail.MessagingException;
032    import javax.mail.Session;
033    
034    import org.apache.commons.configuration.Configuration;
035    import org.apache.commons.configuration.ConfigurationException;
036    import org.apache.commons.configuration.HierarchicalConfiguration;
037    import org.apache.commons.configuration.HierarchicalConfiguration.Node;
038    import org.apache.james.dnsservice.api.DNSService;
039    import org.apache.james.domainlist.api.DomainList;
040    import org.apache.james.lifecycle.api.Configurable;
041    import org.apache.james.lifecycle.api.LogEnabled;
042    import org.apache.james.queue.api.MailQueue;
043    import org.apache.james.user.api.UsersRepository;
044    import org.apache.james.user.api.UsersRepositoryException;
045    import org.slf4j.Logger;
046    
047    /**
048     * <p>
049     * Class <code>FetchMail</code> is an Avalon task that is periodically triggered
050     * to fetch mail from a JavaMail Message Store.
051     * </p>
052     * 
053     * <p>
054     * The lifecycle of an instance of <code>FetchMail</code> is managed by Avalon.
055     * The <code>configure(Configuration)</code> method is invoked to parse and
056     * validate Configuration properties. The targetTriggered(String) method is
057     * invoked to execute the task.
058     * </p>
059     * 
060     * <p>
061     * When triggered, a sorted list of Message Store Accounts to be processed is
062     * built. Each Message Store Account is processed by delegating to
063     * <code>StoreProcessor</code>.
064     * </p>
065     * 
066     * <p>
067     * There are two kinds of Message Store Accounts, static and dynamic. Static
068     * accounts are expliciltly declared in the Configuration. Dynamic accounts are
069     * built each time the task is executed, one per each user defined to James,
070     * using the James user name with a configurable prefix and suffix to define the
071     * host user identity and recipient identity for each Account. Dynamic accounts
072     * allow <code>FetchMail</code> to fetch mail for all James users without
073     * modifying the Configuration parameters or restarting the Avalon server.
074     * </p>
075     * 
076     * <p>
077     * To fully understand the operations supported by this task, read the Class
078     * documention for each Class in the delegation chain starting with this class'
079     * delegate, <code>StoreProcessor</code>.
080     * </p>
081     */
082    public class FetchMail implements Runnable, LogEnabled, Configurable {
083        /**
084         * Key fields for DynamicAccounts.
085         */
086        private final static class DynamicAccountKey {
087            /**
088             * The base user name without prfix or suffix
089             */
090            private String fieldUserName;
091    
092            /**
093             * The sequence number of the parameters used to construct the Account
094             */
095            private int fieldSequenceNumber;
096    
097            /**
098             * Constructor for DynamicAccountKey.
099             */
100            private DynamicAccountKey() {
101                super();
102            }
103    
104            /**
105             * Constructor for DynamicAccountKey.
106             */
107            public DynamicAccountKey(String userName, int sequenceNumber) {
108                this();
109                setUserName(userName);
110                setSequenceNumber(sequenceNumber);
111            }
112    
113            /**
114             * @see java.lang.Object#equals(Object)
115             */
116            public boolean equals(Object obj) {
117                if (null == obj)
118                    return false;
119                if (!(obj.getClass() == getClass()))
120                    return false;
121                return (getUserName().equals(((DynamicAccountKey) obj).getUserName()) && getSequenceNumber() == ((DynamicAccountKey) obj).getSequenceNumber());
122            }
123    
124            /**
125             * @see java.lang.Object#hashCode()
126             */
127            public int hashCode() {
128                return getUserName().hashCode() ^ getSequenceNumber();
129            }
130    
131            /**
132             * Returns the sequenceNumber.
133             * 
134             * @return int
135             */
136            public int getSequenceNumber() {
137                return fieldSequenceNumber;
138            }
139    
140            /**
141             * Returns the userName.
142             * 
143             * @return String
144             */
145            public String getUserName() {
146                return fieldUserName;
147            }
148    
149            /**
150             * Sets the sequenceNumber.
151             * 
152             * @param sequenceNumber
153             *            The sequenceNumber to set
154             */
155            protected void setSequenceNumber(int sequenceNumber) {
156                fieldSequenceNumber = sequenceNumber;
157            }
158    
159            /**
160             * Sets the userName.
161             * 
162             * @param userName
163             *            The userName to set
164             */
165            protected void setUserName(String userName) {
166                fieldUserName = userName;
167            }
168    
169        }
170    
171        private final static class ParsedDynamicAccountParameters {
172            private String fieldUserPrefix;
173            private String fieldUserSuffix;
174    
175            private String fieldPassword;
176    
177            private int fieldSequenceNumber;
178    
179            private boolean fieldIgnoreRecipientHeader;
180            private String fieldRecipientPrefix;
181            private String fieldRecipientSuffix;
182            private String customRecipientHeader;
183    
184            /**
185             * Constructor for ParsedDynamicAccountParameters.
186             */
187            private ParsedDynamicAccountParameters() {
188                super();
189            }
190    
191            /**
192             * Constructor for ParsedDynamicAccountParameters.
193             */
194            public ParsedDynamicAccountParameters(int sequenceNumber, Configuration configuration) throws ConfigurationException {
195                this();
196                setSequenceNumber(sequenceNumber);
197                setUserPrefix(configuration.getString("[@userprefix]", ""));
198                setUserSuffix(configuration.getString("[@usersuffix]", ""));
199                setRecipientPrefix(configuration.getString("[@recipientprefix]", ""));
200                setRecipientSuffix(configuration.getString("[@recipientsuffix]", ""));
201                setPassword(configuration.getString("[@password]"));
202                setIgnoreRecipientHeader(configuration.getBoolean("[@ignorercpt-header]"));
203                setCustomRecipientHeader(configuration.getString("[@customrcpt-header]", ""));
204            }
205    
206            /**
207             * Returns the custom recipient header.
208             * 
209             * @return String
210             */
211            public String getCustomRecipientHeader() {
212                return this.customRecipientHeader;
213            }
214    
215            /**
216             * Returns the recipientprefix.
217             * 
218             * @return String
219             */
220            public String getRecipientPrefix() {
221                return fieldRecipientPrefix;
222            }
223    
224            /**
225             * Returns the recipientsuffix.
226             * 
227             * @return String
228             */
229            public String getRecipientSuffix() {
230                return fieldRecipientSuffix;
231            }
232    
233            /**
234             * Returns the userprefix.
235             * 
236             * @return String
237             */
238            public String getUserPrefix() {
239                return fieldUserPrefix;
240            }
241    
242            /**
243             * Returns the userSuffix.
244             * 
245             * @return String
246             */
247            public String getUserSuffix() {
248                return fieldUserSuffix;
249            }
250    
251            /**
252             * Sets the custom recipient header.
253             * 
254             * @param customRecipientHeader
255             *            The header to be used
256             */
257            public void setCustomRecipientHeader(String customRecipientHeader) {
258                this.customRecipientHeader = customRecipientHeader;
259            }
260    
261            /**
262             * Sets the recipientprefix.
263             * 
264             * @param recipientprefix
265             *            The recipientprefix to set
266             */
267            protected void setRecipientPrefix(String recipientprefix) {
268                fieldRecipientPrefix = recipientprefix;
269            }
270    
271            /**
272             * Sets the recipientsuffix.
273             * 
274             * @param recipientsuffix
275             *            The recipientsuffix to set
276             */
277            protected void setRecipientSuffix(String recipientsuffix) {
278                fieldRecipientSuffix = recipientsuffix;
279            }
280    
281            /**
282             * Sets the userprefix.
283             * 
284             * @param userprefix
285             *            The userprefix to set
286             */
287            protected void setUserPrefix(String userprefix) {
288                fieldUserPrefix = userprefix;
289            }
290    
291            /**
292             * Sets the userSuffix.
293             * 
294             * @param userSuffix
295             *            The userSuffix to set
296             */
297            protected void setUserSuffix(String userSuffix) {
298                fieldUserSuffix = userSuffix;
299            }
300    
301            /**
302             * Returns the password.
303             * 
304             * @return String
305             */
306            public String getPassword() {
307                return fieldPassword;
308            }
309    
310            /**
311             * Sets the ignoreRecipientHeader.
312             * 
313             * @param ignoreRecipientHeader
314             *            The ignoreRecipientHeader to set
315             */
316            protected void setIgnoreRecipientHeader(boolean ignoreRecipientHeader) {
317                fieldIgnoreRecipientHeader = ignoreRecipientHeader;
318            }
319    
320            /**
321             * Sets the password.
322             * 
323             * @param password
324             *            The password to set
325             */
326            protected void setPassword(String password) {
327                fieldPassword = password;
328            }
329    
330            /**
331             * Returns the ignoreRecipientHeader.
332             * 
333             * @return boolean
334             */
335            public boolean isIgnoreRecipientHeader() {
336                return fieldIgnoreRecipientHeader;
337            }
338    
339            /**
340             * Returns the sequenceNumber.
341             * 
342             * @return int
343             */
344            public int getSequenceNumber() {
345                return fieldSequenceNumber;
346            }
347    
348            /**
349             * Sets the sequenceNumber.
350             * 
351             * @param sequenceNumber
352             *            The sequenceNumber to set
353             */
354            protected void setSequenceNumber(int sequenceNumber) {
355                fieldSequenceNumber = sequenceNumber;
356            }
357    
358        }
359    
360        /**
361         * @see org.apache.avalon.cornerstone.services.scheduler.Target#targetTriggered(String)
362         */
363        private boolean fieldFetching = false;
364    
365        /**
366         * The Configuration for this task
367         */
368        private ParsedConfiguration fieldConfiguration;
369    
370        /**
371         * A List of ParsedDynamicAccountParameters, one for every <alllocal> entry
372         * in the configuration.
373         */
374        private List<ParsedDynamicAccountParameters> fieldParsedDynamicAccountParameters;
375    
376        /**
377         * The Static Accounts for this task. These are setup when the task is
378         * configured.
379         */
380        private List<Account> fieldStaticAccounts;
381    
382        /**
383         * The JavaMail Session for this fetch task.
384         */
385    
386        private Session fieldSession;
387    
388        /**
389         * The Dynamic Accounts for this task. These are setup each time the
390         * fetchtask is run.
391         */
392        private Map<DynamicAccountKey, DynamicAccount> fieldDynamicAccounts;
393    
394        /**
395         * The Local Users repository
396         */
397        private UsersRepository fieldLocalUsers;
398    
399        /**
400         * The DNSService
401         */
402        private DNSService dnsServer;
403    
404        private Logger logger;
405    
406        private MailQueue queue;
407    
408        private DomainList domainList;
409    
410        /**
411         * Constructor for POP3mail.
412         */
413        public FetchMail() {
414            super();
415        }
416    
417        /**
418         * Method configure parses and validates the Configuration data and creates
419         * a new <code>ParsedConfiguration</code>, an <code>Account</code> for each
420         * configured static account and a
421         * <code>ParsedDynamicAccountParameters</code> for each dynamic account.
422         * 
423         * @see org.apache.james.lifecycle.api.Configurable#configure(HierarchicalConfiguration)
424         */
425        @SuppressWarnings("unchecked")
426        public void configure(HierarchicalConfiguration configuration) throws ConfigurationException {
427            // Set any Session parameters passed in the Configuration
428            setSessionParameters(configuration);
429    
430            // Create the ParsedConfiguration used in the delegation chain
431            ParsedConfiguration parsedConfiguration = new ParsedConfiguration(configuration, logger, getLocalUsers(), getDNSService(), getDomainList(), getMailQueue());
432    
433            setParsedConfiguration(parsedConfiguration);
434    
435            // Setup the Accounts
436            List<HierarchicalConfiguration> allAccounts = configuration.configurationsAt("accounts");
437            if (allAccounts.size() < 1)
438                throw new ConfigurationException("Missing <accounts> section.");
439            if (allAccounts.size() > 1)
440                throw new ConfigurationException("Too many <accounts> sections, there must be exactly one");
441            HierarchicalConfiguration accounts = allAccounts.get(0);
442    
443            if (accounts.getKeys().hasNext() == false)
444                throw new ConfigurationException("Missing <account> section.");
445    
446            List<Node> accountsChildren = accounts.getRoot().getChildren();
447            int i = 0;
448    
449            // Create an Account for every configured account
450            for (Node accountsChild : accountsChildren) {
451    
452                String accountsChildName = accountsChild.getName();
453    
454                List<HierarchicalConfiguration> accountsChildConfig = accounts.configurationsAt(accountsChildName);
455                HierarchicalConfiguration conf = accountsChildConfig.get(i);
456    
457                if ("alllocal".equals(accountsChildName)) {
458                    // <allLocal> is dynamic, save the parameters for accounts to
459                    // be created when the task is triggered
460                    getParsedDynamicAccountParameters().add(new ParsedDynamicAccountParameters(i, conf));
461                    continue;
462                }
463    
464                if ("account".equals(accountsChildName)) {
465                    // Create an Account for the named user and
466                    // add it to the list of static accounts
467                    getStaticAccounts().add(new Account(i, parsedConfiguration, conf.getString("[@user]"), conf.getString("[@password]"), conf.getString("[@recipient]"), conf.getBoolean("[@ignorercpt-header]"), conf.getString("[@customrcpt-header]", ""), getSession()));
468                    continue;
469                }
470    
471                throw new ConfigurationException("Illegal token: <" + accountsChildName + "> in <accounts>");
472            }
473            i++;
474        }
475    
476        /**
477         * Method target triggered fetches mail for each configured account.
478         * 
479         */
480        public void run() {
481            // if we are already fetching then just return
482            if (isFetching()) {
483                logger.info("Triggered fetch cancelled. A fetch is already in progress.");
484                return;
485            }
486    
487            // Enter Fetching State
488            try {
489                setFetching(true);
490                logger.info("Fetcher starting fetches");
491    
492                logJavaMailProperties();
493    
494                // Update the dynamic accounts,
495                // merge with the static accounts and
496                // sort the accounts so they are in the order
497                // they were entered in config.xml
498                updateDynamicAccounts();
499                ArrayList<Account> mergedAccounts = new ArrayList<Account>(getDynamicAccounts().size() + getStaticAccounts().size());
500                mergedAccounts.addAll(getDynamicAccounts().values());
501                mergedAccounts.addAll(getStaticAccounts());
502                Collections.sort(mergedAccounts);
503    
504                StringBuilder logMessage = new StringBuilder(64);
505                logMessage.append("Processing ");
506                logMessage.append(getStaticAccounts().size());
507                logMessage.append(" static accounts and ");
508                logMessage.append(getDynamicAccounts().size());
509                logMessage.append(" dynamic accounts.");
510                logger.info(logMessage.toString());
511    
512                // Fetch each account
513                Iterator<Account> accounts = mergedAccounts.iterator();
514                while (accounts.hasNext()) {
515                    try {
516                        new StoreProcessor(accounts.next()).process();
517                    } catch (MessagingException ex) {
518                        logger.error("A MessagingException has terminated processing of this Account", ex);
519                    }
520                }
521            } catch (Exception ex) {
522                logger.error("An Exception has terminated this fetch.", ex);
523            } finally {
524                logger.info("Fetcher completed fetches");
525    
526                // Exit Fetching State
527                setFetching(false);
528            }
529        }
530    
531        @SuppressWarnings("unchecked")
532        private void logJavaMailProperties() {
533            // if debugging, list the JavaMail property key/value pairs
534            // for this Session
535            if (logger.isDebugEnabled()) {
536                logger.debug("Session properties:");
537                Properties properties = getSession().getProperties();
538                Enumeration e = properties.keys();
539                while (e.hasMoreElements()) {
540                    String key = (String) e.nextElement();
541                    String val = (String) properties.get(key);
542                    if (val.length() > 40) {
543                        val = val.substring(0, 37) + "...";
544                    }
545                    logger.debug(key + "=" + val);
546    
547                }
548            }
549        }
550    
551        /**
552         * Returns the fetching.
553         * 
554         * @return boolean
555         */
556        protected boolean isFetching() {
557            return fieldFetching;
558        }
559    
560        /**
561         * Sets the fetching.
562         * 
563         * @param fetching
564         *            The fetching to set
565         */
566        protected void setFetching(boolean fetching) {
567            fieldFetching = fetching;
568        }
569    
570        /**
571         * Returns the configuration.
572         * 
573         * @return ParsedConfiguration
574         */
575        protected ParsedConfiguration getConfiguration() {
576            return fieldConfiguration;
577        }
578    
579        /**
580         * Sets the configuration.
581         * 
582         * @param configuration
583         *            The configuration to set
584         */
585        protected void setParsedConfiguration(ParsedConfiguration configuration) {
586            fieldConfiguration = configuration;
587        }
588    
589        /**
590         * Returns the localUsers.
591         * 
592         * @return UsersRepository
593         */
594        protected UsersRepository getLocalUsers() {
595            return fieldLocalUsers;
596        }
597    
598        /**
599         * Returns the DNSService.
600         * 
601         * @return DNSService
602         */
603        protected DNSService getDNSService() {
604            return dnsServer;
605        }
606    
607        public void setDNSService(DNSService dns) {
608            this.dnsServer = dns;
609        }
610    
611        public void setUsersRepository(UsersRepository urepos) {
612            this.fieldLocalUsers = urepos;
613        }
614    
615        public final void setLog(Logger logger) {
616            this.logger = logger;
617        }
618    
619        /**
620         * Returns the accounts. Initializes if required.
621         * 
622         * @return List
623         */
624        protected List<Account> getStaticAccounts() {
625            if (null == getStaticAccountsBasic()) {
626                updateStaticAccounts();
627                return getStaticAccounts();
628            }
629            return fieldStaticAccounts;
630        }
631    
632        /**
633         * Returns the staticAccounts.
634         * 
635         * @return List
636         */
637        private List<Account> getStaticAccountsBasic() {
638            return fieldStaticAccounts;
639        }
640    
641        /**
642         * Sets the accounts.
643         * 
644         * @param accounts
645         *            The accounts to set
646         */
647        protected void setStaticAccounts(List<Account> accounts) {
648            fieldStaticAccounts = accounts;
649        }
650    
651        /**
652         * Updates the staticAccounts.
653         */
654        protected void updateStaticAccounts() {
655            setStaticAccounts(computeStaticAccounts());
656        }
657    
658        /**
659         * Updates the ParsedDynamicAccountParameters.
660         */
661        protected void updateParsedDynamicAccountParameters() {
662            setParsedDynamicAccountParameters(computeParsedDynamicAccountParameters());
663        }
664    
665        /**
666         * Updates the dynamicAccounts.
667         */
668        protected void updateDynamicAccounts() throws ConfigurationException {
669            setDynamicAccounts(computeDynamicAccounts());
670        }
671    
672        /**
673         * Computes the staticAccounts.
674         */
675        protected List<Account> computeStaticAccounts() {
676            return new ArrayList<Account>();
677        }
678    
679        /**
680         * Computes the ParsedDynamicAccountParameters.
681         */
682        protected List<ParsedDynamicAccountParameters> computeParsedDynamicAccountParameters() {
683            return new ArrayList<ParsedDynamicAccountParameters>();
684        }
685    
686        /**
687         * Computes the dynamicAccounts.
688         */
689        protected Map<DynamicAccountKey, DynamicAccount> computeDynamicAccounts() throws ConfigurationException {
690            Map<DynamicAccountKey, DynamicAccount> newAccounts;
691            try {
692                newAccounts = new HashMap<DynamicAccountKey, DynamicAccount>(getLocalUsers().countUsers() * getParsedDynamicAccountParameters().size());
693            } catch (UsersRepositoryException e) {
694                throw new ConfigurationException("Unable to acces UsersRepository", e);
695            }
696            Map<DynamicAccountKey, DynamicAccount> oldAccounts = getDynamicAccountsBasic();
697            if (null == oldAccounts)
698                oldAccounts = new HashMap<DynamicAccountKey, DynamicAccount>(0);
699    
700            Iterator<ParsedDynamicAccountParameters> parameterIterator = getParsedDynamicAccountParameters().iterator();
701    
702            // Process each ParsedDynamicParameters
703            while (parameterIterator.hasNext()) {
704                Map<DynamicAccountKey, DynamicAccount> accounts = computeDynamicAccounts(oldAccounts, (ParsedDynamicAccountParameters) parameterIterator.next());
705                // Remove accounts from oldAccounts.
706                // This avoids an average 2*N increase in heapspace used as the
707                // newAccounts are created.
708                Iterator<DynamicAccountKey> oldAccountsIterator = oldAccounts.keySet().iterator();
709                while (oldAccountsIterator.hasNext()) {
710                    if (accounts.containsKey(oldAccountsIterator.next()))
711                        oldAccountsIterator.remove();
712                }
713                // Add this parameter's accounts to newAccounts
714                newAccounts.putAll(accounts);
715            }
716            return newAccounts;
717        }
718    
719        /**
720         * Returns the dynamicAccounts. Initializes if required.
721         * 
722         * @return Map
723         */
724        protected Map<DynamicAccountKey, DynamicAccount> getDynamicAccounts() throws ConfigurationException {
725            if (null == getDynamicAccountsBasic()) {
726                updateDynamicAccounts();
727                return getDynamicAccounts();
728            }
729            return fieldDynamicAccounts;
730        }
731    
732        /**
733         * Returns the dynamicAccounts.
734         * 
735         * @return Map
736         */
737        private Map<DynamicAccountKey, DynamicAccount> getDynamicAccountsBasic() {
738            return fieldDynamicAccounts;
739        }
740    
741        /**
742         * Sets the dynamicAccounts.
743         * 
744         * @param dynamicAccounts
745         *            The dynamicAccounts to set
746         */
747        protected void setDynamicAccounts(Map<DynamicAccountKey, DynamicAccount> dynamicAccounts) {
748            fieldDynamicAccounts = dynamicAccounts;
749        }
750    
751        /**
752         * Compute the dynamicAccounts for the passed parameters. Accounts for
753         * existing users are copied and accounts for new users are created.
754         * 
755         * @param oldAccounts
756         * @param parameters
757         * @return Map - The current Accounts
758         * @throws ConfigurationException
759         */
760        protected Map<DynamicAccountKey, DynamicAccount> computeDynamicAccounts(Map<DynamicAccountKey, DynamicAccount> oldAccounts, ParsedDynamicAccountParameters parameters) throws ConfigurationException {
761    
762            Map<DynamicAccountKey, DynamicAccount> accounts;
763            Iterator<String> usersIterator;
764            try {
765                accounts = new HashMap<DynamicAccountKey, DynamicAccount>(getLocalUsers().countUsers());
766                usersIterator = getLocalUsers().list();
767    
768            } catch (UsersRepositoryException e) {
769                throw new ConfigurationException("Unable to access UsersRepository", e);
770            }
771            while (usersIterator.hasNext()) {
772                String userName = usersIterator.next();
773                DynamicAccountKey key = new DynamicAccountKey(userName, parameters.getSequenceNumber());
774                DynamicAccount account = oldAccounts.get(key);
775                if (null == account) {
776                    // Create a new DynamicAccount
777                    account = new DynamicAccount(parameters.getSequenceNumber(), getConfiguration(), userName, parameters.getUserPrefix(), parameters.getUserSuffix(), parameters.getPassword(), parameters.getRecipientPrefix(), parameters.getRecipientSuffix(), parameters.isIgnoreRecipientHeader(),
778                            parameters.getCustomRecipientHeader(), getSession());
779                }
780                accounts.put(key, account);
781            }
782            return accounts;
783        }
784    
785        /**
786         * Resets the dynamicAccounts.
787         */
788        protected void resetDynamicAccounts() {
789            setDynamicAccounts(null);
790        }
791    
792        /**
793         * Returns the ParsedDynamicAccountParameters.
794         * 
795         * @return List
796         */
797        protected List<ParsedDynamicAccountParameters> getParsedDynamicAccountParameters() {
798            if (null == getParsedDynamicAccountParametersBasic()) {
799                updateParsedDynamicAccountParameters();
800                return getParsedDynamicAccountParameters();
801            }
802            return fieldParsedDynamicAccountParameters;
803        }
804    
805        /**
806         * Returns the ParsedDynamicAccountParameters.
807         * 
808         * @return List
809         */
810        private List<ParsedDynamicAccountParameters> getParsedDynamicAccountParametersBasic() {
811            return fieldParsedDynamicAccountParameters;
812        }
813    
814        /**
815         * Sets the ParsedDynamicAccountParameters.
816         * 
817         * @param parsedDynamicAccountParameters
818         *            The ParsedDynamicAccountParameters to set
819         */
820        protected void setParsedDynamicAccountParameters(List<ParsedDynamicAccountParameters> parsedDynamicAccountParameters) {
821            fieldParsedDynamicAccountParameters = parsedDynamicAccountParameters;
822        }
823    
824        /**
825         * Returns the session, lazily initialized if required.
826         * 
827         * @return Session
828         */
829        protected Session getSession() {
830            Session session = null;
831            if (null == (session = getSessionBasic())) {
832                updateSession();
833                return getSession();
834            }
835            return session;
836        }
837    
838        /**
839         * Returns the session.
840         * 
841         * @return Session
842         */
843        private Session getSessionBasic() {
844            return fieldSession;
845        }
846    
847        /**
848         * Answers a new Session.
849         * 
850         * @return Session
851         */
852        protected Session computeSession() {
853            // Make separate properties instance so the
854            // fetchmail.xml <javaMailProperties> can override the
855            // property values without interfering with other fetchmail instances
856            return Session.getInstance(new Properties(System.getProperties()));
857        }
858    
859        /**
860         * Updates the current Session.
861         */
862        protected void updateSession() {
863            setSession(computeSession());
864        }
865    
866        /**
867         * Sets the session.
868         * 
869         * @param session
870         *            The session to set
871         */
872        protected void setSession(Session session) {
873            fieldSession = session;
874        }
875    
876        /**
877         * Propagate any Session parameters in the configuration to the Session.
878         * 
879         * @param configuration
880         *            The configuration containing the parameters
881         * @throws ConfigurationException
882         */
883        @SuppressWarnings("unchecked")
884        protected void setSessionParameters(HierarchicalConfiguration configuration) throws ConfigurationException {
885    
886            if (configuration.getKeys("javaMailProperties.property").hasNext()) {
887                Properties properties = getSession().getProperties();
888                List<HierarchicalConfiguration> allProperties = configuration.configurationsAt("javaMailProperties.property");
889                for (int i = 0; i < allProperties.size(); i++) {
890                    HierarchicalConfiguration propConf = allProperties.get(i);
891                    properties.setProperty(propConf.getString("[@name]"), propConf.getString("[@value]"));
892                    if (logger.isDebugEnabled()) {
893                        StringBuilder messageBuffer = new StringBuilder("Set property name: ");
894                        messageBuffer.append(propConf.getString("[@name]"));
895                        messageBuffer.append(" to: ");
896                        messageBuffer.append(propConf.getString("[@value]"));
897                        logger.debug(messageBuffer.toString());
898                    }
899                }
900            }
901        }
902    
903        public void setMailQueue(MailQueue queue) {
904            this.queue = queue;
905        }
906    
907        public MailQueue getMailQueue() {
908            return queue;
909        }
910    
911        public void setDomainList(DomainList domainList) {
912            this.domainList = domainList;
913        }
914    
915        public DomainList getDomainList() {
916            return domainList;
917        }
918    }