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