All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.james.mailbox.jpa.mail.JPAMessageMapper Maven / Gradle / Ivy

There is a newer version: 3.8.1
Show newest version
/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you under the Apache License, Version 2.0 (the            *
 * "License"); you may not use this file except in compliance   *
 * with the License.  You may obtain a copy of the License at   *
 *                                                              *
 *   http://www.apache.org/licenses/LICENSE-2.0                 *
 *                                                              *
 * Unless required by applicable law or agreed to in writing,   *
 * software distributed under the License is distributed on an  *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
 * KIND, either express or implied.  See the License for the    *
 * specific language governing permissions and limitations      *
 * under the License.                                           *
 ****************************************************************/
package org.apache.james.mailbox.jpa.mail;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.mail.Flags;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.apache.james.mailbox.MessageUid;
import org.apache.james.mailbox.ModSeq;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.jpa.JPAId;
import org.apache.james.mailbox.jpa.JPATransactionalMapper;
import org.apache.james.mailbox.jpa.mail.MessageUtils.MessageChangedFlags;
import org.apache.james.mailbox.jpa.mail.model.JPAMailbox;
import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMailboxMessage;
import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMailboxMessage;
import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMailboxMessage;
import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMailboxMessage;
import org.apache.james.mailbox.model.Mailbox;
import org.apache.james.mailbox.model.MailboxCounters;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MessageMetaData;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.MessageRange.Type;
import org.apache.james.mailbox.model.UpdatedFlags;
import org.apache.james.mailbox.store.FlagsUpdateCalculator;
import org.apache.james.mailbox.store.mail.MessageMapper;
import org.apache.james.mailbox.store.mail.ModSeqProvider;
import org.apache.james.mailbox.store.mail.UidProvider;
import org.apache.james.mailbox.store.mail.model.MailboxMessage;
import org.apache.james.mailbox.store.mail.utils.ApplicableFlagCalculator;
import org.apache.openjpa.persistence.ArgumentException;

import com.github.fge.lambdas.Throwing;
import com.github.steveash.guavate.Guavate;
import com.google.common.collect.ImmutableList;

import reactor.core.publisher.Flux;

/**
 * JPA implementation of a {@link MessageMapper}. This class is not thread-safe!
 */
public class JPAMessageMapper extends JPATransactionalMapper implements MessageMapper {
    private static final int UNLIMIT_MAX_SIZE = -1;
    private static final int UNLIMITED = -1;

    private final MessageUtils messageMetadataMapper;
    private final UidProvider uidProvider;
    private final ModSeqProvider modSeqProvider;

    public JPAMessageMapper(UidProvider uidProvider, ModSeqProvider modSeqProvider, EntityManagerFactory entityManagerFactory) {
        super(entityManagerFactory);
        this.messageMetadataMapper = new MessageUtils(uidProvider, modSeqProvider);
        this.uidProvider = uidProvider;
        this.modSeqProvider = modSeqProvider;
    }

    @Override
    public MailboxCounters getMailboxCounters(Mailbox mailbox) throws MailboxException {
        return MailboxCounters.builder()
            .mailboxId(mailbox.getMailboxId())
            .count(countMessagesInMailbox(mailbox))
            .unseen(countUnseenMessagesInMailbox(mailbox))
            .build();
    }

    @Override
    public Flux listAllMessageUids(Mailbox mailbox) {
        return findInMailboxReactive(mailbox, MessageRange.all(), FetchType.Metadata, UNLIMITED)
            .map(MailboxMessage::getUid);
    }

