001 /****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one *
003 * or more contributor license agreements. See the NOTICE file *
004 * distributed with this work for additional information *
005 * regarding copyright ownership. The ASF licenses this file *
006 * to you under the Apache License, Version 2.0 (the *
007 * "License"); you may not use this file except in compliance *
008 * with the License. You may obtain a copy of the License at *
009 * *
010 * http://www.apache.org/licenses/LICENSE-2.0 *
011 * *
012 * Unless required by applicable law or agreed to in writing, *
013 * software distributed under the License is distributed on an *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015 * KIND, either express or implied. See the License for the *
016 * specific language governing permissions and limitations *
017 * under the License. *
018 ****************************************************************/
019 package org.apache.james.mailbox.jpa.mail;
020
021 import java.util.HashMap;
022 import java.util.Iterator;
023 import java.util.List;
024 import java.util.Map;
025
026 import javax.persistence.EntityManager;
027 import javax.persistence.EntityManagerFactory;
028 import javax.persistence.EntityTransaction;
029 import javax.persistence.PersistenceException;
030 import javax.persistence.Query;
031
032 import org.apache.james.mailbox.MailboxSession;
033 import org.apache.james.mailbox.exception.MailboxException;
034 import org.apache.james.mailbox.jpa.mail.model.JPAMailbox;
035 import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage;
036 import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMessage;
037 import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage;
038 import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage;
039 import org.apache.james.mailbox.model.MessageMetaData;
040 import org.apache.james.mailbox.model.MessageRange;
041 import org.apache.james.mailbox.model.MessageRange.Type;
042 import org.apache.james.mailbox.store.SimpleMessageMetaData;
043 import org.apache.james.mailbox.store.mail.AbstractMessageMapper;
044 import org.apache.james.mailbox.store.mail.MessageMapper;
045 import org.apache.james.mailbox.store.mail.ModSeqProvider;
046 import org.apache.james.mailbox.store.mail.UidProvider;
047 import org.apache.james.mailbox.store.mail.model.Mailbox;
048 import org.apache.james.mailbox.store.mail.model.Message;
049 import org.apache.openjpa.persistence.ArgumentException;
050
051 /**
052 * JPA implementation of a {@link MessageMapper}. This class is not thread-safe!
053 */
054 public class JPAMessageMapper extends AbstractMessageMapper<Long> implements MessageMapper<Long> {
055 protected EntityManagerFactory entityManagerFactory;
056 protected EntityManager entityManager;
057
058 public JPAMessageMapper(final MailboxSession session, final UidProvider<Long> uidProvider, ModSeqProvider<Long> modSeqProvider, final EntityManagerFactory entityManagerFactory) {
059 super(session, uidProvider, modSeqProvider);
060 this.entityManagerFactory = entityManagerFactory;
061 }
062
063
064 /**
065 * Return the currently used {@link EntityManager} or a new one if none exists.
066 *
067 * @return entitymanger
068 */
069 public EntityManager getEntityManager() {
070 if (entityManager != null)
071 return entityManager;
072 entityManager = entityManagerFactory.createEntityManager();
073 return entityManager;
074 }
075
076 /**
077 * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin()
078 */
079 protected void begin() throws MailboxException {
080 try {
081 getEntityManager().getTransaction().begin();
082 } catch (PersistenceException e) {
083 throw new MailboxException("Begin of transaction failed", e);
084 }
085 }
086
087 /**
088 * Commit the Transaction and close the EntityManager
089 */
090 protected void commit() throws MailboxException {
091 try {
092 getEntityManager().getTransaction().commit();
093 } catch (PersistenceException e) {
094 throw new MailboxException("Commit of transaction failed",e);
095 }
096 }
097
098 /**
099 * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback()
100 */
101 protected void rollback() throws MailboxException {
102 EntityTransaction transaction = entityManager.getTransaction();
103 // check if we have a transaction to rollback
104 if (transaction.isActive()) {
105 getEntityManager().getTransaction().rollback();
106 }
107 }
108
109 /**
110 * Close open {@link EntityManager}
111 */
112 public void endRequest() {
113 if (entityManager != null) {
114 if (entityManager.isOpen())
115 entityManager.close();
116 entityManager = null;
117 }
118 }
119
120 /**
121 * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.model.MessageRange, org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int)
122 */
123 public Iterator<Message<Long>> findInMailbox(Mailbox<Long> mailbox, MessageRange set, FetchType fType, int max) throws MailboxException {
124 try {
125 List<Message<Long>> results;
126 long from = set.getUidFrom();
127 final long to = set.getUidTo();
128 final Type type = set.getType();
129
130 switch (type) {
131 default:
132 case ALL:
133 results = findMessagesInMailbox(mailbox, max);
134 break;
135 case FROM:
136 results = findMessagesInMailboxAfterUID(mailbox, from, max);
137 break;
138 case ONE:
139 results = findMessagesInMailboxWithUID(mailbox, from);
140 break;
141 case RANGE:
142 results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max);
143 break;
144 }
145
146 return results.iterator();
147
148 } catch (PersistenceException e) {
149 throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e);
150 }
151 }
152
153 @SuppressWarnings("unchecked")
154 private List<Message<Long>> findMessagesInMailboxAfterUID(Mailbox<Long> mailbox, long uid, int batchSize) {
155 Query query = getEntityManager().createNamedQuery("findMessagesInMailboxAfterUID")
156 .setParameter("idParam", mailbox.getMailboxId())
157 .setParameter("uidParam", uid);
158
159 if(batchSize > 0)
160 query.setMaxResults(batchSize);
161
162 return query.getResultList();
163 }
164
165 @SuppressWarnings("unchecked")
166 private List<Message<Long>> findMessagesInMailboxWithUID(Mailbox<Long> mailbox, long uid) {
167 return getEntityManager().createNamedQuery("findMessagesInMailboxWithUID")
168 .setParameter("idParam", mailbox.getMailboxId())
169 .setParameter("uidParam", uid).setMaxResults(1).getResultList();
170 }
171
172 @SuppressWarnings("unchecked")
173 private List<Message<Long>> findMessagesInMailboxBetweenUIDs(Mailbox<Long> mailbox, long from, long to, int batchSize) {
174 Query query = getEntityManager().createNamedQuery("findMessagesInMailboxBetweenUIDs").setParameter("idParam", mailbox.getMailboxId()).setParameter("fromParam", from).setParameter("toParam", to);
175
176 if (batchSize > 0)
177 query.setMaxResults(batchSize);
178
179 return query.getResultList();
180 }
181
182 @SuppressWarnings("unchecked")
183 private List<Message<Long>> findMessagesInMailbox(Mailbox<Long> mailbox, int batchSize) {
184 Query query = getEntityManager().createNamedQuery("findMessagesInMailbox").setParameter("idParam", mailbox.getMailboxId());
185 if(batchSize > 0)
186 query.setMaxResults(batchSize);
187 return query.getResultList();
188 }
189
190
191
192 private Map<Long, MessageMetaData> createMetaData(List<Message<Long>> uids) {
193 final Map<Long, MessageMetaData> data = new HashMap<Long, MessageMetaData>();
194 for (int i = 0; i < uids.size(); i++) {
195 Message<Long> m = uids.get(i);
196 data.put(m.getUid(), new SimpleMessageMetaData(m));
197 }
198 return data;
199 }
200
201
202 private int deleteDeletedMessagesInMailbox(Mailbox<Long> mailbox) {
203 return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailbox").setParameter("idParam", mailbox.getMailboxId()).executeUpdate();
204 }
205
206 private int deleteDeletedMessagesInMailboxAfterUID(Mailbox<Long> mailbox, long uid) {
207 return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxAfterUID")
208 .setParameter("idParam", mailbox.getMailboxId())
209 .setParameter("uidParam", uid).executeUpdate();
210 }
211
212 private int deleteDeletedMessagesInMailboxWithUID(Mailbox<Long> mailbox, long uid) {
213 return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxWithUID")
214 .setParameter("idParam", mailbox.getMailboxId())
215 .setParameter("uidParam", uid).executeUpdate();
216 }
217
218 private int deleteDeletedMessagesInMailboxBetweenUIDs(Mailbox<Long> mailbox, long from, long to) {
219 return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxBetweenUIDs")
220 .setParameter("idParam", mailbox.getMailboxId())
221 .setParameter("fromParam", from)
222 .setParameter("toParam", to).executeUpdate();
223 }
224
225
226 @SuppressWarnings("unchecked")
227 private List<Message<Long>> findDeletedMessagesInMailbox(Mailbox<Long> mailbox) {
228 return getEntityManager().createNamedQuery("findDeletedMessagesInMailbox").setParameter("idParam", mailbox.getMailboxId()).getResultList();
229 }
230
231 @SuppressWarnings("unchecked")
232 private List<Message<Long>> findDeletedMessagesInMailboxAfterUID(Mailbox<Long> mailbox, long uid) {
233 return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxAfterUID")
234 .setParameter("idParam", mailbox.getMailboxId())
235 .setParameter("uidParam", uid).getResultList();
236 }
237
238 @SuppressWarnings("unchecked")
239 private List<Message<Long>> findDeletedMessagesInMailboxWithUID(Mailbox<Long> mailbox, long uid) {
240 return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxWithUID")
241 .setParameter("idParam", mailbox.getMailboxId())
242 .setParameter("uidParam", uid).setMaxResults(1).getResultList();
243 }
244
245 @SuppressWarnings("unchecked")
246 private List<Message<Long>> findDeletedMessagesInMailboxBetweenUIDs(Mailbox<Long> mailbox, long from, long to) {
247 return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxBetweenUIDs")
248 .setParameter("idParam", mailbox.getMailboxId())
249 .setParameter("fromParam", from)
250 .setParameter("toParam", to).getResultList();
251 }
252
253 /**
254 * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(Mailbox)
255 */
256 public long countMessagesInMailbox(Mailbox<Long> mailbox) throws MailboxException {
257 try {
258 return (Long) getEntityManager().createNamedQuery("countMessagesInMailbox").setParameter("idParam", mailbox.getMailboxId()).getSingleResult();
259 } catch (PersistenceException e) {
260 throw new MailboxException("Count of messages failed in mailbox " + mailbox, e);
261 }
262 }
263
264 /**
265 * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(Mailbox)
266 */
267 public long countUnseenMessagesInMailbox(Mailbox<Long> mailbox) throws MailboxException {
268 try {
269 return (Long) getEntityManager().createNamedQuery("countUnseenMessagesInMailbox").setParameter("idParam", mailbox.getMailboxId()).getSingleResult();
270 } catch (PersistenceException e) {
271 throw new MailboxException("Count of useen messages failed in mailbox " + mailbox, e);
272 }
273 }
274
275 /**
276 * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message)
277 */
278 public void delete(Mailbox<Long> mailbox, Message<Long> message) throws MailboxException {
279 try {
280 getEntityManager().remove(message);
281 } catch (PersistenceException e) {
282 throw new MailboxException("Delete of message " + message + " failed in mailbox " + mailbox, e);
283 }
284 }
285
286 /**
287 * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(Mailbox)
288 */
289 @SuppressWarnings("unchecked")
290 public Long findFirstUnseenMessageUid(Mailbox<Long> mailbox) throws MailboxException {
291 try {
292 Query query = getEntityManager().createNamedQuery("findUnseenMessagesInMailboxOrderByUid").setParameter("idParam", mailbox.getMailboxId());
293 query.setMaxResults(1);
294 List<Message<Long>> result = query.getResultList();
295 if (result.isEmpty()) {
296 return null;
297 } else {
298 return result.get(0).getUid();
299 }
300 } catch (PersistenceException e) {
301 throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e);
302 }
303 }
304
305 /**
306 * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(Mailbox)
307 */
308 @SuppressWarnings("unchecked")
309 public List<Long> findRecentMessageUidsInMailbox(Mailbox<Long> mailbox) throws MailboxException {
310 try {
311 Query query = getEntityManager().createNamedQuery("findRecentMessageUidsInMailbox").setParameter("idParam", mailbox.getMailboxId());
312 return query.getResultList();
313 } catch (PersistenceException e) {
314 throw new MailboxException("Search of recent messages failed in mailbox " + mailbox, e);
315 }
316 }
317
318
319
320 /**
321 * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(Mailbox, long, long, Message)
322 */
323 protected MessageMetaData copy(Mailbox<Long> mailbox, long uid, long modSeq, Message<Long> original) throws MailboxException {
324 Message<Long> copy;
325 if (original instanceof JPAStreamingMessage) {
326 copy = new JPAStreamingMessage((JPAMailbox) mailbox, uid, modSeq, original);
327 } else if(original instanceof JPAEncryptedMessage) {
328 copy = new JPAEncryptedMessage((JPAMailbox) mailbox, uid, modSeq, original);
329 } else {
330 copy = new JPAMessage((JPAMailbox) mailbox, uid, modSeq, original);
331 }
332 return save(mailbox, copy);
333 }
334
335
336 /**
337 * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(Mailbox, Message)
338 */
339 protected MessageMetaData save(Mailbox<Long> mailbox, Message<Long> message) throws MailboxException {
340
341 try {
342
343 // We need to reload a "JPA attached" mailbox, because the provide mailbox is already "JPA detached"
344 // If we don't this, we will get an org.apache.openjpa.persistence.ArgumentException.
345 ((AbstractJPAMessage) message).setMailbox(getEntityManager().find(JPAMailbox.class, mailbox.getMailboxId()));
346
347 getEntityManager().persist(message);
348 return new SimpleMessageMetaData(message);
349 } catch (PersistenceException e) {
350 throw new MailboxException("Save of message " + message + " failed in mailbox " + mailbox, e);
351 } catch (ArgumentException e) {
352 throw new MailboxException("Save of message " + message + " failed in mailbox " + mailbox, e);
353 }
354 }
355
356
357 @Override
358 public Map<Long, MessageMetaData> expungeMarkedForDeletionInMailbox(Mailbox<Long> mailbox, MessageRange set) throws MailboxException {
359 try {
360 final Map<Long, MessageMetaData> data;
361 final List<Message<Long>> results;
362 final long from = set.getUidFrom();
363 final long to = set.getUidTo();
364
365 switch (set.getType()) {
366 case ONE:
367 results = findDeletedMessagesInMailboxWithUID(mailbox, from);
368 data = createMetaData(results);
369 deleteDeletedMessagesInMailboxWithUID(mailbox, from);
370 break;
371 case RANGE:
372 results = findDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to);
373 data = createMetaData(results);
374 deleteDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to);
375 break;
376 case FROM:
377 results = findDeletedMessagesInMailboxAfterUID(mailbox, from);
378 data = createMetaData(results);
379 deleteDeletedMessagesInMailboxAfterUID(mailbox, from);
380 break;
381 default:
382 case ALL:
383 results = findDeletedMessagesInMailbox(mailbox);
384 data = createMetaData(results);
385 deleteDeletedMessagesInMailbox(mailbox);
386 break;
387 }
388
389 return data;
390 } catch (PersistenceException e) {
391 throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e);
392 }
393 }
394 }