Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java (revision 0) @@ -0,0 +1,47 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxListenerSupport; +import org.apache.james.mailbox.exception.MailboxException; + +public class CacheInvalidatingMailboxListener implements MailboxListener { + + private MailboxByPathCache mailboxCacheByPath; + private MailboxMetadataCache mailboxMetadataCache; + + public CacheInvalidatingMailboxListener(MailboxByPathCache mailboxCacheByPath, MailboxMetadataCache mailboxMetadataCache) { + this.mailboxCacheByPath = mailboxCacheByPath; + this.mailboxMetadataCache = mailboxMetadataCache; + } + + public void register(MailboxListenerSupport listener) throws MailboxException { + listener.addGlobalListener(this, null); + } + + @Override + public void event(Event event) { + // TODO this needs for sure to be smarter + try { + if (event instanceof MessageEvent) { + // invalidate the metadata caches + invalidateMetadata(event); + } + invalidateMailbox(event); + } catch (MailboxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + private void invalidateMetadata(Event event) throws MailboxException { + //HMM, race conditions welcome? + mailboxMetadataCache.invalidate(mailboxCacheByPath.findMailboxByPath(event.getMailboxPath(), null)); + + } + + private void invalidateMailbox(Event event) { + mailboxCacheByPath.invalidate(event.getMailboxPath()); + } + +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java (revision 0) @@ -0,0 +1,5 @@ +package org.apache.mailbox.caching; + +public interface CacheLoaderFromUnderlying { + Value load(Key key, Underlying underlying) throws Except; +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java (revision 0) @@ -0,0 +1,79 @@ +package org.apache.mailbox.caching; +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + + +public class CachingMailboxMapper implements MailboxMapper { + + private MailboxMapper underlying; + private MailboxByPathCache cache; + + public CachingMailboxMapper(MailboxMapper underlying, MailboxByPathCache cache) { + this.underlying = underlying; + this.cache = cache; + } + + @Override + public void endRequest() { + underlying.endRequest(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + return underlying.execute(transaction); + } + + @Override + public void save(Mailbox mailbox) throws MailboxException { + invalidate(mailbox); + underlying.save(mailbox); + } + + @Override + public void delete(Mailbox mailbox) throws MailboxException { + cache.invalidate(mailbox); + underlying.delete(mailbox); + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxName) + throws MailboxException, MailboxNotFoundException { + try { + return cache.findMailboxByPath(mailboxName, underlying); + } catch (MailboxNotFoundException e) { + cache.invalidate(mailboxName); + throw e; + } + } + + @Override + public List> findMailboxWithPathLike(MailboxPath mailboxPath) + throws MailboxException { + // TODO possible to meaningfully cache it? + return underlying.findMailboxWithPathLike(mailboxPath); + } + + @Override + public boolean hasChildren(Mailbox mailbox, char delimiter) + throws MailboxException, MailboxNotFoundException { + // TODO possible to meaningfully cache it? + return hasChildren(mailbox, delimiter); + } + + @Override + public List> list() throws MailboxException { + // TODO possible to meaningfully cache it? is it used at all? + return underlying.list(); + } + + private void invalidate(Mailbox mailbox) { + cache.invalidate(mailbox); + } + + +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java (revision 0) @@ -0,0 +1,44 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +public class CachingMailboxSessionMapperFactory extends + MailboxSessionMapperFactory { + + private MailboxSessionMapperFactory underlying; + private MailboxByPathCache mailboxByPathCache; + private MailboxMetadataCache mailboxMetadataCache; + + public CachingMailboxSessionMapperFactory(MailboxSessionMapperFactory underlying, MailboxByPathCache mailboxByPathCache, MailboxMetadataCache mailboxMetadataCache) { + this.underlying = underlying; + this.mailboxByPathCache = mailboxByPathCache; + this.mailboxMetadataCache = mailboxMetadataCache; + } + + @Override + protected MessageMapper createMessageMapper(MailboxSession session) + throws MailboxException { + //hmm, syntax error - underlying.createMessageMapper is not visible :( what to do? + return new CachingMessageMapper(underlying.createMessageMapper(session), mailboxByPathCache); + } + + @Override + protected MailboxMapper createMailboxMapper(MailboxSession session) + throws MailboxException { + //hmm, syntax error - underlying.createMessageMapper is not visible :( what to do? + return new CachingMessageMapper(underlying.createMailboxMapper(session), mailboxMetadataCache); + } + + @Override + protected SubscriptionMapper createSubscriptionMapper(MailboxSession session) + throws SubscriptionException { + return underlying.createSubscriptionMapper(session); + } + +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java (revision 0) @@ -0,0 +1,126 @@ +package org.apache.mailbox.caching; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + + +public class CachingMessageMapper implements MessageMapper { + + + private MessageMapper underlying; + private MailboxMetadataCache cache; + + public CachingMessageMapper(MessageMapper underlying, MailboxMetadataCache cache) { + this.underlying = underlying; + this.cache = cache; + } + + @Override + public void endRequest() { + underlying.endRequest(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + return underlying.execute(transaction); + } + + @Override + public Iterator> findInMailbox(Mailbox mailbox, + MessageRange set, + org.apache.james.mailbox.store.mail.MessageMapper.FetchType type, + int limit) throws MailboxException { + return underlying.findInMailbox(mailbox, set, type, limit); + } + + @Override + public Map expungeMarkedForDeletionInMailbox( + Mailbox mailbox, MessageRange set) throws MailboxException { + invalidateMetadata(mailbox); + return underlying.expungeMarkedForDeletionInMailbox(mailbox, set); + } + + @Override + public long countMessagesInMailbox(Mailbox mailbox) + throws MailboxException { + return cache.countMessagesInMailbox(mailbox, underlying); + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) + throws MailboxException { + return cache.countUnseenMessagesInMailbox(mailbox, underlying); + } + + @Override + public void delete(Mailbox mailbox, Message message) + throws MailboxException { + invalidateMetadata(mailbox); + underlying.delete(mailbox, message); + + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) + throws MailboxException { + return cache.findFirstUnseenMessageUid(mailbox, underlying); + } + + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) + throws MailboxException { + // TODO can be meaningfully cached? + return underlying.findRecentMessageUidsInMailbox(mailbox); + } + + @Override + public MessageMetaData add(Mailbox mailbox, Message message) + throws MailboxException { + invalidateMetadata(mailbox); + return underlying.add(mailbox, message); + } + + @Override + public Iterator updateFlags(Mailbox mailbox, Flags flags, + boolean value, boolean replace, MessageRange set) + throws MailboxException { + //check if there are in fact any updates + if (set.iterator().hasNext()) + invalidateMetadata(mailbox); + return underlying.updateFlags(mailbox, flags, value, replace, set); + } + + + @Override + public MessageMetaData copy(Mailbox mailbox, Message original) + throws MailboxException { + invalidateMetadata(mailbox); + return underlying.copy(mailbox, original); + } + + @Override + public long getLastUid(Mailbox mailbox) throws MailboxException { + return cache.getLastUid(mailbox, underlying); + } + + @Override + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + return cache.getHighestModSeq(mailbox, underlying); + } + + private void invalidateMetadata(Mailbox mailbox) { + cache.invalidate(mailbox); + + } + +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java (revision 0) @@ -0,0 +1,21 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public interface MailboxByPathCache { + + public abstract Mailbox findMailboxByPath(MailboxPath mailboxName, + MailboxMapper underlying) throws MailboxNotFoundException, + MailboxException; + + public abstract void invalidate(Mailbox mailbox); + + public abstract void invalidate(MailboxPath mailboxPath); + + // for the purpose of cascading the invalidations; does it make sense? + //public void connectTo(MailboxMetadataCache mailboxMetadataCache); +} \ No newline at end of file Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java (revision 0) @@ -0,0 +1,28 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public interface MailboxMetadataCache { + + public abstract long countMessagesInMailbox(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long countUnseenMessagesInMailbox(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract Long findFirstUnseenMessageUid(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long getLastUid(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long getHighestModSeq(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract void invalidate(Mailbox mailbox); + +// public abstract void invalidate(MailboxPath mailboxPath); + +} \ No newline at end of file Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java (revision 0) @@ -0,0 +1,15 @@ +package org.apache.mailbox.caching.guava; + +import java.util.concurrent.TimeUnit; + +import com.google.common.cache.CacheBuilder; + +public class AbstractGuavaCache { + + // TODO this can probably be instantiated more elegant way + protected static final CacheBuilder BUILDER = + CacheBuilder.newBuilder() + .maximumSize(100000) + .expireAfterWrite(15, TimeUnit.MINUTES); + +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java (revision 0) @@ -0,0 +1,37 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.mailbox.caching.CacheLoaderFromUnderlying; + +import com.google.common.cache.Cache; + +public abstract class GuavaCacheWrapper + implements CacheLoaderFromUnderlying { + + private final Cache cache; +// private final CacheLoaderFromUnderlying loader; + + public GuavaCacheWrapper(Cache cache/*, CacheLoaderFromUnderlying loader*/) { + this.cache = cache; +// this.loader = loader; + } + + public Value get(Key key, Underlying underlying) throws Except { + Value value = cache.getIfPresent(getKeyRepresentation(key)); + if (value != null) + return value; + else { + value = load(key, underlying); + cache.put(getKeyRepresentation(key), value); + return value; + } + + } + + public void invalidate(Key key) { + if (key != null) //needed? + cache.invalidate(getKeyRepresentation(key)); + } + + public abstract KeyRepresentation getKeyRepresentation(Key key); + +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java (revision 0) @@ -0,0 +1,82 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.mailbox.caching.MailboxByPathCache; + +import com.google.common.cache.Cache; + + +public class GuavaMailboxByPathCache extends AbstractGuavaCache implements MailboxByPathCache { + + private final Cache> findMailboxByPathCache = BUILDER.build(); + + private final MailboxByPathCacheWrapper wrapper; + + + public GuavaMailboxByPathCache() { + this.wrapper = new MailboxByPathCacheWrapper(findMailboxByPathCache); + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxNotFoundException, MailboxException { + + return wrapper.get(mailboxName, underlying); + } + +// alternative plain implementation - review and choose the better +// public Mailbox findMailboxByPath(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxNotFoundException, MailboxException { +// Mailbox mailbox = findMailboxByPathCache.getIfPresent(mailboxName.toString()); +// if (mailbox != null) +// return mailbox; +// else { +// mailbox = new MailboxByPathCacheLoaderFromUnderlying().load(mailboxName, underlying); +// findMailboxByPathCache.put(mailboxName.toString(), mailbox); +// return mailbox; +// } +// } + + + + @Override + public void invalidate(Mailbox mailbox) { + invalidate(new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); + } + + @Override + public void invalidate(MailboxPath mailboxPath) { + wrapper.invalidate(mailboxPath); + } + + + //Does it make sense to define such loaders as separate classes for reuse? +// class MailboxByPathCacheLoaderFromUnderlying implements CacheLoaderFromUnderlying, MailboxMapper, MailboxException> { +// @Override +// public Mailbox load(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxException { +// return underlying.findMailboxByPath(mailboxName); +// } +// } + + class MailboxByPathCacheWrapper extends GuavaCacheWrapper, MailboxMapper, String, MailboxException> { + + public MailboxByPathCacheWrapper( + Cache> cache/*, + MailboxByPathCacheLoaderFromUnderlying loader*/) { + super(cache); + } + + @Override + public Mailbox load(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxException { + return underlying.findMailboxByPath(mailboxName); + } + + @Override + public String getKeyRepresentation(MailboxPath key) { + return key.toString(); + } + + } +} Index: mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java =================================================================== --- mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java (revision 0) +++ mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java (revision 0) @@ -0,0 +1,135 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.mailbox.caching.MailboxMetadataCache; + +import com.google.common.cache.Cache; + +public class GuavaMailboxMetadataCache extends AbstractGuavaCache implements MailboxMetadataCache { + + // TODO these can probably be instantiated more elegant way + private final Cache cacheCountMessagesInMailbox = BUILDER.build(); + private final Cache cacheCountUnseenMessagesInMailbox = BUILDER.build(); + private final Cache cacheFindFirstUnseenMessageUid = BUILDER.build(); + private final Cache cacheGetLastUid = BUILDER.build(); + private final Cache cacheGetHighestModSeq = BUILDER.build(); + + private final MetadataCacheWrapper countMessagesInMailboxWrapper = new CountMessagesInMailboxWrapper(cacheCountMessagesInMailbox); + private final MetadataCacheWrapper countUnseenMessagesInMailboxWrapper = new CountUnseenMessagesInMailboxWrapper(cacheCountUnseenMessagesInMailbox); + private final MetadataCacheWrapper findFirstUnseenMessageUid = new FindFirstUnseenMessageUidWrapper(cacheFindFirstUnseenMessageUid); + private final MetadataCacheWrapper highestModSeqWrapper = new HighestModseqCacheWrapper(cacheGetHighestModSeq); + private final MetadataCacheWrapper lastUidWrapper = new LastUidCacheWrapper(cacheGetLastUid); + + @Override + public long countMessagesInMailbox(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return countMessagesInMailboxWrapper.get(mailbox, underlying); + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return countUnseenMessagesInMailboxWrapper.get(mailbox, underlying); + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return findFirstUnseenMessageUid.get(mailbox, underlying); + } + + @Override + public long getLastUid(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return lastUidWrapper.get(mailbox, underlying); + + } + + @Override + public long getHighestModSeq(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return highestModSeqWrapper.get(mailbox, underlying); + } + + @Override + public void invalidate(Mailbox mailbox) { + cacheCountMessagesInMailbox.invalidate(mailbox); + cacheCountUnseenMessagesInMailbox.invalidate(mailbox); + cacheFindFirstUnseenMessageUid.invalidate(mailbox); + lastUidWrapper.invalidate(mailbox); + highestModSeqWrapper.invalidate(mailbox); + } + + + abstract class MetadataCacheWrapper extends GuavaCacheWrapper, Long, MessageMapper, Id, MailboxException> { + + public MetadataCacheWrapper(Cache cache) { + super(cache); + } + + @Override + public Id getKeyRepresentation(Mailbox key) { + return key.getMailboxId(); + } + + } + + class CountMessagesInMailboxWrapper extends MetadataCacheWrapper { + + public CountMessagesInMailboxWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.countMessagesInMailbox(mailbox); + } + + } + + class CountUnseenMessagesInMailboxWrapper extends MetadataCacheWrapper { + + public CountUnseenMessagesInMailboxWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.countUnseenMessagesInMailbox(mailbox); + } + + } + + class FindFirstUnseenMessageUidWrapper extends MetadataCacheWrapper { + + public FindFirstUnseenMessageUidWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.findFirstUnseenMessageUid(mailbox); + } + + } + + class LastUidCacheWrapper extends MetadataCacheWrapper { + public LastUidCacheWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return underlying.getLastUid(mailbox); + } + } + + class HighestModseqCacheWrapper extends MetadataCacheWrapper { + public HighestModseqCacheWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return underlying.getHighestModSeq(mailbox); + } + } + +}