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.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.Map;
29  import java.util.NoSuchElementException;
30  
31  import javax.mail.Flags;
32  import javax.mail.Folder;
33  import javax.mail.Message;
34  import javax.mail.MessagingException;
35  import javax.mail.UIDFolder;
36  import javax.mail.Flags.Flag;
37  import javax.mail.internet.MimeMessage;
38  
39  import org.apache.james.core.MailImpl;
40  import org.apache.mailet.Mail;
41  
42  /**
43   * MailRepository implementation to store mail in a Javamail store which
44   * provides the UID Plus method public long[] addUIDMessages.<br>
45   * <br>
46   * This implementation should be considered as EXPERIMENTAL.
47   * 
48   * TODO examine for thread-safety
49   */
50  public class UIDPlusFolderMailRepository extends
51          AbstractJavamailStoreMailRepository {
52  
53      /**
54       * used to map keys to uid and vice versa
55       */
56      private UidToKeyBidiMap uidToKeyBidiMap = null;
57  
58      public static final int DELIVERY_MODE_CLOSED = 0;
59      public static final int DELIVERY_MODE_TRY = 1;
60      public static final int DELIVERY_MODE_OPEN = 2;
61      
62      private int deliveryMode=DELIVERY_MODE_TRY;
63      
64      protected long addUIDMessage(Message message) throws MessagingException {
65          try {
66              getFolderGateKeeper().use();
67              long[] uids = null;
68              if (deliveryMode != DELIVERY_MODE_OPEN) {
69                  try {
70                      log.debug("Doing a addUIDMessages on maybe closed Folder: isopen="+getFolderGateKeeper().getFolder().isOpen());
71                      uids = ((UIDPlusFolder) getFolderGateKeeper().getFolder())
72                              .addUIDMessages(new Message[] { message });
73                  } catch (IllegalStateException e) {
74                      if (deliveryMode == DELIVERY_MODE_CLOSED) {
75                          log.error("deliveryMode=DELIVERY_MODE_CLOSED",e);
76                          throw e;
77                      } else {
78                          log.debug("switching to DELIVERY_MODE_OPEN",e);
79                          deliveryMode = DELIVERY_MODE_OPEN;
80                      }
81  
82                  }
83              }
84              if (deliveryMode == DELIVERY_MODE_OPEN) {
85                  log.debug("Doing a addUIDMessages on a open Folder");
86                  uids = ((UIDPlusFolder) getFolderGateKeeper().getOpenFolder())
87                          .addUIDMessages(new Message[] { message });
88              }
89              if (uids == null || uids.length != 1) {
90                  throw new RuntimeException(
91                          "Fatal error while storing Message Container: Message was not Appendet");
92              }
93              return uids[0];
94          } finally {
95              getFolderGateKeeper().free();
96          }
97  
98      }
99      
100 
101 
102     /**
103      * Stores a message in this repository.
104      * 
105      * @param mc
106      *            the mail message to store
107      * @throws MessagingException
108      */
109     public void store(Mail mc) throws MessagingException {
110         
111         log.debug("UIDPlusFolder store key:" + mc.getName());
112         if (!mc.getMessage().isSet(Flag.RECENT)) {
113             log.debug("Message didn't have RECENT flag");
114             mc.getMessage().setFlag(Flag.RECENT,true);
115         }
116         String key = mc.getName();
117         boolean wasLocked = true;
118         try {
119             getFolderGateKeeper().use();
120             MimeMessage message = mc.getMessage();
121             synchronized (this) {
122                 wasLocked = getLock().isLocked(key);
123                 if (!wasLocked) {
124                     // If it wasn't locked, we want a lock during the store
125                     lock(key);
126                 }
127             }
128 
129             // insert or update, don't call remove(key) because of locking
130             if (getUidToKeyBidiMap().containsKey(key)) {
131                 // message gets updated an folder stays open. 
132                 Message mm = getMessageFromInbox(key,
133                         (UIDFolder) getFolderGateKeeper().getOpenFolder());
134                 if (mm != null) {
135                     mm.setFlag(Flags.Flag.DELETED, true);
136                     message.setFlag(Flags.Flag.RECENT, false);
137                 }
138             }
139             long uid = addUIDMessage(message);
140             getUidToKeyBidiMap().put(key, uid);
141 
142             log.info("message stored: UID: " + uid + " Key:" + mc.getName());
143         } finally {
144             getFolderGateKeeper().free();
145             if (!wasLocked) {
146                 // If it wasn't locked, we need to unlock now
147                 unlock(key);
148                 synchronized (this) {
149                     notify();
150                 }
151             }
152 
153         }
154     }
155 
156     /**
157      * lazy loads UidToKeyBidiMap
158      */
159     protected UidToKeyBidiMap getUidToKeyBidiMap() {
160         if (uidToKeyBidiMap == null) {
161             uidToKeyBidiMap = new UidToKeyBidiMapImpl();
162         }
163         return uidToKeyBidiMap;
164     }
165 
166     /**
167      * Used for testing
168      * 
169      * @param uidToKeyBidiMap
170      */
171     void setUidToKeyBidiMap(UidToKeyBidiMap uidToKeyBidiMap) {
172         this.uidToKeyBidiMap = uidToKeyBidiMap;
173     }
174 
175     /**
176      * Retrieves a message given a key. At the moment, keys can be obtained from
177      * list() in superinterface Store.Repository
178      * 
179      * @param key
180      *            the key of the message to retrieve
181      * @return the mail corresponding to this key, null if none exists
182      * @throws MessagingException 
183      */
184 
185     public Mail retrieve(String key) throws MessagingException {
186         log.debug("UIDPlusFolder retrieve " + key);
187         try {
188             getFolderGateKeeper().use();
189             MimeMessage mm = getMessageFromInbox(key,
190                     (UIDFolder) getFolderGateKeeper().getOpenFolder());
191             if (mm == null)
192                 return null;
193             Mail mail = new MailImpl();
194             mail.setMessage(mm);
195             mail.setName(key);
196             return mail;
197         } finally {
198             getFolderGateKeeper().free();
199         }
200     }
201 
202     /**
203      * Removes a message identified by key.
204      * 
205      * @param key
206      *            the key of the message to be removed from the repository
207      */
208     public void remove(String key) throws MessagingException {
209 
210         log.debug("UIDFolder remove key:" + key);// , new Throwable());
211         if (lock(key)) {
212             getFolderGateKeeper().use();
213             try {
214                 Message mm = getMessageFromInbox(key,
215                         (UIDFolder) getFolderGateKeeper().getOpenFolder());
216                 if (mm != null) {
217                     mm.setFlag(Flags.Flag.DELETED, true);
218                 }
219                 getUidToKeyBidiMap().removeByKey(key);
220             } finally {
221                 unlock(key);
222                 getFolderGateKeeper().free();
223             }
224         } else {
225             log.debug("could not optain lock");
226             throw new MessagingException("could not optain lock for remove");
227         }
228     }
229 
230     /**
231      * List string keys of messages in repository.
232      * 
233      * @return an <code>Iterator</code> over the list of keys in the
234      *         repository
235      * @throws MessagingException 
236      * 
237      */
238     public Iterator list() throws MessagingException {
239         log.debug("UIDPlusFolder list");
240         try {
241             getFolderGateKeeper().use();
242             // needed for retainAllListedAndAddedByKeys(String[], Collection)
243             String[] keysBefore = getUidToKeyBidiMap().getKeys();
244             // get the messages
245             Message[] msgs = getFolderGateKeeper().getOpenFolder().getMessages();
246             Collection keys = new ArrayList(msgs.length);
247             if (msgs == null)
248                 throw new RuntimeException("inbox.getMessages returned null");
249             for (int i = 0; i < msgs.length; i++) {
250                 try {
251                     long uidvalidity = ((UIDFolder) getFolderGateKeeper().getOpenFolder()).getUIDValidity();
252                     // lookup uid
253                     long uid = ((UIDFolder) getFolderGateKeeper().getOpenFolder()).getUID(msgs[i]);
254                     String key = getUidToKeyBidiMap().getByUid(uid);
255                     if (key == null) {
256                         // generate new key
257                         key = "james-uid:" + uidvalidity + ";" + uid + ";"
258                                 + System.currentTimeMillis() + ";"
259                                 + getRandom().nextLong();
260                         getUidToKeyBidiMap().put(key, uid);
261                     }
262                     keys.add(key);
263                     log.info("list: UID: " + uid + " Key:" + key);
264                 } catch (NoSuchElementException e) {
265                     // no problem, messages could have been deleted in the
266                     // meantime
267                 }
268             }
269             // retain only listed keys, and keys added in the meantime (it would
270             // be fatal to loose those)
271             // I don't care about meanwhile removed, those will fail on next
272             // access
273             // it's a good idea to keep count of cached small
274             getUidToKeyBidiMap()
275                     .retainAllListedAndAddedByKeys(keysBefore, keys);
276             return keys.iterator();
277         } catch (MessagingException e) {
278             throw new RuntimeException(e);
279         } finally {
280             getFolderGateKeeper().free();
281         }
282     }
283 
284     private MimeMessage getMessageFromInbox(String key, UIDFolder inbox)
285             throws MessagingException {
286 
287         long uid = getUidToKeyBidiMap().getByKey(key);
288         if (uid < 0) {
289             return null;
290         }
291 
292         MimeMessage mm = (MimeMessage) inbox.getMessageByUID(uid);
293         log.info("getMessageFromInbox: UID: " + uid + " Key:" + key);
294         if (mm == null) {
295             getUidToKeyBidiMap().removeByKey(key);
296             log.info("Message not Found");
297         }
298         return mm;
299     }
300 
301     /**
302      * 
303      * maybe it could be replaced by BidiMap from commons-collections 3.0+
304      */
305     private class UidToKeyBidiMapImpl implements UidToKeyBidiMap {
306 
307         private Map keyToUid;
308 
309         private Map uidToKey;
310 
311         public UidToKeyBidiMapImpl() {
312             keyToUid = new HashMap();
313             uidToKey = new HashMap();
314         }
315 
316         public synchronized String[] getKeys() {
317             final ArrayList al = new ArrayList(keyToUid.keySet());
318             final String[] keys = (String[]) al.toArray(new String[0]);
319             return keys;
320         }
321 
322         public synchronized void retainAllListedAndAddedByKeys(
323                 final String[] before, final Collection listed) {
324             Collection added = new HashSet(keyToUid.keySet());
325             added.removeAll(Arrays.asList(before));
326             Collection retain = new HashSet(listed);
327             retain.addAll(added);
328             keyToUid.keySet().retainAll(retain);
329             uidToKey.keySet().retainAll(keyToUid.values());
330         }
331 
332         public synchronized void removeByKey(String key) {
333             long uid = getByKey(key);
334             if (uid > -1) {
335                 uidToKey.remove(new Long(uid));
336             }
337             keyToUid.remove(key);
338         }
339 
340         public synchronized long getByKey(String key) {
341             Long lo = (Long) keyToUid.get(key);
342             long l = -1;
343             if (lo != null) {
344                 l = lo.longValue();
345             }
346             return l;
347         }
348 
349         public synchronized String getByUid(long uid) {
350 
351             return (String) uidToKey.get(new Long(uid));
352         }
353 
354         public synchronized boolean containsKey(String key) {
355             return keyToUid.containsKey(key);
356         }
357 
358         public synchronized void put(String key, long uid) {
359             keyToUid.put(key, new Long(uid));
360             uidToKey.put(new Long(uid), key);
361         }
362 
363     }
364 
365     /**
366      * returns a UIDPlusFolderAdapter wrapper
367      * 
368      * @see UIDPlusFolderAdapter
369      */
370     public FolderInterface createAdapter(Folder folder) {
371         return new UIDPlusFolderAdapter(folder);
372     }
373 
374 
375 }