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    }