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  package org.apache.james.transport.mailets;
23  
24  import org.apache.avalon.cornerstone.services.store.Store;
25  import org.apache.avalon.framework.configuration.DefaultConfiguration;
26  import org.apache.avalon.framework.service.ServiceException;
27  import org.apache.avalon.framework.service.ServiceManager;
28  import org.apache.james.Constants;
29  import org.apache.james.core.MailImpl;
30  import org.apache.james.services.MailRepository;
31  import org.apache.james.services.MailServer;
32  import org.apache.mailet.base.GenericMailet;
33  import org.apache.mailet.Mail;
34  import org.apache.mailet.MailAddress;
35  import org.apache.mailet.base.RFC2822Headers;
36  
37  import javax.mail.Header;
38  import javax.mail.MessagingException;
39  import javax.mail.internet.InternetHeaders;
40  import javax.mail.internet.MimeMessage;
41  
42  import java.util.Collection;
43  import java.util.Enumeration;
44  import java.util.HashSet;
45  import java.util.Iterator;
46  import java.util.Vector;
47  
48  /**
49   * Receives a Mail from JamesSpoolManager and takes care of delivery of the
50   * message to local inboxes or a specific repository.
51   * 
52   * Differently from LocalDelivery this does not lookup the UserRepository This
53   * simply store the message in a repository named like the local part of the
54   * recipient address or the full recipient address, depending on the configuration
55   * (repositorySelector).
56   * 
57   * If no repository is specified then this fallback to MailServer.getUserInbox.
58   * Otherwise you can add your own configuration for the repository
59   * 
60   * e.g: <repositoryUrl>file://var/spool/userspools/</repositoryUrl>
61   * <repositoryType>SPOOL</repositoryType>
62   * 
63   * <repositoryUrl>file://var/mail/inboxes/</repositoryUrl> <repositoryType>MAIL</repositoryType>
64   * 
65   * Header "Delivered-To" can be added to every message adding the
66   * <addDeliveryHeader>Delivered-To</addDeliveryHeader>
67   * 
68   * <repositorySelector> defaults to "localpart" and can be changed to "full" if you
69   * prefer to use full recipient emails as repository names.
70   */
71  public class ToMultiRepository extends GenericMailet {
72      /**
73       * The number of mails generated. Access needs to be synchronized for thread
74       * safety and to ensure that all threads see the latest value.
75       */
76      private static int count = 0;
77      private static final Object countLock = new Object();
78  
79      /**
80       * The mailserver reference
81       */
82      private MailServer mailServer;
83  
84      /**
85       * The mailstore
86       */
87      private Store store;
88  
89      /**
90       * The optional repositoryUrl
91       */
92      private String repositoryUrl;
93  
94      /**
95       * The optional repositoryType
96       */
97      private String repositoryType;
98      
99      private final static String SELECTOR_LOCALPART = "localpart";
100     private final static String SELECTOR_FULL = "full";
101     
102     /**
103      * The optional repositorySelector
104      */
105     private String repositorySelector;
106 
107     /**
108      * The delivery header
109      */
110     private String deliveryHeader;
111 
112     /**
113      * resetReturnPath
114      */
115     private boolean resetReturnPath;
116 
117     /**
118      * Delivers a mail to a local mailbox.
119      * 
120      * @param mail
121      *            the mail being processed
122      * 
123      * @throws MessagingException
124      *             if an error occurs while storing the mail
125      */
126     public void service(Mail mail) throws MessagingException {
127         Collection recipients = mail.getRecipients();
128         Collection errors = new Vector();
129 
130         MimeMessage message = mail.getMessage();
131 
132         if (resetReturnPath) {
133             // Set Return-Path and remove all other Return-Path headers from the
134             // message
135             // This only works because there is a placeholder inserted by
136             // MimeMessageWrapper
137             message.setHeader(RFC2822Headers.RETURN_PATH,
138                     (mail.getSender() == null ? "<>" : "<" + mail.getSender()
139                             + ">"));
140         }
141 
142         Enumeration headers;
143         InternetHeaders deliveredTo = new InternetHeaders();
144         if (deliveryHeader != null) {
145             // Copy any Delivered-To headers from the message
146             headers = message
147                     .getMatchingHeaders(new String[] { deliveryHeader });
148             while (headers.hasMoreElements()) {
149                 Header header = (Header) headers.nextElement();
150                 deliveredTo.addHeader(header.getName(), header.getValue());
151             }
152         }
153 
154         for (Iterator i = recipients.iterator(); i.hasNext();) {
155             MailAddress recipient = (MailAddress) i.next();
156             try {
157                 if (deliveryHeader != null) {
158                     // Add qmail's de facto standard Delivered-To header
159                     message.addHeader(deliveryHeader, recipient.toString());
160                 }
161 
162                 storeMail(mail.getSender(), recipient, message);
163 
164                 if (deliveryHeader != null) {
165                     if (i.hasNext()) {
166                         // Remove headers but leave all placeholders
167                         message.removeHeader(deliveryHeader);
168                         headers = deliveredTo.getAllHeaders();
169                         // And restore any original Delivered-To headers
170                         while (headers.hasMoreElements()) {
171                             Header header = (Header) headers.nextElement();
172                             message.addHeader(header.getName(), header
173                                     .getValue());
174                         }
175                     }
176                 }
177             } catch (Exception ex) {
178                 getMailetContext().log("Error while storing mail.", ex);
179                 errors.add(recipient);
180             }
181         }
182 
183         if (!errors.isEmpty()) {
184             // If there were errors, we redirect the email to the ERROR
185             // processor.
186             // In order for this server to meet the requirements of the SMTP
187             // specification, mails on the ERROR processor must be returned to
188             // the sender. Note that this email doesn't include any details
189             // regarding the details of the failure(s).
190             // In the future we may wish to address this.
191             getMailetContext().sendMail(mail.getSender(), errors, mail.getMessage(),
192                     Mail.ERROR);
193         }
194         // We always consume this message
195         mail.setState(Mail.GHOST);
196     }
197 
198     /**
199      * Return a string describing this mailet.
200      * 
201      * @return a string describing this mailet
202      */
203     public String getMailetInfo() {
204         return "ToMultiRepository Mailet";
205     }
206 
207     /**
208      * 
209      * @param sender
210      * @param recipient
211      * @param message
212      * @throws MessagingException
213      */
214     public void storeMail(MailAddress sender, MailAddress recipient,
215             MimeMessage message) throws MessagingException {
216         String username;
217         if (recipient == null) {
218             throw new IllegalArgumentException(
219                     "Recipient for mail to be spooled cannot be null.");
220         }
221         if (message == null) {
222             throw new IllegalArgumentException(
223                     "Mail message to be spooled cannot be null.");
224         }
225         username = recipient.toString();
226 
227         Collection recipients = new HashSet();
228         recipients.add(recipient);
229         MailImpl mail = new MailImpl(getId(), sender, recipients, message);
230         try {
231             MailRepository userInbox = getRepository(username);
232             if (userInbox == null) {
233                 StringBuffer errorBuffer = new StringBuffer(128).append(
234                         "The repository for user ").append(username).append(
235                         " was not found on this server.");
236                 throw new MessagingException(errorBuffer.toString());
237             }
238             userInbox.store(mail);
239         } finally {
240             mail.dispose();
241         }
242     }
243 
244     /**
245      * Return a new mail id.
246      * 
247      * @return a new mail id
248      */
249     public String getId() {
250         final long localCount;
251         synchronized (countLock) {
252             localCount = count++;
253         }
254         StringBuffer idBuffer = new StringBuffer(64).append("Mail").append(
255                 System.currentTimeMillis()).append("-").append(localCount).append('L');
256         return idBuffer.toString();
257     }
258 
259     /**
260      * @see org.apache.mailet.GenericMailet#init()
261      */
262     public void init() throws MessagingException {
263         super.init();
264         ServiceManager compMgr = (ServiceManager) getMailetContext()
265                 .getAttribute(Constants.AVALON_COMPONENT_MANAGER);
266 
267         try {
268             // Instantiate the a MailRepository for outgoing mails
269             store = (Store) compMgr.lookup(Store.ROLE);
270         } catch (ServiceException cnfe) {
271             log("Failed to retrieve Store component:" + cnfe.getMessage());
272         } catch (Exception e) {
273             log("Failed to retrieve Store component:" + e.getMessage());
274         }
275 
276         repositoryUrl = getInitParameter("repositoryUrl");
277         if (repositoryUrl != null) {
278             if (!repositoryUrl.endsWith("/"))
279                 repositoryUrl += "/";
280             repositoryType = getInitParameter("repositoryType");
281             if (repositoryType == null)
282                 repositoryType = "MAIL";
283             repositorySelector = getInitParameter("repositorySelector");
284             if (repositorySelector == null)
285                 repositorySelector = SELECTOR_LOCALPART;
286             if (!SELECTOR_LOCALPART.equals(repositorySelector) && !SELECTOR_FULL.equals(repositorySelector)) {
287                 throw new MessagingException("repositorySelector valid options are "+SELECTOR_FULL+" or "+SELECTOR_LOCALPART);
288             }
289         } else {
290 
291             try {
292                 // Instantiate the a MailRepository for outgoing mails
293                 mailServer = (MailServer) compMgr.lookup(MailServer.ROLE);
294             } catch (ServiceException cnfe) {
295                 log("Failed to retrieve MailServer component:" + cnfe.getMessage());
296             } catch (Exception e) {
297                 log("Failed to retrieve MailServer component:" + e.getMessage());
298             }
299             
300         }
301 
302         deliveryHeader = getInitParameter("addDeliveryHeader");
303         String resetReturnPathString = getInitParameter("resetReturnPath");
304         resetReturnPath = "true".equalsIgnoreCase(resetReturnPathString);
305     }
306 
307     /**
308      * Get the user inbox: if the repositoryUrl is null then get the userinbox
309      * from the mailserver, otherwise lookup the store with the given 
310      * repositoryurl/type
311      *   
312      * @param userName
313      */
314     private MailRepository getRepository(String userName) {
315         MailRepository userInbox;
316         if (repositoryUrl == null) {
317             userInbox = mailServer.getUserInbox(userName);
318         } else {
319             if (SELECTOR_LOCALPART.equals(repositorySelector)) {
320                 // find the username for delivery to that user - localname, ignore the rest
321                 String[] addressParts = userName.split("@");
322                 userName = addressParts[0];
323             }
324                         
325             StringBuffer destinationBuffer = new StringBuffer(192).append(
326             repositoryUrl).append(userName).append("/");
327             String destination = destinationBuffer.toString();
328             DefaultConfiguration mboxConf = new DefaultConfiguration(
329                     "repository", "generated:ToMultiRepository.getUserInbox()");
330             mboxConf.setAttribute("destinationURL", destination);
331             mboxConf.setAttribute("type", repositoryType);
332             try {
333                 userInbox = (MailRepository) store.select(mboxConf);
334             } catch (Exception e) {
335                 log("Cannot open repository " + e);
336                 userInbox = null;
337             }
338         }
339         return userInbox;
340     }
341 
342 }