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