    @Override
    public Iterator findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max)
            throws MailboxException {

        return findAsList(mailbox.getMailboxId(), set, max).iterator();
    }

    private List findAsList(MailboxId mailboxId, MessageRange set, int max) throws MailboxException {
        try {
            MessageUid from = set.getUidFrom();
            MessageUid to = set.getUidTo();
            Type type = set.getType();
            JPAId jpaId = (JPAId) mailboxId;

            switch (type) {
                default:
                case ALL:
                    return findMessagesInMailbox(jpaId, max);
                case FROM:
                    return findMessagesInMailboxAfterUID(jpaId, from, max);
                case ONE:
                    return findMessagesInMailboxWithUID(jpaId, from);
                case RANGE:
                    return findMessagesInMailboxBetweenUIDs(jpaId, from, to, max);
            }
        } catch (PersistenceException e) {
            throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailboxId.serialize(), e);
        }
    }

    @Override
    public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException {
        JPAId mailboxId = (JPAId) mailbox.getMailboxId();
        return countMessagesInMailbox(mailboxId);
    }

    private long countMessagesInMailbox(JPAId mailboxId) throws MailboxException {
        try {
            return (Long) getEntityManager().createNamedQuery("countMessagesInMailbox")
                    .setParameter("idParam", mailboxId.getRawId()).getSingleResult();
        } catch (PersistenceException e) {
            throw new MailboxException("Count of messages failed in mailbox " + mailboxId, e);
        }
    }

    public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException {
        JPAId mailboxId = (JPAId) mailbox.getMailboxId();
        return countUnseenMessagesInMailbox(mailboxId);
    }

    private long countUnseenMessagesInMailbox(JPAId mailboxId) throws MailboxException {
        try {
            return (Long) getEntityManager().createNamedQuery("countUnseenMessagesInMailbox")
                    .setParameter("idParam", mailboxId.getRawId()).getSingleResult();
        } catch (PersistenceException e) {
            throw new MailboxException("Count of useen messages failed in mailbox " + mailboxId, e);
        }
    }

    @Override
    public void delete(Mailbox mailbox, MailboxMessage message) throws MailboxException {
        try {
            AbstractJPAMailboxMessage jpaMessage = getEntityManager().find(AbstractJPAMailboxMessage.class, buildKey(mailbox, message));
            getEntityManager().remove(jpaMessage);

        } catch (PersistenceException e) {
            throw new MailboxException("Delete of message " + message + " failed in mailbox " + mailbox, e);
        }
    }

    private AbstractJPAMailboxMessage.MailboxIdUidKey buildKey(Mailbox mailbox, MailboxMessage message) {
        JPAId mailboxId = (JPAId) mailbox.getMailboxId();
        AbstractJPAMailboxMessage.MailboxIdUidKey key = new AbstractJPAMailboxMessage.MailboxIdUidKey();
        key.mailbox = mailboxId.getRawId();
        key.uid = message.getUid().asLong();
        return key;
    }

    @Override
    @SuppressWarnings("unchecked")
    public MessageUid findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException {
        try {
            JPAId mailboxId = (JPAId) mailbox.getMailboxId();
            Query query = getEntityManager().createNamedQuery("findUnseenMessagesInMailboxOrderByUid").setParameter(
                    "idParam", mailboxId.getRawId());
            query.setMaxResults(1);
            List result = query.getResultList();
            if (result.isEmpty()) {
                return null;
            } else {
                return result.get(0).getUid();
            }
        } catch (PersistenceException e) {
            throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException {
        try {
            JPAId mailboxId = (JPAId) mailbox.getMailboxId();
            Query query = getEntityManager().createNamedQuery("findRecentMessageUidsInMailbox").setParameter("idParam",
                    mailboxId.getRawId());
            List resultList = query.getResultList();
            ImmutableList.Builder results = ImmutableList.builder();
            for (long result: resultList) {
                results.add(MessageUid.of(result));
            }
            return results.build();
        } catch (PersistenceException e) {
            throw new MailboxException("Search of recent messages failed in mailbox " + mailbox, e);
        }
    }

    @Override
    public List retrieveMessagesMarkedForDeletion(Mailbox mailbox, MessageRange messageRange) throws MailboxException {
        try {
            JPAId mailboxId = (JPAId) mailbox.getMailboxId();
            List messages = findDeletedMessages(messageRange, mailboxId);
            return getUidList(messages);
        } catch (PersistenceException e) {
            throw new MailboxException("Search of MessageRange " + messageRange + " failed in mailbox " + mailbox, e);
        }
    }

    private List findDeletedMessages(MessageRange messageRange, JPAId mailboxId) {
        MessageUid from = messageRange.getUidFrom();
        MessageUid to = messageRange.getUidTo();

        switch (messageRange.getType()) {
            case ONE:
                return findDeletedMessagesInMailboxWithUID(mailboxId, from);
            case RANGE:
                return findDeletedMessagesInMailboxBetweenUIDs(mailboxId, from, to);
            case FROM:
                return findDeletedMessagesInMailboxAfterUID(mailboxId, from);
            case ALL:
                return findDeletedMessagesInMailbox(mailboxId);
            default:
                throw new RuntimeException("Cannot find deleted messages, range type " + messageRange.getType() + " doesn't exist");
        }
    }

    @Override
    public Map deleteMessages(Mailbox mailbox, List uids) throws MailboxException {
        JPAId mailboxId = (JPAId) mailbox.getMailboxId();
        Map data = new HashMap<>();
        List ranges = MessageRange.toRanges(uids);

        ranges.forEach(Throwing.consumer(range -> {
            List messages = findAsList(mailboxId, range, JPAMessageMapper.UNLIMITED);
            data.putAll(createMetaData(messages));
            deleteMessages(range, mailboxId);
        }).sneakyThrow());

        return data;
    }

    private void deleteMessages(MessageRange messageRange, JPAId mailboxId) {
        MessageUid from = messageRange.getUidFrom();
        MessageUid to = messageRange.getUidTo();

        switch (messageRange.getType()) {
            case ONE:
                deleteMessagesInMailboxWithUID(mailboxId, from);
                break;
            case RANGE:
                deleteMessagesInMailboxBetweenUIDs(mailboxId, from, to);
                break;
            case FROM:
                deleteMessagesInMailboxAfterUID(mailboxId, from);
                break;
            case ALL:
                deleteMessagesInMailbox(mailboxId);
                break;
            default:
                throw new RuntimeException("Cannot delete messages, range type " + messageRange.getType() + " doesn't exist");
        }
    }

    @Override
    public MessageMetaData move(Mailbox mailbox, MailboxMessage original) throws MailboxException {
        JPAId originalMailboxId = (JPAId) original.getMailboxId();
        JPAMailbox originalMailbox = getEntityManager().find(JPAMailbox.class, originalMailboxId.getRawId());
        
        MessageMetaData messageMetaData = copy(mailbox, original);
        delete(originalMailbox.toMailbox(), original);
        
        return messageMetaData;
    }

    @Override
    public MessageMetaData add(Mailbox mailbox, MailboxMessage message) throws MailboxException {
        messageMetadataMapper.enrichMessage(mailbox, message);

        return save(mailbox, message);
    }

    @Override
    public Iterator updateFlags(Mailbox mailbox, FlagsUpdateCalculator flagsUpdateCalculator,
            MessageRange set) throws MailboxException {
        Iterator messages = findInMailbox(mailbox, set, FetchType.Metadata, UNLIMIT_MAX_SIZE);

        MessageChangedFlags messageChangedFlags = messageMetadataMapper.updateFlags(mailbox, flagsUpdateCalculator, messages);

        for (MailboxMessage mailboxMessage : messageChangedFlags.getChangedFlags()) {
            save(mailbox, mailboxMessage);
        }

        return messageChangedFlags.getUpdatedFlags();
    }

    @Override
    public MessageMetaData copy(Mailbox mailbox, MailboxMessage original) throws MailboxException {
        return copy(mailbox, uidProvider.nextUid(mailbox), modSeqProvider.nextModSeq(mailbox), original);
    }

    @Override
    public Optional getLastUid(Mailbox mailbox) throws MailboxException {
        return uidProvider.lastUid(mailbox);
    }

    @Override
    public ModSeq getHighestModSeq(Mailbox mailbox) throws MailboxException {
        return modSeqProvider.highestModSeq(mailbox);
    }

    @Override
    public Flags getApplicableFlag(Mailbox mailbox) throws MailboxException {
        int maxBatchSize = -1;
        return new ApplicableFlagCalculator(findMessagesInMailbox((JPAId) mailbox.getMailboxId(), maxBatchSize))
            .computeApplicableFlags();
    }

    private MessageMetaData copy(Mailbox mailbox, MessageUid uid, ModSeq modSeq, MailboxMessage original)
            throws MailboxException {
        MailboxMessage copy;
        JPAMailbox currentMailbox = JPAMailbox.from(mailbox);

        if (original instanceof JPAStreamingMailboxMessage) {
            copy = new JPAStreamingMailboxMessage(currentMailbox, uid, modSeq, original);
        } else if (original instanceof JPAEncryptedMailboxMessage) {
            copy = new JPAEncryptedMailboxMessage(currentMailbox, uid, modSeq, original);
        } else {
            copy = new JPAMailboxMessage(currentMailbox, uid, modSeq, original);
        }
        return save(mailbox, copy);
    }

    /**
     * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(Mailbox, MailboxMessage)
     */
    protected MessageMetaData save(Mailbox mailbox, MailboxMessage message) throws MailboxException {
        try {
            // We need to reload a "JPA attached" mailbox, because the provide
            // mailbox is already "JPA detached"
            // If we don't this, we will get an
            // org.apache.openjpa.persistence.ArgumentException.
            JPAId mailboxId = (JPAId) mailbox.getMailboxId();
            JPAMailbox currentMailbox = getEntityManager().find(JPAMailbox.class, mailboxId.getRawId());
            if (message instanceof AbstractJPAMailboxMessage) {
                ((AbstractJPAMailboxMessage) message).setMailbox(currentMailbox);

                getEntityManager().persist(message);
                return message.metaData();
            } else {
                JPAMailboxMessage persistData = new JPAMailboxMessage(currentMailbox, message.getUid(), message.getModSeq(), message);
                persistData.setFlags(message.createFlags());
                getEntityManager().persist(persistData);
                return persistData.metaData();
            }

        } catch (PersistenceException | ArgumentException e) {
            throw new MailboxException("Save of message " + message + " failed in mailbox " + mailbox, e);
        }
    }

    @SuppressWarnings("unchecked")
    private List findMessagesInMailboxAfterUID(JPAId mailboxId, MessageUid from, int batchSize) {
        Query query = getEntityManager().createNamedQuery("findMessagesInMailboxAfterUID")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("uidParam", from.asLong());

        if (batchSize > 0) {
            query.setMaxResults(batchSize);
        }

        return query.getResultList();
    }

    @SuppressWarnings("unchecked")
    private List findMessagesInMailboxWithUID(JPAId mailboxId, MessageUid from) {
        return getEntityManager().createNamedQuery("findMessagesInMailboxWithUID")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("uidParam", from.asLong()).setMaxResults(1)
                .getResultList();
    }

    @SuppressWarnings("unchecked")
    private List findMessagesInMailboxBetweenUIDs(JPAId mailboxId, MessageUid from, MessageUid to,
                                                                         int batchSize) {
        Query query = getEntityManager().createNamedQuery("findMessagesInMailboxBetweenUIDs")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("fromParam", from.asLong())
                .setParameter("toParam", to.asLong());

        if (batchSize > 0) {
            query.setMaxResults(batchSize);
        }

        return query.getResultList();
    }

    @SuppressWarnings("unchecked")
    private List findMessagesInMailbox(JPAId mailboxId, int batchSize) {
        Query query = getEntityManager().createNamedQuery("findMessagesInMailbox").setParameter("idParam",
                mailboxId.getRawId());
        if (batchSize > 0) {
            query.setMaxResults(batchSize);
        }
        return query.getResultList();
    }

    private Map createMetaData(List uids) {
        final Map data = new HashMap<>();
        for (MailboxMessage m : uids) {
            data.put(m.getUid(), m.metaData());
        }
        return data;
    }

    private List getUidList(List messages) {
        return messages.stream()
            .map(message -> message.getUid())
            .collect(Guavate.toImmutableList());
    }

    private int deleteMessagesInMailbox(JPAId mailboxId) {
        return getEntityManager().createNamedQuery("deleteMessagesInMailbox")
                .setParameter("idParam", mailboxId.getRawId()).executeUpdate();
    }

    private int deleteMessagesInMailboxAfterUID(JPAId mailboxId, MessageUid from) {
        return getEntityManager().createNamedQuery("deleteMessagesInMailboxAfterUID")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("uidParam", from.asLong()).executeUpdate();
    }

    private int deleteMessagesInMailboxWithUID(JPAId mailboxId, MessageUid from) {
        return getEntityManager().createNamedQuery("deleteMessagesInMailboxWithUID")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("uidParam", from.asLong()).executeUpdate();
    }

    private int deleteMessagesInMailboxBetweenUIDs(JPAId mailboxId, MessageUid from, MessageUid to) {
        return getEntityManager().createNamedQuery("deleteMessagesInMailboxBetweenUIDs")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("fromParam", from.asLong())
                .setParameter("toParam", to.asLong()).executeUpdate();
    }

    @SuppressWarnings("unchecked")
    private List findDeletedMessagesInMailbox(JPAId mailboxId) {
        return getEntityManager().createNamedQuery("findDeletedMessagesInMailbox")
                .setParameter("idParam", mailboxId.getRawId()).getResultList();
    }

    @SuppressWarnings("unchecked")
    private List findDeletedMessagesInMailboxAfterUID(JPAId mailboxId, MessageUid from) {
        return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxAfterUID")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("uidParam", from.asLong()).getResultList();
    }

    @SuppressWarnings("unchecked")
    private List findDeletedMessagesInMailboxWithUID(JPAId mailboxId, MessageUid from) {
        return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxWithUID")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("uidParam", from.asLong()).setMaxResults(1)
                .getResultList();
    }

    @SuppressWarnings("unchecked")
    private List findDeletedMessagesInMailboxBetweenUIDs(JPAId mailboxId, MessageUid from, MessageUid to) {
        return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxBetweenUIDs")
                .setParameter("idParam", mailboxId.getRawId()).setParameter("fromParam", from.asLong())
                .setParameter("toParam", to.asLong()).getResultList();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy