From fd1e7009022bccd553337095f53e2c4e7964d786 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:20:41 +0200 Subject: [PATCH 01/21] MAILBOX-211 Indent CacheInvalidatingMailboxListener with 4 spaces --- .../CacheInvalidatingMailboxListener.java | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java b/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java index f0c96e994d2..f87f0a423ec 100644 --- a/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java +++ b/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java @@ -6,54 +6,54 @@ import org.apache.james.mailbox.store.mail.model.MailboxId; /** * A MailboxListener that invalidates the configured caches in response to Events - * + * * @param */ public class CacheInvalidatingMailboxListener implements MailboxListener { - private MailboxByPathCache mailboxCacheByPath; - private MailboxMetadataCache mailboxMetadataCache; - - public CacheInvalidatingMailboxListener(MailboxByPathCache mailboxCacheByPath, MailboxMetadataCache mailboxMetadataCache) { - this.mailboxCacheByPath = mailboxCacheByPath; - this.mailboxMetadataCache = mailboxMetadataCache; - } - - /** - * Used to register the CacheInvalidatingMailboxListener as a global listener - * into the main MailboxListener - * - * @param listener - * @throws MailboxException - */ - 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()); - } + private MailboxByPathCache mailboxCacheByPath; + private MailboxMetadataCache mailboxMetadataCache; + + public CacheInvalidatingMailboxListener(MailboxByPathCache mailboxCacheByPath, MailboxMetadataCache mailboxMetadataCache) { + this.mailboxCacheByPath = mailboxCacheByPath; + this.mailboxMetadataCache = mailboxMetadataCache; + } + + /** + * Used to register the CacheInvalidatingMailboxListener as a global listener + * into the main MailboxListener + * + * @param listener + * @throws MailboxException + */ + 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()); + } } From ae96d5a7c8cf8727b7f55838d8777a4163f56597 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:23:20 +0200 Subject: [PATCH 02/21] MAILBOX-211 MailboxListener should indicate its type --- .../apache/james/mailbox/MailboxListener.java | 8 +++++++ .../james/mailbox/AbstractStressTest.java | 4 ++++ .../james/mailbox/util/EventCollector.java | 21 ++++++++++++++++++- .../CacheInvalidatingMailboxListener.java | 5 +++++ .../quota/CassandraCurrentQuotaManager.java | 6 ++++++ ...sticSearchListeningMessageSearchIndex.java | 5 +++++ .../search/LuceneMessageSearchIndex.java | 7 ++++++- .../quota/InMemoryCurrentQuotaManager.java | 14 ++++++++----- .../HashMapDelegatingMailboxListener.java | 5 +++++ .../quota/ListeningCurrentQuotaUpdater.java | 5 +++++ .../store/quota/StoreCurrentQuotaManager.java | 3 +++ .../store/search/LazyMessageSearchIndex.java | 10 ++++++--- .../james/imap/processor/IdleProcessor.java | 5 +++++ .../processor/base/SelectedMailboxImpl.java | 6 +++++- 14 files changed, 93 insertions(+), 11 deletions(-) diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java index 5ee4a797694..fe8f50cb36f 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java @@ -34,6 +34,14 @@ */ public interface MailboxListener { + enum ListenerType { + ONCE, + EACH_NODE, + MAILBOX + } + + ListenerType getType(); + /** * Informs this listener about the given event. * diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java index fa717086a0a..5eac9ef6ecf 100644 --- a/mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java +++ b/mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java @@ -59,6 +59,10 @@ public void testStessTest() throws InterruptedException, MailboxException { getMailboxManager().createMailbox(path, session); getMailboxManager().addListener(path, new MailboxListener() { + @Override + public ListenerType getType() { + return ListenerType.MAILBOX; + } @Override public void event(Event event) { diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java b/mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java index 8624ccaa19d..1d2253707b5 100644 --- a/mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java +++ b/mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java @@ -26,7 +26,26 @@ public class EventCollector implements MailboxListener { - public final List events = new ArrayList(); + private final List events = new ArrayList(); + + private ListenerType listenerType; + + public EventCollector(ListenerType listenerType) { + this.listenerType = listenerType; + } + + public EventCollector() { + this(ListenerType.EACH_NODE); + } + + @Override + public ListenerType getType() { + return listenerType; + } + + public List getEvents() { + return events; + } public void event(Event event) { events.add(event); diff --git a/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java b/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java index f87f0a423ec..e8ab9d4b5c4 100644 --- a/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java +++ b/mailbox/caching/src/main/java/org/apache/james/mailbox/caching/CacheInvalidatingMailboxListener.java @@ -30,6 +30,11 @@ public void register(MailboxListenerSupport listener) throws MailboxException { listener.addGlobalListener(this, null); } + @Override + public ListenerType getType() { + return ListenerType.EACH_NODE; + } + @Override public void event(Event event) { // TODO this needs for sure to be smarter diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java index f8e7f72563f..1376236e577 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java @@ -30,6 +30,7 @@ import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Session; import com.google.common.base.Preconditions; +import org.apache.james.mailbox.MailboxListener; import org.apache.james.mailbox.cassandra.table.CassandraCurrentQuota; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.QuotaRoot; @@ -66,6 +67,11 @@ public CassandraCurrentQuotaManager(Session session) { .where(eq(CassandraCurrentQuota.QUOTA_ROOT, bindMarker()))); } + @Override + public MailboxListener.ListenerType getAssociatedListenerType() { + return MailboxListener.ListenerType.ONCE; + } + @Override public void increase(QuotaRoot quotaRoot, long count, long size) throws MailboxException { checkArguments(count, size); diff --git a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java index ece821d8718..f0942189ab2 100644 --- a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java +++ b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java @@ -58,6 +58,11 @@ public ElasticSearchListeningMessageSearchIndex(MessageMapperFactory factory this.searcher = searcher; } + @Override + public ListenerType getType() { + return ListenerType.ONCE; + } + @Override public Iterator search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) throws MailboxException { return searcher.search(mailbox, searchQuery); diff --git a/mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java b/mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java index 1eb6efe3421..e765ac93b95 100644 --- a/mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java +++ b/mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java @@ -349,7 +349,12 @@ public LuceneMessageSearchIndex(MessageMapperFactory factory, IndexWriter wr super(factory); this.writer = writer; } - + + @Override + public ListenerType getType() { + return ListenerType.EACH_NODE; + } + /** * Set the max count of results which will get returned from a query. The default is {@link #DEFAULT_MAX_QUERY_RESULTS} * diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java index 0e657832e7f..65a752274f8 100644 --- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java +++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java @@ -25,6 +25,11 @@ import javax.inject.Inject; import javax.inject.Singleton; +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.apache.james.mailbox.MailboxListener; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.QuotaRoot; @@ -33,11 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Preconditions; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - @Singleton public class InMemoryCurrentQuotaManager implements StoreCurrentQuotaManager { @@ -55,6 +55,10 @@ public Entry load(QuotaRoot quotaRoot) throws Exception { }); } + @Override + public MailboxListener.ListenerType getAssociatedListenerType() { + return MailboxListener.ListenerType.EACH_NODE; + } @Override public void increase(QuotaRoot quotaRoot, long count, long size) throws MailboxException { diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java index ba08d768668..25309424e47 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java @@ -37,6 +37,11 @@ public class HashMapDelegatingMailboxListener extends AbstractDelegatingMailboxL private Map> listeners = new HashMap>(); private List globalListeners = new ArrayList(); + @Override + public ListenerType getType() { + return ListenerType.EACH_NODE; + } + @Override protected Map> getListeners() { return listeners; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdater.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdater.java index 3a084a72254..8696319ff86 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdater.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdater.java @@ -44,6 +44,11 @@ public void setCurrentQuotaManager(StoreCurrentQuotaManager currentQuotaManager) this.currentQuotaManager = currentQuotaManager; } + @Override + public ListenerType getType() { + return currentQuotaManager.getAssociatedListenerType(); + } + @Override public void event(Event event) { try { diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/StoreCurrentQuotaManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/StoreCurrentQuotaManager.java index d977dd2bdb7..de7b3063913 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/StoreCurrentQuotaManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/StoreCurrentQuotaManager.java @@ -19,12 +19,15 @@ package org.apache.james.mailbox.store.quota; +import org.apache.james.mailbox.MailboxListener; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.QuotaRoot; import org.apache.james.mailbox.quota.CurrentQuotaManager; public interface StoreCurrentQuotaManager extends CurrentQuotaManager { + MailboxListener.ListenerType getAssociatedListenerType(); + void increase(QuotaRoot quotaRoot, long count, long size) throws MailboxException; void decrease(QuotaRoot quotaRoot, long count, long size) throws MailboxException; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java index 3f47f76cd7d..af054dfea33 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java @@ -23,6 +23,7 @@ import javax.mail.Flags; +import org.apache.james.mailbox.MailboxListener; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.MessageRange; @@ -52,9 +53,12 @@ public LazyMessageSearchIndex(ListeningMessageSearchIndex index) { super(index.getFactory()); this.index = index; } - - - + + @Override + public ListenerType getType() { + return index.getType(); + } + @Override public void add(MailboxSession session, Mailbox mailbox, Message message) throws MailboxException { index.add(session, mailbox, message); diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/IdleProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/IdleProcessor.java index a974c148590..d7dea30d2aa 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/IdleProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/IdleProcessor.java @@ -189,5 +189,10 @@ public void event(Event event) { unsolicitedResponses(session, responder, false); } } + + @Override + public ListenerType getType() { + return ListenerType.MAILBOX; + } } } diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java index a28dbe6561a..0c13400f0f0 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java @@ -101,7 +101,11 @@ public SelectedMailboxImpl(final MailboxManager mailboxManager, final ImapSessio this.path = path; init(); } - + + @Override + public ListenerType getType() { + return ListenerType.MAILBOX; + } private void init() throws MailboxException { MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session); From 38cc1e725a0d1184e236e5063a3d53eba5ddf59e Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:31:26 +0200 Subject: [PATCH 03/21] MAILBOX-211 Moving event related interfaces and code to a new package --- .../cassandra/CassandraMessageManager.java | 2 +- .../mailbox/hbase/HBaseMessageManager.java | 2 +- .../james/mailbox/jcr/JCRMessageManager.java | 2 +- .../james/mailbox/jpa/JPAMessageManager.java | 2 +- .../jpa/openjpa/OpenJPAMessageManager.java | 2 +- .../mailbox/store/StoreMailboxManager.java | 4 +- .../mailbox/store/StoreMessageManager.java | 1 + .../AbstractDelegatingMailboxListener.java | 2 +- .../HashMapDelegatingMailboxListener.java | 2 +- .../{ => event}/MailboxEventDispatcher.java | 3 +- .../search/ListeningMessageSearchIndex.java | 8 +- ...t.java => MailboxEventDispatcherTest.java} | 88 +++++++++---------- 12 files changed, 61 insertions(+), 57 deletions(-) rename mailbox/store/src/main/java/org/apache/james/mailbox/store/{ => event}/AbstractDelegatingMailboxListener.java (99%) rename mailbox/store/src/main/java/org/apache/james/mailbox/store/{ => event}/HashMapDelegatingMailboxListener.java (97%) rename mailbox/store/src/main/java/org/apache/james/mailbox/store/{ => event}/MailboxEventDispatcher.java (98%) rename mailbox/store/src/test/java/org/apache/james/mailbox/store/{MailboxEventDispatcherFlagsTest.java => MailboxEventDispatcherTest.java} (82%) diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java index 6758ed4bf44..51b001a73cc 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java @@ -28,7 +28,7 @@ import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; -import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.MailboxSessionMapperFactory; import org.apache.james.mailbox.store.StoreMessageManager; import org.apache.james.mailbox.store.mail.model.Mailbox; diff --git a/mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java b/mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java index e5d450dfa3e..026a36152ee 100644 --- a/mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java +++ b/mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java @@ -27,7 +27,7 @@ import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; -import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.MailboxSessionMapperFactory; import org.apache.james.mailbox.store.StoreMessageManager; import org.apache.james.mailbox.store.mail.model.Mailbox; diff --git a/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java b/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java index 21cd343ee13..e4240de5e4f 100644 --- a/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java +++ b/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java @@ -32,7 +32,7 @@ import org.apache.james.mailbox.jcr.mail.model.JCRMessage; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; -import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.MailboxSessionMapperFactory; import org.apache.james.mailbox.store.StoreMessageManager; import org.apache.james.mailbox.store.mail.model.Message; diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java index 0d98910fb83..b018db9ecd7 100644 --- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java +++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java @@ -32,7 +32,7 @@ import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; -import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.MailboxSessionMapperFactory; import org.apache.james.mailbox.store.StoreMessageManager; import org.apache.james.mailbox.store.mail.model.Mailbox; diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java index 08a70e9a124..74ba14d43f0 100644 --- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java +++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java @@ -35,7 +35,7 @@ import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; -import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.MailboxSessionMapperFactory; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.apache.james.mailbox.store.mail.model.Message; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java index 01924f7bcb2..665f6e0856a 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java @@ -55,12 +55,14 @@ import org.apache.james.mailbox.model.SimpleMailboxACL; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; +import org.apache.james.mailbox.store.event.AbstractDelegatingMailboxListener; +import org.apache.james.mailbox.store.event.HashMapDelegatingMailboxListener; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.mail.MailboxMapper; import org.apache.james.mailbox.store.mail.model.MailboxId; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; import org.apache.james.mailbox.store.quota.DefaultQuotaRootResolver; -import org.apache.james.mailbox.store.quota.ListeningCurrentQuotaUpdater; import org.apache.james.mailbox.store.quota.NoQuotaManager; import org.apache.james.mailbox.store.quota.QuotaUpdater; import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java index 9169b384f1a..3b7d4ffbf8f 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java @@ -60,6 +60,7 @@ import org.apache.james.mailbox.model.UpdatedFlags; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.mail.MessageMapper; import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; import org.apache.james.mailbox.store.mail.MessageMapperFactory; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/AbstractDelegatingMailboxListener.java similarity index 99% rename from mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java rename to mailbox/store/src/main/java/org/apache/james/mailbox/store/event/AbstractDelegatingMailboxListener.java index e33673c3cab..30215f530a9 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/AbstractDelegatingMailboxListener.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * * under the License. * ****************************************************************/ -package org.apache.james.mailbox.store; +package org.apache.james.mailbox.store.event; import java.util.ArrayList; import java.util.List; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/HashMapDelegatingMailboxListener.java similarity index 97% rename from mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java rename to mailbox/store/src/main/java/org/apache/james/mailbox/store/event/HashMapDelegatingMailboxListener.java index 25309424e47..ec604610405 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/HashMapDelegatingMailboxListener.java @@ -17,7 +17,7 @@ * under the License. * ****************************************************************/ -package org.apache.james.mailbox.store; +package org.apache.james.mailbox.store.event; import java.util.ArrayList; import java.util.HashMap; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxEventDispatcher.java similarity index 98% rename from mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java rename to mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxEventDispatcher.java index 07b44dd4332..b4b4d2eb0bf 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxEventDispatcher.java @@ -17,7 +17,7 @@ * under the License. * ****************************************************************/ -package org.apache.james.mailbox.store; +package org.apache.james.mailbox.store.event; import java.util.ArrayList; import java.util.List; @@ -31,6 +31,7 @@ import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.model.MessageMetaData; import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.StoreMailboxPath; import org.apache.james.mailbox.store.mail.model.MailboxId; import org.apache.james.mailbox.store.mail.model.Mailbox; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java index e837441246a..4fbb2184dc8 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java @@ -28,10 +28,10 @@ import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.MessageRange; import org.apache.james.mailbox.model.UpdatedFlags; -import org.apache.james.mailbox.store.MailboxEventDispatcher.AddedImpl; -import org.apache.james.mailbox.store.MailboxEventDispatcher.ExpungedImpl; -import org.apache.james.mailbox.store.MailboxEventDispatcher.FlagsUpdatedImpl; -import org.apache.james.mailbox.store.MailboxEventDispatcher.MailboxDeletionImpl; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher.AddedImpl; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher.ExpungedImpl; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher.FlagsUpdatedImpl; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher.MailboxDeletionImpl; import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; import org.apache.james.mailbox.store.mail.MessageMapperFactory; import org.apache.james.mailbox.store.mail.model.MailboxId; diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherTest.java similarity index 82% rename from mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java rename to mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherTest.java index 527c794a2b2..3ac87859ced 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherTest.java @@ -33,7 +33,7 @@ import org.apache.james.mailbox.model.MessageResult; import org.apache.james.mailbox.model.SimpleMailboxACL; import org.apache.james.mailbox.model.UpdatedFlags; -import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.apache.james.mailbox.util.EventCollector; import org.jmock.Expectations; @@ -45,7 +45,7 @@ import org.junit.runner.RunWith; @RunWith(JMock.class) -public class MailboxEventDispatcherFlagsTest { +public class MailboxEventDispatcherTest { MailboxEventDispatcher dispatcher; @@ -132,9 +132,9 @@ public void setUp() throws Exception { public void testShouldReturnNoChangesWhenSystemFlagsUnchanged() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags( Flags.Flag.DELETED), new Flags(Flags.Flag.DELETED)))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -145,9 +145,9 @@ public void testShouldReturnNoChangesWhenSystemFlagsUnchanged() { public void testShouldShowAnsweredAdded() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags(), new Flags(Flags.Flag.ANSWERED)))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -160,9 +160,9 @@ public void testShouldShowAnsweredAdded() { public void testShouldShowAnsweredRemoved() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags( Flags.Flag.ANSWERED), new Flags()))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -175,9 +175,9 @@ public void testShouldShowAnsweredRemoved() { public void testShouldShowDeletedAdded() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags(), new Flags(Flags.Flag.DELETED)))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -190,9 +190,9 @@ public void testShouldShowDeletedAdded() { public void testShouldShowDeletedRemoved() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags( Flags.Flag.DELETED), new Flags()))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -205,9 +205,9 @@ public void testShouldShowDeletedRemoved() { public void testShouldShowDraftAdded() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags(), new Flags(Flags.Flag.DRAFT)))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -220,9 +220,9 @@ public void testShouldShowDraftAdded() { public void testShouldShowDraftRemoved() { dispatcher.flagsUpdated(session,Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags( Flags.Flag.DRAFT), new Flags()))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -235,9 +235,9 @@ public void testShouldShowDraftRemoved() { public void testShouldShowFlaggedAdded() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags(), new Flags(Flags.Flag.FLAGGED)))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -250,9 +250,9 @@ public void testShouldShowFlaggedAdded() { public void testShouldShowFlaggedRemoved() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags( Flags.Flag.FLAGGED), new Flags()))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -265,9 +265,9 @@ public void testShouldShowFlaggedRemoved() { public void testShouldShowRecentAdded() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags(), new Flags(Flags.Flag.RECENT)))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -280,9 +280,9 @@ public void testShouldShowRecentAdded() { public void testShouldShowRecentRemoved() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags( Flags.Flag.RECENT), new Flags()))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -295,9 +295,9 @@ public void testShouldShowRecentRemoved() { public void testShouldShowSeenAdded() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags(), new Flags(Flags.Flag.SEEN)))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -310,9 +310,9 @@ public void testShouldShowSeenAdded() { public void testShouldShowSeenRemoved() { dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, new Flags( Flags.Flag.SEEN), new Flags()))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); @@ -332,9 +332,9 @@ public void testShouldShowMixedChanges() { updated.add(Flags.Flag.SEEN); dispatcher.flagsUpdated(session, Arrays.asList(result.getUid()), mailbox, Arrays.asList(new UpdatedFlags(result.getUid(), -1, originals, updated))); - assertEquals(1, collector.events.size()); - assertTrue(collector.events.get(0) instanceof MailboxListener.FlagsUpdated); - MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.events + assertEquals(1, collector.getEvents().size()); + assertTrue(collector.getEvents().get(0) instanceof MailboxListener.FlagsUpdated); + MailboxListener.FlagsUpdated event = (MailboxListener.FlagsUpdated) collector.getEvents() .get(0); Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); assertNotNull(iterator); From 5bf40ab7cc3d2cf07694dae87395e26b5ae02e20 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:33:20 +0200 Subject: [PATCH 04/21] MAILBOX-211 Rewrite Default DelegatingMailboxListener - Rely on composition rather than on inheritance for capabilities extension - Generalise it so that working on on the event system will be easier - Rely on well tested Guava stuff instead of hand craffted collections --- .../mailbox/store/StoreMailboxManager.java | 16 +- .../AbstractDelegatingMailboxListener.java | 185 ------------------ .../DefaultDelegatingMailboxListener.java | 103 ++++++++++ ...er.java => DelegatingMailboxListener.java} | 32 +-- .../store/event/MailboxListenerRegistry.java | 77 ++++++++ .../DefaultDelegatingMailboxListenerTest.java | 182 +++++++++++++++++ 6 files changed, 372 insertions(+), 223 deletions(-) delete mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/AbstractDelegatingMailboxListener.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java rename mailbox/store/src/main/java/org/apache/james/mailbox/store/event/{HashMapDelegatingMailboxListener.java => DelegatingMailboxListener.java} (57%) create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxListenerRegistry.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java index 665f6e0856a..8265eb4f6d6 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java @@ -55,8 +55,8 @@ import org.apache.james.mailbox.model.SimpleMailboxACL; import org.apache.james.mailbox.quota.QuotaManager; import org.apache.james.mailbox.quota.QuotaRootResolver; -import org.apache.james.mailbox.store.event.AbstractDelegatingMailboxListener; -import org.apache.james.mailbox.store.event.HashMapDelegatingMailboxListener; +import org.apache.james.mailbox.store.event.DelegatingMailboxListener; +import org.apache.james.mailbox.store.event.DefaultDelegatingMailboxListener; import org.apache.james.mailbox.store.event.MailboxEventDispatcher; import org.apache.james.mailbox.store.mail.MailboxMapper; import org.apache.james.mailbox.store.mail.model.MailboxId; @@ -88,7 +88,7 @@ public class StoreMailboxManager implements MailboxManager public static final int DEFAULT_FETCH_BATCH_SIZE = 200; private MailboxEventDispatcher dispatcher; - private AbstractDelegatingMailboxListener delegatingListener = null; + private DelegatingMailboxListener delegatingListener = null; private final MailboxSessionMapperFactory mailboxSessionMapperFactory; private final Authenticator authenticator; @@ -193,13 +193,13 @@ public void init() throws MailboxException { } /** - * Return the {@link AbstractDelegatingMailboxListener} which is used by this {@link MailboxManager} + * Return the {@link DelegatingMailboxListener} which is used by this {@link MailboxManager} * * @return delegatingListener */ - public AbstractDelegatingMailboxListener getDelegationListener() { + public DelegatingMailboxListener getDelegationListener() { if (delegatingListener == null) { - delegatingListener = new HashMapDelegatingMailboxListener(); + delegatingListener = new DefaultDelegatingMailboxListener(); } return delegatingListener; } @@ -253,12 +253,12 @@ public GroupMembershipResolver getGroupMembershipResolver() { } /** - * Set the {@link AbstractDelegatingMailboxListener} to use with this {@link MailboxManager} instance. If none is set here a {@link HashMapDelegatingMailboxListener} instance will + * Set the {@link DelegatingMailboxListener} to use with this {@link MailboxManager} instance. If none is set here a {@link DefaultDelegatingMailboxListener} instance will * be created lazy * * @param delegatingListener */ - public void setDelegatingMailboxListener(AbstractDelegatingMailboxListener delegatingListener) { + public void setDelegatingMailboxListener(DelegatingMailboxListener delegatingListener) { this.delegatingListener = delegatingListener; dispatcher = new MailboxEventDispatcher(getDelegationListener()); } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/AbstractDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/AbstractDelegatingMailboxListener.java deleted file mode 100644 index 30215f530a9..00000000000 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/AbstractDelegatingMailboxListener.java +++ /dev/null @@ -1,185 +0,0 @@ -/**************************************************************** - * 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.store.event; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.james.mailbox.MailboxListener; -import org.apache.james.mailbox.MailboxListenerSupport; -import org.apache.james.mailbox.MailboxSession; -import org.apache.james.mailbox.exception.MailboxException; -import org.apache.james.mailbox.model.MailboxPath; - -public abstract class AbstractDelegatingMailboxListener implements MailboxListener, MailboxListenerSupport{ - - protected AbstractDelegatingMailboxListener() { - } - - /** - * Receive the event and dispatch it to the right {@link MailboxListener} depending on - * {@link org.apache.james.mailbox.MailboxListener.Event#getMailboxPath()} - */ - public void event(Event event) { - MailboxPath path = event.getMailboxPath(); - Map> listeners = getListeners(); - List mListeners = null; - synchronized (listeners) { - mListeners = listeners.get(path); - if (mListeners != null && mListeners.isEmpty() == false) { - // take snapshot of the listeners list for later - mListeners = new ArrayList(mListeners); - - if (event instanceof MailboxDeletion) { - // remove listeners if the mailbox was deleted - listeners.remove(path); - } else if (event instanceof MailboxRenamed) { - // handle rename events - MailboxRenamed renamed = (MailboxRenamed) event; - List l = listeners.remove(path); - if (l != null) { - listeners.put(renamed.getNewPath(), l); - } - } - - } - - } - //outside the synchronized block against deadlocks from propagated events wanting to lock the listeners - if (mListeners != null) { - int sz = mListeners.size(); - for (int i = 0; i < sz; i++) { - MailboxListener l = mListeners.get(i); - l.event(event); - } - } - - List globalListeners = getGlobalListeners(); - if (globalListeners != null) { - synchronized (globalListeners) { - if (globalListeners.isEmpty() == false) { - List closedListener = new ArrayList(); - //TODO do not fire them inside synchronized block too? - int sz = globalListeners.size(); - for (int i = 0; i < sz; i++) { - MailboxListener l = globalListeners.get(i); - l.event(event); - - } - - - if (closedListener.isEmpty() == false) { - globalListeners.removeAll(closedListener); - } - } - } - } - - } - - /** - * @see org.apache.james.mailbox.MailboxListenerSupport#addListener(org.apache.james.mailbox.model.MailboxPath, org.apache.james.mailbox.MailboxListener, org.apache.james.mailbox.MailboxSession) - */ - public void addListener(MailboxPath path, MailboxListener listener, MailboxSession session) throws MailboxException { - Map> listeners = getListeners(); - - if (listeners != null) { - synchronized (listeners) { - List mListeners = listeners.get(path); - if (mListeners == null) { - mListeners = new ArrayList(); - listeners.put(path, mListeners); - } - if (mListeners.contains(listener) == false) { - mListeners.add(listener); - } - } - } else { - throw new MailboxException("Cannot add MailboxListener to null list"); - } - } - - /** - * @see org.apache.james.mailbox.MailboxListenerSupport#addGlobalListener(org.apache.james.mailbox.MailboxListener, org.apache.james.mailbox.MailboxSession) - */ - public void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { - List gListeners = getGlobalListeners(); - - if (gListeners != null) { - synchronized (gListeners) { - gListeners.add(listener); - } - } else { - throw new MailboxException("Cannot add MailboxListener to null list"); - } - } - - /** - * @see org.apache.james.mailbox.MailboxListenerSupport#removeListener(org.apache.james.mailbox.model.MailboxPath, org.apache.james.mailbox.MailboxListener, org.apache.james.mailbox.MailboxSession) - */ - public void removeListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException { - Map> listeners = getListeners(); - - if (listeners != null) { - synchronized (listeners) { - List mListeners = listeners.get(mailboxPath); - if (mListeners != null) { - mListeners.remove(listener); - if (mListeners.isEmpty()) { - listeners.remove(mailboxPath); - } - } - } - } else { - throw new MailboxException("Cannot remove MailboxListener from null list"); - } - } - - /** - * @see org.apache.james.mailbox.MailboxListenerSupport#removeGlobalListener(org.apache.james.mailbox.MailboxListener, org.apache.james.mailbox.MailboxSession) - */ - public void removeGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { - List gListeners = getGlobalListeners(); - - if (gListeners != null) { - synchronized (gListeners) { - gListeners.remove(listener); - } - } else { - throw new MailboxException("Cannot remove MailboxListener from null list"); - } - } - - /** - * Return the {@link Map} which is used to store the {@link MailboxListener} - * - * @return listeners - */ - protected abstract Map> getListeners(); - - /** - * Return the {@link List} which is used tos tore the global {@link MailboxListener} - * - * @return globalListeners - */ - protected abstract List getGlobalListeners(); - - -} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java new file mode 100644 index 00000000000..16274f37234 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java @@ -0,0 +1,103 @@ +/**************************************************************** + * 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.store.event; + +import java.util.Collection; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +/** + * Receive a {@link org.apache.james.mailbox.MailboxListener.Event} and delegate it to an other + * {@link MailboxListener} depending on the registered name + * + * This is a mono instance Thread safe implementation for DelegatingMailboxListener + */ +public class DefaultDelegatingMailboxListener implements DelegatingMailboxListener { + + private MailboxListenerRegistry registry; + + @Override + public ListenerType getType() { + return ListenerType.EACH_NODE; + } + + public DefaultDelegatingMailboxListener() { + this.registry = new MailboxListenerRegistry(); + } + + @Override + public void addListener(MailboxPath path, MailboxListener listener, MailboxSession session) throws MailboxException { + if (listener.getType() != ListenerType.MAILBOX) { + throw new MailboxException(listener.getClass().getCanonicalName() + " registred on specific MAILBOX operation while its listener type was " + listener.getType()); + } + registry.addListener(path, listener); + } + + @Override + public void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + if (listener.getType() != ListenerType.EACH_NODE && listener.getType() != ListenerType.ONCE) { + throw new MailboxException(listener.getClass().getCanonicalName() + " registered on global event dispatching while its listener type was " + listener.getType()); + } + registry.addGlobalListener(listener); + } + + @Override + public void removeListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException { + registry.removeListener(mailboxPath, listener); + } + + @Override + public void removeGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + registry.removeGlobalListener(listener); + } + + @Override + public void event(Event event) { + Collection listenerSnapshot = registry.getLocalMailboxListeners(event.getMailboxPath()); + if (event instanceof MailboxDeletion) { + registry.deleteRegistryFor(event.getMailboxPath()); + } else if (event instanceof MailboxRenamed) { + MailboxRenamed renamed = (MailboxRenamed) event; + registry.handleRename(renamed.getMailboxPath(), renamed.getNewPath()); + } + deliverEventToMailboxListeners(event, listenerSnapshot); + deliverEventToGlobalListeners(event); + } + + protected void deliverEventToMailboxListeners(Event event, Collection listenerSnapshot) { + for (MailboxListener listener : listenerSnapshot) { + deliverEvent(event, listener); + } + } + + protected void deliverEventToGlobalListeners(Event event) { + for (MailboxListener mailboxListener : registry.getGlobalListeners()) { + deliverEvent(event, mailboxListener); + } + } + + private void deliverEvent(Event event, MailboxListener listener) { + listener.event(event); + } + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/HashMapDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DelegatingMailboxListener.java similarity index 57% rename from mailbox/store/src/main/java/org/apache/james/mailbox/store/event/HashMapDelegatingMailboxListener.java rename to mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DelegatingMailboxListener.java index ec604610405..15b4e8434eb 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/HashMapDelegatingMailboxListener.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DelegatingMailboxListener.java @@ -19,37 +19,9 @@ package org.apache.james.mailbox.store.event; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import org.apache.james.mailbox.MailboxListener; -import org.apache.james.mailbox.model.MailboxPath; - -/** - * Receive a {@link org.apache.james.mailbox.MailboxListener.Event} and delegate it to an other - * {@link MailboxListener} depending on the registered name - * - */ -public class HashMapDelegatingMailboxListener extends AbstractDelegatingMailboxListener{ - - private Map> listeners = new HashMap>(); - private List globalListeners = new ArrayList(); - - @Override - public ListenerType getType() { - return ListenerType.EACH_NODE; - } +import org.apache.james.mailbox.MailboxListenerSupport; - @Override - protected Map> getListeners() { - return listeners; - } +public interface DelegatingMailboxListener extends MailboxListenerSupport, MailboxListener{ - @Override - protected List getGlobalListeners() { - return globalListeners; - } - } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxListenerRegistry.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxListenerRegistry.java new file mode 100644 index 00000000000..40edd8166bb --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxListenerRegistry.java @@ -0,0 +1,77 @@ +/**************************************************************** + * 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.store.event; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class MailboxListenerRegistry { + + private Multimap listeners; + private ConcurrentLinkedQueue globalListeners; + + public MailboxListenerRegistry() { + this.globalListeners = new ConcurrentLinkedQueue(); + this.listeners = Multimaps.synchronizedMultimap(HashMultimap.create()); + } + + public void addListener(MailboxPath path, MailboxListener listener) throws MailboxException { + listeners.put(path, listener); + } + + public void addGlobalListener(MailboxListener listener) throws MailboxException { + globalListeners.add(listener); + } + + public void removeListener(MailboxPath mailboxPath, MailboxListener listener) throws MailboxException { + listeners.remove(mailboxPath, listener); + } + + public void removeGlobalListener(MailboxListener listener) throws MailboxException { + globalListeners.remove(listener); + } + + public List getLocalMailboxListeners(MailboxPath path) { + return ImmutableList.copyOf(listeners.get(path)); + } + + public List getGlobalListeners() { + return ImmutableList.copyOf(globalListeners); + } + + public void deleteRegistryFor(MailboxPath path) { + listeners.removeAll(path); + } + + public void handleRename(MailboxPath oldName, MailboxPath newName) { + listeners.putAll(newName, listeners.removeAll(oldName)); + } + +} diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java new file mode 100644 index 00000000000..b2194c2613e --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java @@ -0,0 +1,182 @@ +/**************************************************************** + * 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.store.event; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.util.EventCollector; +import org.junit.Before; +import org.junit.Test; + +public class DefaultDelegatingMailboxListenerTest { + + private static final MailboxPath MAILBOX_PATH = new MailboxPath("namespace", "user", "name"); + private static final MailboxPath OTHER_MAILBOX_PATH = new MailboxPath("namespace", "other", "name"); + + private DefaultDelegatingMailboxListener defaultDelegatingMailboxListener; + private EventCollector mailboxEventCollector; + private EventCollector eachNodeEventCollector; + private EventCollector onceEventCollector; + + @Before + public void setUp() throws Exception { + mailboxEventCollector = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eachNodeEventCollector = new EventCollector(MailboxListener.ListenerType.EACH_NODE); + onceEventCollector = new EventCollector(MailboxListener.ListenerType.ONCE); + defaultDelegatingMailboxListener = new DefaultDelegatingMailboxListener(); + defaultDelegatingMailboxListener.addListener(MAILBOX_PATH, mailboxEventCollector, null); + defaultDelegatingMailboxListener.addGlobalListener(onceEventCollector, null); + defaultDelegatingMailboxListener.addGlobalListener(eachNodeEventCollector, null); + } + + @Test(expected = MailboxException.class) + public void addListenerShouldThrowOnEACH_NODEListenerType() throws Exception { + MailboxListener mailboxListener = new EventCollector(MailboxListener.ListenerType.EACH_NODE); + defaultDelegatingMailboxListener.addListener(MAILBOX_PATH, mailboxListener, null); + } + + @Test(expected = MailboxException.class) + public void addListenerShouldThrowOnONCEListenerType() throws Exception { + MailboxListener mailboxListener = new EventCollector(MailboxListener.ListenerType.ONCE); + defaultDelegatingMailboxListener.addListener(MAILBOX_PATH, mailboxListener, null); + } + + @Test(expected = MailboxException.class) + public void addGlobalListenerShouldThrowOnMAILBOXListenerType() throws Exception { + MailboxListener mailboxListener = new EventCollector(MailboxListener.ListenerType.MAILBOX); + defaultDelegatingMailboxListener.addGlobalListener(mailboxListener, null); + } + + @Test + public void eventShouldWork() throws Exception { + MailboxListener.Event event = new MailboxListener.Event(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + assertThat(mailboxEventCollector.getEvents()).containsExactly(event); + assertThat(eachNodeEventCollector.getEvents()).containsExactly(event); + assertThat(onceEventCollector.getEvents()).containsExactly(event); + } + + @Test + public void eventShouldOnlyTriggerMAILBOXListenerRelatedToTheEvent() throws Exception { + MailboxListener.Event event = new MailboxListener.Event(null, OTHER_MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + assertThat(mailboxEventCollector.getEvents()).isEmpty(); + assertThat(eachNodeEventCollector.getEvents()).containsExactly(event); + assertThat(onceEventCollector.getEvents()).containsExactly(event); + } + + @Test + public void mailboxRenamedEventShouldUnregisterMAILBOXFromTheirPreviousPath() throws Exception { + MailboxListener.MailboxRenamed event = new MailboxListener.MailboxRenamed(null, MAILBOX_PATH) { + @Override + public MailboxPath getNewPath() { + return OTHER_MAILBOX_PATH; + } + }; + defaultDelegatingMailboxListener.event(event); + MailboxListener.Event secondEvent = new MailboxListener.Event(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(secondEvent); + assertThat(mailboxEventCollector.getEvents()).containsExactly(event); + assertThat(eachNodeEventCollector.getEvents()).containsOnly(event, secondEvent); + assertThat(onceEventCollector.getEvents()).containsExactly(event, secondEvent); + } + + @Test + public void mailboxRenamedEventShouldRegisterMAILBOXToTheirNewPath() throws Exception { + MailboxListener.MailboxRenamed event = new MailboxListener.MailboxRenamed(null, MAILBOX_PATH) { + @Override + public MailboxPath getNewPath() { + return OTHER_MAILBOX_PATH; + } + }; + defaultDelegatingMailboxListener.event(event); + MailboxListener.Event secondEvent = new MailboxListener.Event(null, OTHER_MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(secondEvent); + assertThat(mailboxEventCollector.getEvents()).containsOnly(event, secondEvent); + assertThat(eachNodeEventCollector.getEvents()).containsOnly(event, secondEvent); + assertThat(onceEventCollector.getEvents()).containsExactly(event, secondEvent); + } + + @Test + public void mailboxDeletionShouldUnregisterMAILBOXListeners() throws Exception { + MailboxListener.MailboxDeletion event = new MailboxListener.MailboxDeletion(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + MailboxListener.Event secondEvent = new MailboxListener.Event(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(secondEvent); + assertThat(mailboxEventCollector.getEvents()).containsExactly(event); + assertThat(eachNodeEventCollector.getEvents()).containsOnly(event, secondEvent); + assertThat(onceEventCollector.getEvents()).containsExactly(event, secondEvent); + } + + @Test + public void mailboxDeletionShouldNotRegisterMAILBOXListenerToOtherPaths() throws Exception { + MailboxListener.MailboxDeletion event = new MailboxListener.MailboxDeletion(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + MailboxListener.Event secondEvent = new MailboxListener.Event(null, OTHER_MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(secondEvent); + assertThat(mailboxEventCollector.getEvents()).containsExactly(event); + assertThat(eachNodeEventCollector.getEvents()).containsOnly(event, secondEvent); + assertThat(onceEventCollector.getEvents()).containsExactly(event, secondEvent); + } + + @Test + public void removeListenerShouldWork() throws Exception { + defaultDelegatingMailboxListener.removeListener(MAILBOX_PATH, mailboxEventCollector, null); + MailboxListener.Event event = new MailboxListener.Event(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + assertThat(mailboxEventCollector.getEvents()).isEmpty(); + assertThat(eachNodeEventCollector.getEvents()).containsExactly(event); + assertThat(onceEventCollector.getEvents()).containsExactly(event); + } + + @Test + public void removeListenerShouldNotRemoveAListenerFromADifferentPath() throws Exception { + defaultDelegatingMailboxListener.removeListener(OTHER_MAILBOX_PATH, mailboxEventCollector, null); + MailboxListener.Event event = new MailboxListener.Event(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + assertThat(mailboxEventCollector.getEvents()).containsExactly(event); + assertThat(eachNodeEventCollector.getEvents()).containsExactly(event); + assertThat(onceEventCollector.getEvents()).containsExactly(event); + } + + @Test + public void removeGlobalListenerShouldWorkForONCE() throws Exception { + defaultDelegatingMailboxListener.removeGlobalListener(eachNodeEventCollector, null); + MailboxListener.Event event = new MailboxListener.Event(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + assertThat(mailboxEventCollector.getEvents()).containsExactly(event); + assertThat(eachNodeEventCollector.getEvents()).isEmpty(); + assertThat(onceEventCollector.getEvents()).containsExactly(event); + } + + @Test + public void removeGlobalListenerShouldWorkForEACH_NODE() throws Exception { + defaultDelegatingMailboxListener.removeGlobalListener(onceEventCollector, null); + MailboxListener.Event event = new MailboxListener.Event(null, MAILBOX_PATH) {}; + defaultDelegatingMailboxListener.event(event); + assertThat(mailboxEventCollector.getEvents()).containsExactly(event); + assertThat(eachNodeEventCollector.getEvents()).containsExactly(event); + assertThat(onceEventCollector.getEvents()).isEmpty(); + } + +} From ed11e1502dd380539eab14f817bb3c585b80eb8f Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:34:08 +0200 Subject: [PATCH 05/21] MAILBOX-211 Introduce event factories --- .../mailbox/store/event/EventFactory.java | 176 ++++++++++++++ .../store/event/MailboxEventDispatcher.java | 216 +----------------- .../search/ListeningMessageSearchIndex.java | 27 +-- 3 files changed, 198 insertions(+), 221 deletions(-) create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java new file mode 100644 index 00000000000..90802ea07b2 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java @@ -0,0 +1,176 @@ + + +package org.apache.james.mailbox.store.event; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.StoreMailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.MailboxId; + +import java.util.List; +import java.util.Map; +import java.util.SortedMap; + +public class EventFactory { + + public interface MailboxAware { + Mailbox getMailbox(); + } + + public final class AddedImpl extends MailboxListener.Added implements MailboxAware { + private final Map added; + private final Mailbox mailbox; + + public AddedImpl(final MailboxSession session, final Mailbox mailbox, final SortedMap added) { + super(session, new StoreMailboxPath(mailbox)); + this.added = ImmutableMap.copyOf(added); + this.mailbox = mailbox; + } + + public List getUids() { + return ImmutableList.copyOf(added.keySet()); + } + + public MessageMetaData getMetaData(long uid) { + return added.get(uid); + } + + public Mailbox getMailbox() { + return mailbox; + } + } + + public final class ExpungedImpl extends MailboxListener.Expunged implements MailboxAware { + private final Map uids; + private final Mailbox mailbox; + + public ExpungedImpl(MailboxSession session, final Mailbox mailbox, final Map uids) { + super(session, new StoreMailboxPath(mailbox)); + this.uids = ImmutableMap.copyOf(uids); + this.mailbox = mailbox; + } + + public List getUids() { + return ImmutableList.copyOf(uids.keySet()); + } + + public MessageMetaData getMetaData(long uid) { + return uids.get(uid); + } + + public Mailbox getMailbox() { + return mailbox; + } + } + + public final class FlagsUpdatedImpl extends MailboxListener.FlagsUpdated implements MailboxAware { + private final List uids; + + private final Mailbox mailbox; + + private final List uFlags; + + public FlagsUpdatedImpl(MailboxSession session, final Mailbox mailbox, final List uids, final List uFlags) { + super(session, new StoreMailboxPath(mailbox)); + this.uids = ImmutableList.copyOf(uids); + this.uFlags = ImmutableList.copyOf(uFlags); + this.mailbox = mailbox; + } + + public List getUids() { + return uids; + } + + public List getUpdatedFlags() { + return uFlags; + } + + public Mailbox getMailbox() { + return mailbox; + } + + } + + public final class MailboxDeletionImpl extends MailboxListener.MailboxDeletion implements MailboxAware { + private final Mailbox mailbox; + + public MailboxDeletionImpl(MailboxSession session, Mailbox mailbox) { + super(session, new StoreMailboxPath(mailbox)); + this.mailbox = mailbox; + } + + + public Mailbox getMailbox() { + return mailbox; + } + + } + + public final class MailboxAddedImpl extends MailboxListener.MailboxAdded implements MailboxAware { + + private final Mailbox mailbox; + + public MailboxAddedImpl(MailboxSession session, Mailbox mailbox) { + super(session, new StoreMailboxPath(mailbox)); + this.mailbox = mailbox; + } + + + public Mailbox getMailbox() { + return mailbox; + } + + } + + public final class MailboxRenamedEventImpl extends MailboxListener.MailboxRenamed implements MailboxAware { + + private final MailboxPath newPath; + private final Mailbox newMailbox; + + public MailboxRenamedEventImpl(final MailboxSession session, final MailboxPath oldPath, final Mailbox newMailbox) { + super(session, oldPath); + this.newPath = new StoreMailboxPath(newMailbox); + this.newMailbox = newMailbox; + } + + public MailboxPath getNewPath() { + return newPath; + } + + @Override + public Mailbox getMailbox() { + return newMailbox; + } + } + + public MailboxListener.Added added(MailboxSession session, SortedMap uids, Mailbox mailbox) { + return new AddedImpl(session, mailbox, uids); + } + + public MailboxListener.Expunged expunged(final MailboxSession session, Map uids, Mailbox mailbox) { + return new ExpungedImpl(session, mailbox, uids); + } + + public MailboxListener.FlagsUpdated flagsUpdated(MailboxSession session, final List uids, final Mailbox mailbox, final List uflags) { + return new FlagsUpdatedImpl(session, mailbox, uids, uflags); + } + + public MailboxListener.MailboxRenamed mailboxRenamed(MailboxSession session, MailboxPath from, Mailbox to) { + return new MailboxRenamedEventImpl(session, from, to); + } + + public MailboxListener.MailboxDeletion mailboxDeleted(MailboxSession session, Mailbox mailbox) { + return new MailboxDeletionImpl(session, mailbox); + } + + public MailboxListener.MailboxAdded mailboxAdded(MailboxSession session, Mailbox mailbox) { + return new MailboxAddedImpl(session, mailbox); + } + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxEventDispatcher.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxEventDispatcher.java index b4b4d2eb0bf..324dc7a8763 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxEventDispatcher.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/MailboxEventDispatcher.java @@ -19,19 +19,15 @@ package org.apache.james.mailbox.store.event; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.SortedMap; import org.apache.james.mailbox.MailboxListener; -import org.apache.james.mailbox.MailboxListener.MailboxAdded; -import org.apache.james.mailbox.MailboxListener.MailboxDeletion; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.model.MessageMetaData; import org.apache.james.mailbox.model.UpdatedFlags; -import org.apache.james.mailbox.store.StoreMailboxPath; import org.apache.james.mailbox.store.mail.model.MailboxId; import org.apache.james.mailbox.store.mail.model.Mailbox; @@ -40,260 +36,68 @@ */ public class MailboxEventDispatcher { - private final MailboxListener listener; + private final EventFactory eventFactory; public MailboxEventDispatcher(MailboxListener listener) { this.listener = listener; + this.eventFactory = new EventFactory(); } - /** * Should get called when a new message was added to a Mailbox. All * registered MailboxListener will get triggered then - * + * * @param session The mailbox session * @param uids Sorted map with uids and message meta data * @param mailbox The mailbox */ public void added(MailboxSession session, SortedMap uids, Mailbox mailbox) { - final AddedImpl added = new AddedImpl(session, mailbox, uids); - listener.event(added); + listener.event(eventFactory.added(session, uids, mailbox)); } /** * Should get called when a message was expunged from a Mailbox. All * registered MailboxListener will get triggered then - * + * * @param session The mailbox session * @param uids Sorted map with uids and message meta data * @param mailbox The mailbox */ public void expunged(final MailboxSession session, Map uids, Mailbox mailbox) { - final ExpungedImpl expunged = new ExpungedImpl(session, mailbox, uids); - listener.event(expunged); + listener.event(eventFactory.expunged(session, uids, mailbox)); } /** * Should get called when the message flags were update in a Mailbox. All * registered MailboxListener will get triggered then - * - * @param session - * @param uids - * @param mailbox - * @param uflags */ public void flagsUpdated(MailboxSession session, final List uids, final Mailbox mailbox, final List uflags) { - final FlagsUpdatedImpl flags = new FlagsUpdatedImpl(session, mailbox, uids, uflags); - listener.event(flags); + listener.event(eventFactory.flagsUpdated(session, uids, mailbox, uflags)); } - - /** * Should get called when a Mailbox was renamed. All registered * MailboxListener will get triggered then - * - * @param session - * @param from - * @param to */ public void mailboxRenamed(MailboxSession session, MailboxPath from, Mailbox to) { - listener.event(new MailboxRenamedEventImpl(session, from, to)); - } - - public final class AddedImpl extends MailboxListener.Added { - - /** - * - */ - private static final long serialVersionUID = 1L; - private SortedMap added; - private final Mailbox mailbox; - - public AddedImpl(final MailboxSession session, final Mailbox mailbox, final SortedMap added) { - super(session, new StoreMailboxPath(mailbox)); - this.added = added; - this.mailbox = mailbox; - } - - /** - * @see org.apache.james.mailbox.MailboxListener.MessageEvent#getUids() - */ - public List getUids() { - return new ArrayList(added.keySet()); - } - - /** - * @see org.apache.james.mailbox.MailboxListener.Added#getMetaData(long) - */ - public MessageMetaData getMetaData(long uid) { - return added.get(uid); - } - - public Mailbox getMailbox() { - return mailbox; - } + listener.event(eventFactory.mailboxRenamed(session, from, to)); } - public final class ExpungedImpl extends MailboxListener.Expunged { - /** - * - */ - private static final long serialVersionUID = 1L; - private final Map uids; - private final Mailbox mailbox; - - public ExpungedImpl(MailboxSession session, final Mailbox mailbox, final Map uids) { - super(session, new StoreMailboxPath(mailbox)); - this.uids = uids; - this.mailbox = mailbox; - } - - /** - * @see org.apache.james.mailbox.MailboxListener.MessageEvent#getUids() - */ - public List getUids() { - return new ArrayList(uids.keySet()); - } - - /** - * @see org.apache.james.mailbox.MailboxListener.Expunged#getMetaData(long) - */ - public MessageMetaData getMetaData(long uid) { - return uids.get(uid); - } - - public Mailbox getMailbox() { - return mailbox; - } - } - - public final class FlagsUpdatedImpl extends MailboxListener.FlagsUpdated { - /** - * - */ - private static final long serialVersionUID = 1L; - private final List uids; - - private final Mailbox mailbox; - - private final List uFlags; - - public FlagsUpdatedImpl(MailboxSession session, final Mailbox mailbox, final List uids, final List uFlags) { - super(session, new StoreMailboxPath(mailbox)); - this.uids = uids; - this.uFlags = uFlags; - this.mailbox = mailbox; - } - - /** - * @see org.apache.james.mailbox.MailboxListener.MessageEvent#getUids() - */ - public List getUids() { - return uids; - } - - /** - * @see org.apache.james.mailbox.MailboxListener.FlagsUpdated#getUpdatedFlags() - */ - public List getUpdatedFlags() { - return uFlags; - } - - public Mailbox getMailbox() { - return mailbox; - } - - } - - public final class MailboxDeletionImpl extends MailboxDeletion { - /** - * - */ - private static final long serialVersionUID = 1L; - private final Mailbox mailbox; - - public MailboxDeletionImpl(MailboxSession session, Mailbox mailbox) { - super(session, new StoreMailboxPath(mailbox)); - this.mailbox = mailbox; - } - - - public Mailbox getMailbox() { - return mailbox; - } - - } - - public final class MailboxAddedImpl extends MailboxAdded { - /** - * - */ - private static final long serialVersionUID = 1L; - - private final Mailbox mailbox; - - public MailboxAddedImpl(MailboxSession session, Mailbox mailbox) { - super(session, new StoreMailboxPath(mailbox)); - this.mailbox = mailbox; - } - - - public Mailbox getMailbox() { - return mailbox; - } - - } /** * Should get called when a Mailbox was deleted. All registered * MailboxListener will get triggered then - * - * @param session - * @param mailbox */ public void mailboxDeleted(MailboxSession session, Mailbox mailbox) { - final MailboxDeletion event = new MailboxDeletionImpl(session, mailbox); - listener.event(event); + listener.event(eventFactory.mailboxDeleted(session, mailbox)); } /** * Should get called when a Mailbox was added. All registered * MailboxListener will get triggered then - * - * @param session - * @param mailbox */ public void mailboxAdded(MailboxSession session, Mailbox mailbox) { - final MailboxAdded event = new MailboxAddedImpl(session, mailbox); - listener.event(event); + listener.event(eventFactory.mailboxAdded(session, mailbox)); } - public final class MailboxRenamedEventImpl extends MailboxListener.MailboxRenamed { - /** - * - */ - private static final long serialVersionUID = 1L; - - private final MailboxPath newPath; - private final Mailbox newMailbox; - - public MailboxRenamedEventImpl(final MailboxSession session, final MailboxPath oldPath, final Mailbox newMailbox) { - super(session, oldPath); - this.newPath = new StoreMailboxPath(newMailbox); - this.newMailbox = newMailbox; - } - - /** - * @see - * org.apache.james.mailbox.MailboxListener.MailboxRenamed#getNewPath() - */ - public MailboxPath getNewPath() { - return newPath; - } - - public Mailbox getNewMailbox() { - return newMailbox; - } - } } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java index 4fbb2184dc8..a34271d8f37 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java @@ -28,10 +28,7 @@ import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.MessageRange; import org.apache.james.mailbox.model.UpdatedFlags; -import org.apache.james.mailbox.store.event.MailboxEventDispatcher.AddedImpl; -import org.apache.james.mailbox.store.event.MailboxEventDispatcher.ExpungedImpl; -import org.apache.james.mailbox.store.event.MailboxEventDispatcher.FlagsUpdatedImpl; -import org.apache.james.mailbox.store.event.MailboxEventDispatcher.MailboxDeletionImpl; +import org.apache.james.mailbox.store.event.EventFactory; import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; import org.apache.james.mailbox.store.mail.MessageMapperFactory; import org.apache.james.mailbox.store.mail.model.MailboxId; @@ -75,8 +72,8 @@ public void event(Event event) { try { if (event instanceof MessageEvent) { - if (event instanceof AddedImpl) { - AddedImpl added = (AddedImpl) event; + if (event instanceof EventFactory.AddedImpl) { + EventFactory.AddedImpl added = (EventFactory.AddedImpl) event; final Mailbox mailbox = added.getMailbox(); Iterator uids = added.getUids().iterator(); @@ -93,8 +90,8 @@ public void event(Event event) { } } - } else if (event instanceof ExpungedImpl) { - ExpungedImpl expunged = (ExpungedImpl) event; + } else if (event instanceof EventFactory.ExpungedImpl) { + EventFactory.ExpungedImpl expunged = (EventFactory.ExpungedImpl) event; final Mailbox mailbox = expunged.getMailbox(); List uids = expunged.getUids(); List ranges = MessageRange.toRanges(uids); @@ -106,8 +103,8 @@ public void event(Event event) { session.getLog().debug("Unable to deleted range " + range.toString() + " from index for mailbox " + mailbox, e); } } - } else if (event instanceof FlagsUpdatedImpl) { - FlagsUpdatedImpl flagsUpdated = (FlagsUpdatedImpl) event; + } else if (event instanceof EventFactory.FlagsUpdatedImpl) { + EventFactory.FlagsUpdatedImpl flagsUpdated = (EventFactory.FlagsUpdatedImpl) event; final Mailbox mailbox = flagsUpdated.getMailbox(); Iterator flags = flagsUpdated.getUpdatedFlags().iterator(); @@ -120,9 +117,9 @@ public void event(Event event) { } } } - } else if (event instanceof MailboxDeletionImpl) { + } else if (event instanceof EventFactory.MailboxDeletionImpl) { // delete all indexed messages for the mailbox - delete(session, ((MailboxDeletionImpl) event).getMailbox(), MessageRange.all()); + delete(session, ((EventFactory.MailboxDeletionImpl) event).getMailbox(), MessageRange.all()); } } catch (MailboxException e) { session.getLog().debug("Unable to update index", e); @@ -139,7 +136,7 @@ public boolean isClosed() { /** * Add the {@link Message} for the given {@link Mailbox} to the index - * + * * @param session * @param mailbox * @param message @@ -149,7 +146,7 @@ public boolean isClosed() { /** * Delete the {@link MessageRange} for the given {@link Mailbox} from the index - * + * * @param session * @param mailbox * @param range @@ -160,7 +157,7 @@ public boolean isClosed() { /** * Update the {@link MessageRange} for the given {@link Mailbox} with the new {@link Flags} in the index - * + * * @param session * @param mailbox * @param range From 95e66cd30ed45e0dee46f542cc7cefba842596fd Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:35:41 +0200 Subject: [PATCH 06/21] MAILBOX-211 Introduce MailboxId de serializer --- .../CassandraMailboxIdDeserializer.java | 38 +++++++++++++ .../CassandraMailboxIdDeserializerTest.java | 53 +++++++++++++++++++ .../hbase/HBaseMailboxIdDeserializer.java | 36 +++++++++++++ .../hbase/HBaseMailboxIdDeserializerTest.java | 53 +++++++++++++++++++ .../mailbox/jcr/JCRMailboxIdDeserializer.java | 30 +++++++++++ .../jcr/user/model/JCRSubscription.java | 2 +- .../jcr/JCRMailboxIdDeserializerTest.java | 45 ++++++++++++++++ .../mailbox/jpa/JPAMailboxIdDeserializer.java | 35 ++++++++++++ .../jpa/JPAMailboxIdDeserializerTest.java | 51 ++++++++++++++++++ .../maildir/MaildirMailboxIdDeserializer.java | 35 ++++++++++++ .../MaildirMailboxIdDeserializerTest.java | 52 ++++++++++++++++++ .../InMemoryMailboxIdDeserializer.java | 36 +++++++++++++ .../InMemoryMailboxIdDeserializerTest.java | 52 ++++++++++++++++++ .../MailboxIdDeserialisationException.java | 36 +++++++++++++ .../mail/model/MailboxIdDeserializer.java | 26 +++++++++ .../mailbox/store/TestIdDeserializer.java | 31 +++++++++++ 16 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializer.java create mode 100644 mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializerTest.java create mode 100644 mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializer.java create mode 100644 mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializerTest.java create mode 100644 mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializer.java create mode 100644 mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializerTest.java create mode 100644 mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializer.java create mode 100644 mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializerTest.java create mode 100644 mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializer.java create mode 100644 mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializerTest.java create mode 100644 mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializer.java create mode 100644 mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializerTest.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserialisationException.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserializer.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/TestIdDeserializer.java diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializer.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializer.java new file mode 100644 index 00000000000..4d8aa5f3ca6 --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializer.java @@ -0,0 +1,38 @@ +/**************************************************************** + * 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.cassandra; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; + +import java.util.UUID; + +public class CassandraMailboxIdDeserializer implements MailboxIdDeserializer { + + @Override + public CassandraId deserialize(String serializedMailboxId) throws MailboxIdDeserialisationException { + try { + return CassandraId.of(UUID.fromString(serializedMailboxId)); + } catch (Exception e) { + throw new MailboxIdDeserialisationException("Error de-serialising " + serializedMailboxId, e); + } + } + +} diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializerTest.java new file mode 100644 index 00000000000..02391a022e3 --- /dev/null +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxIdDeserializerTest.java @@ -0,0 +1,53 @@ +/**************************************************************** + * 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.cassandra; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.junit.Before; +import org.junit.Test; + +import java.util.UUID; + +public class CassandraMailboxIdDeserializerTest { + + private static final String UUID_STRING = "5530370f-44c6-4647-990e-7768ce5131d4"; + private static final String MALFORMED_UUID_STRING = "xyz"; + private static final CassandraId CASSANDRA_ID = CassandraId.of(UUID.fromString(UUID_STRING)); + + private CassandraMailboxIdDeserializer mailboxIdDeserializer; + + @Before + public void setUp() { + mailboxIdDeserializer = new CassandraMailboxIdDeserializer(); + } + + @Test + public void deserializeShouldWork() throws Exception { + assertThat(mailboxIdDeserializer.deserialize(UUID_STRING)).isEqualTo(CASSANDRA_ID); + } + + @Test(expected = MailboxIdDeserialisationException.class) + public void deserializeShouldThrowOnMalformedData() throws Exception { + mailboxIdDeserializer.deserialize(MALFORMED_UUID_STRING); + } + +} diff --git a/mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializer.java b/mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializer.java new file mode 100644 index 00000000000..7b6a9a2928a --- /dev/null +++ b/mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializer.java @@ -0,0 +1,36 @@ +/**************************************************************** + * 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.hbase; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; +import java.util.UUID; + +public class HBaseMailboxIdDeserializer implements MailboxIdDeserializer { + + @Override + public HBaseId deserialize(String serializedMailboxId) throws MailboxIdDeserialisationException { + try { + return HBaseId.of(UUID.fromString(serializedMailboxId)); + } catch (Exception e) { + throw new MailboxIdDeserialisationException("Error deserializing " + serializedMailboxId, e); + } + } +} diff --git a/mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializerTest.java b/mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializerTest.java new file mode 100644 index 00000000000..38ae5262c5d --- /dev/null +++ b/mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxIdDeserializerTest.java @@ -0,0 +1,53 @@ +/**************************************************************** + * 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.hbase; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.junit.Before; +import org.junit.Test; + +import java.util.UUID; + +public class HBaseMailboxIdDeserializerTest { + + private static final String UUID_STRING = "5530370f-44c6-4647-990e-7768ce5131d4"; + private static final String MALFORMED_UUID_STRING = "xyz"; + private static final HBaseId HBASE_ID = HBaseId.of(UUID.fromString(UUID_STRING)); + + private HBaseMailboxIdDeserializer mailboxIdDeserializer; + + @Before + public void setUp() { + mailboxIdDeserializer = new HBaseMailboxIdDeserializer(); + } + + @Test + public void deserializeShouldWork() throws Exception { + assertThat(mailboxIdDeserializer.deserialize(UUID_STRING)).isEqualTo(HBASE_ID); + } + + @Test(expected = MailboxIdDeserialisationException.class) + public void deserializeShouldThrowOnMalformedData() throws Exception { + mailboxIdDeserializer.deserialize(MALFORMED_UUID_STRING); + } + +} diff --git a/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializer.java b/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializer.java new file mode 100644 index 00000000000..4f0800ea9a8 --- /dev/null +++ b/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializer.java @@ -0,0 +1,30 @@ +/**************************************************************** + * 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.jcr; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; + +public class JCRMailboxIdDeserializer implements MailboxIdDeserializer { + + @Override + public JCRId deserialize(String serializedMailboxId) { + return JCRId.of(serializedMailboxId); + } +} diff --git a/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java b/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java index 79720e4c009..22a1550921d 100644 --- a/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java +++ b/mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java @@ -62,7 +62,7 @@ public JCRSubscription(String username, String mailbox, Logger log) { /* * (non-Javadoc) * - * @see org.apache.james.mailbox.store.user.model.Subscription#getMailbox() + * @see org.apache.james.mailbox.store.user.model.Subscription#getMailboxPath() */ public String getMailbox() { return mailbox; diff --git a/mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializerTest.java b/mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializerTest.java new file mode 100644 index 00000000000..79b7a64e965 --- /dev/null +++ b/mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxIdDeserializerTest.java @@ -0,0 +1,45 @@ +/**************************************************************** + * 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.jcr; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; +import org.junit.Before; +import org.junit.Test; + +public class JCRMailboxIdDeserializerTest { + + private static final String SERIALIZED_ID = "some_string"; + private static final JCRId JCR_ID = JCRId.of(SERIALIZED_ID); + + private MailboxIdDeserializer mailboxIdDeserializer; + + @Before + public void setUp() { + mailboxIdDeserializer = new JCRMailboxIdDeserializer(); + } + + @Test + public void deserializeShouldWork() throws Exception { + assertThat(mailboxIdDeserializer.deserialize(SERIALIZED_ID)).isEqualTo(JCR_ID); + } + +} diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializer.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializer.java new file mode 100644 index 00000000000..43ca7304db2 --- /dev/null +++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializer.java @@ -0,0 +1,35 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; + +public class JPAMailboxIdDeserializer implements MailboxIdDeserializer { + + @Override + public JPAId deserialize(String serializedMailboxId) throws MailboxIdDeserialisationException { + try { + return JPAId.of(Long.valueOf(serializedMailboxId)); + } catch (Exception e) { + throw new MailboxIdDeserialisationException("Error deserializing " + serializedMailboxId, e); + } + } +} diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializerTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializerTest.java new file mode 100644 index 00000000000..0713ef499ee --- /dev/null +++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxIdDeserializerTest.java @@ -0,0 +1,51 @@ +/**************************************************************** + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.junit.Before; +import org.junit.Test; + +public class JPAMailboxIdDeserializerTest { + + private static final String SERIALIZED_ID = "123456789012"; + private static final String MALFORMED_SERIALIZED_ID = "abcz"; + private static final JPAId JPA_ID = JPAId.of(Long.valueOf(SERIALIZED_ID)); + + private JPAMailboxIdDeserializer mailboxIdDeserializer; + + @Before + public void setUp() { + mailboxIdDeserializer = new JPAMailboxIdDeserializer(); + } + + @Test + public void deserializeShouldWork() throws Exception { + assertThat(mailboxIdDeserializer.deserialize(SERIALIZED_ID)).isEqualTo(JPA_ID); + } + + @Test(expected = MailboxIdDeserialisationException.class) + public void deserializeShouldThrowOnMalformedData() throws Exception { + mailboxIdDeserializer.deserialize(MALFORMED_SERIALIZED_ID); + } + +} diff --git a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializer.java b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializer.java new file mode 100644 index 00000000000..48a2ace4625 --- /dev/null +++ b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializer.java @@ -0,0 +1,35 @@ +/**************************************************************** + * 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.maildir; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; + +public class MaildirMailboxIdDeserializer implements MailboxIdDeserializer { + + @Override + public MaildirId deserialize(String serializedMailboxId) throws MailboxIdDeserialisationException { + try { + return MaildirId.of(Integer.valueOf(serializedMailboxId)); + } catch (Exception e) { + throw new MailboxIdDeserialisationException("Can not deserialize " + serializedMailboxId, e); + } + } +} diff --git a/mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializerTest.java b/mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializerTest.java new file mode 100644 index 00000000000..a56a0fce501 --- /dev/null +++ b/mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxIdDeserializerTest.java @@ -0,0 +1,52 @@ +/**************************************************************** + * 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.maildir; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; +import org.junit.Before; +import org.junit.Test; + +public class MaildirMailboxIdDeserializerTest { + + private static final String SERIALIZED_ID = "123"; + private static final String MALFORMED_SERIALIZED_ID = "az"; + private static final MaildirId MAILDIR_ID = MaildirId.of(Integer.valueOf(SERIALIZED_ID)); + + private MailboxIdDeserializer mailboxIdDeserializer; + + @Before + public void setUp() { + mailboxIdDeserializer = new MaildirMailboxIdDeserializer(); + } + + @Test + public void deserializeShouldWork() throws Exception { + assertThat(mailboxIdDeserializer.deserialize(SERIALIZED_ID)).isEqualTo(MAILDIR_ID); + } + + @Test(expected = MailboxIdDeserialisationException.class) + public void deserializeShouldThrowOnMalformedData() throws Exception { + mailboxIdDeserializer.deserialize(MALFORMED_SERIALIZED_ID); + } + +} diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializer.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializer.java new file mode 100644 index 00000000000..4995b8ade39 --- /dev/null +++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializer.java @@ -0,0 +1,36 @@ +/**************************************************************** + * 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.inmemory; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; + +public class InMemoryMailboxIdDeserializer implements MailboxIdDeserializer { + + @Override + public InMemoryId deserialize(String serializedMailboxId) throws MailboxIdDeserialisationException { + try { + return InMemoryId.of(Long.valueOf(serializedMailboxId)); + } catch (Exception e) { + throw new MailboxIdDeserialisationException("Error deserializing " + serializedMailboxId, e); + } + } + +} diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializerTest.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializerTest.java new file mode 100644 index 00000000000..9829ade7bef --- /dev/null +++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxIdDeserializerTest.java @@ -0,0 +1,52 @@ +/**************************************************************** + * 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.inmemory; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class InMemoryMailboxIdDeserializerTest { + + private static final String SERIALIZED_ID = "1234567890123"; + private static final String MALFORMED_SERIALIZED_ID = "aEZ"; + private static final InMemoryId IN_MEMORY_ID = InMemoryId.of(Long.valueOf(SERIALIZED_ID)); + + private MailboxIdDeserializer mailboxIdDeserializer; + + @Before + public void setUp() { + mailboxIdDeserializer = new InMemoryMailboxIdDeserializer(); + } + + @Test + public void deserializeShouldWork() throws MailboxIdDeserialisationException { + assertThat(mailboxIdDeserializer.deserialize(SERIALIZED_ID)).isEqualTo(IN_MEMORY_ID); + } + + @Test(expected = MailboxIdDeserialisationException.class) + public void deserializeShouldThrowOnMalformedData() throws MailboxIdDeserialisationException { + mailboxIdDeserializer.deserialize(MALFORMED_SERIALIZED_ID); + } + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserialisationException.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserialisationException.java new file mode 100644 index 00000000000..8deea45447f --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserialisationException.java @@ -0,0 +1,36 @@ +/**************************************************************** + * 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.store.mail.model; + +import org.apache.james.mailbox.exception.MailboxException; + +public class MailboxIdDeserialisationException extends MailboxException { + + public MailboxIdDeserialisationException() { + } + + public MailboxIdDeserialisationException(String message) { + super(message); + } + + public MailboxIdDeserialisationException(String msg, Exception cause) { + super(msg, cause); + } +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserializer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserializer.java new file mode 100644 index 00000000000..f0fdcb35233 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxIdDeserializer.java @@ -0,0 +1,26 @@ +/**************************************************************** + * 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.store.mail.model; + +public interface MailboxIdDeserializer { + + Id deserialize(String serializedMailboxId) throws MailboxIdDeserialisationException; + +} diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/TestIdDeserializer.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/TestIdDeserializer.java new file mode 100644 index 00000000000..0432a390500 --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/TestIdDeserializer.java @@ -0,0 +1,31 @@ +/**************************************************************** + * 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.store; + +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; + +public class TestIdDeserializer implements MailboxIdDeserializer { + + @Override + public TestId deserialize(String serializedMailboxId) { + return TestId.of(Long.valueOf(serializedMailboxId)); + } + +} From e0fec71b950c909f4e47995acd3cbe1cd9bc9af1 Mon Sep 17 00:00:00 2001 From: benwa Date: Tue, 3 Nov 2015 09:33:51 +0100 Subject: [PATCH 07/21] MAILBOX-211 Move SimpleMailboxACLJsonConverterTest to a new package --- .../SimpleMailboxACLJsonConverterTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename mailbox/{cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils => store/src/test/java/org/apache/james/mailbox/store/json}/SimpleMailboxACLJsonConverterTest.java (90%) diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/SimpleMailboxACLJsonConverterTest.java similarity index 90% rename from mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java rename to mailbox/store/src/test/java/org/apache/james/mailbox/store/json/SimpleMailboxACLJsonConverterTest.java index 90359bf99f0..accd82ee320 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/SimpleMailboxACLJsonConverterTest.java @@ -17,15 +17,15 @@ * under the License. * ****************************************************************/ -package org.apache.james.mailbox.cassandra.mail.utils; +package org.apache.james.mailbox.store.json; -import static net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER; import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import java.util.HashMap; import java.util.Map; +import net.javacrumbs.jsonunit.core.Option; import org.apache.james.mailbox.model.MailboxACL; import org.apache.james.mailbox.model.SimpleMailboxACL; import org.junit.Test; @@ -36,7 +36,7 @@ public class ACLMapBuilder { private Map map; public ACLMapBuilder() { - map = new HashMap<>(); + map = new HashMap(); } public ACLMapBuilder addSingleUserEntryToMap() { @@ -61,7 +61,7 @@ public ACLMapBuilder addSingleGroupEntryToMap() { } public MailboxACL buildAsACL() { - return new SimpleMailboxACL(new HashMap<>(map)); + return new SimpleMailboxACL(new HashMap(map)); } } @@ -70,35 +70,35 @@ public MailboxACL buildAsACL() { public void emptyACLShouldBeWellSerialized() throws Exception { assertThatJson(SimpleMailboxACLJsonConverter.toJson(SimpleMailboxACL.EMPTY)) .isEqualTo("{\"entries\":{}}") - .when(IGNORING_ARRAY_ORDER); + .when(Option.IGNORING_ARRAY_ORDER); } @Test public void singleUserEntryACLShouldBeWellSerialized() throws Exception { assertThatJson(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleUserEntryToMap().buildAsACL())) .isEqualTo("{\"entries\":{\"-user\":2040}}") - .when(IGNORING_ARRAY_ORDER); + .when(Option.IGNORING_ARRAY_ORDER); } @Test public void singleGroupEntryACLShouldBeWellSerialized() throws Exception { assertThatJson(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleGroupEntryToMap().buildAsACL())) .isEqualTo("{\"entries\":{\"-$group\":2032}}") - .when(IGNORING_ARRAY_ORDER); + .when(Option.IGNORING_ARRAY_ORDER); } @Test public void singleSpecialEntryACLShouldBeWellSerialized() throws Exception { assertThatJson(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleSpecialEntryToMap().buildAsACL())) .isEqualTo("{\"entries\":{\"-special\":1968}}") - .when(IGNORING_ARRAY_ORDER); + .when(Option.IGNORING_ARRAY_ORDER); } @Test public void multipleEntriesACLShouldBeWellSerialized() throws Exception { assertThatJson(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleUserEntryToMap().addSingleGroupEntryToMap().buildAsACL())) .isEqualTo("{\"entries\":{\"-user\":2040,\"-$group\":2032}}") - .when(IGNORING_ARRAY_ORDER); + .when(Option.IGNORING_ARRAY_ORDER); } @Test From a06146fd6942dedee33909691eef2c7b7e10849e Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:37:57 +0200 Subject: [PATCH 08/21] MAILBOX-211 Event serialization --- .../apache/james/mailbox/MailboxListener.java | 19 +- mailbox/pom.xml | 6 + mailbox/store/pom.xml | 25 +++ .../mailbox/store/event/EventSerializer.java | 29 +++ .../store/json/JacksonEventSerializer.java | 47 ++++ .../store/json/JsonEventSerializer.java | 33 +++ .../json/MessagePackEventSerializer.java | 35 +++ .../json}/SimpleMailboxACLJsonConverter.java | 2 +- .../store/json/event/EventConverter.java | 204 ++++++++++++++++++ .../store/json/event/MailboxConverter.java | 92 ++++++++ .../event/dto/EventDataTransferObject.java | 151 +++++++++++++ .../store/json/event/dto/EventType.java | 32 +++ .../event/dto/FlagsDataTransferObject.java | 83 +++++++ .../event/dto/LocaleDataTransferObject.java | 49 +++++ .../event/dto/MailboxDataTransferObject.java | 143 ++++++++++++ .../dto/MailboxPathDataTransferObject.java | 48 +++++ .../dto/MailboxSessionDataTransferObject.java | 120 +++++++++++ .../MessageMetaDataDataTransferObject.java | 93 ++++++++ .../dto/UpdatedFlagsDataTransferObject.java | 50 +++++ .../store/json/EventSerializerTest.java | 137 ++++++++++++ .../store/json/JsonEventSerializerTest.java | 36 ++++ .../json/MessagePackEventSerializerTest.java | 36 ++++ 22 files changed, 1467 insertions(+), 3 deletions(-) create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventSerializer.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JacksonEventSerializer.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JsonEventSerializer.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/MessagePackEventSerializer.java rename mailbox/{cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils => store/src/main/java/org/apache/james/mailbox/store/json}/SimpleMailboxACLJsonConverter.java (98%) create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/EventConverter.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/MailboxConverter.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventDataTransferObject.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventType.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/FlagsDataTransferObject.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/LocaleDataTransferObject.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxDataTransferObject.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxPathDataTransferObject.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxSessionDataTransferObject.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MessageMetaDataDataTransferObject.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/UpdatedFlagsDataTransferObject.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/json/EventSerializerTest.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/json/JsonEventSerializerTest.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/json/MessagePackEventSerializerTest.java diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java index fe8f50cb36f..fda12101d1d 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java @@ -173,7 +173,22 @@ public MessageEvent(MailboxSession session, MailboxPath path) { public abstract List getUids(); } - public abstract class Expunged extends MessageEvent { + public abstract class MetaDataHoldingEvent extends MessageEvent { + + public MetaDataHoldingEvent(MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Return the flags which were set for the afected message + * + * @return flags + */ + public abstract MessageMetaData getMetaData(long uid); + + } + + public abstract class Expunged extends MetaDataHoldingEvent { /** * @@ -212,7 +227,7 @@ public FlagsUpdated(MailboxSession session, MailboxPath path) { /** * A mailbox event related to added message */ - public abstract class Added extends MessageEvent { + public abstract class Added extends MetaDataHoldingEvent { /** * diff --git a/mailbox/pom.xml b/mailbox/pom.xml index 50eec11d287..fe3c84210db 100644 --- a/mailbox/pom.xml +++ b/mailbox/pom.xml @@ -101,6 +101,7 @@ 2.4 2.6 1.6 + 1.7 1.4 1.9 1.8.3 @@ -319,6 +320,11 @@ + + commons-codec + commons-codec + ${commons-codec.version} + commons-lang commons-lang diff --git a/mailbox/store/pom.xml b/mailbox/store/pom.xml index 48ab78e7f84..88bbaa049d0 100644 --- a/mailbox/store/pom.xml +++ b/mailbox/store/pom.xml @@ -48,6 +48,10 @@ org.slf4j slf4j-api + + com.fasterxml.jackson.core + jackson-databind + commons-io commons-io @@ -64,10 +68,26 @@ commons-lang commons-lang + + commons-codec + commons-codec + com.google.guava guava + + net.javacrumbs.json-unit + json-unit + 1.5.5 + test + + + net.javacrumbs.json-unit + json-unit-fluent + 1.5.5 + test + org.slf4j slf4j-simple @@ -97,6 +117,11 @@ mockito-core test + + org.msgpack + jackson-dataformat-msgpack + 0.7.0-p9 + org.apache.james apache-james-mailbox-api diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventSerializer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventSerializer.java new file mode 100644 index 00000000000..8ab49b78824 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventSerializer.java @@ -0,0 +1,29 @@ +/**************************************************************** + * 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.store.event; + +import org.apache.james.mailbox.MailboxListener; + +public interface EventSerializer { + + byte[] serializeEvent(MailboxListener.Event event) throws Exception; + + MailboxListener.Event deSerializeEvent(byte[] serializedEvent) throws Exception; +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JacksonEventSerializer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JacksonEventSerializer.java new file mode 100644 index 00000000000..a5444d54558 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JacksonEventSerializer.java @@ -0,0 +1,47 @@ +/**************************************************************** + * 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.store.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.json.event.EventConverter; +import org.apache.james.mailbox.store.json.event.dto.EventDataTransferObject; +import org.apache.james.mailbox.store.mail.model.MailboxId; + +public class JacksonEventSerializer implements EventSerializer { + + private final EventConverter eventConverter; + private final ObjectMapper objectMapper; + + public JacksonEventSerializer(EventConverter eventConverter, ObjectMapper objectMapper) { + this.eventConverter = eventConverter; + this.objectMapper = objectMapper; + } + + public byte[] serializeEvent(MailboxListener.Event event) throws Exception { + return objectMapper.writeValueAsBytes(eventConverter.convertToDataTransferObject(event)); + } + + public MailboxListener.Event deSerializeEvent(byte[] serializedEvent) throws Exception { + EventDataTransferObject eventDataTransferObject = objectMapper.readValue(serializedEvent, EventDataTransferObject.class); + return eventConverter.retrieveEvent(eventDataTransferObject); + } +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JsonEventSerializer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JsonEventSerializer.java new file mode 100644 index 00000000000..356b318c9e4 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/JsonEventSerializer.java @@ -0,0 +1,33 @@ +/**************************************************************** + * 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.store.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.james.mailbox.store.json.event.EventConverter; +import org.apache.james.mailbox.store.json.event.MailboxConverter; +import org.apache.james.mailbox.store.mail.model.MailboxId; + +public class JsonEventSerializer extends JacksonEventSerializer { + + public JsonEventSerializer(EventConverter eventConverter) { + super(eventConverter, new ObjectMapper()); + } + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/MessagePackEventSerializer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/MessagePackEventSerializer.java new file mode 100644 index 00000000000..9bf5db2bbd8 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/MessagePackEventSerializer.java @@ -0,0 +1,35 @@ +/**************************************************************** + * 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.store.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.james.mailbox.store.json.event.EventConverter; +import org.apache.james.mailbox.store.mail.model.MailboxId; +import org.msgpack.jackson.dataformat.MessagePackFactory; + +/** + * Message Pack ( http://msgpack.org/ ) Event Serializer + */ +public class MessagePackEventSerializer extends JacksonEventSerializer { + + public MessagePackEventSerializer(EventConverter eventConverter) { + super(eventConverter, new ObjectMapper(new MessagePackFactory())); + } +} diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/SimpleMailboxACLJsonConverter.java similarity index 98% rename from mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java rename to mailbox/store/src/main/java/org/apache/james/mailbox/store/json/SimpleMailboxACLJsonConverter.java index b910ad249d2..cc4381af3cd 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/SimpleMailboxACLJsonConverter.java @@ -17,7 +17,7 @@ * under the License. * ****************************************************************/ -package org.apache.james.mailbox.cassandra.mail.utils; +package org.apache.james.mailbox.store.json; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/EventConverter.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/EventConverter.java new file mode 100644 index 00000000000..98d5a5be84b --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/EventConverter.java @@ -0,0 +1,204 @@ +/**************************************************************** + * 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.store.json.event; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.event.EventFactory; +import org.apache.james.mailbox.store.json.event.dto.EventDataTransferObject; +import org.apache.james.mailbox.store.json.event.dto.EventType; +import org.apache.james.mailbox.store.json.event.dto.MailboxDataTransferObject; +import org.apache.james.mailbox.store.json.event.dto.MailboxPathDataTransferObject; +import org.apache.james.mailbox.store.json.event.dto.MailboxSessionDataTransferObject; +import org.apache.james.mailbox.store.json.event.dto.MessageMetaDataDataTransferObject; +import org.apache.james.mailbox.store.json.event.dto.UpdatedFlagsDataTransferObject; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.MailboxId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +public class EventConverter { + + private static final Logger LOG = LoggerFactory.getLogger(EventConverter.class); + + private final EventFactory eventFactory; + private final MailboxConverter mailboxConverter; + + public EventConverter(MailboxConverter mailboxConverter) { + this.eventFactory = new EventFactory(); + this.mailboxConverter = mailboxConverter; + } + + public EventDataTransferObject convertToDataTransferObject(MailboxListener.Event event) throws Exception { + MailboxDataTransferObject mailboxDataTransferObject = mailboxConverter.extractMailboxDataTransferObject(event); + if (event instanceof MailboxListener.Added) { + return constructMeteDataHoldingEventProxy(EventType.ADDED, + event.getSession(), + mailboxDataTransferObject, + ((MailboxListener.Added) event).getUids(), + (MailboxListener.Added) event); + } else if (event instanceof MailboxListener.Expunged) { + return constructMeteDataHoldingEventProxy(EventType.DELETED, + event.getSession(), mailboxDataTransferObject, + ((MailboxListener.Expunged) event).getUids(), + (MailboxListener.Expunged) event); + } else if (event instanceof MailboxListener.FlagsUpdated) { + return constructFalgsUpdatedProxy(event.getSession(), + mailboxDataTransferObject, + ((MailboxListener.FlagsUpdated) event).getUids(), + ((MailboxListener.FlagsUpdated) event).getUpdatedFlags()); + } else if ( event instanceof MailboxListener.MailboxRenamed) { + return constructMailboxRenamedProxy(event.getSession(), + mailboxDataTransferObject, + event.getMailboxPath()); + } else if (event instanceof MailboxListener.MailboxDeletion) { + return constructMailboxEventProxy(EventType.MAILBOX_DELETED, + event.getSession(), + mailboxDataTransferObject); + } else if (event instanceof MailboxListener.MailboxAdded) { + return constructMailboxEventProxy(EventType.MAILBOX_ADDED, + event.getSession(), + mailboxDataTransferObject); + } else { + throw new Exception("You are trying to serialize an event that can't be serialized"); + } + } + + public MailboxListener.Event retrieveEvent(EventDataTransferObject eventDataTransferObject) throws Exception { + Mailbox mailbox = mailboxConverter.retrieveMailbox(eventDataTransferObject.getMailbox()); + switch (eventDataTransferObject.getType()) { + case ADDED: + return eventFactory.added(eventDataTransferObject.getSession().getMailboxSession(), + retrieveMetadata(eventDataTransferObject.getMetaDataProxyMap()), + mailbox); + case DELETED: + return eventFactory.expunged(eventDataTransferObject.getSession().getMailboxSession(), + retrieveMetadata(eventDataTransferObject.getMetaDataProxyMap()), + mailbox); + case FLAGS: + return eventFactory.flagsUpdated(eventDataTransferObject.getSession().getMailboxSession(), + eventDataTransferObject.getUids(), + mailbox, + retrieveUpdatedFlags(eventDataTransferObject.getUpdatedFlags())); + case MAILBOX_ADDED: + return eventFactory.mailboxAdded(eventDataTransferObject.getSession().getMailboxSession(), mailbox); + case MAILBOX_DELETED: + return eventFactory.mailboxDeleted(eventDataTransferObject.getSession().getMailboxSession(), mailbox); + case MAILBOX_RENAMED: + return eventFactory.mailboxRenamed(eventDataTransferObject.getSession().getMailboxSession(), + eventDataTransferObject.getFrom().getPath(), + mailbox); + default: + throw new Exception("Can not deserialize unknown event"); + } + } + + private EventDataTransferObject constructMailboxEventProxy(EventType eventType, + MailboxSession mailboxSession, + MailboxDataTransferObject mailboxIntermediate) { + return EventDataTransferObject.builder() + .type(eventType) + .session(new MailboxSessionDataTransferObject(mailboxSession)) + .mailbox(mailboxIntermediate) + .build(); + } + + private EventDataTransferObject constructMailboxRenamedProxy(MailboxSession mailboxSession, + MailboxDataTransferObject mailboxIntermediate, + MailboxPath from) { + return EventDataTransferObject.builder() + .type(EventType.MAILBOX_RENAMED) + .session(new MailboxSessionDataTransferObject(mailboxSession)) + .mailbox(mailboxIntermediate) + .from(new MailboxPathDataTransferObject(from)) + .build(); + } + + private EventDataTransferObject constructFalgsUpdatedProxy(MailboxSession session, + MailboxDataTransferObject mailboxIntermediate, + List uids, + List updatedFlagsList) { + ArrayList updatedFlagsDataTransferObjects = new ArrayList(); + for(UpdatedFlags updatedFlags : updatedFlagsList) { + updatedFlagsDataTransferObjects.add(new UpdatedFlagsDataTransferObject(updatedFlags)); + } + return EventDataTransferObject.builder() + .type(EventType.FLAGS) + .session(new MailboxSessionDataTransferObject(session)) + .mailbox(mailboxIntermediate) + .uids(uids) + .updatedFlags(updatedFlagsDataTransferObjects) + .build(); + } + + private EventDataTransferObject constructMeteDataHoldingEventProxy(EventType eventType, + MailboxSession mailboxSession, + MailboxDataTransferObject mailboxIntermediate, + List uids, + MailboxListener.MetaDataHoldingEvent event) { + HashMap metaDataProxyMap = new HashMap(); + for(Long uid : uids) { + metaDataProxyMap.put(uid, new MessageMetaDataDataTransferObject( + event.getMetaData(uid) + )); + } + return EventDataTransferObject.builder() + .type(eventType) + .session(new MailboxSessionDataTransferObject(mailboxSession)) + .mailbox(mailboxIntermediate) + .uids(uids) + .metaData(metaDataProxyMap) + .build(); + } + + private SortedMap retrieveMetadata(Map metaDataProxyMap) { + if(metaDataProxyMap != null) { + TreeMap result = new TreeMap(); + Set> entrySet = metaDataProxyMap.entrySet(); + for (Map.Entry entry : entrySet) { + result.put(entry.getKey(), entry.getValue().getMetadata()); + } + return result; + } else { + LOG.warn("Event serialization problem : No metadata"); + return null; + } + } + + private List retrieveUpdatedFlags(List updatedFlagsDataTransferObject) { + List result = new ArrayList(); + for(UpdatedFlagsDataTransferObject proxy : updatedFlagsDataTransferObject) { + result.add(proxy.retrieveUpdatedFlags()); + } + return result; + } + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/MailboxConverter.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/MailboxConverter.java new file mode 100644 index 00000000000..3891bdc27ec --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/MailboxConverter.java @@ -0,0 +1,92 @@ +/**************************************************************** + * 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.store.json.event; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.event.EventFactory; +import org.apache.james.mailbox.store.json.SimpleMailboxACLJsonConverter; +import org.apache.james.mailbox.store.json.event.dto.MailboxDataTransferObject; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.MailboxId; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserialisationException; +import org.apache.james.mailbox.store.mail.model.MailboxIdDeserializer; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class MailboxConverter { + + private final static Logger LOGGER = LoggerFactory.getLogger(MailboxConverter.class); + + private final MailboxIdDeserializer mailboxIdDeserializer; + + public MailboxConverter(MailboxIdDeserializer mailboxIdDeserializer) { + this.mailboxIdDeserializer = mailboxIdDeserializer; + } + + public Mailbox retrieveMailbox(MailboxDataTransferObject mailboxDataTransferObject) { + SimpleMailbox mailbox = new SimpleMailbox(new MailboxPath(mailboxDataTransferObject.getNamespace(), + mailboxDataTransferObject.getUser(), + mailboxDataTransferObject.getName()), + mailboxDataTransferObject.getUidValidity()); + try { + mailbox.setACL(SimpleMailboxACLJsonConverter.toACL(mailboxDataTransferObject.getSerializedACL())); + mailbox.setMailboxId(mailboxIdDeserializer.deserialize(mailboxDataTransferObject.getSerializedMailboxId())); + } catch (IOException e) { + LOGGER.warn("Failed to deserialize ACL", e); + } catch (MailboxIdDeserialisationException e) { + LOGGER.warn("Failed to deserialize mailbox ID", e); + } + return mailbox; + } + + public MailboxDataTransferObject convertMailboxDataTransferObject(Mailbox mailbox) { + return MailboxDataTransferObject.builder() + .serializedMailboxId(mailbox.getMailboxId().serialize()) + .namespace(mailbox.getNamespace()) + .user(mailbox.getUser()) + .name(mailbox.getName()) + .uidValidity(mailbox.getUidValidity()) + .serializedACL(getSerializedACL(mailbox)) + .build(); + } + + @SuppressWarnings("unchecked") + public MailboxDataTransferObject extractMailboxDataTransferObject(MailboxListener.Event event) { + if (event instanceof EventFactory.MailboxAware) { + return convertMailboxDataTransferObject(((EventFactory.MailboxAware) event).getMailbox()); + } else { + throw new RuntimeException("Unsupported event class : " + event.getClass().getCanonicalName()); + } + } + + private String getSerializedACL(Mailbox mailbox) { + try { + return SimpleMailboxACLJsonConverter.toJson(mailbox.getACL()); + } catch (JsonProcessingException e) { + return "{\"entries\":{}}"; + } + } + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventDataTransferObject.java new file mode 100644 index 00000000000..c7d02c58e6f --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventDataTransferObject.java @@ -0,0 +1,151 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; + +public class EventDataTransferObject { + + public static class Builder { + + private EventType type; + private MailboxDataTransferObject mailbox; + private MailboxSessionDataTransferObject session; + private List uids; + private Map metaData; + private List updatedFlags; + private MailboxPathDataTransferObject from; + + public Builder type(EventType type) { + this.type = type; + return this; + } + + public Builder mailbox(MailboxDataTransferObject mailbox) { + this.mailbox = mailbox; + return this; + } + + public Builder session(MailboxSessionDataTransferObject session) { + this.session = session; + return this; + } + + public Builder from(MailboxPathDataTransferObject from) { + this.from = from; + return this; + } + + public Builder uids(List uids) { + this.uids = uids; + return this; + } + + public Builder metaData(Map metaData) { + this.metaData = metaData; + return this; + } + + public Builder updatedFlags(List updatedFlagsList) { + this.updatedFlags = updatedFlagsList; + return this; + } + + public EventDataTransferObject build() { + return new EventDataTransferObject(type, mailbox, session, uids, metaData, updatedFlags, from); + } + + } + + public static Builder builder() { + return new Builder(); + } + + @JsonProperty() + private EventType type; + @JsonProperty() + private MailboxDataTransferObject mailbox; + @JsonProperty() + private MailboxSessionDataTransferObject session; + @JsonProperty() + private List uids; + @JsonProperty() + private Map metaData; + @JsonProperty() + private List updatedFlags; + @JsonProperty() + private MailboxPathDataTransferObject from; + + public EventDataTransferObject() {} + + public EventDataTransferObject(EventType type, + MailboxDataTransferObject mailbox, + MailboxSessionDataTransferObject session, + List uids, + Map metaData, + List updatedFlags, + MailboxPathDataTransferObject from) { + this.type = type; + this.mailbox = mailbox; + this.session = session; + this.uids = uids; + this.metaData = metaData; + this.updatedFlags = updatedFlags; + this.from = from; + } + + @JsonIgnore + public EventType getType() { + return type; + } + + @JsonIgnore + public MailboxDataTransferObject getMailbox() { + return mailbox; + } + + @JsonIgnore + public MailboxSessionDataTransferObject getSession() { + return session; + } + + @JsonIgnore + public List getUids() { + return uids; + } + + @JsonIgnore + public Map getMetaDataProxyMap() { + return metaData; + } + + @JsonIgnore + public List getUpdatedFlags() { + return updatedFlags; + } + + @JsonIgnore + public MailboxPathDataTransferObject getFrom() { + return from; + } +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventType.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventType.java new file mode 100644 index 00000000000..b0850a3a916 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/EventType.java @@ -0,0 +1,32 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; + +@JsonFormat(shape= JsonFormat.Shape.NUMBER) +public enum EventType { + ADDED, + DELETED, + FLAGS, + MAILBOX_RENAMED, + MAILBOX_ADDED, + MAILBOX_DELETED +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/FlagsDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/FlagsDataTransferObject.java new file mode 100644 index 00000000000..9b2459f6f6e --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/FlagsDataTransferObject.java @@ -0,0 +1,83 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.mail.Flags; + +public class FlagsDataTransferObject { + @JsonProperty() + private boolean answered; + @JsonProperty() + private boolean flagged; + @JsonProperty() + private boolean recent; + @JsonProperty() + private boolean deleted; + @JsonProperty() + private boolean draft; + @JsonProperty() + private boolean seen; + @JsonProperty() + private String[] userFlags; + + public FlagsDataTransferObject() { + + } + + public FlagsDataTransferObject(Flags flags) { + this.answered = flags.contains(Flags.Flag.ANSWERED); + this.flagged = flags.contains(Flags.Flag.FLAGGED); + this.recent = flags.contains(Flags.Flag.RECENT); + this.deleted = flags.contains(Flags.Flag.DELETED); + this.draft = flags.contains(Flags.Flag.DRAFT); + this.seen = flags.contains(Flags.Flag.SEEN); + this.userFlags = flags.getUserFlags(); + } + + @JsonIgnore + public Flags getFlags() { + Flags result = new Flags(); + if(answered) { + result.add(Flags.Flag.ANSWERED); + } + if(flagged) { + result.add(Flags.Flag.FLAGGED); + } + if(recent) { + result.add(Flags.Flag.RECENT); + } + if(deleted) { + result.add(Flags.Flag.DELETED); + } + if(draft) { + result.add(Flags.Flag.DRAFT); + } + if(seen) { + result.add(Flags.Flag.SEEN); + } + for(String flag : userFlags) { + result.add(flag); + } + return result; + } +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/LocaleDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/LocaleDataTransferObject.java new file mode 100644 index 00000000000..2b9bd186931 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/LocaleDataTransferObject.java @@ -0,0 +1,49 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Locale; + +public class LocaleDataTransferObject { + @JsonProperty() + private String country; + @JsonProperty() + private String variant; + @JsonProperty() + private String language; + + public LocaleDataTransferObject() { + + } + + public LocaleDataTransferObject(Locale locale) { + this.country = locale.getCountry(); + this.variant = locale.getVariant(); + this.language = locale.getLanguage(); + } + + @JsonIgnore + public Locale getLocale() { + return new Locale(language, country, variant); + } +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxDataTransferObject.java new file mode 100644 index 00000000000..f7fdfa50303 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxDataTransferObject.java @@ -0,0 +1,143 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class MailboxDataTransferObject { + + public static class Builder { + private String serializedMailboxId; + private String namespace; + private String user; + private String name; + private long uidValidity; + private String serializedACL; + + private Builder() { + + } + + public Builder serializedMailboxId(String serializedMailboxId) { + this.serializedMailboxId = serializedMailboxId; + return this; + } + + public Builder namespace(String namespace) { + this.namespace = namespace; + return this; + } + + public Builder user(String user) { + this.user = user; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder uidValidity(long uidValidity) { + this.uidValidity = uidValidity; + return this; + } + + public Builder serializedACL(String serializedACL) { + this.serializedACL = serializedACL; + return this; + } + + public MailboxDataTransferObject build() { + return new MailboxDataTransferObject(serializedMailboxId, + namespace, + user, + name, + uidValidity, + serializedACL); + } + } + + public static Builder builder() { + return new Builder(); + } + + @JsonProperty() + private String serializedMailboxId; + @JsonProperty() + private String namespace; + @JsonProperty() + private String user; + @JsonProperty() + private String name; + @JsonProperty() + private long uidValidity; + @JsonProperty() + private String serializedACL; + + public MailboxDataTransferObject() { + + } + + private MailboxDataTransferObject(String serializedMailboxId, + String namespace, + String user, + String name, + long uidValidity, + String serializedACL) { + this.serializedMailboxId = serializedMailboxId; + this.namespace = namespace; + this.user = user; + this.name = name; + this.uidValidity = uidValidity; + this.serializedACL = serializedACL; + } + + @JsonIgnore + public String getSerializedMailboxId() { + return serializedMailboxId; + } + + @JsonIgnore + public String getNamespace() { + return namespace; + } + + @JsonIgnore + public String getUser() { + return user; + } + + @JsonIgnore + public String getName() { + return name; + } + + @JsonIgnore + public long getUidValidity() { + return uidValidity; + } + + @JsonIgnore + public String getSerializedACL() { + return serializedACL; + } +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxPathDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxPathDataTransferObject.java new file mode 100644 index 00000000000..c52113dfe17 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxPathDataTransferObject.java @@ -0,0 +1,48 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.james.mailbox.model.MailboxPath; + +public class MailboxPathDataTransferObject { + @JsonProperty() + private String user; + @JsonProperty() + private String namespace; + @JsonProperty() + private String name; + + public MailboxPathDataTransferObject() { + + } + + public MailboxPathDataTransferObject(MailboxPath path) { + this.user = path.getUser(); + this.name = path.getName(); + this.namespace = path.getNamespace(); + } + + @JsonIgnore + public MailboxPath getPath() { + return new MailboxPath(namespace, user, name); + } +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxSessionDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxSessionDataTransferObject.java new file mode 100644 index 00000000000..3277d4cfa7e --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MailboxSessionDataTransferObject.java @@ -0,0 +1,120 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.store.SimpleMailboxSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class MailboxSessionDataTransferObject { + @JsonProperty() + private long sessionId; + @JsonProperty() + private String username; + @JsonProperty() + private List sharedSpaces; + @JsonProperty() + private String otherUserSpace; + @JsonProperty() + private char separator; + @JsonProperty() + private List locales; + @JsonProperty("r") + private int sessionType; + + private static final Logger LOG = LoggerFactory.getLogger(MailboxSessionDataTransferObject.class); + + public MailboxSessionDataTransferObject() { + + } + + public MailboxSessionDataTransferObject(MailboxSession session) { + username = session.getUser().getUserName(); + sharedSpaces = new ArrayList(session.getSharedSpaces()); + otherUserSpace = session.getOtherUsersSpace(); + separator = session.getPathDelimiter(); + sessionType = extractSessionType(session); + sessionId = session.getSessionId(); + locales = Lists.transform(session.getUser().getLocalePreferences(), new Function() { + public LocaleDataTransferObject apply(Locale locale) { + return new LocaleDataTransferObject(locale); + } + }); + } + + @JsonIgnore + public MailboxSession getMailboxSession() { + return new SimpleMailboxSession(sessionId, + username, + "", + LOG, + retrieveLocales(), + sharedSpaces, + otherUserSpace, + separator, + retrieveSessionType()); + } + + private List retrieveLocales() { + if (locales != null) { + return Lists.transform(locales, new Function() { + public Locale apply(LocaleDataTransferObject localeDataTransferObject) { + return localeDataTransferObject.getLocale(); + } + }); + } else { + return new ArrayList(); + } + } + + private MailboxSession.SessionType retrieveSessionType() { + switch(this.sessionType) { + case 0: + return MailboxSession.SessionType.User; + case 1: + return MailboxSession.SessionType.System; + default: + LOG.warn("Unknown session type number while deserializing. Assuming user instead"); + return MailboxSession.SessionType.User; + } + } + + private int extractSessionType(MailboxSession session) { + switch(session.getType()) { + case User: + return 0; + case System: + return 1; + default: + LOG.warn("Unknow session type while serializing mailbox session"); + return 0; + } + } + +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MessageMetaDataDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MessageMetaDataDataTransferObject.java new file mode 100644 index 00000000000..2331d4ff86c --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/MessageMetaDataDataTransferObject.java @@ -0,0 +1,93 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class MessageMetaDataDataTransferObject { + @JsonProperty() + private long uid; + @JsonProperty() + private long modseq; + @JsonProperty() + private FlagsDataTransferObject flags; + @JsonProperty() + private long size; + @JsonProperty() + private String date; + + private static final Logger LOG = LoggerFactory.getLogger(MessageMetaDataDataTransferObject.class); + + private static final ThreadLocal simpleDateFormat = new ThreadLocal(){ + protected SimpleDateFormat initialValue() + { + return new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + } + }; + + private static Date parse(String date) throws ParseException { + if (date != null) { + return simpleDateFormat.get().parse(date); + } else { + return null; + } + } + + private static String format(Date date) { + if (date != null) { + return simpleDateFormat.get().format(date); + } else { + return null; + } + } + + + public MessageMetaDataDataTransferObject() { + + } + + public MessageMetaDataDataTransferObject(MessageMetaData metadata) { + this.uid = metadata.getUid(); + this.modseq = metadata.getModSeq(); + this.flags = new FlagsDataTransferObject(metadata.getFlags()); + this.size = metadata.getSize(); + this.date = format(metadata.getInternalDate()); + } + + @JsonIgnore + public SimpleMessageMetaData getMetadata() { + try { + return new SimpleMessageMetaData(uid, modseq, flags.getFlags(), size, parse(date)); + } catch(ParseException parseException) { + LOG.error("Parse exception while parsing date while deserializing metadata upon event serialization. Using nowadays date instead."); + return new SimpleMessageMetaData(uid, modseq, flags.getFlags(), size, new Date()); + } + + } +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/UpdatedFlagsDataTransferObject.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/UpdatedFlagsDataTransferObject.java new file mode 100644 index 00000000000..e78b529a640 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/event/dto/UpdatedFlagsDataTransferObject.java @@ -0,0 +1,50 @@ +/**************************************************************** + * 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.store.json.event.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.james.mailbox.model.UpdatedFlags; + +public class UpdatedFlagsDataTransferObject { + @JsonProperty("uid") + private long uid; + @JsonProperty("modseq") + private long modseq; + @JsonProperty("oldFlags") + private FlagsDataTransferObject oldFlags; + @JsonProperty("newFlags") + private FlagsDataTransferObject newFlags; + + public UpdatedFlagsDataTransferObject() { + } + + public UpdatedFlagsDataTransferObject(UpdatedFlags updatedFlags) { + this.uid = updatedFlags.getUid(); + this.modseq = updatedFlags.getModSeq(); + this.oldFlags = new FlagsDataTransferObject(updatedFlags.getOldFlags()); + this.newFlags = new FlagsDataTransferObject(updatedFlags.getNewFlags()); + } + + public UpdatedFlags retrieveUpdatedFlags() { + return new UpdatedFlags(uid, modseq, oldFlags.getFlags(), newFlags.getFlags()); + } + +} \ No newline at end of file diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/EventSerializerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/EventSerializerTest.java new file mode 100644 index 00000000000..8e5162e38af --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/EventSerializerTest.java @@ -0,0 +1,137 @@ +/**************************************************************** + * 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.store.json; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.Lists; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.TestId; +import org.apache.james.mailbox.store.event.EventFactory; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.junit.Before; +import org.junit.Test; + +import javax.mail.Flags; +import java.util.TreeMap; + +public abstract class EventSerializerTest { + + public static final long UID = 42L; + public static final long MOD_SEQ = 24L; + public static final UpdatedFlags UPDATED_FLAGS = new UpdatedFlags(UID, MOD_SEQ, new Flags(), new Flags(Flags.Flag.SEEN)); + public static final Flags FLAGS = new Flags(); + public static final long SIZE = 45L; + public static final SimpleMessageMetaData MESSAGE_META_DATA = new SimpleMessageMetaData(UID, MOD_SEQ, FLAGS, SIZE, null); + public static final MailboxPath FROM = new MailboxPath("namespace", "user", "name"); + private EventSerializer serializer; + private EventFactory eventFactory; + private MailboxSession mailboxSession; + private SimpleMailbox mailbox; + + abstract EventSerializer createSerializer(); + + @Before + public void setUp() { + eventFactory = new EventFactory(); + serializer = createSerializer(); + mailboxSession = new MockMailboxSession("benwa"); + mailbox = new SimpleMailbox(new MailboxPath("#private", "benwa", "name"), 42); + mailbox.setMailboxId(TestId.of(28L)); + } + + @Test + public void addedEventShouldBeWellConverted() throws Exception { + TreeMap treeMap = new TreeMap(); + treeMap.put(UID, MESSAGE_META_DATA); + MailboxListener.Event event = eventFactory.added(mailboxSession, treeMap, mailbox); + byte[] serializedEvent = serializer.serializeEvent(event); + MailboxListener.Event deserializedEvent = serializer.deSerializeEvent(serializedEvent); + assertThat(deserializedEvent.getMailboxPath()).isEqualTo(event.getMailboxPath()); + assertThat(deserializedEvent.getSession().getSessionId()).isEqualTo(event.getSession().getSessionId()); + assertThat(deserializedEvent).isInstanceOf(MailboxListener.Added.class); + assertThat(((MailboxListener.Added)deserializedEvent).getUids()).containsOnly(UID); + assertThat(((MailboxListener.Added)deserializedEvent).getMetaData(UID)).isEqualTo(MESSAGE_META_DATA); + } + + @Test + public void expungedEventShouldBeWellConverted() throws Exception { + TreeMap treeMap = new TreeMap(); + treeMap.put(UID, MESSAGE_META_DATA); + MailboxListener.Event event = eventFactory.expunged(mailboxSession, treeMap, mailbox); + byte[] serializedEvent = serializer.serializeEvent(event); + MailboxListener.Event deserializedEvent = serializer.deSerializeEvent(serializedEvent); + assertThat(deserializedEvent.getMailboxPath()).isEqualTo(event.getMailboxPath()); + assertThat(deserializedEvent.getSession().getSessionId()).isEqualTo(event.getSession().getSessionId()); + assertThat(deserializedEvent).isInstanceOf(MailboxListener.Expunged.class); + assertThat(((MailboxListener.Expunged)deserializedEvent).getUids()).containsOnly(UID); + assertThat(((MailboxListener.Expunged)deserializedEvent).getMetaData(UID)).isEqualTo(MESSAGE_META_DATA); + } + + @Test + public void flagsUpdatedEventShouldBeWellConverted() throws Exception { + MailboxListener.Event event = eventFactory.flagsUpdated(mailboxSession, Lists.newArrayList(UID), mailbox, Lists.newArrayList(UPDATED_FLAGS)); + byte[] serializedEvent = serializer.serializeEvent(event); + MailboxListener.Event deserializedEvent = serializer.deSerializeEvent(serializedEvent); + assertThat(deserializedEvent.getMailboxPath()).isEqualTo(event.getMailboxPath()); + assertThat(deserializedEvent.getSession().getSessionId()).isEqualTo(event.getSession().getSessionId()); + assertThat(deserializedEvent).isInstanceOf(MailboxListener.FlagsUpdated.class); + assertThat(((MailboxListener.FlagsUpdated)event).getUpdatedFlags()).containsOnly(UPDATED_FLAGS); + } + + @Test + public void mailboxAddedShouldBeWellConverted() throws Exception { + MailboxListener.Event event = eventFactory.mailboxAdded(mailboxSession, mailbox); + byte[] serializedEvent = serializer.serializeEvent(event); + MailboxListener.Event deserializedEvent = serializer.deSerializeEvent(serializedEvent); + assertThat(deserializedEvent.getMailboxPath()).isEqualTo(event.getMailboxPath()); + assertThat(deserializedEvent.getSession().getSessionId()).isEqualTo(event.getSession().getSessionId()); + assertThat(deserializedEvent).isInstanceOf(MailboxListener.MailboxAdded.class); + } + + @Test + public void mailboxDeletionShouldBeWellConverted() throws Exception { + MailboxListener.Event event = eventFactory.mailboxDeleted(mailboxSession, mailbox); + byte[] serializedEvent = serializer.serializeEvent(event); + MailboxListener.Event deserializedEvent = serializer.deSerializeEvent(serializedEvent); + assertThat(deserializedEvent.getMailboxPath()).isEqualTo(event.getMailboxPath()); + assertThat(deserializedEvent.getSession().getSessionId()).isEqualTo(event.getSession().getSessionId()); + assertThat(deserializedEvent).isInstanceOf(MailboxListener.MailboxDeletion.class); + } + + @Test + public void mailboxRenamedShouldBeWellConverted() throws Exception { + MailboxListener.Event event = eventFactory.mailboxRenamed(mailboxSession, FROM, mailbox); + byte[] serializedEvent = serializer.serializeEvent(event); + MailboxListener.Event deserializedEvent = serializer.deSerializeEvent(serializedEvent); + assertThat(deserializedEvent.getMailboxPath()).isEqualTo(event.getMailboxPath()); + assertThat(deserializedEvent.getSession().getSessionId()).isEqualTo(event.getSession().getSessionId()); + assertThat(deserializedEvent).isInstanceOf(MailboxListener.MailboxRenamed.class); + assertThat(deserializedEvent.getMailboxPath()).isEqualTo(event.getMailboxPath()); + } + +} diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/JsonEventSerializerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/JsonEventSerializerTest.java new file mode 100644 index 00000000000..2a8abcaedde --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/JsonEventSerializerTest.java @@ -0,0 +1,36 @@ +/**************************************************************** + * 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.store.json; + +import org.apache.james.mailbox.store.TestId; +import org.apache.james.mailbox.store.TestIdDeserializer; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.json.event.EventConverter; +import org.apache.james.mailbox.store.json.event.MailboxConverter; + +public class JsonEventSerializerTest extends EventSerializerTest { + + @Override + EventSerializer createSerializer() { + return new JsonEventSerializer( + new EventConverter( + new MailboxConverter(new TestIdDeserializer()))); + } +} diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/MessagePackEventSerializerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/MessagePackEventSerializerTest.java new file mode 100644 index 00000000000..709e4cc77f5 --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/MessagePackEventSerializerTest.java @@ -0,0 +1,36 @@ +/**************************************************************** + * 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.store.json; + +import org.apache.james.mailbox.store.TestId; +import org.apache.james.mailbox.store.TestIdDeserializer; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.json.event.EventConverter; +import org.apache.james.mailbox.store.json.event.MailboxConverter; + +public class MessagePackEventSerializerTest extends EventSerializerTest { + + @Override + EventSerializer createSerializer() { + return new MessagePackEventSerializer( + new EventConverter( + new MailboxConverter(new TestIdDeserializer()))); + } +} From f0902d5b36a29f9e0d5b8aa95ba14e1d1ada9d72 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:38:35 +0200 Subject: [PATCH 09/21] MAILBOX-211 Interfaces abstracting a publish subscribe system --- .../store/publisher/MessageConsumer.java | 33 +++++++++++++ .../store/publisher/MessageReceiver.java | 26 ++++++++++ .../mailbox/store/publisher/Publisher.java | 35 ++++++++++++++ .../james/mailbox/store/publisher/Topic.java | 47 +++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageConsumer.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageReceiver.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Publisher.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Topic.java diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageConsumer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageConsumer.java new file mode 100644 index 00000000000..b03de3fbd79 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageConsumer.java @@ -0,0 +1,33 @@ +/**************************************************************** + * 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.store.publisher; + +/** + * Will consume messages from exterior sources for the given MessageReceiver + */ +public interface MessageConsumer { + + void setMessageReceiver(MessageReceiver messageReceiver); + + void init(Topic topic) throws Exception; + + void destroy() throws Exception; + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageReceiver.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageReceiver.java new file mode 100644 index 00000000000..3d76524669e --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/MessageReceiver.java @@ -0,0 +1,26 @@ +/**************************************************************** + * 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.store.publisher; + +public interface MessageReceiver { + + void receiveSerializedEvent(byte[] serializedEvent); + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Publisher.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Publisher.java new file mode 100644 index 00000000000..2fcbbfcfc66 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Publisher.java @@ -0,0 +1,35 @@ +/**************************************************************** + * 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.store.publisher; + +import java.io.Closeable; + +/** + * The Publisher can be used to send information outside James. + * + * For instance you can send information to a message queue like Kafka or perform a POST on a restful API + */ +public interface Publisher extends Closeable{ + + void publish(Topic topic, byte[] message); + + void init(); + +} \ No newline at end of file diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Topic.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Topic.java new file mode 100644 index 00000000000..4b8b13b25b4 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/publisher/Topic.java @@ -0,0 +1,47 @@ +/**************************************************************** + * 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.store.publisher; + +import com.google.common.base.Objects; + +public class Topic { + + private final String value; + + public Topic(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Topic topic = (Topic) o; + return Objects.equal(this.value, topic.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} From 2ea5e162301fa6c4559ebab2ccd046f36bdcc2ca Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 30 Oct 2015 11:58:14 +0100 Subject: [PATCH 10/21] MAILBOX-211 Add a registration system based on MailboxPaths and a Cassandra based implementation --- .../cassandra/CassandraMailboxModule.java | 16 +- .../CassandraMailboxPathRegisterMapper.java | 91 +++++ .../cassandra/mail/CassandraACLMapper.java | 2 +- .../CassandraMailboxPathRegisterTable.java | 41 ++ ...ssandraMailboxPathRegistrerMapperTest.java | 106 +++++ .../DistantMailboxPathRegister.java | 195 ++++++++++ .../DistantMailboxPathRegisterMapper.java | 35 ++ .../distributed/MailboxPathRegister.java | 58 +++ .../DistantMailboxPathRegisterTest.java | 364 ++++++++++++++++++ 9 files changed, 905 insertions(+), 3 deletions(-) create mode 100644 mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegisterMapper.java create mode 100644 mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathRegisterTable.java create mode 100644 mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegister.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterMapper.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/MailboxPathRegister.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterTest.java diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxModule.java index 43fb9f0d763..bc1ef5af59c 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxModule.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxModule.java @@ -31,6 +31,7 @@ import org.apache.james.mailbox.cassandra.table.CassandraCurrentQuota; import org.apache.james.mailbox.cassandra.table.CassandraDefaultMaxQuota; import org.apache.james.mailbox.cassandra.table.CassandraMailboxCountersTable; +import org.apache.james.mailbox.cassandra.table.CassandraMailboxPathRegisterTable; import org.apache.james.mailbox.cassandra.table.CassandraMailboxTable; import org.apache.james.mailbox.cassandra.table.CassandraMaxQuota; import org.apache.james.mailbox.cassandra.table.CassandraMessageModseqTable; @@ -128,7 +129,12 @@ public CassandraMailboxModule() { SchemaBuilder.createTable(CassandraDefaultMaxQuota.TABLE_NAME) .ifNotExists() .addPartitionKey(CassandraDefaultMaxQuota.TYPE, text()) - .addColumn(CassandraDefaultMaxQuota.VALUE, bigint()))); + .addColumn(CassandraDefaultMaxQuota.VALUE, bigint())), + new CassandraTable(CassandraMailboxPathRegisterTable.TABLE_NAME, + SchemaBuilder.createTable(CassandraMailboxPathRegisterTable.TABLE_NAME) + .ifNotExists() + .addUDTPartitionKey(CassandraMailboxPathRegisterTable.MAILBOX_PATH, SchemaBuilder.frozen(CassandraMailboxPathRegisterTable.MAILBOX_PATH)) + .addClusteringColumn(CassandraMailboxPathRegisterTable.TOPIC, text()))); index = Arrays.asList( new CassandraIndex( SchemaBuilder.createIndex(CassandraIndex.INDEX_PREFIX + CassandraMailboxTable.TABLE_NAME) @@ -171,7 +177,13 @@ public CassandraMailboxModule() { .ifNotExists() .addColumn(CassandraMessageTable.Properties.NAMESPACE, text()) .addColumn(CassandraMessageTable.Properties.NAME, text()) - .addColumn(CassandraMessageTable.Properties.VALUE, text()))); + .addColumn(CassandraMessageTable.Properties.VALUE, text())), + new CassandraType(CassandraMailboxPathRegisterTable.MAILBOX_PATH, + SchemaBuilder.createType(CassandraMailboxPathRegisterTable.MAILBOX_PATH) + .ifNotExists() + .addColumn(CassandraMailboxPathRegisterTable.MailboxPath.NAMESPACE, text()) + .addColumn(CassandraMailboxPathRegisterTable.MailboxPath.NAME, text()) + .addColumn(CassandraMailboxPathRegisterTable.MailboxPath.USER, text()))); } @Override diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegisterMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegisterMapper.java new file mode 100644 index 00000000000..fd648f53d2c --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegisterMapper.java @@ -0,0 +1,91 @@ +/**************************************************************** + * 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.cassandra.event.distributed; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.driver.core.querybuilder.QueryBuilder.delete; +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto; +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; +import static com.datastax.driver.core.querybuilder.QueryBuilder.ttl; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.UDTValue; +import org.apache.james.backends.cassandra.init.CassandraTypesProvider; +import org.apache.james.backends.cassandra.utils.CassandraUtils; +import org.apache.james.mailbox.cassandra.table.CassandraMailboxPathRegisterTable; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.event.distributed.DistantMailboxPathRegisterMapper; +import org.apache.james.mailbox.store.publisher.Topic; + +import java.util.Set; +import java.util.stream.Collectors; + +public class CassandraMailboxPathRegisterMapper implements DistantMailboxPathRegisterMapper { + + private final Session session; + private final CassandraTypesProvider typesProvider; + private final int cassandraTimeOutInS; + private final PreparedStatement insertStatement; + private final PreparedStatement deleteStatement; + private final PreparedStatement selectStatement; + + public CassandraMailboxPathRegisterMapper(Session session, CassandraTypesProvider typesProvider, int cassandraTimeOutInS) { + this.session = session; + this.typesProvider = typesProvider; + this.cassandraTimeOutInS = cassandraTimeOutInS; + this.insertStatement = session.prepare(insertInto(CassandraMailboxPathRegisterTable.TABLE_NAME) + .value(CassandraMailboxPathRegisterTable.MAILBOX_PATH, bindMarker()) + .value(CassandraMailboxPathRegisterTable.TOPIC, bindMarker()) + .using(ttl(bindMarker()))); + this.deleteStatement = session.prepare(delete().from(CassandraMailboxPathRegisterTable.TABLE_NAME) + .where(eq(CassandraMailboxPathRegisterTable.MAILBOX_PATH, bindMarker())) + .and(eq(CassandraMailboxPathRegisterTable.TOPIC, bindMarker()))); + this.selectStatement = session.prepare(select().from(CassandraMailboxPathRegisterTable.TABLE_NAME) + .where(eq(CassandraMailboxPathRegisterTable.MAILBOX_PATH, bindMarker()))); + } + + @Override + public Set getTopics(MailboxPath mailboxPath) { + return CassandraUtils.convertToStream(session.execute(selectStatement.bind(buildUDTFromMailboxPath(mailboxPath)))) + .map(row -> new Topic(row.getString(CassandraMailboxPathRegisterTable.TOPIC))) + .collect(Collectors.toSet()); + } + + @Override + public void doRegister(MailboxPath mailboxPath, Topic topic) { + session.execute(insertStatement.bind(buildUDTFromMailboxPath(mailboxPath), topic.getValue(), cassandraTimeOutInS)); + } + + @Override + public void doUnRegister(MailboxPath mailboxPath, Topic topic) { + session.execute(deleteStatement.bind(buildUDTFromMailboxPath(mailboxPath), topic.getValue())); + } + + private UDTValue buildUDTFromMailboxPath(MailboxPath path) { + return typesProvider.getDefinedUserType(CassandraMailboxPathRegisterTable.MAILBOX_PATH) + .newValue() + .setString(CassandraMailboxPathRegisterTable.MailboxPath.NAMESPACE, path.getNamespace()) + .setString(CassandraMailboxPathRegisterTable.MailboxPath.USER, path.getUser()) + .setString(CassandraMailboxPathRegisterTable.MailboxPath.NAME, path.getName()); + } + +} diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java index 24e0f11b57d..9441c9dcc79 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java @@ -32,13 +32,13 @@ import org.apache.james.backends.cassandra.utils.LightweightTransactionException; import org.apache.james.mailbox.cassandra.CassandraId; import org.apache.james.backends.cassandra.utils.FunctionRunnerWithRetry; -import org.apache.james.mailbox.cassandra.mail.utils.SimpleMailboxACLJsonConverter; import org.apache.james.mailbox.cassandra.table.CassandraACLTable; import org.apache.james.mailbox.cassandra.table.CassandraMailboxTable; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.UnsupportedRightException; import org.apache.james.mailbox.model.MailboxACL; import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.json.SimpleMailboxACLJsonConverter; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathRegisterTable.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathRegisterTable.java new file mode 100644 index 00000000000..48146cc46b7 --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathRegisterTable.java @@ -0,0 +1,41 @@ +/**************************************************************** + * 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.cassandra.table; + +/** + * Created by benwa on 05/10/15. + *

+ * Project under the Apache v 2 license + */ +public interface CassandraMailboxPathRegisterTable { + + String TABLE_NAME = "mailboxPathRegister"; + + String MAILBOX_PATH = "mailboxPath"; + + String TOPIC = "topic"; + + interface MailboxPath { + String NAMESPACE = "namespace"; + String USER = "user"; + String NAME = "name"; + } + +} diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java new file mode 100644 index 00000000000..6a44b9b2c18 --- /dev/null +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java @@ -0,0 +1,106 @@ +/**************************************************************** + * 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.cassandra.event.distributed; + +import org.apache.james.backends.cassandra.CassandraClusterSingleton; +import org.apache.james.mailbox.cassandra.CassandraMailboxModule; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.publisher.Topic; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CassandraMailboxPathRegistrerMapperTest { + + private static final CassandraClusterSingleton cassandra = CassandraClusterSingleton.create(new CassandraMailboxModule()); + private static final MailboxPath MAILBOX_PATH = new MailboxPath("namespace", "user", "name"); + private static final MailboxPath MAILBOX_PATH_2 = new MailboxPath("namespace2", "user2", "name2"); + private static final Topic TOPIC = new Topic("topic"); + private static final int CASSANDRA_TIME_OUT_IN_S = 1; + private static final int CASSANDRA_TIME_OUT_IN_MS = 1000 * CASSANDRA_TIME_OUT_IN_S; + private static final Topic TOPIC_2 = new Topic("topic2"); + + private CassandraMailboxPathRegisterMapper mapper; + + @Before + public void setUp() { + mapper = new CassandraMailboxPathRegisterMapper(cassandra.getConf(), cassandra.getTypesProvider(), CASSANDRA_TIME_OUT_IN_S); + } + + @After + public void tearDown() { + cassandra.clearAllTables(); + } + + @Test + public void getTopicsShouldReturnEmptyResultByDefault() { + assertThat(mapper.getTopics(MAILBOX_PATH)).isEmpty(); + } + + @Test + public void doRegisterShouldWork() { + mapper.doRegister(MAILBOX_PATH, TOPIC); + assertThat(mapper.getTopics(MAILBOX_PATH)).containsOnly(TOPIC); + } + + @Test + public void doRegisterShouldBeMailboxPathSpecific() { + mapper.doRegister(MAILBOX_PATH, TOPIC); + assertThat(mapper.getTopics(MAILBOX_PATH_2)).isEmpty(); + } + + @Test + public void doRegisterShouldAllowMultipleTopics() { + mapper.doRegister(MAILBOX_PATH, TOPIC); + mapper.doRegister(MAILBOX_PATH, TOPIC_2); + assertThat(mapper.getTopics(MAILBOX_PATH)).containsOnly(TOPIC, TOPIC_2); + } + + @Test + public void doUnRegisterShouldWork() { + mapper.doRegister(MAILBOX_PATH, TOPIC); + mapper.doUnRegister(MAILBOX_PATH, TOPIC); + assertThat(mapper.getTopics(MAILBOX_PATH)).isEmpty(); + } + + @Test + public void doUnregisterShouldBeMailboxSpecific() { + mapper.doRegister(MAILBOX_PATH, TOPIC); + mapper.doUnRegister(MAILBOX_PATH_2, TOPIC); + assertThat(mapper.getTopics(MAILBOX_PATH)).containsOnly(TOPIC); + } + + @Test + public void doUnregisterShouldBeTopicSpecific() { + mapper.doRegister(MAILBOX_PATH, TOPIC); + mapper.doUnRegister(MAILBOX_PATH, TOPIC_2); + assertThat(mapper.getTopics(MAILBOX_PATH)).containsOnly(TOPIC); + } + + @Test + public void entriesShouldExpire() throws Exception { + mapper.doRegister(MAILBOX_PATH, TOPIC); + Thread.sleep(2 * CASSANDRA_TIME_OUT_IN_MS); + assertThat(mapper.getTopics(MAILBOX_PATH)).isEmpty(); + } + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegister.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegister.java new file mode 100644 index 00000000000..be367aa835d --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegister.java @@ -0,0 +1,195 @@ +/**************************************************************** + * 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.store.event.distributed; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.publisher.Topic; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class DistantMailboxPathRegister implements MailboxPathRegister { + private static final int DEFAULT_MAX_RETRY = 1000; + private final ConcurrentHashMap registeredMailboxPathCount; + private final DistantMailboxPathRegisterMapper mapper; + private final Topic topic; + private final Timer timer; + private final int maxRetry; + private final long schedulerPeriodInS; + + public DistantMailboxPathRegister(DistantMailboxPathRegisterMapper mapper, long schedulerPeriodInS) { + this(mapper, DEFAULT_MAX_RETRY, schedulerPeriodInS); + } + + public DistantMailboxPathRegister(DistantMailboxPathRegisterMapper mapper, int maxRetry, long schedulerPeriodInS) { + this.maxRetry = maxRetry; + this.mapper = mapper; + this.registeredMailboxPathCount = new ConcurrentHashMap(); + this.topic = new Topic(UUID.randomUUID().toString()); + this.timer = new Timer(); + this.schedulerPeriodInS = schedulerPeriodInS; + } + + @PostConstruct + public void init() { + timer.schedule(new TimerTask() { + @Override + public void run() { + Set> snapshot = ImmutableSet.copyOf(registeredMailboxPathCount.entrySet()); + for(Map.Entry entry : snapshot) { + if (entry.getValue() > 0) { + mapper.doRegister(entry.getKey(), topic); + } + } + } + }, 0L, schedulerPeriodInS * 1000); + } + + @PreDestroy + public void destroy() { + timer.cancel(); + timer.purge(); + } + + @Override + public Set getTopics(MailboxPath mailboxPath) { + return mapper.getTopics(mailboxPath); + } + + @Override + public Topic getLocalTopic() { + return topic; + } + + @Override + public void register(MailboxPath path) throws MailboxException { + int count = 0; + boolean success = false; + while (count < maxRetry && !success) { + count ++; + success = tryRegister(path); + } + if (!success) { + throw new MailboxException(maxRetry + " reached while trying to register " + path); + } + } + + @Override + public void unregister(MailboxPath path) throws MailboxException { + int count = 0; + boolean success = false; + while (count < maxRetry && !success) { + count ++; + success = tryUnregister(path); + } + if (!success) { + throw new MailboxException(maxRetry + " reached while trying to unregister " + path); + } + } + + @Override + public void doCompleteUnRegister(MailboxPath mailboxPath) { + registeredMailboxPathCount.remove(mailboxPath); + mapper.doUnRegister(mailboxPath, topic); + } + + @Override + public void doRename(MailboxPath oldPath, MailboxPath newPath) throws MailboxException { + try { + int count = 0; + boolean success = false; + while (count < maxRetry && !success) { + success = tryCountTransfer(oldPath, newPath); + } + if (!success) { + throw new MailboxException(maxRetry + " reached while trying to rename " + oldPath + " in " + newPath); + } + } finally { + doCompleteUnRegister(oldPath); + } + } + + private boolean tryCountTransfer(MailboxPath oldPath, MailboxPath newPath) throws MailboxException { + Long oldEntry = registeredMailboxPathCount.get(oldPath); + if (oldEntry == null) { + throw new MailboxException("Renamed entry does not exists"); + } + Long entry = registeredMailboxPathCount.get(newPath); + if (entry != null) { + return registeredMailboxPathCount.replace(newPath, entry, oldEntry + entry); + } else { + if (registeredMailboxPathCount.putIfAbsent(newPath, oldEntry) == null) { + mapper.doRegister(newPath, topic); + return true; + } + return false; + } + } + + private boolean tryRegister(MailboxPath path) { + Long entry = registeredMailboxPathCount.get(path); + Long newEntry = entry; + if (newEntry == null) { + newEntry = 0L; + } + newEntry++; + if (entry != null) { + return registeredMailboxPathCount.replace(path, entry, newEntry); + } else { + if (registeredMailboxPathCount.putIfAbsent(path, newEntry) == null) { + mapper.doRegister(path, topic); + return true; + } + return false; + } + } + + private boolean tryUnregister(MailboxPath path) throws MailboxException { + Long entry = registeredMailboxPathCount.get(path); + Long newEntry = entry; + if (newEntry == null) { + throw new MailboxException("Removing a non registered mailboxPath"); + } + newEntry--; + if (newEntry != 0) { + return registeredMailboxPathCount.replace(path, entry, newEntry); + } else { + if (registeredMailboxPathCount.remove(path, entry)) { + mapper.doUnRegister(path, topic); + return true; + } + return false; + } + } + + @VisibleForTesting + ConcurrentHashMap getRegisteredMailboxPathCount() { + return registeredMailboxPathCount; + } +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterMapper.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterMapper.java new file mode 100644 index 00000000000..8c5f6453aba --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterMapper.java @@ -0,0 +1,35 @@ +/**************************************************************** + * 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.store.event.distributed; + +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.publisher.Topic; + +import java.util.Set; + +public interface DistantMailboxPathRegisterMapper { + + Set getTopics(MailboxPath mailboxPath); + + void doRegister(MailboxPath mailboxPath, Topic topic); + + void doUnRegister(MailboxPath mailboxPath, Topic topic); + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/MailboxPathRegister.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/MailboxPathRegister.java new file mode 100644 index 00000000000..a27c908893b --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/MailboxPathRegister.java @@ -0,0 +1,58 @@ +/**************************************************************** + * 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.store.event.distributed; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.publisher.Topic; + +import java.util.Set; + +/** + * The TopicDispatcher allow you to : + * + * - know to which queues you will need to send an event + * - get the topic this James instance will be pooling + */ +public interface MailboxPathRegister { + + /** + * Given a MailboxPath, we want to get the different topics we need to send the event to. + * + * @param mailboxPath MailboxPath + * @return List of topics concerned by this MailboxPath + */ + Set getTopics(MailboxPath mailboxPath); + + /** + * Get the topic this James instance will consume + * + * @return The topic this James instance will consume + */ + Topic getLocalTopic(); + + void register(MailboxPath path) throws MailboxException; + + void unregister(MailboxPath path) throws MailboxException; + + void doCompleteUnRegister(MailboxPath mailboxPath); + + void doRename(MailboxPath oldPath, MailboxPath newPath) throws MailboxException; +} \ No newline at end of file diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterTest.java new file mode 100644 index 00000000000..2227a797b92 --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/DistantMailboxPathRegisterTest.java @@ -0,0 +1,364 @@ +/**************************************************************** + * 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.store.event.distributed; + +import static org.assertj.core.api.Assertions.assertThat; + +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Sets; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class DistantMailboxPathRegisterTest { + + private static final MailboxPath MAILBOX_PATH = new MailboxPath("namespace", "user", "name"); + private static final MailboxPath NEW_MAILBOX_PATH = new MailboxPath("namespace_new", "user_new", "name_new"); + private static final String TOPIC = "topic"; + + private DistantMailboxPathRegisterMapper mockedMapper; + private DistantMailboxPathRegister register; + + @Before + public void setUp() { + mockedMapper = mock(DistantMailboxPathRegisterMapper.class); + register = new DistantMailboxPathRegister(mockedMapper, 1); + } + + @Test(expected = MailboxException.class) + public void doRenameShouldThrowIfTryingToRenameNonExistingPath() throws Exception { + register.doRename(MAILBOX_PATH, NEW_MAILBOX_PATH); + verifyNoMoreInteractions(mockedMapper); + } + + @Test + public void getTopicsShouldWork() { + final HashSet result = Sets.newHashSet(TOPIC); + when(mockedMapper.getTopics(MAILBOX_PATH)).thenAnswer(new Answer>() { + @Override + public Set answer(InvocationOnMock invocationOnMock) throws Throwable { + return result; + } + }); + assertThat(register.getTopics(MAILBOX_PATH)).isEqualTo(result); + } + + @Test + public void registerShouldWork() throws MailboxException { + register.register(MAILBOX_PATH); + verify(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + verifyNoMoreInteractions(mockedMapper); + } + + @Test + public void registerShouldCallMapperOnce() throws MailboxException { + register.register(MAILBOX_PATH); + verify(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + register.register(MAILBOX_PATH); + verifyNoMoreInteractions(mockedMapper); + } + + @Test + public void unregisterShouldWork() throws MailboxException { + register.register(MAILBOX_PATH); + verify(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + register.unregister(MAILBOX_PATH); + verify(mockedMapper).doUnRegister(MAILBOX_PATH, register.getLocalTopic()); + verifyNoMoreInteractions(mockedMapper); + } + + + @Test + public void unregisterShouldNotCallMapperIfListenersAreStillPresent() throws MailboxException { + register.register(MAILBOX_PATH); + verify(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + register.register(MAILBOX_PATH); + register.unregister(MAILBOX_PATH); + verifyNoMoreInteractions(mockedMapper); + verifyNoMoreInteractions(mockedMapper); + } + + @Test + public void unregisterShouldWorkWhenMultipleListenersWereRegistered() throws MailboxException { + register.register(MAILBOX_PATH); + verify(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + register.register(MAILBOX_PATH); + register.unregister(MAILBOX_PATH); + verifyNoMoreInteractions(mockedMapper); + register.unregister(MAILBOX_PATH); + verify(mockedMapper).doUnRegister(MAILBOX_PATH, register.getLocalTopic()); + verifyNoMoreInteractions(mockedMapper); + } + + @Test + public void doRenameShouldWork() throws Exception { + register.register(MAILBOX_PATH); + verify(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + register.doRename(MAILBOX_PATH, NEW_MAILBOX_PATH); + verify(mockedMapper).doRegister(NEW_MAILBOX_PATH, register.getLocalTopic()); + verify(mockedMapper).doUnRegister(MAILBOX_PATH, register.getLocalTopic()); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(NEW_MAILBOX_PATH, 1L); + verifyNoMoreInteractions(mockedMapper); + } + + @Test + public void doRenameShouldWorkWhenEntryAlreadyExists() throws Exception { + register.register(MAILBOX_PATH); + verify(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + register.register(NEW_MAILBOX_PATH); + verify(mockedMapper).doRegister(NEW_MAILBOX_PATH, register.getLocalTopic()); + register.doRename(MAILBOX_PATH, NEW_MAILBOX_PATH); + verify(mockedMapper).doUnRegister(MAILBOX_PATH, register.getLocalTopic()); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(NEW_MAILBOX_PATH, 2L); + verifyNoMoreInteractions(mockedMapper); + } + + @Test + public void mapShouldBeEmptyInitially() { + assertThat(register.getRegisteredMailboxPathCount()).isEmpty(); + } + + @Test + public void mapShouldContainOneListenerOnPathAfterRegister() throws MailboxException { + register.register(MAILBOX_PATH); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(MAILBOX_PATH, 1L); + } + + @Test + public void mapShouldContainTwoListenerOnPathAfterTwoRegister() throws MailboxException { + register.register(MAILBOX_PATH); + register.register(MAILBOX_PATH); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(MAILBOX_PATH, 2L); + } + + @Test + public void mapListenerCountShouldBeOkAfterTwoRegisterAndOneUnregister() throws MailboxException { + register.register(MAILBOX_PATH); + register.register(MAILBOX_PATH); + register.unregister(MAILBOX_PATH); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(MAILBOX_PATH, 1L); + } + + @Test + public void mapListenerCountShouldBeEmptyAfterTwoRegisterAndOneUnregister() throws MailboxException { + register.register(MAILBOX_PATH); + register.unregister(MAILBOX_PATH); + assertThat(register.getRegisteredMailboxPathCount()).isEmpty(); + } + + @Test + public void mapListenerCountShouldBeEmptyAfterDoCompleteUnregister() throws MailboxException { + register.register(MAILBOX_PATH); + register.doCompleteUnRegister(MAILBOX_PATH); + assertThat(register.getRegisteredMailboxPathCount()).isEmpty(); + } + + @Test + public void mapListenerCountShouldHandleRename() throws Exception { + register.register(MAILBOX_PATH); + register.doRename(MAILBOX_PATH, NEW_MAILBOX_PATH); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(NEW_MAILBOX_PATH, 1L); + } + + @Test + public void mapListenerCountShouldHandleRenameWhenEntryAlreadyExists() throws Exception { + register.register(MAILBOX_PATH); + register.register(NEW_MAILBOX_PATH); + register.doRename(MAILBOX_PATH, NEW_MAILBOX_PATH); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(NEW_MAILBOX_PATH, 2L); + } + + @Test + public void registerShouldNotBeAffectedByMapperError() throws MailboxException { + doThrow(new RuntimeException()).when(mockedMapper).doRegister(MAILBOX_PATH, register.getLocalTopic()); + try { + register.register(MAILBOX_PATH); + fail("Register should have thrown"); + } catch (RuntimeException e) { + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(MAILBOX_PATH, 1L); + } + } + + @Test + public void unregisterShouldNotBeAffectedByMapperErrors() throws MailboxException { + register.register(MAILBOX_PATH); + doThrow(new RuntimeException()).when(mockedMapper).doUnRegister(MAILBOX_PATH, register.getLocalTopic()); + try { + register.unregister(MAILBOX_PATH); + fail("Register should have thrown"); + } catch (RuntimeException e) { + assertThat(register.getRegisteredMailboxPathCount()).isEmpty(); + } + } + + @Test + public void renameShouldNotBeAffectedByMapperErrors() throws MailboxException { + register.register(MAILBOX_PATH); + doThrow(new RuntimeException()).when(mockedMapper).doRegister(NEW_MAILBOX_PATH, register.getLocalTopic()); + try { + register.doRename(MAILBOX_PATH, NEW_MAILBOX_PATH); + fail("Register should have thrown"); + } catch (RuntimeException e) { + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(NEW_MAILBOX_PATH, 1L) + .doesNotContainKey(MAILBOX_PATH); + } + } + + @Test + public void completeUnregisterShouldNotBeAffectedByMapperErrors() throws MailboxException { + register.register(MAILBOX_PATH); + doThrow(new RuntimeException()).when(mockedMapper).doUnRegister(MAILBOX_PATH, register.getLocalTopic()); + try { + register.doCompleteUnRegister(MAILBOX_PATH); + fail("Register should have thrown"); + } catch (RuntimeException e) { + assertThat(register.getRegisteredMailboxPathCount()).isEmpty(); + } + } + + @Test + public void registerShouldWorkInAConcurrentEnvironment() throws Exception { + int numTask = 2; + final long increments = 100; + ExecutorService executorService = Executors.newFixedThreadPool(numTask); + for (int i = 0; i < numTask; i++) { + executorService.submit(new Runnable() { + @Override + public void run() { + try { + int j = 0; + while (j < increments) { + register.register(MAILBOX_PATH); + j++; + } + } catch (Exception e) { + fail("Exception caught in thread", e); + } + } + }); + } + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(MAILBOX_PATH, numTask * increments); + } + + @Test + public void unregisterShouldWorkInAConcurrentEnvironment() throws Exception { + int numTask = 2; + final long increments = 100; + for (int i = 0; i < numTask * increments; i++) { + register.register(MAILBOX_PATH); + } + ExecutorService executorService = Executors.newFixedThreadPool(numTask); + for (int i = 0; i < numTask; i++) { + executorService.submit(new Runnable() { + @Override + public void run() { + try { + int j = 0; + while (j < increments) { + register.unregister(MAILBOX_PATH); + j++; + } + } catch (Exception e) { + fail("Exception caught in thread", e); + } + } + }); + } + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + assertThat(register.getRegisteredMailboxPathCount()).isEmpty(); + } + + @Test + public void unregisterMixedWithRegisterShouldWorkInAConcurrentEnvironment() throws Exception { + int numTask = 2; + final long increments = 100; + for (int i = 0; i < increments; i++) { + register.register(MAILBOX_PATH); + } + ExecutorService executorService = Executors.newFixedThreadPool(2* numTask); + for (int i = 0; i < numTask; i++) { + executorService.submit(new Runnable() { + @Override + public void run() { + try { + int j = 0; + while (j < increments) { + register.register(MAILBOX_PATH); + j++; + } + } catch (Exception e) { + fail("Exception caught in thread", e); + } + } + }); + executorService.submit(new Runnable() { + @Override + public void run() { + try { + int j = 0; + while (j < increments) { + register.unregister(MAILBOX_PATH); + j++; + } + } catch (Exception e) { + fail("Exception caught in thread", e); + } + } + }); + } + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + assertThat(register.getRegisteredMailboxPathCount()).containsEntry(MAILBOX_PATH, increments); + } + + @Test + public void schedulerShouldWork() throws Exception { + register.register(MAILBOX_PATH); + try { + register.init(); + Thread.sleep(1050); + + } finally { + register.destroy(); + } + verify(mockedMapper, times(3)).doRegister(MAILBOX_PATH, register.getLocalTopic()); + verifyNoMoreInteractions(mockedMapper); + } + +} From f70f7f6f0d035ccecfa41c1df80046c179cdf82b Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 08:42:07 +0200 Subject: [PATCH 11/21] MAILBOX-211 Event processing should not be stopped by error thrown --- .../DefaultDelegatingMailboxListener.java | 12 ++++++- .../DefaultDelegatingMailboxListenerTest.java | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java index 16274f37234..6350849fe2b 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java @@ -97,7 +97,17 @@ protected void deliverEventToGlobalListeners(Event event) { } private void deliverEvent(Event event, MailboxListener listener) { - listener.event(event); + try { + listener.event(event); + } catch(Throwable throwable) { + event.getSession() + .getLog() + .error("Error while processing listener " + + listener.getClass().getCanonicalName() + + " for " + + event.getClass().getCanonicalName(), + throwable); + } } } diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java index b2194c2613e..141c9cafa0d 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListenerTest.java @@ -21,15 +21,31 @@ import static org.assertj.core.api.Assertions.assertThat; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.mock.MockMailboxSession; import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.util.EventCollector; import org.junit.Before; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DefaultDelegatingMailboxListenerTest { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDelegatingMailboxListenerTest.class); + private static final MailboxPath MAILBOX_PATH = new MailboxPath("namespace", "user", "name"); private static final MailboxPath OTHER_MAILBOX_PATH = new MailboxPath("namespace", "other", "name"); @@ -179,4 +195,20 @@ public void removeGlobalListenerShouldWorkForEACH_NODE() throws Exception { assertThat(onceEventCollector.getEvents()).isEmpty(); } + @Test + public void listenersErrorsShouldNotBePropageted() throws Exception { + MailboxSession session = new MockMailboxSession("benwa"); + MailboxListener.Event event = new MailboxListener.Event(session, MAILBOX_PATH) {}; + MailboxListener mockedListener = mock(MailboxListener.class); + when(mockedListener.getType()).thenAnswer(new Answer() { + @Override + public MailboxListener.ListenerType answer(InvocationOnMock invocation) throws Throwable { + return MailboxListener.ListenerType.ONCE; + } + }); + doThrow(new RuntimeException()).when(mockedListener).event(event); + defaultDelegatingMailboxListener.addGlobalListener(mockedListener, null); + defaultDelegatingMailboxListener.event(event); + } + } From ce5cb28984679cb1656b7e651cfdcb2ef0ac5976 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 10:38:11 +0200 Subject: [PATCH 12/21] MAILBOX-211 Distributed Delegating Mailbox Listener and a registered based implementation --- .../DefaultDelegatingMailboxListener.java | 4 +- .../DistributedDelegatingMailboxListener.java | 27 ++ .../RegisteredDelegatingMailboxListener.java | 174 +++++++++++ ...gisteredDelegatingMailboxListenerTest.java | 278 ++++++++++++++++++ 4 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistributedDelegatingMailboxListener.java create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListener.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListenerTest.java diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java index 6350849fe2b..2222d7bfe99 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/DefaultDelegatingMailboxListener.java @@ -74,9 +74,9 @@ public void removeGlobalListener(MailboxListener listener, MailboxSession sessio @Override public void event(Event event) { Collection listenerSnapshot = registry.getLocalMailboxListeners(event.getMailboxPath()); - if (event instanceof MailboxDeletion) { + if (event instanceof MailboxDeletion && listenerSnapshot.size() > 0) { registry.deleteRegistryFor(event.getMailboxPath()); - } else if (event instanceof MailboxRenamed) { + } else if (event instanceof MailboxRenamed && listenerSnapshot.size() > 0) { MailboxRenamed renamed = (MailboxRenamed) event; registry.handleRename(renamed.getMailboxPath(), renamed.getNewPath()); } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistributedDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistributedDelegatingMailboxListener.java new file mode 100644 index 00000000000..d78688f6880 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/DistributedDelegatingMailboxListener.java @@ -0,0 +1,27 @@ +/**************************************************************** + * 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.store.event.distributed; + +import org.apache.james.mailbox.store.event.DelegatingMailboxListener; +import org.apache.james.mailbox.store.publisher.MessageReceiver; + +public interface DistributedDelegatingMailboxListener extends DelegatingMailboxListener, MessageReceiver { + +} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListener.java new file mode 100644 index 00000000000..021360d1d5a --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListener.java @@ -0,0 +1,174 @@ +/**************************************************************** + * 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.store.event.distributed; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.event.MailboxListenerRegistry; +import org.apache.james.mailbox.store.publisher.MessageConsumer; +import org.apache.james.mailbox.store.publisher.Publisher; +import org.apache.james.mailbox.store.publisher.Topic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Set; + +public class RegisteredDelegatingMailboxListener implements DistributedDelegatingMailboxListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(RegisteredDelegatingMailboxListener.class); + + private final MailboxListenerRegistry mailboxListenerRegistry; + private final MailboxPathRegister mailboxPathRegister; + private final Publisher publisher; + private final EventSerializer eventSerializer; + + public RegisteredDelegatingMailboxListener(EventSerializer eventSerializer, + Publisher publisher, + MessageConsumer messageConsumer, + MailboxPathRegister mailboxPathRegister) throws Exception { + this.eventSerializer = eventSerializer; + this.publisher = publisher; + this.mailboxPathRegister = mailboxPathRegister; + this.mailboxListenerRegistry = new MailboxListenerRegistry(); + messageConsumer.setMessageReceiver(this); + messageConsumer.init(mailboxPathRegister.getLocalTopic()); + } + + @Override + public ListenerType getType() { + return ListenerType.ONCE; + } + + @Override + public void addListener(MailboxPath path, MailboxListener listener, MailboxSession session) throws MailboxException { + mailboxListenerRegistry.addListener(path, listener); + mailboxPathRegister.register(path); + } + + @Override + public void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + if (listener.getType().equals(ListenerType.EACH_NODE)) { + throw new MailboxException("Attempt to register a global listener that need to be called on each node while using a non compatible delegating listeners"); + } + mailboxListenerRegistry.addGlobalListener(listener); + } + + @Override + public void removeListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException { + mailboxListenerRegistry.removeListener(mailboxPath, listener); + mailboxPathRegister.unregister(mailboxPath); + } + + @Override + public void removeGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + mailboxListenerRegistry.removeGlobalListener(listener); + } + + @Override + public void event(Event event) { + try { + deliverEventToOnceGlobalListeners(event); + deliverToMailboxPathRegisteredListeners(event); + sendToRemoteJames(event); + } catch (Throwable t) { + event.getSession() + .getLog() + .error("Error while delegating event " + event.getClass().getCanonicalName(), t); + } + } + + public void receiveSerializedEvent(byte[] serializedEvent) { + try { + Event event = eventSerializer.deSerializeEvent(serializedEvent); + deliverToMailboxPathRegisteredListeners(event); + } catch (Exception e) { + LOGGER.error("Error while receiving serialized event", e); + } + } + + private void deliverToMailboxPathRegisteredListeners(Event event) throws MailboxException { + Collection listenerSnapshot = mailboxListenerRegistry.getLocalMailboxListeners(event.getMailboxPath()); + if (event instanceof MailboxDeletion && listenerSnapshot.size() > 0) { + mailboxListenerRegistry.deleteRegistryFor(event.getMailboxPath()); + mailboxPathRegister.doCompleteUnRegister(event.getMailboxPath()); + } else if (event instanceof MailboxRenamed && listenerSnapshot.size() > 0) { + MailboxRenamed renamed = (MailboxRenamed) event; + mailboxListenerRegistry.handleRename(renamed.getMailboxPath(), renamed.getNewPath()); + mailboxPathRegister.doRename(renamed.getMailboxPath(), renamed.getNewPath()); + } + for (MailboxListener listener : listenerSnapshot) { + deliverEvent(event, listener); + } + } + + private void deliverEventToOnceGlobalListeners(Event event) { + for (MailboxListener mailboxListener : mailboxListenerRegistry.getGlobalListeners()) { + if (mailboxListener.getType() == ListenerType.ONCE) { + deliverEvent(event, mailboxListener); + } + } + } + + private void sendToRemoteJames(Event event) { + Set topics = mailboxPathRegister.getTopics(event.getMailboxPath()); + topics.remove(mailboxPathRegister.getLocalTopic()); + if (topics.size() > 0) { + sendEventToRemotesJamesByTopic(event, topics); + } + } + + private void sendEventToRemotesJamesByTopic(Event event, Set topics) { + byte[] serializedEvent; + try { + serializedEvent = eventSerializer.serializeEvent(event); + } catch (Exception e) { + event.getSession() + .getLog() + .error("Unable to serialize " + event.getClass().getCanonicalName(), e); + return; + } + for (Topic topic : topics) { + try { + publisher.publish(topic, serializedEvent); + } catch (Throwable t) { + event.getSession().getLog().error("Unable to send serialized event to topic " + topic); + } + } + } + + private void deliverEvent(Event event, MailboxListener listener) { + try { + listener.event(event); + } catch(Throwable throwable) { + event.getSession() + .getLog() + .error("Error while processing listener " + + listener.getClass().getCanonicalName() + + " for " + + event.getClass().getCanonicalName(), + throwable); + } + } + +} diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListenerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListenerTest.java new file mode 100644 index 00000000000..977acdfbc7a --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/RegisteredDelegatingMailboxListenerTest.java @@ -0,0 +1,278 @@ +/**************************************************************** + * 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.store.event.distributed; + +import static org.assertj.core.api.Assertions.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Sets; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.publisher.MessageConsumer; +import org.apache.james.mailbox.store.publisher.Publisher; +import org.apache.james.mailbox.store.publisher.Topic; +import org.apache.james.mailbox.util.EventCollector; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.Set; + +public class RegisteredDelegatingMailboxListenerTest { + + private static final MailboxPath MAILBOX_PATH = new MailboxPath("namespace", "user", "name"); + private static final MailboxPath MAILBOX_PATH_NEW = new MailboxPath("namespace_new", "user_new", "name_new"); + private static final Topic TOPIC = new Topic("topic"); + private static final Topic TOPIC_2 = new Topic("topic_2"); + private static final byte[] BYTES = new byte[0]; + private static final MailboxSession mailboxSession = new MockMailboxSession("benwa"); + public static final MailboxListener.Event EVENT = new MailboxListener.Event(mailboxSession, MAILBOX_PATH) {}; + + private RegisteredDelegatingMailboxListener testee; + private MailboxPathRegister mockedMailboxPathRegister; + private EventSerializer mockedEventSerializer; + private Publisher mockedPublisher; + private EventCollector mailboxEventCollector; + private EventCollector eachEventCollector; + private EventCollector onceEventCollector; + + @Before + public void setUp() throws Exception { + mockedEventSerializer = mock(EventSerializer.class); + mockedPublisher = mock(Publisher.class); + mockedMailboxPathRegister = mock(MailboxPathRegister.class); + MessageConsumer messageConsumer = mock(MessageConsumer.class); + testee = new RegisteredDelegatingMailboxListener(mockedEventSerializer, mockedPublisher, messageConsumer, mockedMailboxPathRegister); + mailboxEventCollector = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eachEventCollector = new EventCollector(MailboxListener.ListenerType.EACH_NODE); + onceEventCollector = new EventCollector(MailboxListener.ListenerType.ONCE); + } + + @Test + public void eventShouldBeLocallyDeliveredIfThereIsNoOtherRegisteredServers() throws Exception { + testee.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + verify(mockedMailboxPathRegister).register(MAILBOX_PATH); + when(mockedMailboxPathRegister.getTopics(MAILBOX_PATH)).thenAnswer(new Answer>() { + @Override + public Set answer(InvocationOnMock invocation) throws Throwable { + return Sets.newHashSet(TOPIC); + } + }); + when(mockedMailboxPathRegister.getLocalTopic()).thenAnswer(new Answer() { + @Override + public Topic answer(InvocationOnMock invocation) throws Throwable { + return TOPIC; + } + }); + testee.event(EVENT); + assertThat(mailboxEventCollector.getEvents()).containsOnly(EVENT); + verify(mockedMailboxPathRegister, times(2)).getLocalTopic(); + verify(mockedMailboxPathRegister).getTopics(MAILBOX_PATH); + verifyNoMoreInteractions(mockedEventSerializer); + verifyNoMoreInteractions(mockedPublisher); + verifyNoMoreInteractions(mockedMailboxPathRegister); + } + + @Test + public void eventShouldBeRemotelySent() throws Exception { + testee.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + verify(mockedMailboxPathRegister).register(MAILBOX_PATH); + when(mockedMailboxPathRegister.getTopics(MAILBOX_PATH)).thenAnswer(new Answer>() { + @Override + public Set answer(InvocationOnMock invocation) throws Throwable { + return Sets.newHashSet(TOPIC, TOPIC_2); + } + }); + when(mockedMailboxPathRegister.getLocalTopic()).thenAnswer(new Answer() { + @Override + public Topic answer(InvocationOnMock invocation) throws Throwable { + return TOPIC; + } + }); + when(mockedEventSerializer.serializeEvent(EVENT)).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return BYTES; + } + }); + testee.event(EVENT); + assertThat(mailboxEventCollector.getEvents()).containsOnly(EVENT); + verify(mockedMailboxPathRegister, times(2)).getLocalTopic(); + verify(mockedMailboxPathRegister).getTopics(MAILBOX_PATH); + verify(mockedEventSerializer).serializeEvent(EVENT); + verify(mockedPublisher).publish(TOPIC_2, BYTES); + verifyNoMoreInteractions(mockedEventSerializer); + verifyNoMoreInteractions(mockedPublisher); + verifyNoMoreInteractions(mockedMailboxPathRegister); + } + + @Test + public void onceListenersShouldBeTriggered() throws Exception { + MailboxListener.Event event = new MailboxListener.Event(mailboxSession, MAILBOX_PATH) {}; + testee.addGlobalListener(onceEventCollector, mailboxSession); + when(mockedMailboxPathRegister.getTopics(MAILBOX_PATH)).thenAnswer(new Answer>() { + @Override + public Set answer(InvocationOnMock invocation) throws Throwable { + return Sets.newHashSet(TOPIC); + } + }); + when(mockedMailboxPathRegister.getLocalTopic()).thenAnswer(new Answer() { + @Override + public Topic answer(InvocationOnMock invocation) throws Throwable { + return TOPIC; + } + }); + testee.event(event); + assertThat(onceEventCollector.getEvents()).containsOnly(event); + verify(mockedMailboxPathRegister, times(2)).getLocalTopic(); + verify(mockedMailboxPathRegister).getTopics(MAILBOX_PATH); + verifyNoMoreInteractions(mockedEventSerializer); + verifyNoMoreInteractions(mockedPublisher); + verifyNoMoreInteractions(mockedMailboxPathRegister); + } + + @Test(expected = MailboxException.class) + public void eachNodeListenersShouldBeRejected() throws Exception { + testee.addGlobalListener(eachEventCollector, mailboxSession); + } + + @Test + public void distantEventShouldBeLocallyDelivered() throws Exception { + testee.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + verify(mockedMailboxPathRegister).register(MAILBOX_PATH); + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return EVENT; + } + }); + testee.receiveSerializedEvent(BYTES); + assertThat(mailboxEventCollector.getEvents()).containsOnly(EVENT); + verify(mockedMailboxPathRegister).getLocalTopic(); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer); + verifyNoMoreInteractions(mockedPublisher); + verifyNoMoreInteractions(mockedMailboxPathRegister); + } + + + @Test + public void distantEventShouldNotBeDeliveredToOnceGlobalListeners() throws Exception { + testee.addGlobalListener(onceEventCollector, mailboxSession); + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return EVENT; + } + }); + testee.receiveSerializedEvent(BYTES); + assertThat(onceEventCollector.getEvents()).isEmpty(); + verify(mockedMailboxPathRegister).getLocalTopic(); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer); + verifyNoMoreInteractions(mockedPublisher); + verifyNoMoreInteractions(mockedMailboxPathRegister); + } + + @Test + public void deletionEventsShouldBeWellHandled() throws Exception { + MailboxListener.Event event = new MailboxListener.MailboxDeletion(mailboxSession, MAILBOX_PATH); + testee.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + verify(mockedMailboxPathRegister).register(MAILBOX_PATH); + when(mockedMailboxPathRegister.getTopics(MAILBOX_PATH)).thenAnswer(new Answer>() { + @Override + public Set answer(InvocationOnMock invocation) throws Throwable { + return Sets.newHashSet(TOPIC, TOPIC_2); + } + }); + when(mockedMailboxPathRegister.getLocalTopic()).thenAnswer(new Answer() { + @Override + public Topic answer(InvocationOnMock invocation) throws Throwable { + return TOPIC; + } + }); + when(mockedEventSerializer.serializeEvent(event)).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return BYTES; + } + }); + testee.event(event); + assertThat(mailboxEventCollector.getEvents()).containsOnly(event); + verify(mockedMailboxPathRegister, times(2)).getLocalTopic(); + verify(mockedMailboxPathRegister).getTopics(MAILBOX_PATH); + verify(mockedMailboxPathRegister).doCompleteUnRegister(MAILBOX_PATH); + verify(mockedEventSerializer).serializeEvent(event); + verify(mockedPublisher).publish(TOPIC_2, BYTES); + verifyNoMoreInteractions(mockedEventSerializer); + verifyNoMoreInteractions(mockedPublisher); + verifyNoMoreInteractions(mockedMailboxPathRegister); + } + + @Test + public void renameEventsShouldBeWellHandled() throws Exception { + MailboxListener.Event event = new MailboxListener.MailboxRenamed(mailboxSession, MAILBOX_PATH) { + @Override + public MailboxPath getNewPath() { + return MAILBOX_PATH_NEW; + } + }; + testee.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + verify(mockedMailboxPathRegister).register(MAILBOX_PATH); + when(mockedMailboxPathRegister.getTopics(MAILBOX_PATH)).thenAnswer(new Answer>() { + @Override + public Set answer(InvocationOnMock invocation) throws Throwable { + return Sets.newHashSet(TOPIC, TOPIC_2); + } + }); + when(mockedMailboxPathRegister.getLocalTopic()).thenAnswer(new Answer() { + @Override + public Topic answer(InvocationOnMock invocation) throws Throwable { + return TOPIC; + } + }); + when(mockedEventSerializer.serializeEvent(event)).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return BYTES; + } + }); + testee.event(event); + assertThat(mailboxEventCollector.getEvents()).containsOnly(event); + verify(mockedMailboxPathRegister, times(2)).getLocalTopic(); + verify(mockedMailboxPathRegister).getTopics(MAILBOX_PATH); + verify(mockedMailboxPathRegister).doRename(MAILBOX_PATH, MAILBOX_PATH_NEW); + verify(mockedEventSerializer).serializeEvent(event); + verify(mockedPublisher).publish(TOPIC_2, BYTES); + verifyNoMoreInteractions(mockedEventSerializer); + verifyNoMoreInteractions(mockedPublisher); + verifyNoMoreInteractions(mockedMailboxPathRegister); + } +} From 1198021e80f1e94758a44505df3675bd8ca03ce8 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 9 Oct 2015 17:12:20 +0200 Subject: [PATCH 13/21] MAILBOX-211 Adding a Broadcast based event system --- .../BroadcastDelegatingMailboxListener.java | 136 +++++++++++ ...roadcastDelegatingMailboxListenerTest.java | 228 ++++++++++++++++++ 2 files changed, 364 insertions(+) create mode 100644 mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListener.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerTest.java diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListener.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListener.java new file mode 100644 index 00000000000..df6e124d8c2 --- /dev/null +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListener.java @@ -0,0 +1,136 @@ +/**************************************************************** + * 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.store.event.distributed; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.event.MailboxListenerRegistry; +import org.apache.james.mailbox.store.publisher.MessageConsumer; +import org.apache.james.mailbox.store.publisher.Publisher; +import org.apache.james.mailbox.store.publisher.Topic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; + +public class BroadcastDelegatingMailboxListener implements DistributedDelegatingMailboxListener { + + private final static Logger LOGGER = LoggerFactory.getLogger(BroadcastDelegatingMailboxListener.class); + + private final MailboxListenerRegistry mailboxListenerRegistry; + private final Publisher publisher; + private final EventSerializer eventSerializer; + private Topic globalTopic; + + public BroadcastDelegatingMailboxListener(Publisher publisher, + MessageConsumer messageConsumer, + EventSerializer eventSerializer, + String globalTopic) throws Exception { + this.mailboxListenerRegistry = new MailboxListenerRegistry(); + this.publisher = publisher; + this.eventSerializer = eventSerializer; + this.globalTopic = new Topic(globalTopic); + messageConsumer.setMessageReceiver(this); + messageConsumer.init(this.globalTopic); + } + + @Override + public ListenerType getType() { + return ListenerType.ONCE; + } + + @Override + public void addListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException { + mailboxListenerRegistry.addListener(mailboxPath, listener); + } + + @Override + public void removeListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException { + mailboxListenerRegistry.removeListener(mailboxPath, listener); + } + + @Override + public void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + mailboxListenerRegistry.addGlobalListener(listener); + } + + @Override + public void removeGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + mailboxListenerRegistry.removeGlobalListener(listener); + } + + @Override + public void event(Event event) { + deliverEventToGlobalListeners(event, ListenerType.ONCE); + try { + publisher.publish(globalTopic, eventSerializer.serializeEvent(event)); + } catch (Throwable t) { + event.getSession().getLog().error("Error while sending event to publisher", t); + } + } + + public void receiveSerializedEvent(byte[] serializedEvent) { + try { + Event event = eventSerializer.deSerializeEvent(serializedEvent); + deliverToMailboxPathRegisteredListeners(event); + deliverEventToGlobalListeners(event, ListenerType.EACH_NODE); + } catch (Exception e) { + LOGGER.error("Error while receiving serialized event", e); + } + } + + private void deliverToMailboxPathRegisteredListeners(Event event) { + Collection listenerSnapshot = mailboxListenerRegistry.getLocalMailboxListeners(event.getMailboxPath()); + if (event instanceof MailboxDeletion) { + mailboxListenerRegistry.deleteRegistryFor(event.getMailboxPath()); + } else if (event instanceof MailboxRenamed) { + MailboxRenamed renamed = (MailboxRenamed) event; + mailboxListenerRegistry.handleRename(renamed.getMailboxPath(), renamed.getNewPath()); + } + for (MailboxListener listener : listenerSnapshot) { + deliverEvent(event, listener); + } + } + + private void deliverEventToGlobalListeners(Event event, ListenerType type) { + for (MailboxListener mailboxListener : mailboxListenerRegistry.getGlobalListeners()) { + if (mailboxListener.getType() == type) { + deliverEvent(event, mailboxListener); + } + } + } + + private void deliverEvent(Event event, MailboxListener listener) { + try { + listener.event(event); + } catch(Throwable throwable) { + event.getSession() + .getLog() + .error("Error while processing listener " + + listener.getClass().getCanonicalName() + + " for " + + event.getClass().getCanonicalName(), + throwable); + } + } +} diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerTest.java new file mode 100644 index 00000000000..b8af665f6a1 --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerTest.java @@ -0,0 +1,228 @@ +/**************************************************************** + * 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.store.event.distributed; + +import static org.assertj.core.api.Assertions.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.event.EventSerializer; +import org.apache.james.mailbox.store.publisher.MessageConsumer; +import org.apache.james.mailbox.store.publisher.Publisher; +import org.apache.james.mailbox.store.publisher.Topic; +import org.apache.james.mailbox.util.EventCollector; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class BroadcastDelegatingMailboxListenerTest { + + private static final MailboxPath MAILBOX_PATH = new MailboxPath("namespace", "user", "name"); + private static final MailboxPath MAILBOX_PATH_NEW = new MailboxPath("namespace_new", "user_new", "name_new"); + private static final Topic TOPIC = new Topic("topic"); + private static final byte[] BYTES = new byte[0]; + private static final MailboxSession mailboxSession = new MockMailboxSession("benwa"); + + public static final MailboxListener.Event EVENT = new MailboxListener.Event(mailboxSession, MAILBOX_PATH) {}; + + private BroadcastDelegatingMailboxListener broadcastDelegatingMailboxListener; + private Publisher mockedPublisher; + private EventSerializer mockedEventSerializer; + private EventCollector mailboxEventCollector; + private EventCollector eachEventCollector; + private EventCollector onceEventCollector; + + @Before + public void setUp() throws Exception { + mockedEventSerializer = mock(EventSerializer.class); + mockedPublisher = mock(Publisher.class); + MessageConsumer messageConsumer = mock(MessageConsumer.class); + broadcastDelegatingMailboxListener = new BroadcastDelegatingMailboxListener(mockedPublisher, messageConsumer, mockedEventSerializer, TOPIC.getValue()); + mailboxEventCollector = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eachEventCollector = new EventCollector(MailboxListener.ListenerType.EACH_NODE); + onceEventCollector = new EventCollector(MailboxListener.ListenerType.ONCE); + } + + @Test + public void eventWithNoRegisteredListenersShouldWork() throws Exception { + when(mockedEventSerializer.serializeEvent(EVENT)).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return BYTES; + } + }); + broadcastDelegatingMailboxListener.event(EVENT); + verify(mockedEventSerializer).serializeEvent(EVENT); + verify(mockedPublisher).publish(TOPIC, BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + } + + @Test + public void eventWithMailboxRegisteredListenerShouldWork() throws Exception { + broadcastDelegatingMailboxListener.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + when(mockedEventSerializer.serializeEvent(EVENT)).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return BYTES; + } + }); + broadcastDelegatingMailboxListener.event(EVENT); + assertThat(mailboxEventCollector.getEvents()).isEmpty(); + verify(mockedEventSerializer).serializeEvent(EVENT); + verify(mockedPublisher).publish(TOPIC, BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + } + + @Test + public void eventWithEachRegisteredListenerShouldWork() throws Exception { + broadcastDelegatingMailboxListener.addGlobalListener(eachEventCollector, mailboxSession); + when(mockedEventSerializer.serializeEvent(EVENT)).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return BYTES; + } + }); + broadcastDelegatingMailboxListener.event(EVENT); + assertThat(eachEventCollector.getEvents()).isEmpty(); + verify(mockedEventSerializer).serializeEvent(EVENT); + verify(mockedPublisher).publish(TOPIC, BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + } + + @Test + public void eventWithOnceRegisteredListenerShouldWork() throws Exception { + broadcastDelegatingMailboxListener.addGlobalListener(onceEventCollector, mailboxSession); + when(mockedEventSerializer.serializeEvent(EVENT)).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + return BYTES; + } + }); + broadcastDelegatingMailboxListener.event(EVENT); + assertThat(onceEventCollector.getEvents()).containsOnly(EVENT); + verify(mockedEventSerializer).serializeEvent(EVENT); + verify(mockedPublisher).publish(TOPIC, BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + } + + @Test + public void receiveSerializedEventShouldWorkWithNoRegisteredListeners() throws Exception { + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return EVENT; + } + }); + broadcastDelegatingMailboxListener.receiveSerializedEvent(BYTES); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + } + + @Test + public void receiveSerializedEventShouldWorkWithMailboxRegisteredListeners() throws Exception { + broadcastDelegatingMailboxListener.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return EVENT; + } + }); + broadcastDelegatingMailboxListener.receiveSerializedEvent(BYTES); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + assertThat(mailboxEventCollector.getEvents()).containsOnly(EVENT); + } + + @Test + public void receiveSerializedEventShouldWorkWithEachRegisteredListeners() throws Exception { + broadcastDelegatingMailboxListener.addGlobalListener(eachEventCollector, mailboxSession); + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return EVENT; + } + }); + broadcastDelegatingMailboxListener.receiveSerializedEvent(BYTES); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + assertThat(eachEventCollector.getEvents()).containsOnly(EVENT); + } + + @Test + public void receiveSerializedEventShouldWorkWithOnceRegisteredListeners() throws Exception { + broadcastDelegatingMailboxListener.addGlobalListener(onceEventCollector, mailboxSession); + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return EVENT; + } + }); + broadcastDelegatingMailboxListener.receiveSerializedEvent(BYTES); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + assertThat(onceEventCollector.getEvents()).isEmpty(); + } + + @Test + public void deletionDistantEventsShouldBeWellHandled() throws Exception { + final MailboxListener.Event event = new MailboxListener.MailboxDeletion(mailboxSession, MAILBOX_PATH); + broadcastDelegatingMailboxListener.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return event; + } + }); + broadcastDelegatingMailboxListener.receiveSerializedEvent(BYTES); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + assertThat(mailboxEventCollector.getEvents()).containsOnly(event); + } + + @Test + public void renameDistantEventsShouldBeWellHandled() throws Exception { + final MailboxListener.Event event = new MailboxListener.MailboxRenamed(mailboxSession, MAILBOX_PATH) { + @Override + public MailboxPath getNewPath() { + return MAILBOX_PATH_NEW; + } + }; + when(mockedEventSerializer.deSerializeEvent(BYTES)).thenAnswer(new Answer() { + @Override + public MailboxListener.Event answer(InvocationOnMock invocation) throws Throwable { + return event; + } + }); + broadcastDelegatingMailboxListener.addListener(MAILBOX_PATH, mailboxEventCollector, mailboxSession); + broadcastDelegatingMailboxListener.receiveSerializedEvent(BYTES); + verify(mockedEventSerializer).deSerializeEvent(BYTES); + verifyNoMoreInteractions(mockedEventSerializer, mockedPublisher); + assertThat(mailboxEventCollector.getEvents()).containsOnly(event); + } + +} From ee4c36b67631cf73e44ca842aa08bb630cba80d9 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 30 Oct 2015 10:37:25 +0100 Subject: [PATCH 14/21] MAILBOX-211 Broadcast integration test --- ...egatingMailboxListenerIntegrationTest.java | 147 ++++++++++++++++++ .../event/distributed/PublisherReceiver.java | 71 +++++++++ 2 files changed, 218 insertions(+) create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerIntegrationTest.java create mode 100644 mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/PublisherReceiver.java diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerIntegrationTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerIntegrationTest.java new file mode 100644 index 00000000000..02386c42e4c --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/BroadcastDelegatingMailboxListenerIntegrationTest.java @@ -0,0 +1,147 @@ +/**************************************************************** + * 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.store.event.distributed; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.store.TestId; +import org.apache.james.mailbox.store.TestIdDeserializer; +import org.apache.james.mailbox.store.event.EventFactory; +import org.apache.james.mailbox.store.json.MessagePackEventSerializer; +import org.apache.james.mailbox.store.json.event.EventConverter; +import org.apache.james.mailbox.store.json.event.MailboxConverter; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.apache.james.mailbox.util.EventCollector; +import org.junit.Before; +import org.junit.Test; + +import java.util.TreeMap; + +/** + Integration tests for BroadcastDelegatingMailboxListener. + + We simulate communications using message queues in memory and check the Listener works as intended. + */ +public class BroadcastDelegatingMailboxListenerIntegrationTest { + + public static final MailboxPath MAILBOX_PATH_1 = new MailboxPath("#private", "user", "mbx"); + public static final MailboxPath MAILBOX_PATH_2 = new MailboxPath("#private", "user", "mbx.other"); + public static final String TOPIC = "TOPIC"; + private BroadcastDelegatingMailboxListener broadcastDelegatingMailboxListener1; + private BroadcastDelegatingMailboxListener broadcastDelegatingMailboxListener2; + private BroadcastDelegatingMailboxListener broadcastDelegatingMailboxListener3; + private EventCollector eventCollectorMailbox1; + private EventCollector eventCollectorMailbox2; + private EventCollector eventCollectorMailbox3; + private EventCollector eventCollectorOnce1; + private EventCollector eventCollectorOnce2; + private EventCollector eventCollectorOnce3; + private EventCollector eventCollectorEach1; + private EventCollector eventCollectorEach2; + private EventCollector eventCollectorEach3; + private MailboxSession mailboxSession; + + @Before + public void setUp() throws Exception { + PublisherReceiver publisherReceiver = new PublisherReceiver(); + broadcastDelegatingMailboxListener1 = new BroadcastDelegatingMailboxListener(publisherReceiver, + publisherReceiver, + new MessagePackEventSerializer( + new EventConverter(new MailboxConverter(new TestIdDeserializer())) + ), + TOPIC); + broadcastDelegatingMailboxListener2 = new BroadcastDelegatingMailboxListener(publisherReceiver, + publisherReceiver, + new MessagePackEventSerializer( + new EventConverter(new MailboxConverter(new TestIdDeserializer())) + ), + TOPIC); + broadcastDelegatingMailboxListener3 = new BroadcastDelegatingMailboxListener(publisherReceiver, + publisherReceiver, + new MessagePackEventSerializer( + new EventConverter(new MailboxConverter(new TestIdDeserializer())) + ), + TOPIC); + eventCollectorMailbox1 = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eventCollectorMailbox2 = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eventCollectorMailbox3 = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eventCollectorOnce1 = new EventCollector(MailboxListener.ListenerType.ONCE); + eventCollectorOnce2 = new EventCollector(MailboxListener.ListenerType.ONCE); + eventCollectorOnce3 = new EventCollector(MailboxListener.ListenerType.ONCE); + eventCollectorEach1 = new EventCollector(MailboxListener.ListenerType.EACH_NODE); + eventCollectorEach2 = new EventCollector(MailboxListener.ListenerType.EACH_NODE); + eventCollectorEach3 = new EventCollector(MailboxListener.ListenerType.EACH_NODE); + mailboxSession = new MockMailboxSession("Test"); + broadcastDelegatingMailboxListener1.addGlobalListener(eventCollectorOnce1, mailboxSession); + broadcastDelegatingMailboxListener2.addGlobalListener(eventCollectorOnce2, mailboxSession); + broadcastDelegatingMailboxListener3.addGlobalListener(eventCollectorOnce3, mailboxSession); + broadcastDelegatingMailboxListener1.addGlobalListener(eventCollectorEach1, mailboxSession); + broadcastDelegatingMailboxListener2.addGlobalListener(eventCollectorEach2, mailboxSession); + broadcastDelegatingMailboxListener3.addGlobalListener(eventCollectorEach3, mailboxSession); + broadcastDelegatingMailboxListener1.addListener(MAILBOX_PATH_1, eventCollectorMailbox1, mailboxSession); + broadcastDelegatingMailboxListener2.addListener(MAILBOX_PATH_1, eventCollectorMailbox2, mailboxSession); + broadcastDelegatingMailboxListener3.addListener(MAILBOX_PATH_2, eventCollectorMailbox3, mailboxSession); + } + + @Test + public void mailboxEventListenersShouldBeTriggeredIfRegistered() throws Exception { + SimpleMailbox simpleMailbox = new SimpleMailbox(MAILBOX_PATH_1, 42); + simpleMailbox.setMailboxId(TestId.of(52)); + final MailboxListener.Event event = new EventFactory().added(mailboxSession, new TreeMap(), simpleMailbox); + + broadcastDelegatingMailboxListener1.event(event); + + assertThat(eventCollectorMailbox1.getEvents()).hasSize(1); + assertThat(eventCollectorMailbox2.getEvents()).hasSize(1); + assertThat(eventCollectorMailbox3.getEvents()).isEmpty(); + } + + @Test + public void onceEventListenersShouldBeTriggeredOnceAcrossTheCluster() { + SimpleMailbox simpleMailbox = new SimpleMailbox(MAILBOX_PATH_1, 42); + simpleMailbox.setMailboxId(TestId.of(52)); + final MailboxListener.Event event = new EventFactory().added(mailboxSession, new TreeMap(), simpleMailbox); + + broadcastDelegatingMailboxListener1.event(event); + + assertThat(eventCollectorOnce1.getEvents()).hasSize(1); + assertThat(eventCollectorOnce2.getEvents()).isEmpty(); + assertThat(eventCollectorOnce3.getEvents()).isEmpty(); + } + + @Test + public void eachEventListenersShouldBeTriggeredOnEachNode() { + SimpleMailbox simpleMailbox = new SimpleMailbox(MAILBOX_PATH_1, 42); + simpleMailbox.setMailboxId(TestId.of(52)); + final MailboxListener.Event event = new EventFactory().added(mailboxSession, new TreeMap(), simpleMailbox); + + broadcastDelegatingMailboxListener1.event(event); + + assertThat(eventCollectorEach1.getEvents()).hasSize(1); + assertThat(eventCollectorEach2.getEvents()).hasSize(1); + assertThat(eventCollectorEach3.getEvents()).hasSize(1); + } + +} diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/PublisherReceiver.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/PublisherReceiver.java new file mode 100644 index 00000000000..907b6687eb3 --- /dev/null +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/event/distributed/PublisherReceiver.java @@ -0,0 +1,71 @@ +/**************************************************************** + * 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.store.event.distributed; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import org.apache.james.mailbox.store.publisher.MessageConsumer; +import org.apache.james.mailbox.store.publisher.MessageReceiver; +import org.apache.james.mailbox.store.publisher.Publisher; +import org.apache.james.mailbox.store.publisher.Topic; + +public class PublisherReceiver implements Publisher, MessageConsumer { + + private final Multimap messageReceiverMultimap; + // Test code is mutable. Agree, this is not nice, but quite convenient . MessageConsumer is designed to handle only one message receiver. + // Here we want to emulate a complete event systems, across multiple servers... + private MessageReceiver messageReceiver; + + public PublisherReceiver() { + this.messageReceiverMultimap = HashMultimap.create(); + } + + @Override + public void close() { + + } + + @Override + public void publish(Topic topic, byte[] message) { + for (MessageReceiver messageReceiver : messageReceiverMultimap.get(topic)) { + messageReceiver.receiveSerializedEvent(message); + } + } + + @Override + public void init() { + + } + + @Override + public void setMessageReceiver(MessageReceiver messageReceiver) { + this.messageReceiver = messageReceiver; + } + + @Override + public void init(Topic topic) throws Exception { + messageReceiverMultimap.put(topic, messageReceiver); + } + + @Override + public void destroy() throws Exception { + + } +} From a4aad5c5da8160d932ac68dcbfcabdff6b5f30e5 Mon Sep 17 00:00:00 2001 From: benwa Date: Sun, 25 Oct 2015 21:02:25 +0100 Subject: [PATCH 15/21] MAILBOX-211 Integration test for Cassandra based distributed event system. --- ...tributedMailboxDelegatingListenerTest.java | 153 ++++++++++++++++++ ...ssandraMailboxPathRegistrerMapperTest.java | 4 +- 2 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraBasedRegisteredDistributedMailboxDelegatingListenerTest.java diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraBasedRegisteredDistributedMailboxDelegatingListenerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraBasedRegisteredDistributedMailboxDelegatingListenerTest.java new file mode 100644 index 00000000000..915e6a72ff7 --- /dev/null +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraBasedRegisteredDistributedMailboxDelegatingListenerTest.java @@ -0,0 +1,153 @@ +/**************************************************************** + * 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.cassandra.event.distributed; + +import org.apache.james.backends.cassandra.CassandraCluster; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.cassandra.CassandraMailboxModule; +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.TestId; +import org.apache.james.mailbox.store.TestIdDeserializer; +import org.apache.james.mailbox.store.event.EventFactory; +import org.apache.james.mailbox.store.event.distributed.DistantMailboxPathRegister; +import org.apache.james.mailbox.store.event.distributed.PublisherReceiver; +import org.apache.james.mailbox.store.event.distributed.RegisteredDelegatingMailboxListener; +import org.apache.james.mailbox.store.json.MessagePackEventSerializer; +import org.apache.james.mailbox.store.json.event.EventConverter; +import org.apache.james.mailbox.store.json.event.MailboxConverter; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.apache.james.mailbox.util.EventCollector; +import org.junit.Before; +import org.junit.Test; + +import java.util.TreeMap; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + Integration tests for RegisteredDelegatingMailboxListener using a cassandra back-end. + + We simulate communications using message queues in memory and check the Listener works as intended. + */ +public class CassandraBasedRegisteredDistributedMailboxDelegatingListenerTest { + + public static final MailboxPath MAILBOX_PATH_1 = new MailboxPath("#private", "user", "mbx"); + public static final MailboxPath MAILBOX_PATH_2 = new MailboxPath("#private", "user", "mbx.other"); + public static final int CASSANDRA_TIME_OUT_IN_S = 10; + public static final int SCHEDULER_PERIOD_IN_S = 20; + + private CassandraCluster cassandraClusterSingleton = CassandraCluster.create(new CassandraMailboxModule()); + private RegisteredDelegatingMailboxListener registeredDelegatingMailboxListener1; + private RegisteredDelegatingMailboxListener registeredDelegatingMailboxListener2; + private RegisteredDelegatingMailboxListener registeredDelegatingMailboxListener3; + private EventCollector eventCollectorMailbox1; + private EventCollector eventCollectorMailbox2; + private EventCollector eventCollectorMailbox3; + private EventCollector eventCollectorOnce1; + private EventCollector eventCollectorOnce2; + private EventCollector eventCollectorOnce3; + private MailboxSession mailboxSession; + + @Before + public void setUp() throws Exception { + PublisherReceiver publisherReceiver = new PublisherReceiver(); + DistantMailboxPathRegister mailboxPathRegister1 = new DistantMailboxPathRegister( + new CassandraMailboxPathRegisterMapper( + cassandraClusterSingleton.getConf(), + cassandraClusterSingleton.getTypesProvider(), + CASSANDRA_TIME_OUT_IN_S), + SCHEDULER_PERIOD_IN_S); + registeredDelegatingMailboxListener1 = new RegisteredDelegatingMailboxListener( + new MessagePackEventSerializer<>( + new EventConverter<>(new MailboxConverter<>(new TestIdDeserializer())) + ), + publisherReceiver, + publisherReceiver, + mailboxPathRegister1); + DistantMailboxPathRegister mailboxPathRegister2 = new DistantMailboxPathRegister( + new CassandraMailboxPathRegisterMapper( + cassandraClusterSingleton.getConf(), + cassandraClusterSingleton.getTypesProvider(), + CASSANDRA_TIME_OUT_IN_S), + SCHEDULER_PERIOD_IN_S); + registeredDelegatingMailboxListener2 = new RegisteredDelegatingMailboxListener( + new MessagePackEventSerializer<>( + new EventConverter<>(new MailboxConverter<>(new TestIdDeserializer())) + ), + publisherReceiver, + publisherReceiver, + mailboxPathRegister2); + DistantMailboxPathRegister mailboxPathRegister3 = new DistantMailboxPathRegister( + new CassandraMailboxPathRegisterMapper( + cassandraClusterSingleton.getConf(), + cassandraClusterSingleton.getTypesProvider(), + CASSANDRA_TIME_OUT_IN_S), + SCHEDULER_PERIOD_IN_S); + registeredDelegatingMailboxListener3 = new RegisteredDelegatingMailboxListener( + new MessagePackEventSerializer<>( + new EventConverter<>(new MailboxConverter<>(new TestIdDeserializer())) + ), + publisherReceiver, + publisherReceiver, + mailboxPathRegister3); + eventCollectorMailbox1 = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eventCollectorMailbox2 = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eventCollectorMailbox3 = new EventCollector(MailboxListener.ListenerType.MAILBOX); + eventCollectorOnce1 = new EventCollector(MailboxListener.ListenerType.ONCE); + eventCollectorOnce2 = new EventCollector(MailboxListener.ListenerType.ONCE); + eventCollectorOnce3 = new EventCollector(MailboxListener.ListenerType.ONCE); + mailboxSession = new MockMailboxSession("Test"); + registeredDelegatingMailboxListener1.addGlobalListener(eventCollectorOnce1, mailboxSession); + registeredDelegatingMailboxListener2.addGlobalListener(eventCollectorOnce2, mailboxSession); + registeredDelegatingMailboxListener3.addGlobalListener(eventCollectorOnce3, mailboxSession); + registeredDelegatingMailboxListener1.addListener(MAILBOX_PATH_1, eventCollectorMailbox1, mailboxSession); + registeredDelegatingMailboxListener2.addListener(MAILBOX_PATH_1, eventCollectorMailbox2, mailboxSession); + registeredDelegatingMailboxListener3.addListener(MAILBOX_PATH_2, eventCollectorMailbox3, mailboxSession); + } + + @Test + public void mailboxEventListenersShouldBeTriggeredIfRegistered() throws Exception { + SimpleMailbox simpleMailbox = new SimpleMailbox<>(MAILBOX_PATH_1, 42); + simpleMailbox.setMailboxId(TestId.of(52)); + final MailboxListener.Event event = new EventFactory().added(mailboxSession, new TreeMap<>(), simpleMailbox); + + registeredDelegatingMailboxListener1.event(event); + + assertThat(eventCollectorMailbox1.getEvents()).hasSize(1); + assertThat(eventCollectorMailbox2.getEvents()).hasSize(1); + assertThat(eventCollectorMailbox3.getEvents()).isEmpty(); + } + + @Test + public void onceEventListenersShouldBeTriggeredOnceAcrossTheCluster() { + SimpleMailbox simpleMailbox = new SimpleMailbox<>(MAILBOX_PATH_1, 42); + simpleMailbox.setMailboxId(TestId.of(52)); + final MailboxListener.Event event = new EventFactory().added(mailboxSession, new TreeMap<>(), simpleMailbox); + + registeredDelegatingMailboxListener1.event(event); + + assertThat(eventCollectorOnce1.getEvents()).hasSize(1); + assertThat(eventCollectorOnce2.getEvents()).isEmpty(); + assertThat(eventCollectorOnce3.getEvents()).isEmpty(); + } + +} diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java index 6a44b9b2c18..00ffc3f11f3 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/event/distributed/CassandraMailboxPathRegistrerMapperTest.java @@ -19,7 +19,7 @@ package org.apache.james.mailbox.cassandra.event.distributed; -import org.apache.james.backends.cassandra.CassandraClusterSingleton; +import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.mailbox.cassandra.CassandraMailboxModule; import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.store.publisher.Topic; @@ -31,7 +31,7 @@ public class CassandraMailboxPathRegistrerMapperTest { - private static final CassandraClusterSingleton cassandra = CassandraClusterSingleton.create(new CassandraMailboxModule()); + private static final CassandraCluster cassandra = CassandraCluster.create(new CassandraMailboxModule()); private static final MailboxPath MAILBOX_PATH = new MailboxPath("namespace", "user", "name"); private static final MailboxPath MAILBOX_PATH_2 = new MailboxPath("namespace2", "user2", "name2"); private static final Topic TOPIC = new Topic("topic"); From c363ed56e9d7a9ee1e887ab0e7b085ea762e2386 Mon Sep 17 00:00:00 2001 From: benwa Date: Tue, 27 Oct 2015 11:09:30 +0100 Subject: [PATCH 16/21] MAILBOX-211 Adding Kafka publishing system --- mailbox/kafka/pom.xml | 249 ++++++++++++++++++ .../mailbox/kafka/KafkaMessageConsumer.java | 130 +++++++++ .../james/mailbox/kafka/KafkaPublisher.java | 77 ++++++ .../james/mailbox/KafkaMessagingTest.java | 68 +++++ .../org/apache/james/mailbox/LocalKafka.java | 58 ++++ .../apache/james/mailbox/LocalZooKeeper.java | 56 ++++ .../kafka/src/test/resources/kafka.properties | 120 +++++++++ .../src/test/resources/zookeeper.properties | 21 ++ mailbox/pom.xml | 1 + server/app/pom.xml | 4 + server/pom.xml | 5 + 11 files changed, 789 insertions(+) create mode 100644 mailbox/kafka/pom.xml create mode 100644 mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaMessageConsumer.java create mode 100644 mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaPublisher.java create mode 100644 mailbox/kafka/src/test/java/org/apache/james/mailbox/KafkaMessagingTest.java create mode 100644 mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalKafka.java create mode 100644 mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalZooKeeper.java create mode 100644 mailbox/kafka/src/test/resources/kafka.properties create mode 100644 mailbox/kafka/src/test/resources/zookeeper.properties diff --git a/mailbox/kafka/pom.xml b/mailbox/kafka/pom.xml new file mode 100644 index 00000000000..73d892b90d6 --- /dev/null +++ b/mailbox/kafka/pom.xml @@ -0,0 +1,249 @@ + + + + + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + + 4.0.0 + + Apache James :: Mailbox :: Kafka + + apache-james-mailbox-kafka + + + + disable-build-for-older-jdk + + (,1.8) + + + + + maven-jar-plugin + + + default-jar + none + + + jar + none + + + test-jar + none + + + + + maven-compiler-plugin + + + default-compile + none + + + default-testCompile + none + + + + + maven-surefire-plugin + + + default-test + none + + + + + maven-source-plugin + + + attach-sources + none + + + + + maven-install-plugin + + + default-install + none + + + + + maven-resources-plugin + + + default-resources + none + + + default-testResources + none + + + + + maven-site-plugin + + + attach-descriptor + none + + + + + + + + build-for-jdk-8 + + [1.8,) + + + + ${project.groupId} + apache-james-mailbox-api + + + ${project.groupId} + apache-james-mailbox-api + test + test-jar + + + ${project.groupId} + apache-james-mailbox-store + + + ${project.groupId} + apache-james-mailbox-store + test + test-jar + + + + junit + junit + test + + + org.apache.kafka + kafka_2.10 + 0.8.2.2 + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + + + + maven-assembly-plugin + + + + fully.qualified.MainClass + + + + jar-with-dependencies + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + disable-animal-sniffer + + [1.6,) + + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + check_java_6 + none + + + + + + + + + + + + maven-assembly-plugin + + + + fully.qualified.MainClass + + + + jar-with-dependencies + + + + + + + \ No newline at end of file diff --git a/mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaMessageConsumer.java b/mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaMessageConsumer.java new file mode 100644 index 00000000000..7d0e2e6a761 --- /dev/null +++ b/mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaMessageConsumer.java @@ -0,0 +1,130 @@ +/**************************************************************** + * 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.kafka; + +import kafka.consumer.ConsumerConfig; +import kafka.consumer.KafkaStream; +import kafka.javaapi.consumer.ConsumerConnector; +import kafka.message.MessageAndMetadata; +import org.apache.james.mailbox.store.publisher.MessageConsumer; +import org.apache.james.mailbox.store.publisher.MessageReceiver; +import org.apache.james.mailbox.store.publisher.Topic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class KafkaMessageConsumer implements MessageConsumer { + + private class Consumer implements Runnable { + + private KafkaStream m_stream; + + public Consumer(KafkaStream a_stream) { + m_stream = a_stream; + } + + @SuppressWarnings("unchecked") + public void run() { + for (MessageAndMetadata aM_stream : (Iterable>) m_stream) { + messageReceiver.receiveSerializedEvent(aM_stream.message()); + } + } + } + + private static final String ZK_SESSION_TIMEOUT = "400"; + private static final String ZK_SYNC_TIME = "200"; + private static final String AUTO_COMMIT8INTERVAL_MS ="1000"; + private static final Logger LOG = LoggerFactory.getLogger(KafkaMessageConsumer.class); + + private final ConsumerConnector consumer; + private final int numberOfTread; + private MessageReceiver messageReceiver; + private ExecutorService executor; + private boolean isInitialized; + + + public KafkaMessageConsumer(String zookeeperConnectionString, + String groupId, + int numberOfThread) { + this.consumer = kafka.consumer.Consumer.createJavaConsumerConnector(createConsumerConfig(zookeeperConnectionString, groupId)); + this.numberOfTread = numberOfThread; + this.isInitialized = false; + } + + @Override + public void setMessageReceiver(MessageReceiver messageReceiver) { + if (!isInitialized) { + this.messageReceiver = messageReceiver; + } else { + throw new RuntimeException("Can not change the MessageReceiver of a running KafkaMessageConsumer"); + } + } + + @PreDestroy + public void destroy() { + if (consumer != null) consumer.shutdown(); + if (executor != null) executor.shutdown(); + this.isInitialized = false; + } + + @PostConstruct + public void init(Topic topic) { + if(!isInitialized) { + this.isInitialized = true; + List> streams = getKafkaStreams(topic.getValue()); + executor = Executors.newFixedThreadPool(numberOfTread); + startConsuming(streams); + } else { + LOG.warn("This Kafka Message Receiver was already launched."); + } + } + + private List> getKafkaStreams(String topic) { + Map topicCountMap = new HashMap<>(); + topicCountMap.put(topic, numberOfTread); + Map>> consumerMap = consumer.createMessageStreams(topicCountMap); + return consumerMap.get(topic); + } + + private void startConsuming(List> streams) { + for (final KafkaStream stream : streams) { + executor.submit(new Consumer(stream)); + } + } + + private ConsumerConfig createConsumerConfig(String zookeeperConnectionString, String groupId) { + Properties props = new Properties(); + props.put("zookeeper.connect", zookeeperConnectionString); + props.put("group.id", groupId); + props.put("zookeeper.session.timeout.ms", ZK_SESSION_TIMEOUT); + props.put("zookeeper.sync.time.ms", ZK_SYNC_TIME); + props.put("auto.commit.interval.ms", AUTO_COMMIT8INTERVAL_MS); + return new ConsumerConfig(props); + } + +} \ No newline at end of file diff --git a/mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaPublisher.java b/mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaPublisher.java new file mode 100644 index 00000000000..3ebcda09217 --- /dev/null +++ b/mailbox/kafka/src/main/java/org/apache/james/mailbox/kafka/KafkaPublisher.java @@ -0,0 +1,77 @@ +/**************************************************************** + * 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.kafka; + +import kafka.javaapi.producer.Producer; +import kafka.producer.KeyedMessage; +import kafka.producer.ProducerConfig; +import org.apache.james.mailbox.store.publisher.Publisher; +import org.apache.james.mailbox.store.publisher.Topic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.Properties; + +public class KafkaPublisher implements Publisher { + + private static final Logger LOG = LoggerFactory.getLogger(KafkaPublisher.class); + + private Producer producer; + private final int kafka_port; + private final String kafka_ip; + private boolean producerLaunched; + + public KafkaPublisher(String kafkaHostIpString, int kafka_port) { + this.kafka_ip = kafkaHostIpString; + this.kafka_port = kafka_port; + producerLaunched = false; + } + + @PostConstruct + @Override + public void init() { + if (!producerLaunched) { + Properties props = new Properties(); + props.put("metadata.broker.list", kafka_ip + ":" + kafka_port); + props.put("serializer.class", "kafka.serializer.DefaultEncoder"); + props.put("request.required.acks", "1"); + ProducerConfig config = new ProducerConfig(props); + producer = new Producer<>(config); + producerLaunched = true; + } else { + LOG.warn("Kafka producer was already instantiated"); + } + } + + + @Override + public void publish(Topic topic, byte[] message) { + producer.send(new KeyedMessage<>(topic.getValue(), message)); + } + + @PreDestroy + @Override + public void close() { + producer.close(); + } + +} diff --git a/mailbox/kafka/src/test/java/org/apache/james/mailbox/KafkaMessagingTest.java b/mailbox/kafka/src/test/java/org/apache/james/mailbox/KafkaMessagingTest.java new file mode 100644 index 00000000000..eb948d8ce46 --- /dev/null +++ b/mailbox/kafka/src/test/java/org/apache/james/mailbox/KafkaMessagingTest.java @@ -0,0 +1,68 @@ +/**************************************************************** + * 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; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.apache.james.mailbox.kafka.KafkaMessageConsumer; +import org.apache.james.mailbox.kafka.KafkaPublisher; +import org.apache.james.mailbox.store.publisher.MessageReceiver; +import org.apache.james.mailbox.store.publisher.Topic; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class KafkaMessagingTest { + + public static final Topic TOPIC = new Topic("TOPIC"); + public static final byte[] MESSAGE = new byte[10]; + + private LocalKafka kafka; + private KafkaPublisher kafkaPublisher; + private MessageReceiver messageReceiver; + + @Before + public void setUp() throws Exception { + kafka = new LocalKafka(); + Thread.sleep(5000); + kafkaPublisher = new KafkaPublisher("127.0.0.1", 9092); + kafkaPublisher.init(); + messageReceiver = mock(MessageReceiver.class); + KafkaMessageConsumer kafkaMessageConsumer = new KafkaMessageConsumer("localhost", "0123456789", 2); + kafkaMessageConsumer.setMessageReceiver(messageReceiver); + kafkaMessageConsumer.init(TOPIC); + Thread.sleep(5000); + } + + @After + public void tearDown() throws Exception { + kafka.stop(); + } + + @Test + public void testSomething() throws Exception { + kafkaPublisher.publish(TOPIC, MESSAGE); + verify(messageReceiver).receiveSerializedEvent(MESSAGE); + verifyNoMoreInteractions(messageReceiver); + } + +} diff --git a/mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalKafka.java b/mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalKafka.java new file mode 100644 index 00000000000..cf9c29fbae3 --- /dev/null +++ b/mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalKafka.java @@ -0,0 +1,58 @@ +/**************************************************************** + * 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; + +import java.io.IOException; +import java.util.Properties; +import kafka.server.KafkaConfig; +import kafka.server.KafkaServerStartable; + + +public class LocalKafka { + + private static Properties getPropertiesFromFile(String name) throws IOException { + Properties zkProperties = new Properties(); + zkProperties.load(Class.class.getResourceAsStream(name)); + return zkProperties; + } + + public final KafkaServerStartable kafka; + public final LocalZooKeeper zookeeper; + + public LocalKafka() throws IOException, InterruptedException { + this(getPropertiesFromFile("/kafka.properties"), getPropertiesFromFile("/zookeeper.properties")); + } + + public LocalKafka(Properties kafkaProperties, Properties zkProperties) throws IOException, InterruptedException { + KafkaConfig kafkaConfig = new KafkaConfig(kafkaProperties); + + //start local zookeeper + zookeeper = new LocalZooKeeper(zkProperties); + + //start local kafka broker + kafka = new KafkaServerStartable(kafkaConfig); + kafka.startup(); + } + + public void stop(){ + kafka.shutdown(); + } + +} diff --git a/mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalZooKeeper.java b/mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalZooKeeper.java new file mode 100644 index 00000000000..db5f2b37e12 --- /dev/null +++ b/mailbox/kafka/src/test/java/org/apache/james/mailbox/LocalZooKeeper.java @@ -0,0 +1,56 @@ +/**************************************************************** + * 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; + +import java.io.IOException; +import java.util.Properties; + +import org.apache.zookeeper.server.ServerConfig; +import org.apache.zookeeper.server.ZooKeeperServerMain; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; + +public class LocalZooKeeper { + + ZooKeeperServerMain zooKeeperServer; + + public LocalZooKeeper(Properties zkProperties) throws IOException{ + QuorumPeerConfig quorumConfiguration = new QuorumPeerConfig(); + try { + quorumConfiguration.parseProperties(zkProperties); + } catch(Exception e) { + throw new RuntimeException(e); + } + + zooKeeperServer = new ZooKeeperServerMain(); + final ServerConfig configuration = new ServerConfig(); + configuration.readFrom(quorumConfiguration); + + new Thread() { + public void run() { + try { + zooKeeperServer.runFromConfig(configuration); + } catch (IOException e) { + System.out.println("ZooKeeper Failed"); + e.printStackTrace(System.err); + } + } + }.start(); + } +} diff --git a/mailbox/kafka/src/test/resources/kafka.properties b/mailbox/kafka/src/test/resources/kafka.properties new file mode 100644 index 00000000000..48a4cdbee7a --- /dev/null +++ b/mailbox/kafka/src/test/resources/kafka.properties @@ -0,0 +1,120 @@ +# 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. + +############################# Server Basics ############################# + +# The id of the broker. This must be set to a unique integer for each broker. +broker.id=0 + +############################# Socket Server Settings ############################# + +# The port the socket server listens on +port=9092 + +# Hostname the broker will bind to. If not set, the server will bind to all interfaces +#host.name=localhost + +# Hostname the broker will advertise to producers and consumers. If not set, it uses the +# value for "host.name" if configured. Otherwise, it will use the value returned from +# java.net.InetAddress.getCanonicalHostName(). +#advertised.host.name= + +# The port to publish to ZooKeeper for clients to use. If this is not set, +# it will publish the same port that the broker binds to. +#advertised.port= + +# The number of threads handling network requests +num.network.threads=3 + +# The number of threads doing disk I/O +num.io.threads=8 + +# The send buffer (SO_SNDBUF) used by the socket server +socket.send.buffer.bytes=102400 + +# The receive buffer (SO_RCVBUF) used by the socket server +socket.receive.buffer.bytes=102400 + +# The maximum size of a request that the socket server will accept (protection against OOM) +socket.request.max.bytes=104857600 + + +############################# Log Basics ############################# + +# A comma seperated list of directories under which to store log files +log.dirs=/tmp/kafka-logs + +# The default number of log partitions per topic. More partitions allow greater +# parallelism for consumption, but this will also result in more files across +# the brokers. +num.partitions=1 + +# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown. +# This value is recommended to be increased for installations with data dirs located in RAID array. +num.recovery.threads.per.data.dir=1 + +############################# Log Flush Policy ############################# + +# Messages are immediately written to the filesystem but by default we only fsync() to sync +# the OS cache lazily. The following configurations control the flush of data to disk. +# There are a few important trade-offs here: +# 1. Durability: Unflushed data may be lost if you are not using replication. +# 2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush. +# 3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to exceessive seeks. +# The settings below allow one to configure the flush policy to flush data after a period of time or +# every N messages (or both). This can be done globally and overridden on a per-topic basis. + +# The number of messages to accept before forcing a flush of data to disk +#log.flush.interval.messages=10000 + +# The maximum amount of time a message can sit in a log before we force a flush +#log.flush.interval.ms=1000 + +############################# Log Retention Policy ############################# + +# The following configurations control the disposal of log segments. The policy can +# be set to delete segments after a period of time, or after a given size has accumulated. +# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens +# from the end of the log. + +# The minimum age of a log file to be eligible for deletion +log.retention.hours=168 + +# A size-based retention policy for logs. Segments are pruned from the log as long as the remaining +# segments don't drop below log.retention.bytes. +#log.retention.bytes=1073741824 + +# The maximum size of a log segment file. When this size is reached a new log segment will be created. +log.segment.bytes=1073741824 + +# The interval at which log segments are checked to see if they can be deleted according +# to the retention policies +log.retention.check.interval.ms=300000 + +# By default the log cleaner is disabled and the log retention policy will default to just delete segments after their retention expires. +# If log.cleaner.enable=true is set the cleaner will be enabled and individual logs can then be marked for log compaction. +log.cleaner.enable=false + +############################# Zookeeper ############################# + +# Zookeeper connection string (see zookeeper docs for details). +# This is a comma separated host:port pairs, each corresponding to a zk +# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002". +# You can also append an optional chroot string to the urls to specify the +# root directory for all kafka znodes. +zookeeper.connect=localhost:2181 + +# Timeout in ms for connecting to zookeeper +zookeeper.connection.timeout.ms=6000 \ No newline at end of file diff --git a/mailbox/kafka/src/test/resources/zookeeper.properties b/mailbox/kafka/src/test/resources/zookeeper.properties new file mode 100644 index 00000000000..dddc4850a7b --- /dev/null +++ b/mailbox/kafka/src/test/resources/zookeeper.properties @@ -0,0 +1,21 @@ +# 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. + +# the directory where the snapshot is stored. +dataDir=/tmp/zookeeper +# the port at which the clients will connect +clientPort=2181 +# disable the per-ip limit on the number of connections since this is a non-production config +maxClientCnxns=0 \ No newline at end of file diff --git a/mailbox/pom.xml b/mailbox/pom.xml index fe3c84210db..453266050a0 100644 --- a/mailbox/pom.xml +++ b/mailbox/pom.xml @@ -63,6 +63,7 @@ tika tool zoo-seq-provider + kafka diff --git a/server/app/pom.xml b/server/app/pom.xml index ba110cf76e9..3ed3b67c1a8 100644 --- a/server/app/pom.xml +++ b/server/app/pom.xml @@ -411,6 +411,10 @@ + + org.apache.james + apache-james-mailbox-kafka + org.apache.james apache-james-mailbox-spring diff --git a/server/pom.xml b/server/pom.xml index df9447a2841..0c0e3a28e76 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -536,6 +536,11 @@ ${mailbox.version} test-jar + + org.apache.james + apache-james-mailbox-kafka + ${mailbox.version} + org.apache.james apache-james-mailbox-api From ffeee6c4828fd7312772e11bf132b1886f73922f Mon Sep 17 00:00:00 2001 From: benwa Date: Tue, 3 Nov 2015 20:28:37 +0100 Subject: [PATCH 17/21] MAILBOX-211 Mailbox Id deserializer should be Spring defined --- .../main/resources/META-INF/spring/mailbox-cassandra.xml | 2 ++ .../src/main/resources/META-INF/spring/mailbox-hbase.xml | 2 ++ .../src/main/resources/META-INF/spring/mailbox-jcr.xml | 2 ++ .../src/main/resources/META-INF/spring/mailbox-jpa.xml | 2 ++ .../main/resources/META-INF/spring/mailbox-maildir.xml | 2 ++ .../src/main/resources/META-INF/spring/mailbox-memory.xml | 2 ++ .../MailboxConfigurationBeanFactoryPostProcessor.java | 8 ++++++++ 7 files changed, 20 insertions(+) diff --git a/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml b/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml index ea58611a0c4..43787244361 100644 --- a/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml +++ b/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml @@ -80,4 +80,6 @@ + + diff --git a/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml b/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml index 383a616ea60..7e3d2cc30c9 100644 --- a/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml +++ b/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml @@ -62,4 +62,6 @@ + + diff --git a/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml b/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml index 3fc54de5594..c6847501711 100644 --- a/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml +++ b/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml @@ -81,4 +81,6 @@ + + diff --git a/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml b/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml index 4ae026bcc1a..c7d5fa22881 100644 --- a/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml +++ b/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml @@ -83,4 +83,6 @@ + + diff --git a/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml b/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml index c47895e730d..9c0363f3758 100644 --- a/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml +++ b/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml @@ -56,4 +56,6 @@ + + diff --git a/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml b/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml index b401bd28934..296b292a932 100644 --- a/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml +++ b/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml @@ -49,4 +49,6 @@ + + diff --git a/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java b/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java index d0b654b9d8c..f33a6cbd48c 100644 --- a/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java +++ b/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java @@ -51,30 +51,37 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) String mailbox = null; String subscription = null; String messageMapperFactory = null; + String mailboxIdDeserializer = null; if (provider.equalsIgnoreCase("jpa")) { mailbox = "jpa-mailboxmanager"; subscription = "jpa-subscriptionManager"; messageMapperFactory = "jpa-sessionMapperFactory"; + mailboxIdDeserializer = "jpa-mailbox-id-deserializer"; } else if (provider.equalsIgnoreCase("memory")) { mailbox = "memory-mailboxmanager"; subscription = "memory-subscriptionManager"; messageMapperFactory = "memory-sessionMapperFactory"; + mailboxIdDeserializer = "memory-mailbox-id-deserializer"; } else if (provider.equalsIgnoreCase("jcr")) { mailbox = "jcr-mailboxmanager"; subscription = "jcr-subscriptionManager"; messageMapperFactory = "jcr-sessionMapperFactory"; + mailboxIdDeserializer = "jcr-mailbox-id-deserializer"; } else if (provider.equalsIgnoreCase("maildir")) { mailbox = "maildir-mailboxmanager"; subscription = "maildir-subscriptionManager"; messageMapperFactory = "maildir-sessionMapperFactory"; + mailboxIdDeserializer = "maildir-mailbox-id-deserializer"; } else if (provider.equalsIgnoreCase("hbase")) { mailbox = "hbase-mailboxmanager"; subscription = "hbase-subscriptionManager"; messageMapperFactory = "hbase-sessionMapperFactory"; + mailboxIdDeserializer = "hbase-mailbox-id-deserializer"; } else if (provider.equalsIgnoreCase("cassandra")) { mailbox = "cassandra-mailboxmanager"; subscription = "cassandra-subscriptionManager"; messageMapperFactory = "cassandra-sessionMapperFactory"; + mailboxIdDeserializer = "cassandra-mailbox-id-deserializer"; } if (mailbox == null) @@ -82,6 +89,7 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) registry.registerAlias(mailbox, "mailboxmanager"); registry.registerAlias(subscription, "subscriptionManager"); registry.registerAlias(messageMapperFactory, "messageMapperFactory"); + registry.registerAlias(mailboxIdDeserializer, "mailbox-id-deserializer"); } catch (ConfigurationException e) { throw new FatalBeanException("Unable to config the mailboxmanager", e); From c8503412f6d7691ec2ee938f095f5afdc8a3ad3c Mon Sep 17 00:00:00 2001 From: benwa Date: Thu, 29 Oct 2015 00:03:49 +0100 Subject: [PATCH 18/21] MAILBOX-211 Add a configuration reader for Spring about the Event system --- .../META-INF/spring/mailbox-cassandra.xml | 7 ++ .../META-INF/spring/mailbox-hbase.xml | 1 + .../resources/META-INF/spring/mailbox-jcr.xml | 1 + .../resources/META-INF/spring/mailbox-jpa.xml | 1 + .../main/resources/META-INF/spring/kafka.xml | 42 +++++++ .../META-INF/spring/mailbox-maildir.xml | 1 + .../META-INF/spring/event-system.xml | 69 ++++++++++++ .../META-INF/spring/spring-mailbox.xml | 1 + .../resources/cassandra-template.properties | 4 +- .../src/main/resources/events-template.xml | 82 ++++++++++++++ .../app/src/main/resources/events.properties | 38 +++++++ ...ConfigurationBeanFactoryPostProcessor.java | 104 ++++++++++++++++++ ...ConfigurationBeanFactoryPostProcessor.java | 3 + .../org/apache/james/spring-server.xml | 5 + 14 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 mailbox/kafka/src/main/resources/META-INF/spring/kafka.xml create mode 100644 mailbox/spring/src/main/resources/META-INF/spring/event-system.xml create mode 100644 server/app/src/main/resources/events-template.xml create mode 100644 server/app/src/main/resources/events.properties create mode 100644 server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/EventsConfigurationBeanFactoryPostProcessor.java diff --git a/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml b/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml index 43787244361..3e2c733c721 100644 --- a/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml +++ b/mailbox/cassandra/src/main/resources/META-INF/spring/mailbox-cassandra.xml @@ -37,6 +37,7 @@ + + + + + + + diff --git a/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml b/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml index 7e3d2cc30c9..5fe31b84c80 100644 --- a/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml +++ b/mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml @@ -46,6 +46,7 @@ + diff --git a/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml b/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml index c6847501711..3f4ea8401d6 100644 --- a/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml +++ b/mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml @@ -36,6 +36,7 @@ + diff --git a/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml b/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml index c7d5fa22881..71223184fee 100644 --- a/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml +++ b/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml @@ -39,6 +39,7 @@ + diff --git a/mailbox/kafka/src/main/resources/META-INF/spring/kafka.xml b/mailbox/kafka/src/main/resources/META-INF/spring/kafka.xml new file mode 100644 index 00000000000..870cebfd7e8 --- /dev/null +++ b/mailbox/kafka/src/main/resources/META-INF/spring/kafka.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml b/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml index 9c0363f3758..ae811980ff7 100644 --- a/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml +++ b/mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml @@ -43,6 +43,7 @@ + diff --git a/mailbox/spring/src/main/resources/META-INF/spring/event-system.xml b/mailbox/spring/src/main/resources/META-INF/spring/event-system.xml new file mode 100644 index 00000000000..60d6297b365 --- /dev/null +++ b/mailbox/spring/src/main/resources/META-INF/spring/event-system.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml b/mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml index dc3ff3a47f3..05af73801e0 100644 --- a/mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml +++ b/mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml @@ -57,5 +57,6 @@ + diff --git a/server/app/src/main/resources/cassandra-template.properties b/server/app/src/main/resources/cassandra-template.properties index 647246eed24..0dd2c2a473a 100644 --- a/server/app/src/main/resources/cassandra-template.properties +++ b/server/app/src/main/resources/cassandra-template.properties @@ -23,4 +23,6 @@ cassandra.ip=127.0.0.1 cassandra.port=9042 cassandra.keyspace=apache_james -cassandra.replication.factor=1 \ No newline at end of file +cassandra.replication.factor=1 + +cassandra.mailbox.path.register.mapper.ttl=1800 \ No newline at end of file diff --git a/server/app/src/main/resources/events-template.xml b/server/app/src/main/resources/events-template.xml new file mode 100644 index 00000000000..ca1bd44a55a --- /dev/null +++ b/server/app/src/main/resources/events-template.xml @@ -0,0 +1,82 @@ + + + + + + + + default + + + + + + + + + + + + \ No newline at end of file diff --git a/server/app/src/main/resources/events.properties b/server/app/src/main/resources/events.properties new file mode 100644 index 00000000000..696bc26d862 --- /dev/null +++ b/server/app/src/main/resources/events.properties @@ -0,0 +1,38 @@ +# 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. +# + +# This template file can be used as example for James Server configuration +# DO NOT USE IT AS SUCH AND ADAPT IT TO YOUR NEEDS + +# The following properties needs to be set +# if you want to use Kafka for inter node messaging for your event system. +# +# This needs to be done if you used kafka in event.xml + +kafka.ip=127.0.0.1 +kafka.port=9092 +zookeeper.connection.string=localhost +group.id=azerty +event.thread.count=4 + +# This property needs to be set if you want to use the Broadcast Mailbox Delegating Listener + +global.topic=JAMES_TOPIC + +distant.mailbox.path.register.max.retries=100 +distant.mailbox.path.register.refresh=900 diff --git a/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/EventsConfigurationBeanFactoryPostProcessor.java b/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/EventsConfigurationBeanFactoryPostProcessor.java new file mode 100644 index 00000000000..efd5d83413a --- /dev/null +++ b/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/EventsConfigurationBeanFactoryPostProcessor.java @@ -0,0 +1,104 @@ +/**************************************************************** + * 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.container.spring.bean.factorypostprocessor; + +import com.google.common.base.Strings; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.james.container.spring.lifecycle.ConfigurationProvider; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +public class EventsConfigurationBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + ConfigurationProvider confProvider = beanFactory.getBean(ConfigurationProvider.class); + try { + HierarchicalConfiguration config = confProvider.getConfiguration("events"); + String type = config.getString("type", "default"); + String serialization = config.getString("serialization", "json"); + String publisher = config.getString("publisher", "kafka"); + String registration = config.getString("registration", "cassandra"); + String delegatingListenerAlias = getDelegatingListenerAlias(type); + String serializationAlias = getSerializationAlias(serialization); + String registrationAlias = getRegistrationAlias(registration); + String publisherAlias = null; + String consumerAlias = null; + + if (publisher.equals("kafka")) { + publisherAlias = "kafka-publisher"; + consumerAlias = "kafka-consumer"; + } + + detectInvalidValue(delegatingListenerAlias, "Delegating listener type " + type + " not supported!"); + beanFactory.registerAlias(delegatingListenerAlias, "delegating-listener"); + if (!delegatingListenerAlias.equals("default")) { + detectInvalidValue(serializationAlias, "Serialization system type " + serialization + " not supported!"); + beanFactory.registerAlias(serializationAlias, "event-serializer"); + detectInvalidValue(publisherAlias, "Publisher system type " + publisher + " not supported!"); + beanFactory.registerAlias(publisherAlias, "publisher"); + beanFactory.registerAlias(consumerAlias, "consumer"); + if (delegatingListenerAlias.equals("registered")) { + detectInvalidValue(registrationAlias, "Registration system type " + registration + " not supported!"); + beanFactory.registerAlias(registrationAlias, "distant-mailbox-path-register-mapper"); + } + } + + } catch (ConfigurationException e) { + throw new FatalBeanException("Unable to config the mailboxmanager", e); + } + } + + private void detectInvalidValue(String registrationAlias, String message) throws ConfigurationException { + if (Strings.isNullOrEmpty(registrationAlias)) { + throw new ConfigurationException(message); + } + } + + private String getRegistrationAlias(String registration) { + if (registration.equals("cassandra")) { + return "cassandra-mailbox-path-register-mapper"; + } + return null; + } + + private String getSerializationAlias(String serialization) { + if (serialization.equals("json")) { + return "json-event-serializer"; + } else if (serialization.equals("message-pack")) { + return "message-pack-event-serializer"; + } + return null; + } + + private String getDelegatingListenerAlias(String type) { + if (type.equals("default")) { + return "default-delegating-listener"; + } else if (type.equals("broadcast")) { + return "broadcast-delegating-listener"; + } else if (type.equals("registered")) { + return "registered-delegating-listener"; + } + return null; + } +} diff --git a/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java b/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java index f33a6cbd48c..ffbe0b5d180 100644 --- a/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java +++ b/server/container/spring/src/main/java/org/apache/james/container/spring/bean/factorypostprocessor/MailboxConfigurationBeanFactoryPostProcessor.java @@ -24,8 +24,11 @@ import org.apache.james.container.spring.lifecycle.ConfigurationProvider; import org.springframework.beans.BeansException; import org.springframework.beans.FatalBeanException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; /** diff --git a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml index fe5022289bd..6bc5f5137af 100644 --- a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml +++ b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml @@ -135,6 +135,11 @@ + + + + + From b31143815de62f5845fd55f58769451ed334ae08 Mon Sep 17 00:00:00 2001 From: benwa Date: Fri, 30 Oct 2015 18:55:37 +0100 Subject: [PATCH 19/21] MAILBOX-211 Event system documentation --- server/src/site/xdoc/config-events.xml | 153 +++++++++++++++++++++++++ server/src/site/xdoc/config.xml | 5 + 2 files changed, 158 insertions(+) create mode 100644 server/src/site/xdoc/config-events.xml diff --git a/server/src/site/xdoc/config-events.xml b/server/src/site/xdoc/config-events.xml new file mode 100644 index 00000000000..7deffbf681c --- /dev/null +++ b/server/src/site/xdoc/config-events.xml @@ -0,0 +1,153 @@ + + + + + + Apache James Server 3 - Quota Configuration + + + + +

+ +

Consult events-template.xml in SVN to get some examples and hints.

+ +

Use this configuration to define the type of Event System you want.

+ +

+ James relies on an event system. Each operations performed on the mailbox will trigger related events. Some software + components (MailboxListeners) can register themselves on this event system to be called when an event is fired. +

+ +

+ Here are typical use cases for Mailbox Listeners (non exhaustive list) : +

    +
  • Message search indexation, for instance in Lucene or ElasticSearch
  • +
  • Local cache invalidation (caching mailbox project)
  • +
  • Quota calculation
  • +
  • IMAP IDLE feature : live notification of actions performed on a mailbox, allowing publish subscribe on mailboxes events
  • +
  • Message Sequence Number consistence
  • +
+ The Mailbox Listeners can be classified in two categories : +
    +
  • Mailbox registered : The mailbox listener is only notified on events affecting this mailbox. IDLE is a good example of this.
  • +
  • Global Listeners : This event listener is triggered upon each events.
  • +
+ Note that Global Listeners can also be classified in two categories : +
    +
  • Those which needs to be triggered only once in your cluster. For instance ElasticSearch indexing is an example of this.
  • +
  • Those which needs to be triggered on each servers. For instance, each Lucene indexer needs to be triggered on each server + for the search feature to stay consistent.
  • +
+

+ +

+ The default implementation is a synchronous in memory event system. The performance are really good, as their is no need to serialize + events, and no network overhead. However, this event system is limited to one computer and you might want a distributed systems. +

+ +

+ Other implementations, distributed environment friendly are available. +

+ +

+ The simplest one is broadcast based. Each James servers listen the same message queue, and each James server will be notified upon events. + Here are the pros and cons of this implementations : +

    + Pros: +
  • It supports every type of listener described above
  • +
  • It allows you to scale your James infrastructure without changing your middlewares. You just need a message queue
  • + Cons : +
  • Your scalability is limited as each servers is notified on all events
  • +
  • Network overhead on event transmissions
  • +
  • Event serialization and deserialization
  • +
+ To use this implementation, you need two other components (that will be discussed) : a publishing system and an event serializer +

+ +

+ The other mode is based on registrations. + Each server reads messages from a dedicated message queue, and other servers send messages addressed to this sever on this message queue. + Registrations are performed on an eternal data-store supporting document deletion after a fixed amount of time. + These registrations are periodically refreshed. This data-store is then triggered on event generation, that, if needed are serialized and + send to the given queues. + The pros and cons of this implementations are : +

    + Pros : +
  • Linear scalability
  • +
  • A server receives only events concerning him
  • + Cons : +
  • Possible event serialization costs
  • +
  • Registration and registration refresh costs
  • +
  • Need to find interested servers on event delivery
  • +
  • Network overhead on event transmissions
  • +
+

+ +

Failure modes

+ +
    + Default implementation : +
  • The default implementation might not deliver some events on server stop.
  • +
+ +
    + Broadcast implementation : +
  • The broadcast implementation might not deliver some events on server stop.
  • +
  • The broadcast implementation is tight to limitation of the underlying publisher.
  • +
+ +
    + Registered implementation : +
  • The registered implementation might not deliver some events on server stop.
  • +
  • The registered implementation is tight to limitation of the underlying publisher, and underlying registration system.
  • +
+ +

Publisher

+ +

+ Available implementation is Kafka based. Kafka ensure at least one delivery. This means some messages might be delivered two times. +

+ +

Event serializer

+ +

There are two types of event serialization systems : +

    +
  • Json : events are converted to JSON
  • +
  • Message Pack : a binary representation of JSON. 2 times smaller in average but two times longer to compute. It allows you to trade + bandwidth and data readability against CPU time.
  • +
+

+ +

Registration systems

+ +

+ Available implementation is based on Cassandra. It is used on an AP fashion, enforcing availability instead of consistency. Some + messages might get delivered to no more registered servers. This is just extra work. Worst, messages might not be delivered to + recently registered servers. But we make sure that we have the more up to date version of the registrations we can, and will not time out in + the face of network partitions, nor enforce some default behaviour. +

+ +
+ + + + + diff --git a/server/src/site/xdoc/config.xml b/server/src/site/xdoc/config.xml index 2516eb1f66c..9e63639e146 100644 --- a/server/src/site/xdoc/config.xml +++ b/server/src/site/xdoc/config.xml @@ -69,6 +69,11 @@ Quota Configuration + + events.xml + Event system Configuration + + mailrepositorystore.xml Mail Repository Stores Configuration From 2a8daf0e0b5381e8130ca9f2b84f0baab2a03160 Mon Sep 17 00:00:00 2001 From: benwa Date: Tue, 3 Nov 2015 23:52:08 +0100 Subject: [PATCH 20/21] inc max retry --- .../org/apache/james/backends/cassandra/CassandraCluster.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java index 0f72c9a3288..06edbc48a04 100644 --- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java +++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java @@ -41,7 +41,7 @@ public final class CassandraCluster { private static final int REPLICATION_FACTOR = 1; private static final long SLEEP_BEFORE_RETRY = 200; - private static final int MAX_RETRY = 200; + private static final int MAX_RETRY = 2000; private final CassandraModule module; private Session session; From 5f58c434e5c1fffc2f1c20a7b463c7ff15a29759 Mon Sep 17 00:00:00 2001 From: benwa Date: Wed, 4 Nov 2015 12:39:15 +0100 Subject: [PATCH 21/21] Increase timeout --- .../org/apache/james/backends/cassandra/CassandraCluster.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java index 06edbc48a04..c1eea97a123 100644 --- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java +++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java @@ -54,7 +54,7 @@ public static CassandraCluster create(CassandraModule module) throws RuntimeExce private CassandraCluster(CassandraModule module) throws RuntimeException { this.module = module; try { - EmbeddedCassandraServerHelper.startEmbeddedCassandra(); + EmbeddedCassandraServerHelper.startEmbeddedCassandra(20000L); session = new FunctionRunnerWithRetry(MAX_RETRY) .executeAndRetrieveObject(CassandraCluster.this::tryInitializeSession);