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.mailrepository.javamail;
21  
22  import java.io.File;
23  import java.io.FileNotFoundException;
24  import java.net.MalformedURLException;
25  import java.util.Collection;
26  import java.util.Iterator;
27  import java.util.Properties;
28  import java.util.Random;
29  
30  import javax.mail.Folder;
31  import javax.mail.MessagingException;
32  import javax.mail.NoSuchProviderException;
33  import javax.mail.Session;
34  import javax.mail.Store;
35  import javax.mail.URLName;
36  
37  import org.apache.avalon.framework.activity.Initializable;
38  import org.apache.avalon.framework.configuration.Configurable;
39  import org.apache.avalon.framework.configuration.Configuration;
40  import org.apache.avalon.framework.configuration.ConfigurationException;
41  import org.apache.avalon.framework.logger.AbstractLogEnabled;
42  import org.apache.avalon.framework.logger.Logger;
43  import org.apache.avalon.framework.service.ServiceException;
44  import org.apache.avalon.framework.service.ServiceManager;
45  import org.apache.avalon.framework.service.Serviceable;
46  import org.apache.james.services.FileSystem;
47  import org.apache.james.services.MailRepository;
48  import org.apache.mailet.Mail;
49  
50  /**
51   * MailRepository implementation to store mail in a Javamail store
52   * 
53   * This implementation should be considered as EXPERIMENTAL. <br>
54   * <br>
55   * TODO examine for thread-safety
56   */
57  
58  public abstract class AbstractJavamailStoreMailRepository extends
59          AbstractLogEnabled implements MailRepository, StoreGateKeeperAware, FolderAdapterFactory, Configurable,
60          Initializable,Serviceable {
61  
62      /**
63       * Whether 'deep debugging' is turned on.
64       */
65      protected final static boolean DEEP_DEBUG = true;
66  
67      private static final String TYPE = "MAIL";
68  
69      /**
70       * Assembled JavaMail destinationURL, only kept here for debugging via
71       * getDestination()
72       */
73      private String destination;
74  
75      protected Logger log;
76  
77      /**
78       * The underlaying Store can be used externaly via the StoreAware.getStore()
79       * Method
80       */
81      private StoreGateKeeper storeGateKeeper = null;
82  
83      /** 
84       * used internally to generate keys 
85       */
86      private static Random random;
87  
88      /**
89       * this has not been tested yet, so it is not configurable. But it is likely
90       * that there would be memory leaks
91       */
92      protected boolean cacheMessages = false;
93  
94      /**
95       * A lock used to control access to repository elements, locking access
96       * based on the key
97       */
98      private LockInterface lock;
99  
100     /**
101      * inbox only accessable through a FolderGateKeeper
102      */
103     private FolderGateKeeper folderGateKeeper;
104 
105     /**
106      * The directory james is running in
107      */
108     private File home;
109 
110     /**
111      * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
112      */
113     public void service(ServiceManager serviceManager) throws ServiceException {
114         try {
115             home = ((FileSystem) serviceManager.lookup(FileSystem.ROLE)).getBasedir();
116         } catch (FileNotFoundException e) {
117             throw new ServiceException(FileSystem.ROLE, "Cannot find the base directory of the application", e);
118         }
119     }
120 
121     /**
122      * builds destination from attributes destinationURL and postfix.
123      * at the moment james does not hand over additional parameters like postfix.
124      * 
125      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
126      * 
127      */
128     public void configure(Configuration conf) throws ConfigurationException {
129         log.debug("JavamailStoreMailRepository configure");
130         destination = conf.getAttribute("destinationURL");
131         log.debug("JavamailStoreMailRepository.destinationURL: " + destination);
132         if (!destination.endsWith("/")) {
133             destination += "/";
134         }
135         String postfix = conf.getAttribute("postfix", "");
136         if (postfix.length() > 0) {
137             if (postfix.startsWith("/")) {
138                 postfix = postfix.substring(1);
139             }
140             if (!postfix.endsWith("/")) {
141                 postfix += "/";
142             }
143             destination += postfix;
144 
145         }
146 
147         /*
148          * Ugly hack to get the right folder to store the maildir I guess this
149          * could be also configured in config.xml
150          */
151         final int pi = destination.indexOf(':');
152         if (pi < 0) {
153             throw new ConfigurationException("protocol prefix is missing "
154                     + destination);
155         }
156         String withoutProtocol = destination.substring(pi);
157         final String protocol = destination.substring(0, pi);
158         try {
159             if (!withoutProtocol.startsWith(":///")) {         
160                 withoutProtocol = "://"+  getDirAsUrl(home + "/" +withoutProtocol.substring(3));
161             } else {
162                 withoutProtocol = "://" + getDirAsUrl("/" + withoutProtocol.substring(3));
163             }
164         } catch (MalformedURLException e) {
165             throw new ConfigurationException("Invalid url: " + destination);
166         }
167     
168 
169         destination = protocol + withoutProtocol;
170         log.debug("destination: " + destination);
171         Properties mailSessionProps  = new Properties();
172 
173         // That seems to be deprecated
174         // mailSessionProps.put("mail.store.maildir.autocreatedir", "true");
175         Session mailSession = Session.getDefaultInstance(mailSessionProps);
176         try {
177             Store store=mailSession.getStore(new URLName(destination));
178             store.connect();
179             storeGateKeeper = new StoreGateKeeperImpl(store);
180             storeGateKeeper.setFolderAdapterFactory(this);
181         } catch (NoSuchProviderException e) {
182             throw new ConfigurationException("cannot find store provider for "
183                     + destination, e);
184         } catch (MessagingException e) {
185             throw new ConfigurationException("Error connecting store: "
186                     + destination, e);
187         }
188 
189         String checkType = conf.getAttribute("type");
190         if (!checkType.equals(TYPE)) {
191             String exceptionString = "Attempt to configure JavaMailStoreMailRepository as "
192                     + checkType;
193             log.warn(exceptionString);
194             throw new ConfigurationException(exceptionString);
195         }
196         log.debug("JavaMailStoreMailRepository configured");
197     }
198 
199     /**
200      * Does nothing
201      * @see Initializable#initialize()
202      */
203     public void initialize() throws Exception {
204         log.debug("JavaMailStoreMailRepository initialized");
205     }
206     
207     private String getDirAsUrl(String dir) throws MalformedURLException {
208         File f = new File(dir); 
209         return f.toURL().toString();
210     }
211 
212 
213     /**
214      * gets the Lock and creates it, if not present. LockInterface offers functionality
215      * of org.apache.james.util.Lock
216      *
217      * @return lock the LockInterface
218      */
219     protected LockInterface getLock() {
220         if (lock==null) {
221             lock = new LockAdapter();
222         }
223         return lock;
224     }
225 
226     /**
227      * possibility to replace Lock implementation. At the moment only used for testing 
228      *
229      * @param lock the LockInterface to use
230      */
231     void setLock(LockInterface lock) {
232         this.lock=lock;
233     }
234     
235     /**
236      * used in unit tests 
237      * 
238      * @return the destination of the repository
239      */
240     String getDestination() {
241         return destination;
242     }
243 
244     /**
245      * Obtains a lock on a message identified by a key
246      * 
247      * @param key
248      *            the key of the message to be locked
249      * 
250      * @return true if successfully obtained the lock, false otherwise
251      */
252     public boolean lock(String key) {
253         if (getLock().lock(key)) {
254             if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
255                 StringBuffer debugBuffer = new StringBuffer(256).append(
256                         "Locked ").append(key).append(" for ").append(
257                         Thread.currentThread().getName()).append(" @ ").append(
258                         new java.util.Date(System.currentTimeMillis()));
259                 getLogger().debug(debugBuffer.toString());
260             }
261             return true;
262         } else {
263             return false;
264         }
265     }
266 
267     /**
268      * Releases a lock on a message identified by a key
269      * 
270      * @param key
271      *            the key of the message to be unlocked
272      * 
273      * @return true if successfully released the lock, false otherwise
274      */
275     public boolean unlock(String key) {
276         if (getLock().unlock(key)) {
277             if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
278                 StringBuffer debugBuffer = new StringBuffer(256).append(
279                         "Unlocked ").append(key).append(" for ").append(
280                         Thread.currentThread().getName()).append(" @ ").append(
281                         new java.util.Date(System.currentTimeMillis()));
282                 getLogger().debug(debugBuffer.toString());
283             }
284             return true;
285         } else {
286             return false;
287         }
288     }
289 
290     /**
291      * Removes a specified message
292      * 
293      * @param mail
294      *            the message to be removed from the repository
295      * @throws MessagingException
296      */
297     public void remove(Mail mail) throws MessagingException {
298         log.debug("UIDPlusFolder remove by Mail");
299         remove(mail.getName());
300     }
301 
302     /**
303      * Remove a list of messages from disk The collection is simply a list of
304      * mails to delete
305      * 
306      * @param mails
307      * @throws MessagingException
308      */
309     public void remove(final Collection mails) throws MessagingException {
310         log.debug("UIDPlusFolder remove by Collection " + mails.size());
311         if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
312             StringBuffer logBuffer = new StringBuffer(128).append(
313                     this.getClass().getName()).append(
314                     " Removing entry for key ").append(mails);
315 
316             getLogger().debug(logBuffer.toString());
317         }
318         Iterator mailList = mails.iterator();
319 
320         /*
321          * remove every email from the Collection
322          */
323         while (mailList.hasNext()) {
324             remove(((Mail) mailList.next()).getName());
325         }
326     }
327 
328     /**
329      * offers the underlaying Store for external use
330      * 
331      * @return the Store 
332      */
333     public StoreGateKeeper getStore() {
334         return storeGateKeeper;
335     }
336 
337     /**
338      * lazy-loads random 
339      * 
340      * @return random an instance of random
341      */
342     protected static synchronized Random getRandom() {
343         if (random == null) {
344             random = new Random();
345         }
346         return random;
347 
348     }
349     
350     /**
351      * Set the Logger to use
352      * 
353      * @see org.apache.avalon.framework.logger.AbstractLogEnabled#enableLogging(Logger)
354      */
355     public void enableLogging(Logger log) {
356         super.enableLogging(log);
357         this.log=log;
358         
359     }
360     
361     /**
362      * possibility to replace FolderGateKeeper implementation. Only used for
363      * testing
364      * 
365      * @param gk the FolgerGateKeeper to use
366      */
367     void setFolderGateKeeper(FolderGateKeeper gk) {
368         this.folderGateKeeper=gk;
369         
370     }
371     
372     /**
373      * Lazy-load FolderGateKeeper with inbox folder. Inbox folder is created if
374      * not present
375      * @return FolderGateKeeper offering inbox folder for this mail repository
376      */
377     protected FolderGateKeeper getFolderGateKeeper() {
378         if (folderGateKeeper == null) {
379             try {
380                 folderGateKeeper = getStore().getFolder("INBOX");
381                 /*
382                  * Check whether the folder exists, if not create it
383                  */
384                 folderGateKeeper.use();
385                 if (folderGateKeeper.getFolder().exists() == false) {
386                     folderGateKeeper.getFolder().create(Folder.HOLDS_MESSAGES);
387                 }
388                 folderGateKeeper.free();
389             } catch (MessagingException e) {
390                 throw new RuntimeException(
391                         "cannot retrieve inbox for this repository", e);
392             } 
393         }
394         return folderGateKeeper;
395     }
396 }