View Javadoc

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  
23  package org.apache.james.management.impl;
24  
25  import org.apache.avalon.cornerstone.services.store.Store;
26  import org.apache.avalon.framework.configuration.DefaultConfiguration;
27  import org.apache.avalon.framework.container.ContainerUtil;
28  import org.apache.avalon.framework.service.ServiceException;
29  import org.apache.avalon.framework.service.ServiceManager;
30  import org.apache.avalon.framework.service.Serviceable;
31  import org.apache.james.management.SpoolFilter;
32  import org.apache.james.management.SpoolManagementException;
33  import org.apache.james.management.SpoolManagementMBean;
34  import org.apache.james.management.SpoolManagementService;
35  import org.apache.james.services.SpoolRepository;
36  import org.apache.mailet.Mail;
37  import org.apache.mailet.MailAddress;
38  import org.apache.oro.text.regex.Pattern;
39  import org.apache.oro.text.regex.Perl5Matcher;
40  
41  import javax.mail.MessagingException;
42  import javax.mail.Address;
43  import javax.mail.internet.MimeMessage;
44  import java.util.ArrayList;
45  import java.util.Collection;
46  import java.util.Date;
47  import java.util.Iterator;
48  import java.util.List;
49  
50  /**
51   * high-level management of spool contents like list, remove, resend
52   */
53  public class SpoolManagement implements Serviceable, SpoolManagementService, SpoolManagementMBean {
54  
55      private Store mailStore;
56  
57      /**
58       * Set the Store
59       * 
60       * @param mailStore the store
61       */
62      public void setStore(Store mailStore) {
63          this.mailStore = mailStore;
64      }
65  
66      /**
67       * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
68       */
69      public void service(ServiceManager serviceManager) throws ServiceException {
70          Store mailStore = (Store)serviceManager.lookup(Store.ROLE);
71          setStore(mailStore);
72      }
73  
74      /**
75       * Move all mails from the given repository to another repository matching the given filter criteria
76       *
77       * @param srcSpoolRepositoryURL the spool whose item are listed
78       * @param srcState if not NULL, only mails with matching state are returned
79       * @param dstSpoolRepositoryURL the destination spool
80       * @param dstState if not NULL, the state will be changed before storing the message to the new repository.
81       * @param header if not NULL, only mails with at least one header with a value matching headerValueRegex are returned
82       * @param headerValueRegex the regular expression the header must match
83       * @return a counter of moved mails
84       * @throws SpoolManagementException
85       */
86      public int moveSpoolItems(String srcSpoolRepositoryURL, String srcState, String dstSpoolRepositoryURL, String dstState, String header, String headerValueRegex) 
87              throws SpoolManagementException {
88          SpoolFilter filter = new SpoolFilter(srcState, header, headerValueRegex);
89          try {
90              return moveSpoolItems(srcSpoolRepositoryURL, dstSpoolRepositoryURL, dstState, filter);
91          } catch (Exception e) {
92              throw new SpoolManagementException(e);
93          }
94      }
95  
96      /**
97       * Move all mails from the given repository to another repository matching the given filter criteria
98       *
99       * @param srcSpoolRepositoryURL the spool whose item are listed
100      * @param dstSpoolRepositoryURL the destination spool
101      * @param dstState if not NULL, the state will be changed before storing the message to the new repository.
102      * @param filter the filter to select messages from the source repository
103      * @return a counter of moved mails
104      * @throws ServiceException 
105      * @throws MessagingException 
106      * @throws SpoolManagementException
107      */
108     public int moveSpoolItems(String srcSpoolRepositoryURL, String dstSpoolRepositoryURL, String dstState, SpoolFilter filter)
109             throws MessagingException, SpoolManagementException {
110         
111         SpoolRepository srcSpoolRepository;
112         SpoolRepository dstSpoolRepository;
113         srcSpoolRepository = getSpoolRepository(srcSpoolRepositoryURL);
114         dstSpoolRepository = getSpoolRepository(dstSpoolRepositoryURL);
115         
116         // get an iterator of all keys
117         Iterator spoolR = srcSpoolRepository.list();
118 
119         int count = 0;
120         while (spoolR.hasNext()) {
121             String key = spoolR.next().toString();
122             boolean locked = false;
123             try {
124                 locked = srcSpoolRepository.lock(key);
125             } catch (MessagingException e) {
126                 // unable to lock
127             }
128             if (locked) {
129                 Mail m = null;
130                 try {
131                     m = srcSpoolRepository.retrieve(key);
132                     if (filterMatches(m, filter)) {
133                         if (dstState != null) {
134                             m.setState(dstState);
135                         }
136                         dstSpoolRepository.store(m);
137                         srcSpoolRepository.remove(m);
138                         count++;
139                     }
140                 } catch (MessagingException e) {
141                     // unable to retrieve message
142                 } finally {
143                     try {
144                         srcSpoolRepository.unlock(key);
145                     } catch (MessagingException e) {
146                         // unable to unlock
147                     }
148                     ContainerUtil.dispose(m);
149                 }
150             }
151         }
152         
153         return count;
154 
155     }
156 
157     /**
158      * Lists all mails from the given repository matching the given filter criteria 
159      * 
160      * @param spoolRepositoryURL the spool whose item are listed
161      * @param state if not NULL, only mails with matching state are returned
162      * @param header if not NULL, only mails with at least one header with a value matching headerValueRegex are returned
163      * @param headerValueRegex the regular expression the header must match
164      * @return String array, each line describing one matching mail from the spool 
165      * @throws SpoolManagementException
166      */
167     public String[] listSpoolItems(String spoolRepositoryURL, String state, String header, String headerValueRegex) 
168             throws SpoolManagementException {
169         return listSpoolItems(spoolRepositoryURL, new SpoolFilter(state, header, headerValueRegex));
170     }
171 
172     /**
173      * Lists all mails from the given repository matching the given filter criteria 
174      * 
175      * @param spoolRepositoryURL the spool whose item are listed
176      * @param filter the criteria against which all mails are matched
177      * @return String array, each line describing one matching mail from the spool 
178      * @throws SpoolManagementException
179      */
180     public String[] listSpoolItems(String spoolRepositoryURL, SpoolFilter filter) throws SpoolManagementException {
181         List spoolItems;
182         try {
183             spoolItems = getSpoolItems(spoolRepositoryURL, filter);
184         } catch (Exception e) {
185              throw new SpoolManagementException(e);
186         }
187         return (String[]) spoolItems.toArray(new String[]{});
188     }
189 
190     /**
191      * Return true if the given Mail match the given SpoolFilter
192      * 
193      * @param mail the Mail which should be checked
194      * @param filter the SpoolFilter which should be used
195      * @return TRUE, if given mail matches all given filter criteria
196      * @throws SpoolManagementException
197      */
198     protected boolean filterMatches(Mail mail, SpoolFilter filter) throws SpoolManagementException {
199         if (filter == null || !filter.doFilter()) return true;
200 
201         if (filter.doFilterState() && !mail.getState().equalsIgnoreCase(filter.getState())) return false;
202         
203         if (filter.doFilterHeader()) {
204 
205             Perl5Matcher matcher = new Perl5Matcher();
206             
207             // check, if there is a match for every header/regex pair
208             Iterator headers = filter.getHeaders();
209             while (headers.hasNext()) {
210                 String header = (String) headers.next();
211                 
212                 String[] headerValues;
213                 try {
214                     headerValues = mail.getMessage().getHeader(header);
215                     if (headerValues == null) {
216                         // some headers need special retrieval
217                         if (header.equalsIgnoreCase("to")) {
218                             headerValues = addressesToStrings(mail.getMessage().getRecipients(MimeMessage.RecipientType.TO));
219                         }
220                         else if (header.equalsIgnoreCase("cc")) { 
221                             headerValues = addressesToStrings(mail.getMessage().getRecipients(MimeMessage.RecipientType.CC));
222                         }
223                         else if (header.equalsIgnoreCase("bcc")) { 
224                             headerValues = addressesToStrings(mail.getMessage().getRecipients(MimeMessage.RecipientType.BCC));
225                         }
226                         else if (header.equalsIgnoreCase("from")) { 
227                             headerValues = new String[]{mail.getMessage().getSender().toString()};
228                         }
229                     }
230                 } catch (MessagingException e) {
231                     throw new SpoolManagementException("could not filter mail by headers", e);
232                 }
233                 if (headerValues == null) return false; // no header for this criteria
234 
235                 Pattern pattern = filter.getHeaderValueRegexCompiled(header);
236 
237                 // the regex must match at least one entry for the header
238                 boolean matched = false;
239                 for (int i = 0; i < headerValues.length; i++) {
240                     String headerValue = headerValues[i];
241                     if (matcher.matches(headerValue, pattern)) {
242                         matched = true;
243                         break;
244                     }
245                 }
246                 if (!matched) return false;
247             }
248         }
249             
250         return true;
251     }
252 
253     private String[] addressesToStrings(Address[] addresses) {
254         if (addresses == null) return null;
255         if (addresses.length == 0) return new String[]{};
256         String[] addressStrings = new String[addresses.length];
257         for (int i = 0; i < addresses.length; i++) {
258             addressStrings[i] = addresses[i].toString();
259         }
260         return addressStrings;
261     }
262 
263     /**
264      * @see org.apache.james.management.SpoolManagementService#getSpoolItems(String, SpoolFilter)
265      */
266     public List getSpoolItems(String spoolRepositoryURL, SpoolFilter filter)
267             throws MessagingException, SpoolManagementException {
268         SpoolRepository spoolRepository = getSpoolRepository(spoolRepositoryURL);
269 
270         List items = new ArrayList();
271 
272         // get an iterator of all keys
273         Iterator spoolR = spoolRepository.list();
274         while (spoolR.hasNext()) {
275             String key = spoolR.next().toString();
276             Mail m = spoolRepository.retrieve(key);
277 
278             if (filterMatches(m, filter)) {
279                 StringBuffer itemInfo = new StringBuffer();
280                 itemInfo.append("key: ").append(key).append(" sender: ").append(m.getSender()).append(" recipient:");
281                 Collection recipients = m.getRecipients();
282                 for (Iterator iterator = recipients.iterator(); iterator.hasNext();) {
283                     MailAddress mailAddress = (MailAddress) iterator.next();
284                     itemInfo.append(" ").append(mailAddress);
285                 }
286                 items.add(itemInfo.toString());
287             }
288         }
289 
290         return items;
291     }
292 
293     /**
294      * @see org.apache.james.management.SpoolManagementMBean#removeSpoolItems(String, String, String, String, String)
295      */
296     public int removeSpoolItems(String spoolRepositoryURL, String key, String state, String header, String headerValueRegex) 
297             throws SpoolManagementException {
298         return removeSpoolItems(spoolRepositoryURL, key, new SpoolFilter(state, header, headerValueRegex));
299     }
300 
301     /**
302      * Removes all mails from the given repository matching the filter
303      *  
304      * @param spoolRepositoryURL the spool whose item are listed
305      * @param key ID of the mail to be removed. if not NULL, all other filters are ignored
306      * @param filter the criteria against which all mails are matched. only applied if key is NULL.
307      * @return number of removed mails
308      * @throws SpoolManagementException
309      */
310     public int removeSpoolItems(String spoolRepositoryURL, String key, SpoolFilter filter) throws SpoolManagementException {
311         try {
312             return removeSpoolItems(spoolRepositoryURL, key, null, filter);
313         } catch (Exception e) {
314             throw new SpoolManagementException(e);
315         }
316     }
317 
318 
319     /**
320      * @see org.apache.james.management.SpoolManagementService#removeSpoolItems(String, String, List, SpoolFilter)
321      */
322     public int removeSpoolItems(String spoolRepositoryURL, String key, List lockingFailures, SpoolFilter filter) throws SpoolManagementException, MessagingException, SpoolManagementException {
323         int count = 0;
324         SpoolRepository spoolRepository = getSpoolRepository(spoolRepositoryURL);
325 
326         if (key != null) {
327             count = removeMail(spoolRepository, key, count, lockingFailures, null);
328         } else {
329             Iterator spoolR = spoolRepository.list();
330 
331             while (spoolR.hasNext()) {
332                 key = (String)spoolR.next();
333                 count = removeMail(spoolRepository, key, count, lockingFailures, filter);
334             }
335         }
336         return count;
337     }
338 
339     private int removeMail(SpoolRepository spoolRepository, String key, int count, List lockingFailures, SpoolFilter filter) throws MessagingException {
340         try {
341             if (removeMail(spoolRepository, key, filter)) count++;
342         } catch (IllegalStateException e) {
343             lockingFailures.add(key);
344         } catch (SpoolManagementException e) {
345             return count;
346         }
347         return count;
348     }
349 
350     /**
351      * Tries to resend all mails from the given repository matching the given filter criteria 
352      * 
353      * @param spoolRepositoryURL the spool whose item are about to be resend
354      * @param key ID of the mail to be resend. if not NULL, all other filters are ignored
355      * @param filter the SpoolFilter to use
356      * @return int the number of resent mails
357      * @throws SpoolManagementException
358      */
359     public int resendSpoolItems(String spoolRepositoryURL, String key, SpoolFilter filter) throws SpoolManagementException {
360         try {
361             return resendSpoolItems(spoolRepositoryURL, key, null, filter);
362         } catch (Exception e) {
363             throw new SpoolManagementException(e);
364         }
365     }
366 
367     /**
368      * Tries to resend all mails from the given repository matching the given filter criteria 
369      * @param spoolRepositoryURL the spool whose item are about to be resend
370      * @param key ID of the mail to be resend. if not NULL, all other filters are ignored
371      * @param state if not NULL, only mails with matching state are resend
372      * @param header if not NULL, only mails with at least one header with a value matching headerValueRegex are resend
373      * @param headerValueRegex the regular expression the header must match
374      * @return int number of resent mails 
375      * @throws SpoolManagementException
376      */
377     public int resendSpoolItems(String spoolRepositoryURL, String key, String state, String header, String headerValueRegex) throws SpoolManagementException {
378         return resendSpoolItems(spoolRepositoryURL, key, new SpoolFilter(state, header, headerValueRegex));
379     }
380 
381 
382     /**
383      * @see org.apache.james.management.SpoolManagementService#resendSpoolItems(String, String, List, SpoolFilter)
384      */
385     public int resendSpoolItems(String spoolRepositoryURL, String key, List lockingFailures, SpoolFilter filter)
386             throws MessagingException, SpoolManagementException {
387         int count = 0;
388         SpoolRepository spoolRepository = getSpoolRepository(spoolRepositoryURL);
389 
390         // check if an key was given as argument
391         if (key != null) {
392             try {
393                 if (resendMail(spoolRepository, key, filter)) count++;
394             } catch (IllegalStateException e) {
395                 if (lockingFailures != null) lockingFailures.add(key);
396             }
397         } else {
398             // get an iterator of all keys
399             Iterator spoolR = spoolRepository.list();
400 
401             while (spoolR.hasNext()) {
402                 key = spoolR.next().toString();
403                 try {
404                     if (resendMail(spoolRepository, key, filter)) count++;
405                 } catch (IllegalStateException e) {
406                     if (lockingFailures != null) lockingFailures.add(key);
407                 }
408             }
409         }
410         return count;
411     }
412 
413     /**
414      * Resent the mail that belongs to the given key and spoolRepository 
415      * 
416      * @param spoolRepository The spoolRepository
417      * @param key The message key
418      * @param filter
419      * @return true or false
420      * @throws MessagingException Get thrown if there happen an error on modify the mail
421      */
422     private boolean resendMail(SpoolRepository spoolRepository, String key, SpoolFilter filter)
423             throws MessagingException, IllegalStateException, SpoolManagementException {
424         if (!spoolRepository.lock(key)) throw new IllegalStateException("locking failure");
425 
426         // get the mail and set the error_message to "0" that will force the spoolmanager to try to deliver it now!
427         Mail m = spoolRepository.retrieve(key);
428 
429         if (filterMatches(m, filter)) {
430 
431             // this will force Remotedelivery to try deliver the mail now!
432             m.setLastUpdated(new Date(0));
433 
434             // store changes
435             spoolRepository.store(m);
436             spoolRepository.unlock(key);
437 
438             synchronized (spoolRepository) {
439                 spoolRepository.notify();
440             }
441             return true;
442         } else {
443             spoolRepository.unlock(key);
444             return false;
445         }
446     }
447 
448     /**
449      * Remove the mail that belongs to the given key and spoolRepository 
450      * 
451      * @param spoolRepository The spoolRepository
452      * @param key The message key
453      * @param filter
454      * @return true or false
455      * @throws MessagingException Get thrown if there happen an error on modify the mail
456      */
457     private boolean removeMail(SpoolRepository spoolRepository, String key, SpoolFilter filter) 
458             throws MessagingException, SpoolManagementException {
459         if (!spoolRepository.lock(key)) throw new IllegalStateException("locking failure");
460 
461         Mail m = spoolRepository.retrieve(key);
462         if (m == null) throw new SpoolManagementException("mail not available having key " + key);
463         if (!filterMatches(m, filter)) return false;
464         ContainerUtil.dispose(m);
465         spoolRepository.remove(key);
466         return true;
467     }
468 
469     /**
470      * Retrieve a spoolRepository by the given url
471      * 
472      * @param url The spoolRepository url
473      * @return The spoolRepository
474      * @throws ServiceException Get thrown if the spoolRepository can not retrieved
475      */
476     private SpoolRepository getSpoolRepository(String url)
477             throws SpoolManagementException {
478         // Setup all needed data
479         DefaultConfiguration spoolConf = new DefaultConfiguration("spool",
480                 "generated:RemoteManager.java");
481         spoolConf.setAttribute("destinationURL", url);
482         spoolConf.setAttribute("type", "SPOOL");
483 
484         try {
485             return (SpoolRepository) mailStore.select(spoolConf);
486         } catch (ServiceException e) {
487             throw new SpoolManagementException("Exception while looking up for the repository", e);
488         }
489     }
490 
491 
492 }