1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21
22 package org.apache.james.transport;
23
24 import org.apache.avalon.framework.activity.Disposable;
25 import org.apache.avalon.framework.configuration.Configurable;
26 import org.apache.avalon.framework.configuration.Configuration;
27 import org.apache.avalon.framework.configuration.ConfigurationException;
28 import org.apache.avalon.framework.container.ContainerUtil;
29 import org.apache.avalon.framework.logger.AbstractLogEnabled;
30 import org.apache.avalon.framework.service.ServiceException;
31 import org.apache.avalon.framework.service.ServiceManager;
32 import org.apache.avalon.framework.service.Serviceable;
33 import org.apache.james.core.MailImpl;
34 import org.apache.james.services.SpoolRepository;
35 import org.apache.mailet.base.MatcherInverter;
36 import org.apache.mailet.base.GenericMailet;
37 import org.apache.mailet.base.GenericMatcher;
38 import org.apache.mailet.Mail;
39 import org.apache.mailet.MailAddress;
40 import org.apache.mailet.Mailet;
41 import org.apache.mailet.MailetConfig;
42 import org.apache.mailet.MailetException;
43 import org.apache.mailet.Matcher;
44 import org.apache.mailet.MatcherConfig;
45
46 import javax.mail.MessagingException;
47
48 import java.io.PrintWriter;
49 import java.io.StringWriter;
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.Iterator;
53 import java.util.LinkedList;
54 import java.util.List;
55 import java.util.Locale;
56
57 /**
58 * Implements a processor for mails, directing the mail down
59 * the chain of matchers/mailets.
60 *
61 * SAMPLE CONFIGURATION
62 * <processor name="try" onerror="return,log">
63 * <mailet match="RecipientIsLocal" class="LocalDelivery">
64 * </mailet>
65 * <mailet match="All" class="RemoteDelivery">
66 * <delayTime>21600000</delayTime>
67 * <maxRetries>5</maxRetries>
68 * </mailet>
69 * </processor>
70 *
71 * Note that the 'onerror' attribute is not yet supported.
72 *
73 * As of James v2.2.0a5, 'onerror' functionality is implemented, but
74 * it is implemented on the <mailet> tag. The specification is:
75 *
76 * <mailet match="..." class="..."
77 * [onMatchException="{noMatch|matchAll|error|<aProcessorName>}"]
78 * [onMailetException="{ignore|error|<aProcessorName>}"]>
79 *
80 * noMatch: no addresses are considered to match
81 * matchAll: all addresses are considered to match
82 * error: as before, send the message to the ERROR processor
83 *
84 * Otherwise, a processor name can be specified, and the message will
85 * be sent there.
86 *
87 * <P>CVS $Id: LinearProcessor.java 717869 2008-11-15 15:56:18Z rdonkin $</P>
88 * @version 2.2.0
89 */
90 public class LinearProcessor
91 extends AbstractLogEnabled
92 implements Disposable, Configurable, Serviceable, MailProcessor, MailetContainer {
93
94 /**
95 * The name of the matcher used to terminate the matcher chain. The
96 * end of the matcher/mailet chain must be a matcher that matches
97 * all mails and a mailet that sets every mail to GHOST status.
98 * This is necessary to ensure that mails are removed from the spool
99 * in an orderly fashion.
100 */
101 private static final String TERMINATING_MATCHER_NAME = "Terminating%Matcher%Name";
102
103 /**
104 * The name of the mailet used to terminate the mailet chain. The
105 * end of the matcher/mailet chain must be a matcher that matches
106 * all mails and a mailet that sets every mail to GHOST status.
107 * This is necessary to ensure that mails are removed from the spool
108 * in an orderly fashion.
109 */
110 private static final String TERMINATING_MAILET_NAME = "Terminating%Mailet%Name";
111
112 private List mailets; // The list of mailets for this processor
113 private List matchers; // The list of matchers for this processor
114 private volatile boolean listsClosed; // Whether the matcher/mailet lists have been closed.
115 private SpoolRepository spool; // The spool on which this processor is acting
116
117 private MailetLoader mailetLoader;
118
119 private MatcherLoader matchLoader;
120
121 /**
122 * Set the spool to be used by this LinearProcessor.
123 *
124 * @param spool the spool to be used by this processor
125 *
126 * @throws IllegalArgumentException when the spool passed in is null
127 */
128 public void setSpool(SpoolRepository spool) {
129 if (spool == null) {
130 throw new IllegalArgumentException("The spool cannot be null");
131 }
132 this.spool = spool;
133 }
134
135 /**
136 * Set the MailetLoader
137 *
138 * @param mailetLoader the MailetLoader
139 */
140 public void setMailetLoader(MailetLoader mailetLoader) {
141 this.mailetLoader = mailetLoader;
142 }
143
144 /**
145 * Set the MatcherLoader
146 *
147 * @param matchLoader the MatcherLoader
148 */
149 public void setMatchLoader(MatcherLoader matchLoader) {
150 this.matchLoader = matchLoader;
151 }
152
153 /**
154 * <p>The dispose operation is called at the end of a components lifecycle.
155 * Instances of this class use this method to release and destroy any
156 * resources that they own.</p>
157 *
158 * <p>This implementation disposes of all the mailet instances added to the
159 * processor</p>
160 *
161 * @see org.apache.avalon.framework.activity.Disposable#dispose()
162 */
163 public void dispose() {
164 Iterator it = mailets.iterator();
165 boolean debugEnabled = getLogger().isDebugEnabled();
166 while (it.hasNext()) {
167 Mailet mailet = (Mailet)it.next();
168 if (debugEnabled) {
169 getLogger().debug("Shutdown mailet " + mailet.getMailetInfo());
170 }
171 mailet.destroy();
172 }
173 }
174
175 /**
176 * <p>Adds a new <code>Matcher</code> / <code>Mailet</code> pair
177 * to the processor. Checks to ensure that the matcher and
178 * mailet passed in are not null. Synchronized to ensure that
179 * the matchers and mailets are kept in sync.</p>
180 *
181 * <p>It is an essential part of the contract of the LinearProcessor
182 * that a particular matcher/mailet combination be used to
183 * terminate the processor chain. This is done by calling the
184 * closeProcessorList method.</p>
185 *
186 * <p>Once the closeProcessorList has been called any subsequent
187 * call to the add method will result in an IllegalStateException.</p>
188 *
189 * <p>This method is synchronized to protect against corruption of
190 * matcher/mailets lists</p>
191 *
192 * @param matcher the new matcher being added
193 * @param mailet the new mailet being added
194 *
195 * @throws IllegalArgumentException when the matcher or mailet passed in is null
196 * @throws IllegalStateException when this method is called after the processor lists have been closed
197 */
198 public synchronized void add(Matcher matcher, Mailet mailet) {
199 if (matcher == null) {
200 throw new IllegalArgumentException("Null valued matcher passed to LinearProcessor.");
201 }
202 if (mailet == null) {
203 throw new IllegalArgumentException("Null valued mailet passed to LinearProcessor.");
204 }
205 if (listsClosed) {
206 throw new IllegalStateException("Attempt to add matcher/mailet after lists have been closed");
207 }
208 matchers.add(matcher);
209 mailets.add(mailet);
210 }
211
212 /**
213 * <p>Closes the processor matcher/mailet list.</p>
214 *
215 * <p>This method is synchronized to protect against corruption of
216 * matcher/mailets lists</p>
217 *
218 * @throws IllegalStateException when this method is called after the processor lists have been closed
219 */
220 public synchronized void closeProcessorLists() {
221 if (listsClosed) {
222 throw new IllegalStateException("Processor's matcher/mailet lists have already been closed.");
223 }
224 Matcher terminatingMatcher =
225 new GenericMatcher() {
226 public Collection match(Mail mail) {
227 return mail.getRecipients();
228 }
229
230 public String getMatcherInfo() {
231 return TERMINATING_MATCHER_NAME;
232 }
233 };
234 Mailet terminatingMailet =
235 new GenericMailet() {
236 public void service(Mail mail) {
237 if (!(Mail.ERROR.equals(mail.getState()))) {
238 // Don't complain if we fall off the end of the
239 // error processor. That is currently the
240 // normal situation for James, and the message
241 // will show up in the error store.
242 StringBuffer warnBuffer = new StringBuffer(256)
243 .append("Message ")
244 .append(mail.getName())
245 .append(" reached the end of this processor, and is automatically deleted. This may indicate a configuration error.");
246 LinearProcessor.this.getLogger().warn(warnBuffer.toString());
247 }
248 mail.setState(Mail.GHOST);
249 }
250
251 public String getMailetInfo() {
252 return getMailetName();
253 }
254
255 public String getMailetName() {
256 return TERMINATING_MAILET_NAME;
257 }
258 };
259 add(terminatingMatcher, terminatingMailet);
260 listsClosed = true;
261 }
262
263 /**
264 * <p>Processes a single mail message through the chain of matchers and mailets.</p>
265 *
266 * <p>Calls to this method before setSpool has been called with a non-null argument
267 * will result in an <code>IllegalStateException</code>.</p>
268 *
269 * <p>If the matcher/mailet lists have not been closed by a call to the closeProcessorLists
270 * method then a call to this method will result in an <code>IllegalStateException</code>.
271 * The end of the matcher/mailet chain must be a matcher that matches all mails and
272 * a mailet that sets every mail to GHOST status. This is necessary to ensure that
273 * mails are removed from the spool in an orderly fashion. The closeProcessorLists method
274 * ensures this.</p>
275 *
276 * @param mail the new mail to be processed
277 *
278 * @throws IllegalStateException when this method is called before the processor lists have been closed
279 * or the spool has been initialized
280 *
281 * @see org.apache.james.transport.MailProcessor#service(org.apache.mailet.Mail)
282 */
283 public void service(Mail mail) throws MessagingException {
284 if (spool == null) {
285 throw new IllegalStateException("Attempt to service mail before the spool has been set to a non-null value");
286 }
287
288 if (!listsClosed) {
289 throw new IllegalStateException("Attempt to service mail before matcher/mailet lists have been closed");
290 }
291
292 if (getLogger().isDebugEnabled()) {
293 getLogger().debug("Servicing mail: " + mail.getName());
294 }
295 // unprocessed is an array of Lists of Mail objects
296 // the array indicates which matcher/mailet (stage in the linear
297 // processor) that this Mail needs to be processed.
298 // e.g., a Mail in unprocessed[0] needs to be
299 // processed by the first matcher/mailet.
300 //
301 // It is a List of Mail objects at each array spot as multiple Mail
302 // objects could be at the same stage.
303 //
304 // Note that every Mail object in this array will either be the
305 // original Mail object passed in, or a result of this method's
306 // (and hence this thread's) processing.
307
308 List[] unprocessed = new List[matchers.size() + 1];
309
310 for (int i = 0; i < unprocessed.length; i++) {
311 // No need to use synchronization, as this is totally
312 // local to the method
313 unprocessed[i] = new LinkedList();
314 }
315
316 //Add the object to the bottom of the list
317 unprocessed[0].add(mail);
318
319 //This is the original state of the message
320 String originalState = mail.getState();
321
322
323 // The original mail: we should not care to save this mail.
324 // This should be saved in the spoolmanager.
325 Mail originalMail = mail;
326
327 //We'll use these as temporary variables in the loop
328 mail = null; // the message we're currently processing
329 int i = 0; // where in the stage we're looking
330 while (true) {
331 // The last element in the unprocessed array has mail messages
332 // that have completed all stages. We want them to just die,
333 // so we clear that spot to allow garbage collection of the
334 // objects.
335 //
336 // Please note that the presence of the terminating mailet at the end
337 // of the chain is critical to the proper operation
338 // of the LinearProcessor code. If this mailet is not placed
339 // at the end of the chain with a terminating matcher, there is a
340 // potential for configuration or implementation errors to
341 // lead to mails trapped in the spool. This matcher/mailet
342 // combination is added when the closeProcessorList method
343 // is called.
344 unprocessed[unprocessed.length - 1].clear();
345
346 //initialize the mail reference we will be searching on
347 mail = null;
348
349 //Scan through all stages, trying to find a message to process
350 for (i = 0; i < unprocessed.length; i++) {
351 if (unprocessed[i].size() > 0) {
352 //Get the first element from the queue, and remove it from there
353 mail = (Mail)unprocessed[i].remove(0);
354 break;
355 }
356 }
357
358 //Check it we found anything
359 if (mail == null) {
360 //We found no messages to process... we're done servicing the mail object
361 return;
362 }
363
364
365 //Call the matcher and find what recipients match
366 Collection recipients = null;
367 Matcher matcher = (Matcher) matchers.get(i);
368 StringBuffer logMessageBuffer = null;
369 if (getLogger().isDebugEnabled()) {
370 logMessageBuffer =
371 new StringBuffer(128)
372 .append("Checking ")
373 .append(mail.getName())
374 .append(" with ")
375 .append(matcher);
376 getLogger().debug(logMessageBuffer.toString());
377 }
378 try {
379 recipients = matcher.match(mail);
380 if (recipients == null) {
381 //In case the matcher returned null, create an empty Collection
382 recipients = new ArrayList(0);
383 } else if (recipients != mail.getRecipients()) {
384 //Make sure all the objects are MailAddress objects
385 verifyMailAddresses(recipients);
386 }
387 } catch (MessagingException me) {
388 // look in the matcher's mailet's init attributes
389 MailetConfig mailetConfig = ((Mailet) mailets.get(i)).getMailetConfig();
390 String onMatchException = ((MailetConfigImpl) mailetConfig).getInitAttribute("onMatchException");
391 if (onMatchException == null) {
392 onMatchException = Mail.ERROR;
393 } else {
394 onMatchException = onMatchException.trim().toLowerCase(Locale.US);
395 }
396 if (onMatchException.compareTo("nomatch") == 0) {
397 //In case the matcher returned null, create an empty Collection
398 recipients = new ArrayList(0);
399 } else if (onMatchException.compareTo("matchall") == 0) {
400 recipients = mail.getRecipients();
401 // no need to verify addresses
402 } else {
403 handleException(me, mail, matcher.getMatcherConfig().getMatcherName(), onMatchException);
404 }
405 }
406
407 // Split the recipients into two pools. notRecipients will contain the
408 // recipients on the message that the matcher did not return.
409 Collection notRecipients;
410 if (recipients == mail.getRecipients() || recipients.size() == 0) {
411 notRecipients = new ArrayList(0);
412 } else {
413 notRecipients = new ArrayList(mail.getRecipients());
414 notRecipients.removeAll(recipients);
415 }
416
417 if (recipients.size() == 0) {
418 //Everything was not a match... store it in the next spot in the array
419 unprocessed[i + 1].add(mail);
420 continue;
421 }
422 if (notRecipients.size() != 0) {
423 // There are a mix of recipients and not recipients.
424 // We need to clone this message, put the notRecipients on the clone
425 // and store it in the next spot
426 Mail notMail = new MailImpl(mail);
427 notMail.setRecipients(notRecipients);
428 // set the state to the current processor
429 notMail.setState(originalState);
430 unprocessed[i + 1].add(notMail);
431 //We have to set the reduce possible recipients on the old message
432 mail.setRecipients(recipients);
433 }
434 // We have messages that need to process... time to run the mailet.
435 Mailet mailet = (Mailet) mailets.get(i);
436 if (getLogger().isDebugEnabled()) {
437 logMessageBuffer =
438 new StringBuffer(128)
439 .append("Servicing ")
440 .append(mail.getName())
441 .append(" by ")
442 .append(mailet.getMailetInfo());
443 getLogger().debug(logMessageBuffer.toString());
444 }
445 try {
446 mailet.service(mail);
447 // Make sure all the recipients are still MailAddress objects
448 verifyMailAddresses(mail.getRecipients());
449 } catch (MessagingException me) {
450 MailetConfig mailetConfig = mailet.getMailetConfig();
451 String onMailetException = ((MailetConfigImpl) mailetConfig).getInitAttribute("onMailetException");
452 if (onMailetException == null) {
453 onMailetException = Mail.ERROR;
454 } else {
455 onMailetException = onMailetException.trim().toLowerCase(Locale.US);
456 }
457 if (onMailetException.compareTo("ignore") == 0) {
458 // ignore the exception and continue
459 // this option should not be used if the mail object can be changed by the mailet
460 verifyMailAddresses(mail.getRecipients());
461 } else {
462 handleException(me, mail, mailet.getMailetConfig().getMailetName(), onMailetException);
463 }
464 }
465
466 // See if the state was changed by the mailet
467 if (!mail.getState().equals(originalState)) {
468 //If this message was ghosted, we just want to let it die
469 if (mail.getState().equals(Mail.GHOST)) {
470 // let this instance die...
471 ContainerUtil.dispose(mail);
472 mail = null;
473 continue;
474 }
475 // This was just set to another state requiring further processing...
476 // Store this back in the spool and it will get picked up and
477 // run in that processor
478 // We store only mails created by the matcher "splitting"
479 // The original mail will be "stored" by the caller.
480 if (originalMail != mail) {
481 spool.store(mail);
482 ContainerUtil.dispose(mail);
483 }
484 mail = null;
485 continue;
486 } else {
487 // Ok, we made it through with the same state... move it to the next
488 // spot in the array
489 unprocessed[i + 1].add(mail);
490 }
491
492 }
493 }
494
495
496 /**
497 * Checks that all objects in this class are of the form MailAddress.
498 *
499 * @throws MessagingException when the <code>Collection</code> contains objects that are not <code>MailAddress</code> objects
500 */
501 private void verifyMailAddresses(Collection col) throws MessagingException {
502 try {
503 MailAddress addresses[] = (MailAddress[])col.toArray(new MailAddress[0]);
504
505 // Why is this here? According to the javadoc for
506 // java.util.Collection.toArray(Object[]), this should
507 // never happen. The exception will be thrown.
508 if (addresses.length != col.size()) {
509 throw new MailetException("The recipient list contains objects other than MailAddress objects");
510 }
511 } catch (ArrayStoreException ase) {
512 throw new MailetException("The recipient list contains objects other than MailAddress objects");
513 }
514 }
515
516 /**
517 * This is a helper method that updates the state of the mail object to
518 * Mail.ERROR as well as recording the exception to the log
519 *
520 * @param me the exception to be handled
521 * @param mail the mail being processed when the exception was generated
522 * @param offendersName the matcher or mailet than generated the exception
523 * @param nextState the next state to set
524 *
525 * @throws MessagingException thrown always, rethrowing the passed in exception
526 */
527 private void handleException(MessagingException me, Mail mail, String offendersName, String nextState) throws MessagingException {
528 System.err.println("exception! " + me);
529 mail.setState(nextState);
530 StringWriter sout = new StringWriter();
531 PrintWriter out = new PrintWriter(sout, true);
532 StringBuffer exceptionBuffer =
533 new StringBuffer(128)
534 .append("Exception calling ")
535 .append(offendersName)
536 .append(": ")
537 .append(me.getMessage());
538 out.println(exceptionBuffer.toString());
539 Exception e = me;
540 while (e != null) {
541 e.printStackTrace(out);
542 if (e instanceof MessagingException) {
543 e = ((MessagingException)e).getNextException();
544 } else {
545 e = null;
546 }
547 }
548 String errorString = sout.toString();
549 mail.setErrorMessage(errorString);
550 getLogger().error(errorString);
551 throw me;
552 }
553
554 /**
555 * <p>Initialize the processor matcher/mailet list.</p>
556 */
557 public void openProcessorList() {
558 matchers = new ArrayList();
559 mailets = new ArrayList();
560 }
561
562 /**
563 * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
564 */
565 public void configure(Configuration processorConf) throws ConfigurationException {
566 openProcessorList();
567
568 final Configuration[] mailetConfs
569 = processorConf.getChildren( "mailet" );
570 // Loop through the mailet configuration, load
571 // all of the matcher and mailets, and add
572 // them to the processor.
573 for ( int j = 0; j < mailetConfs.length; j++ )
574 {
575 Configuration c = mailetConfs[j];
576 String mailetClassName = c.getAttribute("class");
577 String matcherName = c.getAttribute("match",null);
578 String invertedMatcherName = c.getAttribute("notmatch",null);
579
580 Mailet mailet = null;
581 Matcher matcher = null;
582 try {
583
584 if (matcherName != null && invertedMatcherName != null) {
585 // if no matcher is configured throw an Exception
586 throw new ConfigurationException(
587 "Please configure only match or nomatch per mailet");
588 } else if (matcherName != null) {
589 matcher = matchLoader.getMatcher(matcherName);
590 } else if (invertedMatcherName != null) {
591 matcher = new MatcherInverter(matchLoader
592 .getMatcher(invertedMatcherName));
593
594 } else {
595 // default matcher is All
596 matcher = matchLoader.getMatcher("All");
597 }
598
599 //The matcher itself should log that it's been inited.
600 if (getLogger().isInfoEnabled()) {
601 StringBuffer infoBuffer =
602 new StringBuffer(64)
603 .append("Matcher ")
604 .append(matcherName)
605 .append(" instantiated.");
606 getLogger().info(infoBuffer.toString());
607 }
608 } catch (MessagingException ex) {
609 // **** Do better job printing out exception
610 if (getLogger().isErrorEnabled()) {
611 StringBuffer errorBuffer =
612 new StringBuffer(256)
613 .append("Unable to init matcher ")
614 .append(matcherName)
615 .append(": ")
616 .append(ex.toString());
617 getLogger().error( errorBuffer.toString(), ex );
618 if (ex.getNextException() != null) {
619 getLogger().error( "Caused by nested exception: ", ex.getNextException());
620 }
621 }
622 System.err.println("Unable to init matcher " + matcherName);
623 System.err.println("Check spool manager logs for more details.");
624 //System.exit(1);
625 throw new ConfigurationException("Unable to init matcher",c,ex);
626 }
627 try {
628 mailet = mailetLoader.getMailet(mailetClassName, c);
629 if (getLogger().isInfoEnabled()) {
630 StringBuffer infoBuffer =
631 new StringBuffer(64)
632 .append("Mailet ")
633 .append(mailetClassName)
634 .append(" instantiated.");
635 getLogger().info(infoBuffer.toString());
636 }
637 } catch (MessagingException ex) {
638 // **** Do better job printing out exception
639 if (getLogger().isErrorEnabled()) {
640 StringBuffer errorBuffer =
641 new StringBuffer(256)
642 .append("Unable to init mailet ")
643 .append(mailetClassName)
644 .append(": ")
645 .append(ex.toString());
646 getLogger().error( errorBuffer.toString(), ex );
647 if (ex.getNextException() != null) {
648 getLogger().error( "Caused by nested exception: ", ex.getNextException());
649 }
650 }
651 System.err.println("Unable to init mailet " + mailetClassName);
652 System.err.println("Check spool manager logs for more details.");
653 throw new ConfigurationException("Unable to init mailet",c,ex);
654 }
655 //Add this pair to the processor
656 add(matcher, mailet);
657 }
658
659 // Close the processor matcher/mailet lists.
660 //
661 // Please note that this is critical to the proper operation
662 // of the LinearProcessor code. The processor will not be
663 // able to service mails until this call is made.
664 closeProcessorLists();
665 }
666
667 /**
668 * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
669 */
670 public void service(ServiceManager comp) throws ServiceException {
671 setMailetLoader((MailetLoader) comp.lookup(MailetLoader.ROLE));
672 setMatchLoader((MatcherLoader) comp.lookup(MatcherLoader.ROLE));
673 setSpool( (SpoolRepository) comp.lookup(SpoolRepository.ROLE));
674 }
675
676 public List getMailetConfigs() {
677 List mailetConfigs = new ArrayList();
678 Iterator iterator = mailets.iterator();
679 while (iterator.hasNext()) {
680 Mailet mailet = (Mailet) iterator.next();
681 MailetConfig mailetConfig = mailet.getMailetConfig();
682 if (mailetConfig == null) mailetConfigs.add(new MailetConfigImpl()); // placeholder
683 else mailetConfigs.add(mailetConfig);
684 }
685 return mailetConfigs;
686 }
687
688 public List getMatcherConfigs() {
689 List matcherConfigs = new ArrayList();
690 Iterator iterator = matchers.iterator();
691 while (iterator.hasNext()) {
692 Matcher matcher = (Matcher) iterator.next();
693 MatcherConfig matcherConfig = matcher.getMatcherConfig();
694 if (matcherConfig == null) matcherConfigs.add(new MatcherConfigImpl()); // placeholder
695 else matcherConfigs.add(matcherConfig);
696 }
697 return matcherConfigs;
698 }
699 }