From 30a7aca7adc4a051f72334386e05cbba91e4e8c2 Mon Sep 17 00:00:00 2001 From: Timothy Prepscius Date: Sun, 1 Sep 2013 23:13:22 -0400 Subject: [PATCH] adds the apache james mailbox, how sucky this is --- james/apache-james-mailbox-memory | 1 + james/apache-james-mailbox/.gitignore | 6 + james/apache-james-mailbox/.svn/all-wcprops | 35 + james/apache-james-mailbox/.svn/dir-prop-base | 9 + james/apache-james-mailbox/.svn/entries | 237 ++ .../.svn/text-base/.gitignore.svn-base | 6 + .../.svn/text-base/LICENSE.svn-base | 179 ++ .../.svn/text-base/NOTICE.svn-base | 9 + .../.svn/text-base/README.md.svn-base | 86 + .../.svn/text-base/pom.xml.svn-base | 576 +++++ james/apache-james-mailbox/LICENSE | 179 ++ james/apache-james-mailbox/NOTICE | 9 + james/apache-james-mailbox/README.md | 86 + .../apache-james-mailbox/api/.svn/all-wcprops | 11 + .../api/.svn/dir-prop-base | 8 + james/apache-james-mailbox/api/.svn/entries | 65 + .../api/.svn/prop-base/pom.xml.svn-base | 9 + .../api/.svn/text-base/pom.xml.svn-base | 64 + james/apache-james-mailbox/api/pom.xml | 64 + .../api/src/.svn/all-wcprops | 5 + .../apache-james-mailbox/api/src/.svn/entries | 37 + .../api/src/main/.svn/all-wcprops | 5 + .../api/src/main/.svn/entries | 31 + .../api/src/main/java/.svn/all-wcprops | 5 + .../api/src/main/java/.svn/entries | 31 + .../api/src/main/java/org/.svn/all-wcprops | 5 + .../api/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../api/src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 71 + .../org/apache/james/mailbox/.svn/entries | 414 ++++ .../prop-base/MailboxListener.java.svn-base | 5 + .../prop-base/MailboxManager.java.svn-base | 5 + .../MailboxSessionIdGenerator.java.svn-base | 5 + .../prop-base/MessageManager.java.svn-base | 5 + .../text-base/MailboxListener.java.svn-base | 227 ++ .../MailboxListenerSupport.java.svn-base | 81 + .../text-base/MailboxManager.java.svn-base | 260 ++ .../text-base/MailboxPathLocker.java.svn-base | 69 + .../text-base/MailboxSession.java.svn-base | 164 ++ .../MailboxSessionIdGenerator.java.svn-base | 39 + .../text-base/MessageManager.java.svn-base | 382 +++ .../.svn/text-base/QuotaManager.java.svn-base | 52 + .../.svn/text-base/RequestAware.java.svn-base | 41 + ...ardMailboxMetaDataComparator.java.svn-base | 68 + .../SubscriptionManager.java.svn-base | 68 + .../apache/james/mailbox/MailboxListener.java | 227 ++ .../james/mailbox/MailboxListenerSupport.java | 81 + .../apache/james/mailbox/MailboxManager.java | 260 ++ .../james/mailbox/MailboxPathLocker.java | 69 + .../apache/james/mailbox/MailboxSession.java | 164 ++ .../mailbox/MailboxSessionIdGenerator.java | 39 + .../apache/james/mailbox/MessageManager.java | 382 +++ .../apache/james/mailbox/QuotaManager.java | 52 + .../apache/james/mailbox/RequestAware.java | 41 + .../StandardMailboxMetaDataComparator.java | 68 + .../james/mailbox/SubscriptionManager.java | 68 + .../apache/james/mailbox/acl/.svn/all-wcprops | 29 + .../org/apache/james/mailbox/acl/.svn/entries | 164 ++ .../MailboxACLResolver.java.svn-base | 5 + ...impleGroupMembershipResolver.java.svn-base | 5 + .../UnionMailboxACLResolver.java.svn-base | 5 + .../GroupMembershipResolver.java.svn-base | 35 + .../MailboxACLResolver.java.svn-base | 178 ++ ...impleGroupMembershipResolver.java.svn-base | 81 + .../UnionMailboxACLResolver.java.svn-base | 442 ++++ .../mailbox/acl/GroupMembershipResolver.java | 35 + .../james/mailbox/acl/MailboxACLResolver.java | 178 ++ .../acl/SimpleGroupMembershipResolver.java | 81 + .../mailbox/acl/UnionMailboxACLResolver.java | 442 ++++ .../james/mailbox/exception/.svn/all-wcprops | 89 + .../james/mailbox/exception/.svn/entries | 504 ++++ .../BadCredentialsException.java.svn-base | 5 + .../InsufficientRightsException.java.svn-base | 5 + .../prop-base/MailboxException.java.svn-base | 5 + .../MailboxSecurityException.java.svn-base | 5 + ...nsupportedOperationException.java.svn-base | 5 + .../UnsupportedRightException.java.svn-base | 5 + .../BadCredentialsException.java.svn-base | 34 + .../InsufficientRightsException.java.svn-base | 48 + .../text-base/MailboxException.java.svn-base | 41 + .../MailboxExistsException.java.svn-base | 50 + .../MailboxNotFoundException.java.svn-base | 59 + .../MailboxSecurityException.java.svn-base | 47 + .../MessageRangeException.java.svn-base | 34 + .../OverQuotaException.java.svn-base | 53 + .../text-base/ReadOnlyException.java.svn-base | 48 + .../SubscriptionException.java.svn-base | 37 + ...UnsupportedCriteriaException.java.svn-base | 34 + ...nsupportedOperationException.java.svn-base | 34 + .../UnsupportedRightException.java.svn-base | 52 + .../UnsupportedSearchException.java.svn-base | 33 + .../exception/BadCredentialsException.java | 34 + .../InsufficientRightsException.java | 48 + .../mailbox/exception/MailboxException.java | 41 + .../exception/MailboxExistsException.java | 50 + .../exception/MailboxNotFoundException.java | 59 + .../exception/MailboxSecurityException.java | 47 + .../exception/MessageRangeException.java | 34 + .../mailbox/exception/OverQuotaException.java | 53 + .../mailbox/exception/ReadOnlyException.java | 48 + .../exception/SubscriptionException.java | 37 + .../UnsupportedCriteriaException.java | 34 + .../UnsupportedOperationException.java | 34 + .../exception/UnsupportedRightException.java | 52 + .../exception/UnsupportedSearchException.java | 33 + .../james/mailbox/model/.svn/all-wcprops | 119 + .../apache/james/mailbox/model/.svn/entries | 674 ++++++ .../.svn/prop-base/MailboxACL.java.svn-base | 5 + .../prop-base/MailboxConstants.java.svn-base | 5 + .../prop-base/MailboxMetaData.java.svn-base | 5 + .../.svn/prop-base/MessageRange.java.svn-base | 5 + .../prop-base/MessageResult.java.svn-base | 5 + .../prop-base/SimpleMailboxACL.java.svn-base | 5 + .../.svn/text-base/Content.java.svn-base | 49 + .../text-base/FetchGroupImpl.java.svn-base | 109 + .../.svn/text-base/Headers.java.svn-base | 38 + .../InputStreamContent.java.svn-base | 32 + .../.svn/text-base/MailboxACL.java.svn-base | 333 +++ .../text-base/MailboxConstants.java.svn-base | 43 + .../text-base/MailboxMetaData.java.svn-base | 82 + .../.svn/text-base/MailboxPath.java.svn-base | 230 ++ .../.svn/text-base/MailboxQuery.java.svn-base | 192 ++ .../text-base/MessageMetaData.java.svn-base | 69 + .../.svn/text-base/MessageRange.java.svn-base | 301 +++ .../text-base/MessageResult.java.svn-base | 260 ++ .../MessageResultIterator.java.svn-base | 44 + .../text-base/MimeDescriptor.java.svn-base | 142 ++ .../PartContentDescriptorImpl.java.svn-base | 78 + .../model/.svn/text-base/Quota.java.svn-base | 54 + .../.svn/text-base/SearchQuery.java.svn-base | 2096 +++++++++++++++++ .../text-base/SimpleMailboxACL.java.svn-base | 1069 +++++++++ .../.svn/text-base/UpdatedFlags.java.svn-base | 172 ++ .../apache/james/mailbox/model/Content.java | 49 + .../james/mailbox/model/FetchGroupImpl.java | 109 + .../apache/james/mailbox/model/Headers.java | 38 + .../mailbox/model/InputStreamContent.java | 32 + .../james/mailbox/model/MailboxACL.java | 333 +++ .../james/mailbox/model/MailboxConstants.java | 43 + .../james/mailbox/model/MailboxMetaData.java | 82 + .../james/mailbox/model/MailboxPath.java | 230 ++ .../james/mailbox/model/MailboxQuery.java | 192 ++ .../james/mailbox/model/MessageMetaData.java | 69 + .../james/mailbox/model/MessageRange.java | 301 +++ .../james/mailbox/model/MessageResult.java | 260 ++ .../mailbox/model/MessageResultIterator.java | 44 + .../james/mailbox/model/MimeDescriptor.java | 142 ++ .../model/PartContentDescriptorImpl.java | 78 + .../org/apache/james/mailbox/model/Quota.java | 54 + .../james/mailbox/model/SearchQuery.java | 2096 +++++++++++++++++ .../james/mailbox/model/SimpleMailboxACL.java | 1069 +++++++++ .../james/mailbox/model/UpdatedFlags.java | 172 ++ .../james/mailbox/quota/.svn/all-wcprops | 5 + .../apache/james/mailbox/quota/.svn/entries | 28 + .../api/src/reporting-site/.svn/all-wcprops | 11 + .../api/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../api/src/reporting-site/site.xml | 29 + .../api/src/test/.svn/all-wcprops | 5 + .../api/src/test/.svn/entries | 31 + .../api/src/test/java/.svn/all-wcprops | 5 + .../api/src/test/java/.svn/entries | 31 + .../api/src/test/java/org/.svn/all-wcprops | 5 + .../api/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../api/src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 41 + .../org/apache/james/mailbox/.svn/entries | 244 ++ .../AbstractMailboxManagerTest.java.svn-base | 158 ++ .../AbstractStressTest.java.svn-base | 124 + ...tractSubscriptionManagerTest.java.svn-base | 76 + .../MailboxExceptionTest.java.svn-base | 47 + .../MailboxExpressionTest.java.svn-base | 317 +++ .../text-base/MessageRangeTest.java.svn-base | 95 + .../mailbox/AbstractMailboxManagerTest.java | 158 ++ .../james/mailbox/AbstractStressTest.java | 124 + .../AbstractSubscriptionManagerTest.java | 76 + .../james/mailbox/MailboxExceptionTest.java | 47 + .../james/mailbox/MailboxExpressionTest.java | 317 +++ .../james/mailbox/MessageRangeTest.java | 95 + .../apache/james/mailbox/acl/.svn/all-wcprops | 11 + .../org/apache/james/mailbox/acl/.svn/entries | 62 + .../UnionMailboxACLResolverTest.java.svn-base | 5 + .../UnionMailboxACLResolverTest.java.svn-base | 595 +++++ .../acl/UnionMailboxACLResolverTest.java | 595 +++++ .../james/mailbox/mock/.svn/all-wcprops | 23 + .../apache/james/mailbox/mock/.svn/entries | 130 + .../.svn/text-base/MockMail.java.svn-base | 33 + .../MockMailboxManager.java.svn-base | 165 ++ .../MockMailboxSession.java.svn-base | 104 + .../apache/james/mailbox/mock/MockMail.java | 33 + .../mailbox/mock/MockMailboxManager.java | 165 ++ .../mailbox/mock/MockMailboxSession.java | 104 + .../james/mailbox/model/.svn/all-wcprops | 23 + .../apache/james/mailbox/model/.svn/entries | 130 + .../prop-base/Rfc4314RightsTest.java.svn-base | 5 + .../text-base/Rfc4314RightsTest.java.svn-base | 124 + ...SimpleMailboxACLEntryKeyTest.java.svn-base | 143 ++ .../SimpleMailboxACLTest.java.svn-base | 214 ++ .../mailbox/model/Rfc4314RightsTest.java | 124 + .../model/SimpleMailboxACLEntryKeyTest.java | 143 ++ .../mailbox/model/SimpleMailboxACLTest.java | 214 ++ .../james/mailbox/util/.svn/all-wcprops | 11 + .../apache/james/mailbox/util/.svn/entries | 62 + .../text-base/EventCollector.java.svn-base | 45 + .../james/mailbox/util/EventCollector.java | 45 + .../caching/.svn/all-wcprops | 11 + .../caching/.svn/dir-prop-base | 7 + .../apache-james-mailbox/caching/.svn/entries | 65 + .../caching/.svn/text-base/pom.xml.svn-base | 68 + james/apache-james-mailbox/caching/pom.xml | 68 + .../caching/src/.svn/all-wcprops | 5 + .../caching/src/.svn/entries | 31 + .../caching/src/main/.svn/all-wcprops | 5 + .../caching/src/main/.svn/entries | 31 + .../caching/src/main/java/.svn/all-wcprops | 5 + .../caching/src/main/java/.svn/entries | 31 + .../src/main/java/org/.svn/all-wcprops | 5 + .../caching/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/mailbox/.svn/all-wcprops | 5 + .../main/java/org/apache/mailbox/.svn/entries | 31 + .../apache/mailbox/caching/.svn/all-wcprops | 47 + .../org/apache/mailbox/caching/.svn/entries | 269 +++ ...eInvalidatingMailboxListener.java.svn-base | 58 + .../CacheLoaderFromUnderlying.java.svn-base | 5 + .../CachingMailboxMapper.java.svn-base | 85 + ...gMailboxSessionMapperFactory.java.svn-base | 49 + .../CachingMessageMapper.java.svn-base | 136 ++ .../MailboxByPathCache.java.svn-base | 27 + .../MailboxMetadataCache.java.svn-base | 34 + .../CacheInvalidatingMailboxListener.java | 58 + .../caching/CacheLoaderFromUnderlying.java | 5 + .../mailbox/caching/CachingMailboxMapper.java | 85 + .../CachingMailboxSessionMapperFactory.java | 49 + .../mailbox/caching/CachingMessageMapper.java | 136 ++ .../mailbox/caching/MailboxByPathCache.java | 27 + .../mailbox/caching/MailboxMetadataCache.java | 34 + .../mailbox/caching/guava/.svn/all-wcprops | 29 + .../apache/mailbox/caching/guava/.svn/entries | 164 ++ .../AbstractGuavaCache.java.svn-base | 15 + .../text-base/GuavaCacheWrapper.java.svn-base | 37 + .../GuavaMailboxByPathCache.java.svn-base | 88 + .../GuavaMailboxMetadataCache.java.svn-base | 141 ++ .../caching/guava/AbstractGuavaCache.java | 15 + .../caching/guava/GuavaCacheWrapper.java | 37 + .../guava/GuavaMailboxByPathCache.java | 88 + .../guava/GuavaMailboxMetadataCache.java | 141 ++ .../hbase/.svn/all-wcprops | 11 + .../hbase/.svn/dir-prop-base | 7 + james/apache-james-mailbox/hbase/.svn/entries | 65 + .../hbase/.svn/text-base/pom.xml.svn-base | 140 ++ james/apache-james-mailbox/hbase/pom.xml | 140 ++ .../hbase/src/.svn/all-wcprops | 5 + .../hbase/src/.svn/entries | 40 + .../hbase/src/main/.svn/all-wcprops | 5 + .../hbase/src/main/.svn/entries | 37 + .../hbase/src/main/config/.svn/all-wcprops | 11 + .../hbase/src/main/config/.svn/entries | 62 + .../.svn/text-base/hbase-site.xml.svn-base | 49 + .../hbase/src/main/config/hbase-site.xml | 49 + .../hbase/src/main/java/.svn/all-wcprops | 5 + .../hbase/src/main/java/.svn/entries | 31 + .../hbase/src/main/java/org/.svn/all-wcprops | 5 + .../hbase/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/hbase/.svn/all-wcprops | 53 + .../apache/james/mailbox/hbase/.svn/entries | 309 +++ .../text-base/FlagConvertor.java.svn-base | 133 ++ .../HBaseMailboxManager.java.svn-base | 88 + ...eMailboxSessionMapperFactory.java.svn-base | 154 ++ .../HBaseMessageManager.java.svn-base | 57 + .../.svn/text-base/HBaseNames.java.svn-base | 84 + .../HBaseNonTransactionalMapper.java.svn-base | 39 + .../.svn/text-base/HBaseUtils.java.svn-base | 434 ++++ .../text-base/PropertyConvertor.java.svn-base | 74 + .../james/mailbox/hbase/FlagConvertor.java | 133 ++ .../mailbox/hbase/HBaseMailboxManager.java | 88 + .../HBaseMailboxSessionMapperFactory.java | 154 ++ .../mailbox/hbase/HBaseMessageManager.java | 57 + .../james/mailbox/hbase/HBaseNames.java | 84 + .../hbase/HBaseNonTransactionalMapper.java | 39 + .../james/mailbox/hbase/HBaseUtils.java | 434 ++++ .../mailbox/hbase/PropertyConvertor.java | 74 + .../james/mailbox/hbase/io/.svn/all-wcprops | 17 + .../james/mailbox/hbase/io/.svn/entries | 96 + .../text-base/ChunkInputStream.java.svn-base | 114 + .../text-base/ChunkOutputStream.java.svn-base | 118 + .../mailbox/hbase/io/ChunkInputStream.java | 114 + .../mailbox/hbase/io/ChunkOutputStream.java | 118 + .../james/mailbox/hbase/mail/.svn/all-wcprops | 35 + .../james/mailbox/hbase/mail/.svn/entries | 201 ++ .../HBaseMailboxMapper.java.svn-base | 395 ++++ .../.svn/text-base/HBaseMessage.java.svn-base | 397 ++++ .../HBaseMessageMapper.java.svn-base | 761 ++++++ .../HBaseModSeqProvider.java.svn-base | 95 + .../text-base/HBaseUidProvider.java.svn-base | 112 + .../hbase/mail/HBaseMailboxMapper.java | 395 ++++ .../mailbox/hbase/mail/HBaseMessage.java | 397 ++++ .../hbase/mail/HBaseMessageMapper.java | 761 ++++++ .../hbase/mail/HBaseModSeqProvider.java | 95 + .../mailbox/hbase/mail/HBaseUidProvider.java | 112 + .../mailbox/hbase/mail/model/.svn/all-wcprops | 11 + .../mailbox/hbase/mail/model/.svn/entries | 62 + .../.svn/text-base/HBaseMailbox.java.svn-base | 217 ++ .../hbase/mail/model/HBaseMailbox.java | 217 ++ .../james/mailbox/hbase/user/.svn/all-wcprops | 11 + .../james/mailbox/hbase/user/.svn/entries | 62 + .../HBaseSubscriptionMapper.java.svn-base | 172 ++ .../hbase/user/HBaseSubscriptionMapper.java | 172 ++ .../hbase/src/main/resources/.svn/all-wcprops | 5 + .../hbase/src/main/resources/.svn/entries | 31 + .../main/resources/META-INF/.svn/all-wcprops | 5 + .../src/main/resources/META-INF/.svn/entries | 31 + .../META-INF/spring/.svn/all-wcprops | 11 + .../resources/META-INF/spring/.svn/entries | 62 + .../.svn/text-base/mailbox-hbase.xml.svn-base | 62 + .../META-INF/spring/mailbox-hbase.xml | 62 + .../hbase/src/reporting-site/.svn/all-wcprops | 11 + .../hbase/src/reporting-site/.svn/entries | 62 + .../.svn/text-base/site.xml.svn-base | 29 + .../hbase/src/reporting-site/site.xml | 29 + .../hbase/src/site/.svn/all-wcprops | 5 + .../hbase/src/site/.svn/entries | 31 + .../hbase/src/site/resources/.svn/all-wcprops | 5 + .../hbase/src/site/resources/.svn/entries | 31 + .../site/resources/images/.svn/all-wcprops | 23 + .../src/site/resources/images/.svn/entries | 130 + .../james-hbase-mailbox-schema.png.svn-base | 5 + .../james-hbase-mailbox-schema.mm.svn-base | 130 + .../james-hbase-mailbox-schema.png.svn-base | Bin 0 -> 118995 bytes .../james-hbase-mailbox-schema.svg.svn-base | 842 +++++++ .../images/james-hbase-mailbox-schema.mm | 130 + .../images/james-hbase-mailbox-schema.png | Bin 0 -> 118995 bytes .../images/james-hbase-mailbox-schema.svg | 842 +++++++ .../hbase/src/test/.svn/all-wcprops | 5 + .../hbase/src/test/.svn/entries | 34 + .../hbase/src/test/java/.svn/all-wcprops | 5 + .../hbase/src/test/java/.svn/entries | 31 + .../hbase/src/test/java/org/.svn/all-wcprops | 5 + .../hbase/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/hbase/.svn/all-wcprops | 29 + .../apache/james/mailbox/hbase/.svn/entries | 170 ++ .../HBaseClusterSingleton.java.svn-base | 181 ++ .../HBaseMailboxManagerTest.java.svn-base | 114 + ...lboxSessionMapperFactoryTest.java.svn-base | 135 ++ .../text-base/HBaseUtilsTest.java.svn-base | 137 ++ .../mailbox/hbase/HBaseClusterSingleton.java | 181 ++ .../hbase/HBaseMailboxManagerTest.java | 114 + .../HBaseMailboxSessionMapperFactoryTest.java | 135 ++ .../james/mailbox/hbase/HBaseUtilsTest.java | 137 ++ .../james/mailbox/hbase/mail/.svn/all-wcprops | 23 + .../james/mailbox/hbase/mail/.svn/entries | 133 ++ .../HBaseMailboxMapperTest.java.svn-base | 317 +++ .../HBaseMessageMapperTest.java.svn-base | 221 ++ ...BaseUidAndModSeqProviderTest.java.svn-base | 175 ++ .../hbase/mail/HBaseMailboxMapperTest.java | 317 +++ .../hbase/mail/HBaseMessageMapperTest.java | 221 ++ .../mail/HBaseUidAndModSeqProviderTest.java | 175 ++ .../mailbox/hbase/mail/model/.svn/all-wcprops | 11 + .../mailbox/hbase/mail/model/.svn/entries | 62 + .../text-base/HBaseMailboxTest.java.svn-base | 161 ++ .../hbase/mail/model/HBaseMailboxTest.java | 161 ++ .../james/mailbox/hbase/user/.svn/all-wcprops | 11 + .../james/mailbox/hbase/user/.svn/entries | 62 + .../HBaseSubscriptionMapperTest.java.svn-base | 195 ++ .../user/HBaseSubscriptionMapperTest.java | 195 ++ .../hbase/src/test/resources/.svn/all-wcprops | 17 + .../hbase/src/test/resources/.svn/entries | 96 + .../hadoop-metrics2.properties.svn-base | 9 + .../.svn/prop-base/hdfs-site.xml.svn-base | 9 + .../hadoop-metrics2.properties.svn-base | 8 + .../.svn/text-base/hdfs-site.xml.svn-base | 33 + .../test/resources/hadoop-metrics2.properties | 8 + .../hbase/src/test/resources/hdfs-site.xml | 33 + .../apache-james-mailbox/jcr/.svn/all-wcprops | 11 + .../jcr/.svn/dir-prop-base | 8 + james/apache-james-mailbox/jcr/.svn/entries | 65 + .../jcr/.svn/text-base/pom.xml.svn-base | 102 + james/apache-james-mailbox/jcr/pom.xml | 102 + .../jcr/src/.svn/all-wcprops | 5 + .../apache-james-mailbox/jcr/src/.svn/entries | 37 + .../jcr/src/main/.svn/all-wcprops | 5 + .../jcr/src/main/.svn/entries | 34 + .../jcr/src/main/java/.svn/all-wcprops | 5 + .../jcr/src/main/java/.svn/entries | 31 + .../jcr/src/main/java/org/.svn/all-wcprops | 5 + .../jcr/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../jcr/src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../apache/james/mailbox/jcr/.svn/all-wcprops | 71 + .../org/apache/james/mailbox/jcr/.svn/entries | 408 ++++ .../AbstractJCRScalingMapper.java.svn-base | 180 ++ ...lMailboxSessionJCRRepository.java.svn-base | 56 + .../text-base/JCRImapConstants.java.svn-base | 33 + .../text-base/JCRMailboxManager.java.svn-base | 63 + ...RMailboxSessionMapperFactory.java.svn-base | 82 + .../text-base/JCRMessageManager.java.svn-base | 74 + .../JCRRepositoryAuthenticator.java.svn-base | 61 + .../JCRSubscriptionManager.java.svn-base | 43 + .../jcr/.svn/text-base/JCRUtils.java.svn-base | 81 + .../MailboxSessionJCRRepository.java.svn-base | 111 + .../.svn/text-base/Persistent.java.svn-base | 50 + .../mailbox/jcr/AbstractJCRScalingMapper.java | 180 ++ .../GlobalMailboxSessionJCRRepository.java | 56 + .../james/mailbox/jcr/JCRImapConstants.java | 33 + .../james/mailbox/jcr/JCRMailboxManager.java | 63 + .../jcr/JCRMailboxSessionMapperFactory.java | 82 + .../james/mailbox/jcr/JCRMessageManager.java | 74 + .../jcr/JCRRepositoryAuthenticator.java | 61 + .../mailbox/jcr/JCRSubscriptionManager.java | 43 + .../apache/james/mailbox/jcr/JCRUtils.java | 81 + .../jcr/MailboxSessionJCRRepository.java | 111 + .../apache/james/mailbox/jcr/Persistent.java | 50 + .../james/mailbox/jcr/mail/.svn/all-wcprops | 29 + .../james/mailbox/jcr/mail/.svn/entries | 167 ++ .../text-base/JCRMailboxMapper.java.svn-base | 236 ++ .../text-base/JCRMessageMapper.java.svn-base | 705 ++++++ .../text-base/JCRModSeqProvider.java.svn-base | 68 + .../text-base/JCRUidProvider.java.svn-base | 71 + .../mailbox/jcr/mail/JCRMailboxMapper.java | 236 ++ .../mailbox/jcr/mail/JCRMessageMapper.java | 705 ++++++ .../mailbox/jcr/mail/JCRModSeqProvider.java | 68 + .../mailbox/jcr/mail/JCRUidProvider.java | 71 + .../mailbox/jcr/mail/model/.svn/all-wcprops | 23 + .../james/mailbox/jcr/mail/model/.svn/entries | 130 + .../.svn/text-base/JCRMailbox.java.svn-base | 326 +++ .../.svn/text-base/JCRMessage.java.svn-base | 764 ++++++ .../.svn/text-base/JCRProperty.java.svn-base | 231 ++ .../mailbox/jcr/mail/model/JCRMailbox.java | 326 +++ .../mailbox/jcr/mail/model/JCRMessage.java | 764 ++++++ .../mailbox/jcr/mail/model/JCRProperty.java | 231 ++ .../james/mailbox/jcr/user/.svn/all-wcprops | 11 + .../james/mailbox/jcr/user/.svn/entries | 65 + .../JCRSubscriptionMapper.java.svn-base | 191 ++ .../jcr/user/JCRSubscriptionMapper.java | 191 ++ .../mailbox/jcr/user/model/.svn/all-wcprops | 11 + .../james/mailbox/jcr/user/model/.svn/entries | 62 + .../text-base/JCRSubscription.java.svn-base | 182 ++ .../jcr/user/model/JCRSubscription.java | 182 ++ .../jcr/src/main/resources/.svn/all-wcprops | 17 + .../jcr/src/main/resources/.svn/entries | 99 + .../text-base/jcr-repository.xml.svn-base | 67 + .../.svn/text-base/mailbox-jcr.cnd.svn-base | 61 + .../main/resources/META-INF/.svn/all-wcprops | 5 + .../src/main/resources/META-INF/.svn/entries | 31 + .../META-INF/spring/.svn/all-wcprops | 11 + .../resources/META-INF/spring/.svn/entries | 62 + .../.svn/text-base/mailbox-jcr.xml.svn-base | 81 + .../resources/META-INF/spring/mailbox-jcr.xml | 81 + .../jcr/src/main/resources/jcr-repository.xml | 67 + .../jcr/src/main/resources/mailbox-jcr.cnd | 61 + .../jcr/src/reporting-site/.svn/all-wcprops | 11 + .../jcr/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../jcr/src/reporting-site/site.xml | 29 + .../jcr/src/test/.svn/all-wcprops | 5 + .../jcr/src/test/.svn/entries | 34 + .../jcr/src/test/java/.svn/all-wcprops | 5 + .../jcr/src/test/java/.svn/entries | 31 + .../jcr/src/test/java/org/.svn/all-wcprops | 5 + .../jcr/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../jcr/src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../apache/james/mailbox/jcr/.svn/all-wcprops | 23 + .../org/apache/james/mailbox/jcr/.svn/entries | 130 + .../JCRMailboxManagerTest.java.svn-base | 102 + .../text-base/JCRStressTest.java.svn-base | 86 + .../JCRSubscriptionManagerTest.java.svn-base | 69 + .../mailbox/jcr/JCRMailboxManagerTest.java | 102 + .../james/mailbox/jcr/JCRStressTest.java | 86 + .../jcr/JCRSubscriptionManagerTest.java | 69 + .../jcr/src/test/resources/.svn/all-wcprops | 11 + .../jcr/src/test/resources/.svn/entries | 62 + .../text-base/test-repository.xml.svn-base | 92 + .../src/test/resources/test-repository.xml | 92 + .../apache-james-mailbox/jpa/.svn/all-wcprops | 11 + .../jpa/.svn/dir-prop-base | 10 + james/apache-james-mailbox/jpa/.svn/entries | 65 + .../jpa/.svn/text-base/pom.xml.svn-base | 129 + james/apache-james-mailbox/jpa/pom.xml | 129 + .../jpa/src/.svn/all-wcprops | 5 + .../apache-james-mailbox/jpa/src/.svn/entries | 37 + .../jpa/src/main/.svn/all-wcprops | 5 + .../jpa/src/main/.svn/entries | 34 + .../jpa/src/main/java/.svn/all-wcprops | 5 + .../jpa/src/main/java/.svn/entries | 31 + .../jpa/src/main/java/org/.svn/all-wcprops | 5 + .../jpa/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../jpa/src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../apache/james/mailbox/jpa/.svn/all-wcprops | 35 + .../org/apache/james/mailbox/jpa/.svn/entries | 207 ++ .../text-base/JPAMailboxManager.java.svn-base | 69 + ...AMailboxSessionMapperFactory.java.svn-base | 76 + .../text-base/JPAMessageManager.java.svn-base | 69 + .../JPASubscriptionManager.java.svn-base | 43 + .../JPATransactionalMapper.java.svn-base | 99 + .../james/mailbox/jpa/JPAMailboxManager.java | 69 + .../jpa/JPAMailboxSessionMapperFactory.java | 76 + .../james/mailbox/jpa/JPAMessageManager.java | 69 + .../mailbox/jpa/JPASubscriptionManager.java | 43 + .../mailbox/jpa/JPATransactionalMapper.java | 99 + .../james/mailbox/jpa/mail/.svn/all-wcprops | 29 + .../james/mailbox/jpa/mail/.svn/entries | 167 ++ .../text-base/JPAMailboxMapper.java.svn-base | 170 ++ .../text-base/JPAMessageMapper.java.svn-base | 413 ++++ .../text-base/JPAModSeqProvider.java.svn-base | 85 + .../text-base/JPAUidProvider.java.svn-base | 86 + .../mailbox/jpa/mail/JPAMailboxMapper.java | 170 ++ .../mailbox/jpa/mail/JPAMessageMapper.java | 413 ++++ .../mailbox/jpa/mail/JPAModSeqProvider.java | 85 + .../mailbox/jpa/mail/JPAUidProvider.java | 86 + .../mailbox/jpa/mail/model/.svn/all-wcprops | 23 + .../james/mailbox/jpa/mail/model/.svn/entries | 133 ++ .../.svn/text-base/JPAMailbox.java.svn-base | 235 ++ .../.svn/text-base/JPAProperty.java.svn-base | 180 ++ .../.svn/text-base/JPAUserFlag.java.svn-base | 116 + .../mailbox/jpa/mail/model/JPAMailbox.java | 235 ++ .../mailbox/jpa/mail/model/JPAProperty.java | 180 ++ .../mailbox/jpa/mail/model/JPAUserFlag.java | 116 + .../jpa/mail/model/openjpa/.svn/all-wcprops | 35 + .../jpa/mail/model/openjpa/.svn/entries | 198 ++ .../AbstractJPAMessage.java.svn-base | 559 +++++ .../EncryptDecryptHelper.java.svn-base | 66 + .../JPAEncryptedMessage.java.svn-base | 113 + .../.svn/text-base/JPAMessage.java.svn-base | 107 + .../JPAStreamingMessage.java.svn-base | 118 + .../model/openjpa/AbstractJPAMessage.java | 559 +++++ .../model/openjpa/EncryptDecryptHelper.java | 66 + .../model/openjpa/JPAEncryptedMessage.java | 113 + .../jpa/mail/model/openjpa/JPAMessage.java | 107 + .../model/openjpa/JPAStreamingMessage.java | 118 + .../mailbox/jpa/openjpa/.svn/all-wcprops | 17 + .../james/mailbox/jpa/openjpa/.svn/entries | 96 + .../OpenJPAMailboxManager.java.svn-base | 73 + .../OpenJPAMessageManager.java.svn-base | 85 + .../jpa/openjpa/OpenJPAMailboxManager.java | 73 + .../jpa/openjpa/OpenJPAMessageManager.java | 85 + .../james/mailbox/jpa/user/.svn/all-wcprops | 11 + .../james/mailbox/jpa/user/.svn/entries | 65 + .../JPASubscriptionMapper.java.svn-base | 92 + .../jpa/user/JPASubscriptionMapper.java | 92 + .../mailbox/jpa/user/model/.svn/all-wcprops | 11 + .../james/mailbox/jpa/user/model/.svn/entries | 62 + .../text-base/JPASubscription.java.svn-base | 138 ++ .../jpa/user/model/JPASubscription.java | 138 ++ .../jpa/src/main/resources/.svn/all-wcprops | 11 + .../jpa/src/main/resources/.svn/entries | 65 + .../james-database.properties.svn-base | 26 + .../main/resources/META-INF/.svn/all-wcprops | 11 + .../src/main/resources/META-INF/.svn/entries | 65 + .../.svn/text-base/persistence.xml.svn-base | 42 + .../main/resources/META-INF/persistence.xml | 42 + .../META-INF/spring/.svn/all-wcprops | 11 + .../resources/META-INF/spring/.svn/entries | 62 + .../.svn/text-base/mailbox-jpa.xml.svn-base | 83 + .../resources/META-INF/spring/mailbox-jpa.xml | 83 + .../main/resources/james-database.properties | 26 + .../jpa/src/reporting-site/.svn/all-wcprops | 11 + .../jpa/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../jpa/src/reporting-site/site.xml | 29 + .../jpa/src/test/.svn/all-wcprops | 5 + .../jpa/src/test/.svn/entries | 31 + .../jpa/src/test/java/.svn/all-wcprops | 5 + .../jpa/src/test/java/.svn/entries | 31 + .../jpa/src/test/java/org/.svn/all-wcprops | 5 + .../jpa/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../jpa/src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../apache/james/mailbox/jpa/.svn/all-wcprops | 23 + .../org/apache/james/mailbox/jpa/.svn/entries | 130 + .../JPAMailboxManagerTest.java.svn-base | 128 + .../text-base/JPAStressTest.java.svn-base | 118 + .../JPASubscriptionManagerTest.java.svn-base | 78 + .../mailbox/jpa/JPAMailboxManagerTest.java | 128 + .../james/mailbox/jpa/JPAStressTest.java | 118 + .../jpa/JPASubscriptionManagerTest.java | 78 + .../lucene/.svn/all-wcprops | 11 + .../lucene/.svn/dir-prop-base | 8 + .../apache-james-mailbox/lucene/.svn/entries | 65 + .../lucene/.svn/text-base/pom.xml.svn-base | 89 + james/apache-james-mailbox/lucene/pom.xml | 89 + .../lucene/src/.svn/all-wcprops | 5 + .../lucene/src/.svn/entries | 37 + .../lucene/src/main/.svn/all-wcprops | 5 + .../lucene/src/main/.svn/entries | 34 + .../lucene/src/main/java/.svn/all-wcprops | 5 + .../lucene/src/main/java/.svn/entries | 31 + .../lucene/src/main/java/org/.svn/all-wcprops | 5 + .../lucene/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/lucene/.svn/all-wcprops | 5 + .../apache/james/mailbox/lucene/.svn/entries | 31 + .../mailbox/lucene/search/.svn/all-wcprops | 29 + .../james/mailbox/lucene/search/.svn/entries | 164 ++ .../LenientImapSearchAnalyzer.java.svn-base | 54 + .../LuceneMessageSearchIndex.java.svn-base | 1303 ++++++++++ .../StrictImapSearchAnalyzer.java.svn-base | 59 + .../text-base/UpperCaseFilter.java.svn-base | 52 + .../search/LenientImapSearchAnalyzer.java | 54 + .../search/LuceneMessageSearchIndex.java | 1303 ++++++++++ .../search/StrictImapSearchAnalyzer.java | 59 + .../lucene/search/UpperCaseFilter.java | 52 + .../src/main/resources/.svn/all-wcprops | 5 + .../lucene/src/main/resources/.svn/entries | 31 + .../main/resources/META-INF/.svn/all-wcprops | 5 + .../src/main/resources/META-INF/.svn/entries | 31 + .../META-INF/spring/.svn/all-wcprops | 11 + .../resources/META-INF/spring/.svn/entries | 62 + .../mailbox-index-lucene.xml.svn-base | 44 + .../META-INF/spring/mailbox-index-lucene.xml | 44 + .../src/reporting-site/.svn/all-wcprops | 11 + .../lucene/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../lucene/src/reporting-site/site.xml | 29 + .../lucene/src/test/.svn/all-wcprops | 5 + .../lucene/src/test/.svn/entries | 31 + .../lucene/src/test/java/.svn/all-wcprops | 5 + .../lucene/src/test/java/.svn/entries | 31 + .../lucene/src/test/java/org/.svn/all-wcprops | 5 + .../lucene/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/lucene/.svn/all-wcprops | 5 + .../apache/james/mailbox/lucene/.svn/entries | 31 + .../mailbox/lucene/search/.svn/all-wcprops | 17 + .../james/mailbox/lucene/search/.svn/entries | 96 + ...LuceneMessageSearchIndexTest.java.svn-base | 765 ++++++ ...LuceneMessageSearchIndexText.java.svn-base | 28 + .../search/LuceneMessageSearchIndexTest.java | 765 ++++++ .../StrictLuceneMessageSearchIndexText.java | 28 + .../maildir/.svn/all-wcprops | 11 + .../maildir/.svn/dir-prop-base | 8 + .../apache-james-mailbox/maildir/.svn/entries | 65 + .../maildir/.svn/text-base/pom.xml.svn-base | 90 + james/apache-james-mailbox/maildir/pom.xml | 90 + .../maildir/src/.svn/all-wcprops | 5 + .../maildir/src/.svn/entries | 37 + .../maildir/src/main/.svn/all-wcprops | 5 + .../maildir/src/main/.svn/entries | 34 + .../maildir/src/main/java/.svn/all-wcprops | 5 + .../maildir/src/main/java/.svn/entries | 31 + .../src/main/java/org/.svn/all-wcprops | 5 + .../maildir/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/maildir/.svn/all-wcprops | 35 + .../apache/james/mailbox/maildir/.svn/entries | 204 ++ .../text-base/MaildirFolder.java.svn-base | 1018 ++++++++ ...rMailboxSessionMapperFactory.java.svn-base | 61 + .../MaildirMessageName.java.svn-base | 473 ++++ .../.svn/text-base/MaildirStore.java.svn-base | 291 +++ .../text-base/UidConstraint.java.svn-base | 126 + .../james/mailbox/maildir/MaildirFolder.java | 1018 ++++++++ .../MaildirMailboxSessionMapperFactory.java | 61 + .../mailbox/maildir/MaildirMessageName.java | 473 ++++ .../james/mailbox/maildir/MaildirStore.java | 291 +++ .../james/mailbox/maildir/UidConstraint.java | 126 + .../mailbox/maildir/mail/.svn/all-wcprops | 17 + .../james/mailbox/maildir/mail/.svn/entries | 99 + .../MaildirMailboxMapper.java.svn-base | 323 +++ .../MaildirMessageMapper.java.svn-base | 494 ++++ .../maildir/mail/MaildirMailboxMapper.java | 323 +++ .../maildir/mail/MaildirMessageMapper.java | 494 ++++ .../maildir/mail/model/.svn/all-wcprops | 17 + .../mailbox/maildir/mail/model/.svn/entries | 96 + .../prop-base/MaildirMailbox.java.svn-base | 5 + .../text-base/MaildirMailbox.java.svn-base | 41 + .../text-base/MaildirMessage.java.svn-base | 456 ++++ .../maildir/mail/model/MaildirMailbox.java | 41 + .../maildir/mail/model/MaildirMessage.java | 456 ++++ .../mailbox/maildir/user/.svn/all-wcprops | 11 + .../james/mailbox/maildir/user/.svn/entries | 62 + .../MaildirSubscriptionMapper.java.svn-base | 188 ++ .../user/MaildirSubscriptionMapper.java | 188 ++ .../src/main/resources/.svn/all-wcprops | 11 + .../maildir/src/main/resources/.svn/entries | 65 + .../mailbox-maildir.properties.svn-base | 20 + .../main/resources/META-INF/.svn/all-wcprops | 5 + .../src/main/resources/META-INF/.svn/entries | 31 + .../META-INF/spring/.svn/all-wcprops | 11 + .../resources/META-INF/spring/.svn/entries | 62 + .../text-base/mailbox-maildir.xml.svn-base | 56 + .../META-INF/spring/mailbox-maildir.xml | 56 + .../main/resources/mailbox-maildir.properties | 20 + .../src/reporting-site/.svn/all-wcprops | 11 + .../maildir/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../maildir/src/reporting-site/site.xml | 29 + .../maildir/src/test/.svn/all-wcprops | 5 + .../maildir/src/test/.svn/entries | 31 + .../maildir/src/test/java/.svn/all-wcprops | 5 + .../maildir/src/test/java/.svn/entries | 31 + .../src/test/java/org/.svn/all-wcprops | 5 + .../maildir/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/maildir/.svn/all-wcprops | 35 + .../apache/james/mailbox/maildir/.svn/entries | 198 ++ .../MaildirMailboxManagerTest.java.svn-base | 5 + .../MailderMessageNameTest.java.svn-base | 235 ++ .../MaildirMailboxManagerTest.java.svn-base | 217 ++ .../text-base/MaildirStressTest.java.svn-base | 75 + ...ildirSubscriptionManagerTest.java.svn-base | 36 + .../.svn/text-base/OsDetector.java.svn-base | 35 + .../maildir/MailderMessageNameTest.java | 235 ++ .../maildir/MaildirMailboxManagerTest.java | 217 ++ .../mailbox/maildir/MaildirStressTest.java | 75 + .../MaildirSubscriptionManagerTest.java | 36 + .../james/mailbox/maildir/OsDetector.java | 35 + .../memory/.svn/all-wcprops | 11 + .../memory/.svn/dir-prop-base | 8 + .../apache-james-mailbox/memory/.svn/entries | 65 + .../memory/.svn/prop-base/pom.xml.svn-base | 5 + .../memory/.svn/text-base/pom.xml.svn-base | 74 + james/apache-james-mailbox/memory/build | 1 + james/apache-james-mailbox/memory/pom.xml | 109 + .../memory/src/.svn/all-wcprops | 5 + .../memory/src/.svn/entries | 37 + .../memory/src/main/.svn/all-wcprops | 5 + .../memory/src/main/.svn/entries | 34 + .../memory/src/main/java/.svn/all-wcprops | 5 + .../memory/src/main/java/.svn/entries | 31 + .../memory/src/main/java/core/app | 1 + .../memory/src/main/java/core/callback | 1 + .../src/main/java/core/callbacks/Memory.java | 1 + .../core/connector/ConnectorException.java | 1 + .../main/java/core/connector/FileInfo.java | 1 + .../connector/dropbox/ClientInfoDropbox.java | 1 + .../src/main/java/core/connector/dropbox/sync | 1 + .../java/core/connector/s3/ClientInfoS3.java | 1 + .../src/main/java/core/connector/s3/sync | 1 + .../sync/EncryptedStoreConnector.java | 1 + .../core/connector/sync/StoreConnector.java | 1 + .../memory/src/main/java/core/constants | 1 + .../memory/src/main/java/core/crypt | 1 + .../memory/src/main/java/core/exceptions | 1 + .../memory/src/main/java/core/io | 1 + .../memory/src/main/java/core/server | 1 + .../memory/src/main/java/core/util | 1 + .../memory/src/main/java/mail/core | 1 + .../memory/src/main/java/mail/server/db | 1 + .../memory/src/main/java/mail/server/handler | 1 + .../memory/src/main/java/mail/server/push | 1 + .../memory/src/main/java/mail/server/util | 1 + .../memory/src/main/java/org/.svn/all-wcprops | 5 + .../memory/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/inmemory/.svn/all-wcprops | 11 + .../james/mailbox/inmemory/.svn/entries | 68 + ...yMailboxSessionMapperFactory.java.svn-base | 67 + .../InMemoryMailboxSessionMapperFactory.java | 88 + .../mailbox/inmemory/mail/.svn/all-wcprops | 29 + .../james/mailbox/inmemory/mail/.svn/entries | 164 ++ .../InMemoryMailboxMapper.java.svn-base | 132 ++ .../InMemoryMessageMapper.java.svn-base | 260 ++ .../InMemoryModSeqProvider.java.svn-base | 55 + .../InMemoryUidProvider.java.svn-base | 57 + .../inmemory/mail/InMemoryMailboxMapper.java | 100 + .../inmemory/mail/InMemoryMessageMapper.java | 240 ++ .../inmemory/mail/InMemoryModSeqProvider.java | 60 + .../inmemory/mail/InMemoryUidProvider.java | 62 + .../mailbox/inmemory/user/.svn/all-wcprops | 11 + .../james/mailbox/inmemory/user/.svn/entries | 62 + .../InMemorySubscriptionMapper.java.svn-base | 111 + .../user/InMemorySubscriptionMapper.java | 111 + .../memory/src/main/java/org/bc | 1 + .../memory/src/main/java/org/json | 1 + .../memory/src/main/java/org/timepedia | 1 + .../src/main/resources/.svn/all-wcprops | 5 + .../memory/src/main/resources/.svn/entries | 31 + .../main/resources/META-INF/.svn/all-wcprops | 5 + .../src/main/resources/META-INF/.svn/entries | 31 + .../META-INF/spring/.svn/all-wcprops | 17 + .../resources/META-INF/spring/.svn/entries | 96 + .../mailbox-memory-osgi.xml.svn-base | 39 + .../text-base/mailbox-memory.xml.svn-base | 49 + .../META-INF/spring/mailbox-memory-osgi.xml | 39 + .../META-INF/spring/mailbox-memory.xml | 49 + .../src/reporting-site/.svn/all-wcprops | 11 + .../memory/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../memory/src/reporting-site/site.xml | 29 + .../memory/src/test/.svn/all-wcprops | 5 + .../memory/src/test/.svn/entries | 31 + .../memory/src/test/java/.svn/all-wcprops | 5 + .../memory/src/test/java/.svn/entries | 31 + .../memory/src/test/java/org/.svn/all-wcprops | 5 + .../memory/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/inmemory/.svn/all-wcprops | 11 + .../james/mailbox/inmemory/.svn/entries | 62 + .../InMemoryMailboxManagerTest.java.svn-base | 79 + .../inmemory/InMemoryMailboxManagerTest.java | 79 + james/apache-james-mailbox/pom.xml | 576 +++++ .../spring/.svn/all-wcprops | 11 + .../spring/.svn/dir-prop-base | 8 + .../apache-james-mailbox/spring/.svn/entries | 65 + .../spring/.svn/text-base/pom.xml.svn-base | 178 ++ james/apache-james-mailbox/spring/pom.xml | 178 ++ .../spring/src/.svn/all-wcprops | 5 + .../spring/src/.svn/entries | 37 + .../spring/src/main/.svn/all-wcprops | 5 + .../spring/src/main/.svn/entries | 34 + .../spring/src/main/java/.svn/all-wcprops | 5 + .../spring/src/main/java/.svn/entries | 31 + .../spring/src/main/java/org/.svn/all-wcprops | 5 + .../spring/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/spring/.svn/all-wcprops | 17 + .../apache/james/mailbox/spring/.svn/entries | 96 + .../AnonymousAuthenticator.java.svn-base | 30 + .../text-base/SpringMailbox.java.svn-base | 37 + .../spring/AnonymousAuthenticator.java | 30 + .../james/mailbox/spring/SpringMailbox.java | 37 + .../src/main/resources/.svn/all-wcprops | 17 + .../spring/src/main/resources/.svn/entries | 99 + .../.svn/text-base/log4j.properties.svn-base | 48 + .../.svn/text-base/mailbox-jcr.cnd.svn-base | 67 + .../main/resources/META-INF/.svn/all-wcprops | 5 + .../src/main/resources/META-INF/.svn/entries | 34 + .../resources/META-INF/org/.svn/all-wcprops | 5 + .../main/resources/META-INF/org/.svn/entries | 31 + .../META-INF/org/apache/.svn/all-wcprops | 5 + .../META-INF/org/apache/.svn/entries | 31 + .../org/apache/james/.svn/all-wcprops | 5 + .../META-INF/org/apache/james/.svn/entries | 28 + .../META-INF/spring/.svn/all-wcprops | 35 + .../resources/META-INF/spring/.svn/entries | 198 ++ ...ilbox-authenticator-anonymous.xml.svn-base | 28 + .../mailbox-authenticator-osgi.xml.svn-base | 33 + .../mailbox-locker-osgi.xml.svn-base | 33 + .../text-base/mailbox-locker.xml.svn-base | 28 + .../text-base/spring-mailbox.xml.svn-base | 56 + .../mailbox-authenticator-anonymous.xml | 28 + .../spring/mailbox-authenticator-osgi.xml | 33 + .../META-INF/spring/mailbox-locker-osgi.xml | 33 + .../META-INF/spring/mailbox-locker.xml | 28 + .../META-INF/spring/spring-mailbox.xml | 56 + .../src/main/resources/log4j.properties | 48 + .../spring/src/main/resources/mailbox-jcr.cnd | 67 + .../src/reporting-site/.svn/all-wcprops | 11 + .../spring/src/reporting-site/.svn/entries | 62 + .../.svn/text-base/site.xml.svn-base | 29 + .../spring/src/reporting-site/site.xml | 29 + .../spring/src/test/.svn/all-wcprops | 5 + .../spring/src/test/.svn/entries | 34 + .../spring/src/test/java/.svn/all-wcprops | 5 + .../spring/src/test/java/.svn/entries | 31 + .../spring/src/test/java/org/.svn/all-wcprops | 5 + .../spring/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/spring/.svn/all-wcprops | 11 + .../apache/james/mailbox/spring/.svn/entries | 62 + .../text-base/SpringMailboxTest.java.svn-base | 44 + .../mailbox/spring/SpringMailboxTest.java | 44 + .../src/test/resources/.svn/all-wcprops | 23 + .../spring/src/test/resources/.svn/entries | 133 ++ .../hadoop-metrics2.properties.svn-base | 9 + .../.svn/prop-base/hdfs-site.xml.svn-base | 9 + .../hadoop-metrics2.properties.svn-base | 8 + .../.svn/text-base/hbase-site.xml.svn-base | 49 + .../.svn/text-base/hdfs-site.xml.svn-base | 33 + .../test/resources/META-INF/.svn/all-wcprops | 5 + .../src/test/resources/META-INF/.svn/entries | 31 + .../resources/META-INF/org/.svn/all-wcprops | 5 + .../test/resources/META-INF/org/.svn/entries | 31 + .../META-INF/org/apache/.svn/all-wcprops | 5 + .../META-INF/org/apache/.svn/entries | 31 + .../org/apache/james/.svn/all-wcprops | 17 + .../META-INF/org/apache/james/.svn/entries | 96 + .../prop-base/database.properties.svn-base | 9 + .../spring-mailbox-lucene.xml.svn-base | 9 + .../text-base/database.properties.svn-base | 26 + .../spring-mailbox-lucene.xml.svn-base | 44 + .../org/apache/james/database.properties | 26 + .../apache/james/spring-mailbox-lucene.xml | 44 + .../test/resources/hadoop-metrics2.properties | 8 + .../spring/src/test/resources/hbase-site.xml | 49 + .../spring/src/test/resources/hdfs-site.xml | 33 + .../apache-james-mailbox/src/.svn/all-wcprops | 5 + james/apache-james-mailbox/src/.svn/entries | 34 + .../src/reporting-site/.svn/all-wcprops | 11 + .../src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 36 + .../src/reporting-site/site.xml | 36 + .../src/site/.svn/all-wcprops | 11 + .../src/site/.svn/dir-prop-base | 6 + .../src/site/.svn/entries | 68 + .../src/site/.svn/text-base/site.xml.svn-base | 69 + .../src/site/resources/.svn/all-wcprops | 5 + .../src/site/resources/.svn/entries | 31 + .../site/resources/images/.svn/all-wcprops | 29 + .../src/site/resources/images/.svn/entries | 167 ++ .../prop-base/asf-logo-reduced.gif.svn-base | 5 + .../.svn/prop-base/james-logo.jpg.svn-base | 5 + .../prop-base/james-server-logo.gif.svn-base | 5 + .../images/.svn/prop-base/void.gif.svn-base | 5 + .../text-base/asf-logo-reduced.gif.svn-base | Bin 0 -> 6636 bytes .../.svn/text-base/james-logo.jpg.svn-base | Bin 0 -> 5180 bytes .../text-base/james-server-logo.gif.svn-base | Bin 0 -> 6944 bytes .../images/.svn/text-base/void.gif.svn-base | Bin 0 -> 49 bytes .../resources/images/asf-logo-reduced.gif | Bin 0 -> 6636 bytes .../src/site/resources/images/james-logo.jpg | Bin 0 -> 5180 bytes .../resources/images/james-server-logo.gif | Bin 0 -> 6944 bytes .../resources/images/uml/.svn/all-wcprops | 83 + .../site/resources/images/uml/.svn/entries | 470 ++++ ...es-mailbox-api-mailboxmanager.png.svn-base | 5 + ...es-mailbox-api-mailboxsession.png.svn-base | 5 + ...es-mailbox-api-messagemanager.png.svn-base | 5 + ...-apache-james-mailbox-api-msc.png.svn-base | 5 + ...ilbox-api-subscriptionmanager.png.svn-base | 5 + ...he-james-mailbox-jpa-managers.png.svn-base | 5 + ...ames-mailbox-maildir-managers.png.svn-base | 5 + ...james-mailbox-memory-managers.png.svn-base | 5 + ...-apache-james-mailbox-package.png.svn-base | 5 + ...-mailbox-store-mailboxmanager.png.svn-base | 5 + ...-mailbox-store-messagemanager.png.svn-base | 5 + ...che-james-mailbox-store-model.png.svn-base | 5 + ...box-store-subscriptionmanager.png.svn-base | 5 + ...es-mailbox-api-mailboxmanager.png.svn-base | Bin 0 -> 16030 bytes ...es-mailbox-api-mailboxsession.png.svn-base | Bin 0 -> 17781 bytes ...es-mailbox-api-messagemanager.png.svn-base | Bin 0 -> 45433 bytes ...-apache-james-mailbox-api-msc.png.svn-base | Bin 0 -> 90737 bytes ...ilbox-api-subscriptionmanager.png.svn-base | Bin 0 -> 5550 bytes ...he-james-mailbox-jpa-managers.png.svn-base | Bin 0 -> 29376 bytes ...ames-mailbox-maildir-managers.png.svn-base | Bin 0 -> 32459 bytes ...james-mailbox-memory-managers.png.svn-base | Bin 0 -> 34256 bytes ...-apache-james-mailbox-package.png.svn-base | Bin 0 -> 18180 bytes ...-mailbox-store-mailboxmanager.png.svn-base | Bin 0 -> 66754 bytes ...-mailbox-store-messagemanager.png.svn-base | Bin 0 -> 45095 bytes ...che-james-mailbox-store-model.png.svn-base | Bin 0 -> 46047 bytes ...box-store-subscriptionmanager.png.svn-base | Bin 0 -> 11997 bytes ...pache-james-mailbox-api-mailboxmanager.png | Bin 0 -> 16030 bytes ...pache-james-mailbox-api-mailboxsession.png | Bin 0 -> 17781 bytes ...pache-james-mailbox-api-messagemanager.png | Bin 0 -> 45433 bytes .../uml/org-apache-james-mailbox-api-msc.png | Bin 0 -> 90737 bytes ...-james-mailbox-api-subscriptionmanager.png | Bin 0 -> 5550 bytes .../org-apache-james-mailbox-jpa-managers.png | Bin 0 -> 29376 bytes ...-apache-james-mailbox-maildir-managers.png | Bin 0 -> 32459 bytes ...g-apache-james-mailbox-memory-managers.png | Bin 0 -> 34256 bytes .../uml/org-apache-james-mailbox-package.png | Bin 0 -> 18180 bytes ...che-james-mailbox-store-mailboxmanager.png | Bin 0 -> 66754 bytes ...che-james-mailbox-store-messagemanager.png | Bin 0 -> 45095 bytes .../org-apache-james-mailbox-store-model.png | Bin 0 -> 46047 bytes ...ames-mailbox-store-subscriptionmanager.png | Bin 0 -> 11997 bytes .../src/site/resources/images/void.gif | Bin 0 -> 49 bytes james/apache-james-mailbox/src/site/site.xml | 69 + .../src/site/xdoc/.svn/all-wcprops | 77 + .../src/site/xdoc/.svn/entries | 436 ++++ .../xdoc/.svn/text-base/index.xml.svn-base | 72 + .../.svn/text-base/mailbox-api.xml.svn-base | 84 + .../.svn/text-base/mailbox-guice.xml.svn-base | 33 + .../.svn/text-base/mailbox-hbase.xml.svn-base | 132 ++ .../.svn/text-base/mailbox-jcr.xml.svn-base | 43 + .../.svn/text-base/mailbox-jpa.xml.svn-base | 59 + .../text-base/mailbox-maildir.xml.svn-base | 40 + .../text-base/mailbox-memory.xml.svn-base | 41 + .../text-base/mailbox-spring.xml.svn-base | 62 + .../.svn/text-base/mailbox-store.xml.svn-base | 78 + .../.svn/text-base/mailbox-tool.xml.svn-base | 36 + .../.svn/text-base/source-code.xml.svn-base | 64 + .../src/site/xdoc/index.xml | 72 + .../src/site/xdoc/mailbox-api.xml | 84 + .../src/site/xdoc/mailbox-guice.xml | 33 + .../src/site/xdoc/mailbox-hbase.xml | 132 ++ .../src/site/xdoc/mailbox-jcr.xml | 43 + .../src/site/xdoc/mailbox-jpa.xml | 59 + .../src/site/xdoc/mailbox-maildir.xml | 40 + .../src/site/xdoc/mailbox-memory.xml | 41 + .../src/site/xdoc/mailbox-spring.xml | 62 + .../src/site/xdoc/mailbox-store.xml | 78 + .../src/site/xdoc/mailbox-tool.xml | 36 + .../src/site/xdoc/source-code.xml | 64 + .../store/.svn/all-wcprops | 11 + .../store/.svn/dir-prop-base | 9 + james/apache-james-mailbox/store/.svn/entries | 65 + .../store/.svn/text-base/pom.xml.svn-base | 90 + james/apache-james-mailbox/store/pom.xml | 90 + .../store/src/.svn/all-wcprops | 5 + .../store/src/.svn/entries | 37 + .../store/src/main/.svn/all-wcprops | 5 + .../store/src/main/.svn/entries | 31 + .../store/src/main/java/.svn/all-wcprops | 5 + .../store/src/main/java/.svn/entries | 31 + .../store/src/main/java/org/.svn/all-wcprops | 5 + .../store/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/store/.svn/all-wcprops | 143 ++ .../apache/james/mailbox/store/.svn/entries | 828 +++++++ ...actMailboxSessionIdGenerator.java.svn-base | 5 + .../LazyMimeDescriptor.java.svn-base | 5 + .../MailboxEventDispatcher.java.svn-base | 5 + .../prop-base/MailboxMetaData.java.svn-base | 5 + .../prop-base/MessageResultImpl.java.svn-base | 5 + ...domMailboxSessionIdGenerator.java.svn-base | 5 + .../SimpleMailboxMetaData.java.svn-base | 5 + .../StoreMailboxManager.java.svn-base | 5 + ...actDelegatingMailboxListener.java.svn-base | 185 ++ .../AbstractMailboxPathLocker.java.svn-base | 63 + ...actMailboxSessionIdGenerator.java.svn-base | 48 + .../text-base/Authenticator.java.svn-base | 36 + ...MapDelegatingMailboxListener.java.svn-base | 50 + .../JVMMailboxPathLocker.java.svn-base | 74 + .../LazyMimeDescriptor.java.svn-base | 171 ++ .../MailboxEventDispatcher.java.svn-base | 296 +++ .../text-base/MailboxMetaData.java.svn-base | 156 ++ .../MailboxSessionMapperFactory.java.svn-base | 142 ++ .../text-base/MessageResultImpl.java.svn-base | 422 ++++ .../MimeDescriptorImpl.java.svn-base | 357 +++ ...domMailboxSessionIdGenerator.java.svn-base | 40 + .../.svn/text-base/ResultHeader.java.svn-base | 78 + .../.svn/text-base/ResultUtils.java.svn-base | 264 +++ .../SimpleMailboxMetaData.java.svn-base | 125 + .../SimpleMailboxSession.java.svn-base | 203 ++ .../SimpleMessageMetaData.java.svn-base | 100 + .../StoreMailboxManager.java.svn-base | 593 +++++ .../text-base/StoreMailboxPath.java.svn-base | 43 + .../StoreMessageManager.java.svn-base | 870 +++++++ .../StoreMessageResultIterator.java.svn-base | 292 +++ .../StoreSubscriptionManager.java.svn-base | 135 ++ .../AbstractDelegatingMailboxListener.java | 185 ++ .../store/AbstractMailboxPathLocker.java | 63 + .../AbstractMailboxSessionIdGenerator.java | 48 + .../james/mailbox/store/Authenticator.java | 36 + .../HashMapDelegatingMailboxListener.java | 50 + .../mailbox/store/JVMMailboxPathLocker.java | 74 + .../mailbox/store/LazyMimeDescriptor.java | 171 ++ .../mailbox/store/MailboxEventDispatcher.java | 296 +++ .../james/mailbox/store/MailboxMetaData.java | 156 ++ .../store/MailboxSessionMapperFactory.java | 142 ++ .../mailbox/store/MessageResultImpl.java | 422 ++++ .../mailbox/store/MimeDescriptorImpl.java | 357 +++ .../RandomMailboxSessionIdGenerator.java | 40 + .../james/mailbox/store/ResultHeader.java | 78 + .../james/mailbox/store/ResultUtils.java | 264 +++ .../mailbox/store/SimpleMailboxMetaData.java | 125 + .../mailbox/store/SimpleMailboxSession.java | 203 ++ .../mailbox/store/SimpleMessageMetaData.java | 100 + .../mailbox/store/StoreMailboxManager.java | 593 +++++ .../james/mailbox/store/StoreMailboxPath.java | 43 + .../mailbox/store/StoreMessageManager.java | 870 +++++++ .../store/StoreMessageResultIterator.java | 292 +++ .../store/StoreSubscriptionManager.java | 135 ++ .../james/mailbox/store/mail/.svn/all-wcprops | 59 + .../james/mailbox/store/mail/.svn/entries | 337 +++ ...bstractLockingModSeqProvider.java.svn-base | 63 + .../AbstractLockingUidProvider.java.svn-base | 65 + .../AbstractMessageMapper.java.svn-base | 170 ++ .../text-base/MailboxMapper.java.svn-base | 93 + .../MailboxMapperFactory.java.svn-base | 33 + .../text-base/MessageMapper.java.svn-base | 233 ++ .../MessageMapperFactory.java.svn-base | 34 + .../text-base/ModSeqProvider.java.svn-base | 57 + .../.svn/text-base/UidProvider.java.svn-base | 55 + .../mail/AbstractLockingModSeqProvider.java | 63 + .../mail/AbstractLockingUidProvider.java | 65 + .../store/mail/AbstractMessageMapper.java | 170 ++ .../mailbox/store/mail/MailboxMapper.java | 93 + .../store/mail/MailboxMapperFactory.java | 33 + .../mailbox/store/mail/MessageMapper.java | 233 ++ .../store/mail/MessageMapperFactory.java | 34 + .../mailbox/store/mail/ModSeqProvider.java | 57 + .../james/mailbox/store/mail/UidProvider.java | 55 + .../mailbox/store/mail/model/.svn/all-wcprops | 35 + .../mailbox/store/mail/model/.svn/entries | 201 ++ .../text-base/AbstractMessage.java.svn-base | 123 + .../.svn/text-base/Mailbox.java.svn-base | 91 + .../.svn/text-base/Message.java.svn-base | 210 ++ .../.svn/text-base/Property.java.svn-base | 61 + .../text-base/StandardNames.java.svn-base | 223 ++ .../store/mail/model/AbstractMessage.java | 123 + .../mailbox/store/mail/model/Mailbox.java | 91 + .../mailbox/store/mail/model/Message.java | 210 ++ .../mailbox/store/mail/model/Property.java | 61 + .../store/mail/model/StandardNames.java | 223 ++ .../store/mail/model/impl/.svn/all-wcprops | 29 + .../store/mail/model/impl/.svn/entries | 164 ++ .../text-base/PropertyBuilder.java.svn-base | 478 ++++ .../text-base/SimpleMailbox.java.svn-base | 170 ++ .../text-base/SimpleMessage.java.svn-base | 250 ++ .../text-base/SimpleProperty.java.svn-base | 102 + .../mail/model/impl/PropertyBuilder.java | 478 ++++ .../store/mail/model/impl/SimpleMailbox.java | 170 ++ .../store/mail/model/impl/SimpleMessage.java | 250 ++ .../store/mail/model/impl/SimpleProperty.java | 102 + .../mailbox/store/quota/.svn/all-wcprops | 29 + .../james/mailbox/store/quota/.svn/entries | 164 ++ .../text-base/FixedQuotaManager.java.svn-base | 59 + .../ListeningQuotaManager.java.svn-base | 232 ++ .../PerUserQuotaManager.java.svn-base | 80 + .../.svn/text-base/QuotaImpl.java.svn-base | 64 + .../store/quota/FixedQuotaManager.java | 59 + .../store/quota/ListeningQuotaManager.java | 232 ++ .../store/quota/PerUserQuotaManager.java | 80 + .../james/mailbox/store/quota/QuotaImpl.java | 64 + .../mailbox/store/search/.svn/all-wcprops | 47 + .../james/mailbox/store/search/.svn/entries | 269 +++ .../LazyMessageSearchIndex.java.svn-base | 107 + .../ListeningMessageSearchIndex.java.svn-base | 170 ++ .../MessageSearchIndex.java.svn-base | 49 + .../text-base/MessageSearcher.java.svn-base | 279 +++ .../text-base/MessageSearches.java.svn-base | 605 +++++ .../.svn/text-base/SearchUtil.java.svn-base | 463 ++++ .../SimpleMessageSearchIndex.java.svn-base | 110 + .../store/search/LazyMessageSearchIndex.java | 107 + .../search/ListeningMessageSearchIndex.java | 170 ++ .../store/search/MessageSearchIndex.java | 49 + .../mailbox/store/search/MessageSearcher.java | 279 +++ .../mailbox/store/search/MessageSearches.java | 605 +++++ .../mailbox/store/search/SearchUtil.java | 463 ++++ .../search/SimpleMessageSearchIndex.java | 110 + .../store/search/comparator/.svn/all-wcprops | 65 + .../store/search/comparator/.svn/entries | 368 +++ .../AbstractHeaderComparator.java.svn-base | 58 + .../BaseSubjectComparator.java.svn-base | 53 + .../CombinedComparator.java.svn-base | 102 + .../HeaderDisplayComparator.java.svn-base | 67 + .../HeaderMailboxComparator.java.svn-base | 80 + .../InternalDateComparator.java.svn-base | 48 + .../text-base/ReverseComparator.java.svn-base | 40 + .../SentDateComparator.java.svn-base | 80 + .../text-base/SizeComparator.java.svn-base | 48 + .../text-base/UidComparator.java.svn-base | 48 + .../comparator/AbstractHeaderComparator.java | 58 + .../comparator/BaseSubjectComparator.java | 53 + .../search/comparator/CombinedComparator.java | 102 + .../comparator/HeaderDisplayComparator.java | 67 + .../comparator/HeaderMailboxComparator.java | 80 + .../comparator/InternalDateComparator.java | 48 + .../search/comparator/ReverseComparator.java | 40 + .../search/comparator/SentDateComparator.java | 80 + .../search/comparator/SizeComparator.java | 48 + .../search/comparator/UidComparator.java | 48 + .../mailbox/store/streaming/.svn/all-wcprops | 47 + .../mailbox/store/streaming/.svn/entries | 266 +++ .../LimitingFileInputStream.java.svn-base | 5 + .../BodyOffsetInputStream.java.svn-base | 159 ++ .../.svn/text-base/ByteContent.java.svn-base | 55 + .../CountingInputStream.java.svn-base | 85 + .../text-base/FullByteContent.java.svn-base | 90 + .../InputStreamContent.java.svn-base | 72 + .../LimitingFileInputStream.java.svn-base | 276 +++ .../PartContentBuilder.java.svn-base | 301 +++ .../streaming/BodyOffsetInputStream.java | 159 ++ .../mailbox/store/streaming/ByteContent.java | 55 + .../store/streaming/CountingInputStream.java | 85 + .../store/streaming/FullByteContent.java | 90 + .../store/streaming/InputStreamContent.java | 72 + .../streaming/LimitingFileInputStream.java | 276 +++ .../store/streaming/PartContentBuilder.java | 301 +++ .../store/transaction/.svn/all-wcprops | 23 + .../mailbox/store/transaction/.svn/entries | 130 + .../.svn/text-base/Mapper.java.svn-base | 67 + .../NonTransactionalMapper.java.svn-base | 38 + .../TransactionalMapper.java.svn-base | 67 + .../mailbox/store/transaction/Mapper.java | 67 + .../transaction/NonTransactionalMapper.java | 38 + .../transaction/TransactionalMapper.java | 67 + .../james/mailbox/store/user/.svn/all-wcprops | 17 + .../james/mailbox/store/user/.svn/entries | 99 + .../SubscriptionMapper.java.svn-base | 62 + .../SubscriptionMapperFactory.java.svn-base | 33 + .../store/user/SubscriptionMapper.java | 62 + .../store/user/SubscriptionMapperFactory.java | 33 + .../mailbox/store/user/model/.svn/all-wcprops | 11 + .../mailbox/store/user/model/.svn/entries | 65 + .../.svn/text-base/Subscription.java.svn-base | 45 + .../store/user/model/Subscription.java | 45 + .../store/user/model/impl/.svn/all-wcprops | 11 + .../store/user/model/impl/.svn/entries | 62 + .../SimpleSubscription.java.svn-base | 47 + .../user/model/impl/SimpleSubscription.java | 47 + .../store/src/reporting-site/.svn/all-wcprops | 11 + .../store/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../store/src/reporting-site/site.xml | 29 + .../store/src/test/.svn/all-wcprops | 5 + .../store/src/test/.svn/entries | 31 + .../store/src/test/java/.svn/all-wcprops | 5 + .../store/src/test/java/.svn/entries | 31 + .../store/src/test/java/org/.svn/all-wcprops | 5 + .../store/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/store/.svn/all-wcprops | 83 + .../apache/james/mailbox/store/.svn/entries | 479 ++++ .../MessageResultImplTest.java.svn-base | 5 + ...oreMessageResultIteratorTest.java.svn-base | 5 + ...lboxEventDispatcherFlagsTest.java.svn-base | 349 +++ .../text-base/MessageBuilder.java.svn-base | 63 + .../MessageResultImplTest.java.svn-base | 153 ++ .../text-base/MockAuthenticator.java.svn-base | 43 + ...tBuilderComplexMultipartTest.java.svn-base | 225 ++ ...lderMultipartAlternativeTest.java.svn-base | 137 ++ ...earchUtilsMultipartMixedTest.java.svn-base | 223 ++ .../SearchUtilsRFC822Test.java.svn-base | 119 + .../text-base/SearchUtilsTest.java.svn-base | 805 +++++++ .../SimpleMailboxMembership.java.svn-base | 322 +++ .../text-base/SimpleProperty.java.svn-base | 51 + ...oreMessageResultIteratorTest.java.svn-base | 177 ++ .../StringBuilderChannel.java.svn-base | 51 + .../MailboxEventDispatcherFlagsTest.java | 349 +++ .../james/mailbox/store/MessageBuilder.java | 63 + .../mailbox/store/MessageResultImplTest.java | 153 ++ .../mailbox/store/MockAuthenticator.java | 43 + ...artContentBuilderComplexMultipartTest.java | 225 ++ ...ontentBuilderMultipartAlternativeTest.java | 137 ++ .../store/SearchUtilsMultipartMixedTest.java | 223 ++ .../mailbox/store/SearchUtilsRFC822Test.java | 119 + .../james/mailbox/store/SearchUtilsTest.java | 805 +++++++ .../store/SimpleMailboxMembership.java | 322 +++ .../james/mailbox/store/SimpleProperty.java | 51 + .../store/StoreMessageResultIteratorTest.java | 177 ++ .../mailbox/store/StringBuilderChannel.java | 51 + .../james/mailbox/store/mail/.svn/all-wcprops | 5 + .../james/mailbox/store/mail/.svn/entries | 31 + .../mailbox/store/mail/model/.svn/all-wcprops | 11 + .../mailbox/store/mail/model/.svn/entries | 65 + .../AbstractMessageTest.java.svn-base | 58 + .../store/mail/model/AbstractMessageTest.java | 58 + .../store/mail/model/impl/.svn/all-wcprops | 11 + .../store/mail/model/impl/.svn/entries | 62 + .../text-base/SimpleMessageTest.java.svn-base | 82 + .../mail/model/impl/SimpleMessageTest.java | 82 + .../mailbox/store/search/.svn/all-wcprops | 11 + .../james/mailbox/store/search/.svn/entries | 62 + .../text-base/SearchUtilTest.java.svn-base | 68 + .../mailbox/store/search/SearchUtilTest.java | 68 + .../mailbox/store/streaming/.svn/all-wcprops | 11 + .../mailbox/store/streaming/.svn/entries | 62 + .../BodyOffsetInputStreamTest.java.svn-base | 75 + .../streaming/BodyOffsetInputStreamTest.java | 75 + .../tool/.svn/all-wcprops | 11 + .../tool/.svn/dir-prop-base | 7 + james/apache-james-mailbox/tool/.svn/entries | 65 + .../tool/.svn/text-base/pom.xml.svn-base | 74 + james/apache-james-mailbox/tool/pom.xml | 74 + .../tool/src/.svn/all-wcprops | 5 + .../tool/src/.svn/entries | 37 + .../tool/src/main/.svn/all-wcprops | 5 + .../tool/src/main/.svn/entries | 31 + .../tool/src/main/java/.svn/all-wcprops | 5 + .../tool/src/main/java/.svn/entries | 31 + .../tool/src/main/java/org/.svn/all-wcprops | 5 + .../tool/src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 34 + .../james/mailbox/copier/.svn/all-wcprops | 17 + .../apache/james/mailbox/copier/.svn/entries | 96 + .../text-base/MailboxCopier.java.svn-base | 43 + .../text-base/MailboxCopierImpl.java.svn-base | 162 ++ .../james/mailbox/copier/MailboxCopier.java | 43 + .../mailbox/copier/MailboxCopierImpl.java | 162 ++ .../apache/james/mailbox/jpa/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/jpa/.svn/entries | 31 + .../mailbox/jpa/migrator/.svn/all-wcprops | 11 + .../james/mailbox/jpa/migrator/.svn/entries | 68 + .../.svn/prop-base/JpaMigrator.java.svn-base | 5 + .../.svn/text-base/JpaMigrator.java.svn-base | 74 + .../mailbox/jpa/migrator/JpaMigrator.java | 74 + .../jpa/migrator/command/.svn/all-wcprops | 53 + .../mailbox/jpa/migrator/command/.svn/entries | 300 +++ .../IMAP165JpaMigrateCommand.java.svn-base | 5 + .../IMAP168JpaMigrateCommand.java.svn-base | 5 + .../IMAP172JpaMigrateCommand.java.svn-base | 5 + .../IMAP176JpaMigrateCommand.java.svn-base | 5 + .../IMAP180JpaMigrateCommand.java.svn-base | 5 + .../IMAP184JpaMigrateCommand.java.svn-base | 5 + .../prop-base/JpaMigrateCommand.java.svn-base | 5 + .../prop-base/JpaMigrateQuery.java.svn-base | 5 + .../IMAP165JpaMigrateCommand.java.svn-base | 44 + .../IMAP168JpaMigrateCommand.java.svn-base | 121 + .../IMAP172JpaMigrateCommand.java.svn-base | 43 + .../IMAP176JpaMigrateCommand.java.svn-base | 42 + .../IMAP180JpaMigrateCommand.java.svn-base | 158 ++ .../IMAP184JpaMigrateCommand.java.svn-base | 42 + .../text-base/JpaMigrateCommand.java.svn-base | 41 + .../text-base/JpaMigrateQuery.java.svn-base | 39 + .../command/IMAP165JpaMigrateCommand.java | 44 + .../command/IMAP168JpaMigrateCommand.java | 121 + .../command/IMAP172JpaMigrateCommand.java | 43 + .../command/IMAP176JpaMigrateCommand.java | 42 + .../command/IMAP180JpaMigrateCommand.java | 158 ++ .../command/IMAP184JpaMigrateCommand.java | 42 + .../migrator/command/JpaMigrateCommand.java | 41 + .../jpa/migrator/command/JpaMigrateQuery.java | 39 + .../jpa/migrator/exception/.svn/all-wcprops | 11 + .../jpa/migrator/exception/.svn/entries | 62 + .../JpaMigrateException.java.svn-base | 5 + .../JpaMigrateException.java.svn-base | 47 + .../exception/JpaMigrateException.java | 47 + .../tool/src/reporting-site/.svn/all-wcprops | 11 + .../tool/src/reporting-site/.svn/entries | 62 + .../.svn/prop-base/site.xml.svn-base | 9 + .../.svn/text-base/site.xml.svn-base | 29 + .../tool/src/reporting-site/site.xml | 29 + .../tool/src/test/.svn/all-wcprops | 5 + .../tool/src/test/.svn/entries | 31 + .../tool/src/test/java/.svn/all-wcprops | 5 + .../tool/src/test/java/.svn/entries | 31 + .../tool/src/test/java/org/.svn/all-wcprops | 5 + .../tool/src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 34 + .../james/mailbox/copier/.svn/all-wcprops | 11 + .../apache/james/mailbox/copier/.svn/entries | 62 + .../text-base/MailboxCopierTest.java.svn-base | 170 ++ .../mailbox/copier/MailboxCopierTest.java | 170 ++ .../apache/james/mailbox/jpa/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/jpa/.svn/entries | 31 + .../mailbox/jpa/migrator/.svn/all-wcprops | 11 + .../james/mailbox/jpa/migrator/.svn/entries | 62 + .../prop-base/JpaMigratorTest.java.svn-base | 5 + .../text-base/JpaMigratorTest.java.svn-base | 84 + .../mailbox/jpa/migrator/JpaMigratorTest.java | 84 + .../zoo-seq-provider/.svn/all-wcprops | 11 + .../zoo-seq-provider/.svn/dir-prop-base | 9 + .../zoo-seq-provider/.svn/entries | 65 + .../.svn/prop-base/pom.xml.svn-base | 5 + .../.svn/text-base/pom.xml.svn-base | 83 + .../zoo-seq-provider/pom.xml | 83 + .../zoo-seq-provider/src/.svn/all-wcprops | 5 + .../zoo-seq-provider/src/.svn/entries | 34 + .../src/main/.svn/all-wcprops | 5 + .../zoo-seq-provider/src/main/.svn/entries | 34 + .../src/main/java/.svn/all-wcprops | 5 + .../src/main/java/.svn/entries | 31 + .../src/main/java/org/.svn/all-wcprops | 5 + .../src/main/java/org/.svn/entries | 31 + .../src/main/java/org/apache/.svn/all-wcprops | 5 + .../src/main/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../main/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/store/.svn/all-wcprops | 5 + .../apache/james/mailbox/store/.svn/entries | 31 + .../james/mailbox/store/mail/.svn/all-wcprops | 11 + .../james/mailbox/store/mail/.svn/entries | 62 + .../prop-base/ZooUidProvider.java.svn-base | 5 + .../text-base/ZooUidProvider.java.svn-base | 96 + .../mailbox/store/mail/ZooUidProvider.java | 96 + .../src/main/resources/.svn/all-wcprops | 5 + .../src/main/resources/.svn/entries | 28 + .../src/test/.svn/all-wcprops | 5 + .../zoo-seq-provider/src/test/.svn/entries | 31 + .../src/test/java/.svn/all-wcprops | 5 + .../src/test/java/.svn/entries | 31 + .../src/test/java/org/.svn/all-wcprops | 5 + .../src/test/java/org/.svn/entries | 31 + .../src/test/java/org/apache/.svn/all-wcprops | 5 + .../src/test/java/org/apache/.svn/entries | 31 + .../java/org/apache/james/.svn/all-wcprops | 5 + .../test/java/org/apache/james/.svn/entries | 31 + .../org/apache/james/mailbox/.svn/all-wcprops | 5 + .../org/apache/james/mailbox/.svn/entries | 31 + .../james/mailbox/store/.svn/all-wcprops | 5 + .../apache/james/mailbox/store/.svn/entries | 31 + .../james/mailbox/store/mail/.svn/all-wcprops | 11 + .../james/mailbox/store/mail/.svn/entries | 62 + .../ZooUidProviderTest.java.svn-base | 5 + .../ZooUidProviderTest.java.svn-base | 106 + .../store/mail/ZooUidProviderTest.java | 106 + 1460 files changed, 129739 insertions(+) create mode 120000 james/apache-james-mailbox-memory create mode 100644 james/apache-james-mailbox/.gitignore create mode 100644 james/apache-james-mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/.svn/text-base/.gitignore.svn-base create mode 100644 james/apache-james-mailbox/.svn/text-base/LICENSE.svn-base create mode 100644 james/apache-james-mailbox/.svn/text-base/NOTICE.svn-base create mode 100644 james/apache-james-mailbox/.svn/text-base/README.md.svn-base create mode 100644 james/apache-james-mailbox/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/LICENSE create mode 100644 james/apache-james-mailbox/NOTICE create mode 100644 james/apache-james-mailbox/README.md create mode 100644 james/apache-james-mailbox/api/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/api/.svn/entries create mode 100644 james/apache-james-mailbox/api/.svn/prop-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/api/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/api/pom.xml create mode 100644 james/apache-james-mailbox/api/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxListener.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxSessionIdGenerator.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MessageManager.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListener.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListenerSupport.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxPathLocker.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSession.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSessionIdGenerator.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MessageManager.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/QuotaManager.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/RequestAware.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/StandardMailboxMetaDataComparator.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/SubscriptionManager.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListenerSupport.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxPathLocker.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSessionIdGenerator.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/QuotaManager.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/RequestAware.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/StandardMailboxMetaDataComparator.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/SubscriptionManager.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/MailboxACLResolver.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/SimpleGroupMembershipResolver.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolver.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/GroupMembershipResolver.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/MailboxACLResolver.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/SimpleGroupMembershipResolver.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolver.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/GroupMembershipResolver.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/SimpleGroupMembershipResolver.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/BadCredentialsException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/InsufficientRightsException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxSecurityException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedOperationException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedRightException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/BadCredentialsException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/InsufficientRightsException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxExistsException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxNotFoundException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxSecurityException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MessageRangeException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/OverQuotaException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/ReadOnlyException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/SubscriptionException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedCriteriaException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedOperationException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedRightException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedSearchException.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/BadCredentialsException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/InsufficientRightsException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxExistsException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxNotFoundException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxSecurityException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MessageRangeException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/OverQuotaException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/ReadOnlyException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/SubscriptionException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedCriteriaException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedOperationException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedSearchException.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxACL.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxConstants.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageRange.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageResult.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/SimpleMailboxACL.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Content.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/FetchGroupImpl.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Headers.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/InputStreamContent.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxACL.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxConstants.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxPath.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxQuery.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageRange.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResult.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResultIterator.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MimeDescriptor.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/PartContentDescriptorImpl.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Quota.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SearchQuery.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACL.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/UpdatedFlags.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Content.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/FetchGroupImpl.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Headers.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/InputStreamContent.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxConstants.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxMetaData.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxQuery.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageRange.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResultIterator.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MimeDescriptor.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/PartContentDescriptorImpl.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SimpleMailboxACL.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/api/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/api/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/api/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractMailboxManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractStressTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractSubscriptionManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExceptionTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExpressionTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MessageRangeTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractMailboxManagerTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractSubscriptionManagerTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExceptionTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExpressionTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MessageRangeTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolverTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolverTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/UnionMailboxACLResolverTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMail.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxSession.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMail.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxManager.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxSession.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/prop-base/Rfc4314RightsTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/Rfc4314RightsTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLEntryKeyTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLTest.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/Rfc4314RightsTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLEntryKeyTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLTest.java create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/entries create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/text-base/EventCollector.java.svn-base create mode 100644 james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java create mode 100644 james/apache-james-mailbox/caching/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/caching/.svn/entries create mode 100644 james/apache-james-mailbox/caching/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/caching/pom.xml create mode 100644 james/apache-james-mailbox/caching/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheInvalidatingMailboxListener.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheLoaderFromUnderlying.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxMapper.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxSessionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxByPathCache.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxMetadataCache.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/entries create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/AbstractGuavaCache.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaCacheWrapper.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxByPathCache.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxMetadataCache.java.svn-base create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java create mode 100644 james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java create mode 100644 james/apache-james-mailbox/hbase/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/hbase/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/hbase/pom.xml create mode 100644 james/apache-james-mailbox/hbase/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/config/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/config/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/config/.svn/text-base/hbase-site.xml.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/config/hbase-site.xml create mode 100644 james/apache-james-mailbox/hbase/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/FlagConvertor.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMessageManager.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNames.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNonTransactionalMapper.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtils.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/PropertyConvertor.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/FlagConvertor.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxManager.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactory.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNames.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNonTransactionalMapper.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseUtils.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/PropertyConvertor.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkInputStream.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkOutputStream.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkInputStream.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkOutputStream.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapper.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessage.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseModSeqProvider.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapper.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessage.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseModSeqProvider.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseUidProvider.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailbox.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailbox.java create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapper.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapper.java create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/text-base/mailbox-hbase.xml.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml create mode 100644 james/apache-james-mailbox/hbase/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/hbase/src/site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/site/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/.svn/prop-base/james-hbase-mailbox-schema.png.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.mm.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.png.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.svg.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.mm create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.png create mode 100644 james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.svg create mode 100644 james/apache-james-mailbox/hbase/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseClusterSingleton.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactoryTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtilsTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseClusterSingleton.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxManagerTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactoryTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseUtilsTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapperTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapperTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidAndModSeqProviderTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapperTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapperTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseUidAndModSeqProviderTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailboxTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailboxTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapperTest.java.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapperTest.java create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/.svn/entries create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/hadoop-metrics2.properties create mode 100644 james/apache-james-mailbox/hbase/src/test/resources/hdfs-site.xml create mode 100644 james/apache-james-mailbox/jcr/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/jcr/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/jcr/pom.xml create mode 100644 james/apache-james-mailbox/jcr/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/AbstractJCRScalingMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/GlobalMailboxSessionJCRRepository.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRImapConstants.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxSessionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMessageManager.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRRepositoryAuthenticator.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManager.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRUtils.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/MailboxSessionJCRRepository.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/Persistent.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/AbstractJCRScalingMapper.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/GlobalMailboxSessionJCRRepository.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRImapConstants.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxManager.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxSessionMapperFactory.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRRepositoryAuthenticator.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRSubscriptionManager.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRUtils.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/MailboxSessionJCRRepository.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/Persistent.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMailboxMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRModSeqProvider.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRUidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMailboxMapper.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMessageMapper.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRModSeqProvider.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRUidProvider.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMailbox.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMessage.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRProperty.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMailbox.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMessage.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRProperty.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/text-base/JCRSubscriptionMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/JCRSubscriptionMapper.java create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/text-base/JCRSubscription.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/jcr-repository.xml.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jcr.xml.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/jcr-repository.xml create mode 100644 james/apache-james-mailbox/jcr/src/main/resources/mailbox-jcr.cnd create mode 100644 james/apache-james-mailbox/jcr/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/jcr/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRStressTest.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxManagerTest.java create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRStressTest.java create mode 100644 james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRSubscriptionManagerTest.java create mode 100644 james/apache-james-mailbox/jcr/src/test/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jcr/src/test/resources/.svn/entries create mode 100644 james/apache-james-mailbox/jcr/src/test/resources/.svn/text-base/test-repository.xml.svn-base create mode 100644 james/apache-james-mailbox/jcr/src/test/resources/test-repository.xml create mode 100644 james/apache-james-mailbox/jpa/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/jpa/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/jpa/pom.xml create mode 100644 james/apache-james-mailbox/jpa/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxSessionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMessageManager.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManager.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPATransactionalMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxManager.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxSessionMapperFactory.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPASubscriptionManager.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPATransactionalMapper.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMailboxMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAModSeqProvider.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAUidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMailboxMapper.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMessageMapper.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAModSeqProvider.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAUidProvider.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAMailbox.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAProperty.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAUserFlag.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAMailbox.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAProperty.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAUserFlag.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/AbstractJPAMessage.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/EncryptDecryptHelper.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAEncryptedMessage.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAMessage.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAStreamingMessage.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMessage.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/EncryptDecryptHelper.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAEncryptedMessage.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAMessage.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAStreamingMessage.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMessageManager.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/text-base/JPASubscriptionMapper.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/JPASubscriptionMapper.java create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/text-base/JPASubscription.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/JPASubscription.java create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/.svn/text-base/james-database.properties.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/text-base/persistence.xml.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/persistence.xml create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jpa.xml.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml create mode 100644 james/apache-james-mailbox/jpa/src/main/resources/james-database.properties create mode 100644 james/apache-james-mailbox/jpa/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/jpa/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/entries create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAStressTest.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAStressTest.java create mode 100644 james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java create mode 100644 james/apache-james-mailbox/lucene/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/lucene/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/lucene/pom.xml create mode 100644 james/apache-james-mailbox/lucene/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LenientImapSearchAnalyzer.java.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndex.java.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictImapSearchAnalyzer.java.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/UpperCaseFilter.java.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LenientImapSearchAnalyzer.java create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/StrictImapSearchAnalyzer.java create mode 100644 james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/UpperCaseFilter.java create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/text-base/mailbox-index-lucene.xml.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/mailbox-index-lucene.xml create mode 100644 james/apache-james-mailbox/lucene/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/lucene/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/entries create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndexTest.java.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictLuceneMessageSearchIndexText.java.svn-base create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndexTest.java create mode 100644 james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/StrictLuceneMessageSearchIndexText.java create mode 100644 james/apache-james-mailbox/maildir/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/maildir/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/maildir/pom.xml create mode 100644 james/apache-james-mailbox/maildir/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirFolder.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxSessionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMessageName.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStore.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/UidConstraint.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxSessionMapperFactory.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMessageName.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/UidConstraint.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMailboxMapper.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/prop-base/MaildirMailbox.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMailbox.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMessage.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMailbox.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/text-base/MaildirSubscriptionMapper.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/MaildirSubscriptionMapper.java create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/.svn/text-base/mailbox-maildir.properties.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/text-base/mailbox-maildir.xml.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml create mode 100644 james/apache-james-mailbox/maildir/src/main/resources/mailbox-maildir.properties create mode 100644 james/apache-james-mailbox/maildir/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/maildir/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/entries create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/prop-base/MaildirMailboxManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MailderMessageNameTest.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStressTest.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirSubscriptionManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/OsDetector.java.svn-base create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MailderMessageNameTest.java create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxManagerTest.java create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirStressTest.java create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirSubscriptionManagerTest.java create mode 100644 james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/OsDetector.java create mode 100644 james/apache-james-mailbox/memory/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/memory/.svn/entries create mode 100644 james/apache-james-mailbox/memory/.svn/prop-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/memory/.svn/text-base/pom.xml.svn-base create mode 100755 james/apache-james-mailbox/memory/build create mode 100644 james/apache-james-mailbox/memory/pom.xml create mode 100644 james/apache-james-mailbox/memory/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/.svn/entries create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/app create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/callback create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/callbacks/Memory.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/ConnectorException.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/FileInfo.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/ClientInfoDropbox.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/sync create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/s3/ClientInfoS3.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/s3/sync create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/sync/EncryptedStoreConnector.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/connector/sync/StoreConnector.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/constants create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/crypt create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/exceptions create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/io create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/server create mode 120000 james/apache-james-mailbox/memory/src/main/java/core/util create mode 120000 james/apache-james-mailbox/memory/src/main/java/mail/core create mode 120000 james/apache-james-mailbox/memory/src/main/java/mail/server/db create mode 120000 james/apache-james-mailbox/memory/src/main/java/mail/server/handler create mode 120000 james/apache-james-mailbox/memory/src/main/java/mail/server/push create mode 120000 james/apache-james-mailbox/memory/src/main/java/mail/server/util create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxSessionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMailboxMapper.java.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryModSeqProvider.java.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryUidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMailboxMapper.java create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryModSeqProvider.java create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryUidProvider.java create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/text-base/InMemorySubscriptionMapper.java.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/InMemorySubscriptionMapper.java create mode 120000 james/apache-james-mailbox/memory/src/main/java/org/bc create mode 120000 james/apache-james-mailbox/memory/src/main/java/org/json create mode 120000 james/apache-james-mailbox/memory/src/main/java/org/timepedia create mode 100644 james/apache-james-mailbox/memory/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory-osgi.xml.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory.xml.svn-base create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory-osgi.xml create mode 100644 james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml create mode 100644 james/apache-james-mailbox/memory/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/memory/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/memory/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/memory/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/entries create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxManagerTest.java.svn-base create mode 100644 james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManagerTest.java create mode 100644 james/apache-james-mailbox/pom.xml create mode 100644 james/apache-james-mailbox/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/spring/.svn/entries create mode 100644 james/apache-james-mailbox/spring/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/pom.xml create mode 100644 james/apache-james-mailbox/spring/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/AnonymousAuthenticator.java.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailbox.java.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/AnonymousAuthenticator.java create mode 100644 james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/SpringMailbox.java create mode 100644 james/apache-james-mailbox/spring/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/log4j.properties.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-anonymous.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-osgi.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker-osgi.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/spring-mailbox.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-anonymous.xml create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-osgi.xml create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker-osgi.xml create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker.xml create mode 100644 james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml create mode 100644 james/apache-james-mailbox/spring/src/main/resources/log4j.properties create mode 100644 james/apache-james-mailbox/spring/src/main/resources/mailbox-jcr.cnd create mode 100644 james/apache-james-mailbox/spring/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/spring/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailboxTest.java.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/SpringMailboxTest.java create mode 100644 james/apache-james-mailbox/spring/src/test/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/resources/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hbase-site.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/database.properties.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/spring-mailbox-lucene.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/database.properties.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/spring-mailbox-lucene.xml.svn-base create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/database.properties create mode 100644 james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/spring-mailbox-lucene.xml create mode 100644 james/apache-james-mailbox/spring/src/test/resources/hadoop-metrics2.properties create mode 100644 james/apache-james-mailbox/spring/src/test/resources/hbase-site.xml create mode 100644 james/apache-james-mailbox/spring/src/test/resources/hdfs-site.xml create mode 100644 james/apache-james-mailbox/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/src/.svn/entries create mode 100644 james/apache-james-mailbox/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/src/site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/src/site/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/src/site/.svn/entries create mode 100644 james/apache-james-mailbox/src/site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/src/site/resources/.svn/entries create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/entries create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/asf-logo-reduced.gif.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-logo.jpg.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-server-logo.gif.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/void.gif.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/text-base/asf-logo-reduced.gif.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/text-base/james-logo.jpg.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/text-base/james-server-logo.gif.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/.svn/text-base/void.gif.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/asf-logo-reduced.gif create mode 100644 james/apache-james-mailbox/src/site/resources/images/james-logo.jpg create mode 100644 james/apache-james-mailbox/src/site/resources/images/james-server-logo.gif create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/entries create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-api-mailboxmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-api-mailboxsession.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-api-messagemanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-api-msc.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-api-subscriptionmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-jpa-managers.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-maildir-managers.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-memory-managers.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-package.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-store-mailboxmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-store-messagemanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-store-model.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/prop-base/org-apache-james-mailbox-store-subscriptionmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-mailboxmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-mailboxsession.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-messagemanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-msc.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-subscriptionmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-jpa-managers.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-maildir-managers.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-memory-managers.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-package.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-mailboxmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-messagemanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-model.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-subscriptionmanager.png.svn-base create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-mailboxmanager.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-mailboxsession.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-messagemanager.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-msc.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-subscriptionmanager.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-jpa-managers.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-maildir-managers.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-memory-managers.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-package.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-mailboxmanager.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-messagemanager.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-model.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-subscriptionmanager.png create mode 100644 james/apache-james-mailbox/src/site/resources/images/void.gif create mode 100644 james/apache-james-mailbox/src/site/site.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/entries create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/index.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-api.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-guice.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-hbase.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jcr.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jpa.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-maildir.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-memory.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-spring.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-store.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-tool.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/.svn/text-base/source-code.xml.svn-base create mode 100644 james/apache-james-mailbox/src/site/xdoc/index.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-api.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-guice.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-hbase.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-jcr.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-jpa.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-maildir.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-memory.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-spring.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-store.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/mailbox-tool.xml create mode 100644 james/apache-james-mailbox/src/site/xdoc/source-code.xml create mode 100644 james/apache-james-mailbox/store/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/store/.svn/entries create mode 100644 james/apache-james-mailbox/store/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/store/pom.xml create mode 100644 james/apache-james-mailbox/store/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/AbstractMailboxSessionIdGenerator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/LazyMimeDescriptor.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxEventDispatcher.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImpl.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/RandomMailboxSessionIdGenerator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/SimpleMailboxMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractDelegatingMailboxListener.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxPathLocker.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxSessionIdGenerator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/Authenticator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/HashMapDelegatingMailboxListener.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/JVMMailboxPathLocker.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/LazyMimeDescriptor.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcher.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxSessionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImpl.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MimeDescriptorImpl.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/RandomMailboxSessionIdGenerator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultHeader.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultUtils.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxSession.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMessageMetaData.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxManager.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxPath.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageManager.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIterator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreSubscriptionManager.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxPathLocker.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxSessionIdGenerator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/Authenticator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/JVMMailboxPathLocker.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxMetaData.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxSessionMapperFactory.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MimeDescriptorImpl.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/RandomMailboxSessionIdGenerator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultHeader.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultUtils.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxMetaData.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxSession.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMessageMetaData.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxPath.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingModSeqProvider.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingUidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractMessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapper.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapper.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ModSeqProvider.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/UidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingModSeqProvider.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingUidProvider.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapperFactory.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapperFactory.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ModSeqProvider.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/UidProvider.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessage.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Mailbox.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Message.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Property.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/StandardNames.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/AbstractMessage.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Mailbox.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Property.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/PropertyBuilder.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMailbox.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessage.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleProperty.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleProperty.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/FixedQuotaManager.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/ListeningQuotaManager.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/PerUserQuotaManager.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/QuotaImpl.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedQuotaManager.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningQuotaManager.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/PerUserQuotaManager.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/QuotaImpl.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/LazyMessageSearchIndex.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/ListeningMessageSearchIndex.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearchIndex.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearcher.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearches.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtil.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SimpleMessageSearchIndex.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearchIndex.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearcher.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SearchUtil.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/AbstractHeaderComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/BaseSubjectComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/CombinedComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderDisplayComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderMailboxComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/InternalDateComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/ReverseComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SentDateComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SizeComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/UidComparator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/AbstractHeaderComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/BaseSubjectComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/CombinedComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderDisplayComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderMailboxComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/InternalDateComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/ReverseComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SizeComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/UidComparator.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/prop-base/LimitingFileInputStream.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStream.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/ByteContent.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/CountingInputStream.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/FullByteContent.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/InputStreamContent.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/LimitingFileInputStream.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/PartContentBuilder.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStream.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/ByteContent.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/CountingInputStream.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/FullByteContent.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/InputStreamContent.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/LimitingFileInputStream.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/PartContentBuilder.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/Mapper.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/NonTransactionalMapper.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/TransactionalMapper.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/Mapper.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/NonTransactionalMapper.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/TransactionalMapper.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapper.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapperFactory.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapper.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapperFactory.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/text-base/Subscription.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/Subscription.java create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/text-base/SimpleSubscription.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/SimpleSubscription.java create mode 100644 james/apache-james-mailbox/store/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/store/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/store/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/store/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImplTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMessageResultIteratorTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcherFlagsTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageBuilder.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImplTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MockAuthenticator.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderComplexMultipartTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderMultipartAlternativeTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsMultipartMixedTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsRFC822Test.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMembership.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleProperty.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIteratorTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StringBuilderChannel.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageResultImplTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MockAuthenticator.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderComplexMultipartTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderMultipartAlternativeTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsMultipartMixedTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsRFC822Test.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleMailboxMembership.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleProperty.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StringBuilderChannel.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessageTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AbstractMessageTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessageTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessageTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtilTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/SearchUtilTest.java create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/entries create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStreamTest.java.svn-base create mode 100644 james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStreamTest.java create mode 100644 james/apache-james-mailbox/tool/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/tool/.svn/entries create mode 100644 james/apache-james-mailbox/tool/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/tool/pom.xml create mode 100644 james/apache-james-mailbox/tool/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopier.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierImpl.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopier.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopierImpl.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigrator.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigrator.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/JpaMigrator.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP165JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP168JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP172JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP176JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP180JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP184JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateQuery.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP165JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP168JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP172JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP176JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP180JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP184JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateCommand.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateQuery.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP165JpaMigrateCommand.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP168JpaMigrateCommand.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP172JpaMigrateCommand.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP176JpaMigrateCommand.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP180JpaMigrateCommand.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP184JpaMigrateCommand.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateCommand.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateQuery.java create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/prop-base/JpaMigrateException.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/text-base/JpaMigrateException.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/JpaMigrateException.java create mode 100644 james/apache-james-mailbox/tool/src/reporting-site/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/reporting-site/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/reporting-site/.svn/prop-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/tool/src/reporting-site/.svn/text-base/site.xml.svn-base create mode 100644 james/apache-james-mailbox/tool/src/reporting-site/site.xml create mode 100644 james/apache-james-mailbox/tool/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierTest.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/MailboxCopierTest.java create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/entries create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigratorTest.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigratorTest.java.svn-base create mode 100644 james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/JpaMigratorTest.java create mode 100644 james/apache-james-mailbox/zoo-seq-provider/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/.svn/dir-prop-base create mode 100644 james/apache-james-mailbox/zoo-seq-provider/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/.svn/prop-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/zoo-seq-provider/.svn/text-base/pom.xml.svn-base create mode 100644 james/apache-james-mailbox/zoo-seq-provider/pom.xml create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProvider.java.svn-base create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/ZooUidProvider.java create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProviderTest.java.svn-base create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProviderTest.java.svn-base create mode 100644 james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/ZooUidProviderTest.java diff --git a/james/apache-james-mailbox-memory b/james/apache-james-mailbox-memory new file mode 120000 index 0000000..b7bc66f --- /dev/null +++ b/james/apache-james-mailbox-memory @@ -0,0 +1 @@ +apache-james-mailbox/memory \ No newline at end of file diff --git a/james/apache-james-mailbox/.gitignore b/james/apache-james-mailbox/.gitignore new file mode 100644 index 0000000..e2fbf32 --- /dev/null +++ b/james/apache-james-mailbox/.gitignore @@ -0,0 +1,6 @@ +.idea/ +target +*.iml +derby.log +var +log diff --git a/james/apache-james-mailbox/.svn/all-wcprops b/james/apache-james-mailbox/.svn/all-wcprops new file mode 100644 index 0000000..a5058df --- /dev/null +++ b/james/apache-james-mailbox/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 47 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk +END +NOTICE +K 25 +svn:wc:ra_dav:version-url +V 54 +/repos/asf/!svn/ver/1140981/james/mailbox/trunk/NOTICE +END +LICENSE +K 25 +svn:wc:ra_dav:version-url +V 55 +/repos/asf/!svn/ver/1140981/james/mailbox/trunk/LICENSE +END +.gitignore +K 25 +svn:wc:ra_dav:version-url +V 58 +/repos/asf/!svn/ver/1429793/james/mailbox/trunk/.gitignore +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 55 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/pom.xml +END +README.md +K 25 +svn:wc:ra_dav:version-url +V 57 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/README.md +END diff --git a/james/apache-james-mailbox/.svn/dir-prop-base b/james/apache-james-mailbox/.svn/dir-prop-base new file mode 100644 index 0000000..e1e543f --- /dev/null +++ b/james/apache-james-mailbox/.svn/dir-prop-base @@ -0,0 +1,9 @@ +K 10 +svn:ignore +V 18 +.* +target +var +log + +END diff --git a/james/apache-james-mailbox/.svn/entries b/james/apache-james-mailbox/.svn/entries new file mode 100644 index 0000000..9f881dd --- /dev/null +++ b/james/apache-james-mailbox/.svn/entries @@ -0,0 +1,237 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +NOTICE +file + + + + +2013-09-02T02:54:40.000000Z +9fd1bc161f9ca2039bb1b4739710a0b6 +2011-06-29T07:14:24.677999Z +1140981 +rdonkin + + + + + + + + + + + + + + + + + + + + + +418 + +maildir +dir + +LICENSE +file + + + + +2013-09-02T02:54:40.000000Z +13ad9758cf5d8c437249d7a892c54852 +2011-06-29T07:14:24.677999Z +1140981 +rdonkin + + + + + + + + + + + + + + + + + + + + + +10183 + +hbase +dir + +zoo-seq-provider +dir + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:40.000000Z +8961b605d2e08836c2eb6075a50ef985 +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + + + + + + + + +22284 + +store +dir + +caching +dir + +README.md +file + + + + +2013-09-02T02:54:40.000000Z +ca07870fb4b8faa65e1e43b6f179924c +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +3109 + +spring +dir + +.gitignore +file + + + + +2013-09-02T02:54:40.000000Z +ec50350e18fc2c63fd4abf14e9c4c473 +2013-01-07T13:21:20.783231Z +1429793 +ieugen + + + + + + + + + + + + + + + + + + + + + +38 + +memory +dir + +api +dir + +jpa +dir + +lucene +dir + +tool +dir + +jcr +dir + diff --git a/james/apache-james-mailbox/.svn/text-base/.gitignore.svn-base b/james/apache-james-mailbox/.svn/text-base/.gitignore.svn-base new file mode 100644 index 0000000..e2fbf32 --- /dev/null +++ b/james/apache-james-mailbox/.svn/text-base/.gitignore.svn-base @@ -0,0 +1,6 @@ +.idea/ +target +*.iml +derby.log +var +log diff --git a/james/apache-james-mailbox/.svn/text-base/LICENSE.svn-base b/james/apache-james-mailbox/.svn/text-base/LICENSE.svn-base new file mode 100644 index 0000000..fa09157 --- /dev/null +++ b/james/apache-james-mailbox/.svn/text-base/LICENSE.svn-base @@ -0,0 +1,179 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/.svn/text-base/NOTICE.svn-base b/james/apache-james-mailbox/.svn/text-base/NOTICE.svn-base new file mode 100644 index 0000000..9229afd --- /dev/null +++ b/james/apache-james-mailbox/.svn/text-base/NOTICE.svn-base @@ -0,0 +1,9 @@ + ========================================================================= + == NOTICE file for use with the Apache License, Version 2.0, == + ========================================================================= + + Apache JAMES Mailbox + Copyright 2009-2011 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). diff --git a/james/apache-james-mailbox/.svn/text-base/README.md.svn-base b/james/apache-james-mailbox/.svn/text-base/README.md.svn-base new file mode 100644 index 0000000..6b08d36 --- /dev/null +++ b/james/apache-james-mailbox/.svn/text-base/README.md.svn-base @@ -0,0 +1,86 @@ +Apache James Mailbox project +============================ + +The James Mailbox project aims to provide an email (message) store. The main user of the Mailbox project is James Server +project. The implementations can be used standalone and do not depend on James Server. + +The project defines the Mailbox API and has several implementations that you can use. More details bellow. + +Overview +======== + +Apache James Mailbox has the following project (Maven) structure: + +~~~ +|-- api -- Mailbox API +|-- hbase -- Mailbox implementation over HBase +|-- jcr -- Mailbox implementation over Java Content Repository (JCR) +|-- jpa -- Database Mailbox implementation using Java Persistence API +|-- lucene -- Email indexing module with Apache Lucene +|-- maildir -- Email storage using Maildir format http://en.wikipedia.org/wiki/Maildir +|-- memory -- In memory Mailbox implementation - good for testing +|-- spring -- Spring module - starts a specific mailbox implementation +|-- store -- Common base/utility classes used in all mailbox implementations +|-- tool -- Database migration/mailbox export tool +|-- zoo-seq-provider -- Distributed unique ID generator using Zookeeper and Curator (Clustering James Mailbox) +~~~ + +Mailbox JPA +=========== + +Persist email messages inside any database that is supported by your Java Persistence Api provider. Currently James uses +OpenJPA (http://openjpa.apache.org/), but it's easy to implement your own. + +Mailbox 'In memory' message store +================================= + +In module **memory**, does not persist emails. It just keeps them in memory. Fast, and good for testing. +**Note:** Not to be used in production. + +Mailbox JCR +=========== + +Uses Java Content Repository as a persistence layer. Uses Jackrabbit as a provider (http://jackrabbit.apache.org/), +but you could swap in any provider. Comes with all the nice features that Jackrabbit has. + + +Mailbox Maildir +=============== + +Implements the Maildir standard for email storage (http://en.wikipedia.org/wiki/Maildir). Works only on GNU/Linux and other +*Nix systems. + + +Mailbox HBase +============= + +Uses Apache HBase (http://hbase.apache.org/) for storing email messages. Provides a scalable email storage. To have a fully +distributed email server you will also need, among others: + +* distributed UID generation, look at Zookeeper Sequence Provider (**zoo-seq-provider**) for distributed locking and Mailbox manipulation +* distributed SMTP/IMAP access +* other + +Zookeeper Sequence Provider +========================== + +Uses Zookeeper and Curator Framework for generating distributed unique ID's, needed for mailbox management from multiple +instances of James (IMAP) servers. + + +Building +======== + +The primary build tool for Apache James Mailbox is maven 3. + +On a new checkout start by running +~~~ + $ mvn clean package +~~~ + +This will compiled all modules + +For just building without running junit tests: +~~~ + $ mvn clean package -DskiTests=true +~~~ diff --git a/james/apache-james-mailbox/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..8fd084d --- /dev/null +++ b/james/apache-james-mailbox/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,576 @@ + + + + 4.0.0 + + + james-project + org.apache.james + 1.8.2 + + + + apache-james-mailbox + 0.6-SNAPSHOT + pom + + + + + apache.snapshots + Apache Snapshot Repository + http://repository.apache.org/snapshots + + false + + + + + Apache James :: Mailbox + Apache James Mailbox + http://james.apache.org/mailbox + 2010 + + + api + caching + hbase + jcr + jpa + lucene + maildir + memory + store + spring + tool + zoo-seq-provider + + + + scm:svn:http://svn.apache.org/repos/asf/james/mailbox/trunk + scm:svn:https://svn.apache.org/repos/asf/james/mailbox/trunk + http://svn.apache.org/viewcvs.cgi/james/mailbox/trunk?root=Apache-SVN + + + JIRA + http://issues.apache.org/jira/browse/MAILBOX + + + + + mailbox-website + scpexe://people.apache.org/www/james.apache.org/mailbox + + + + + UTF-8 + javax.activation + activation + javax.mail + mail + 2.2.1 + 1.0.2 + 1 + 0.7.2 + 1.4.1 + 1.1.1 + 2.5.1 + 0.92.0 + 1.0.1 + 3.1.2.RELEASE + 2.4 + 2.6 + 1.6 + 1.4 + 1.9 + 1.8.3 + 1.3.170 + 10.9.1.0 + 1.1 + 2.0 + 2.5.2 + 3.6.0 + 2.9.1 + 1.3.04 + 1.1.1 + 1.1 + 1.8.3 + 2.5.0 + 1.7.2 + 4.11 + 1.9.0 + 13.0 + + + + + + com.google.guava + guava + ${guava.version} + + + + org.apache.james + apache-james-mailbox-api + ${project.version} + + + org.apache.james + apache-james-mailbox-api + ${project.version} + test-jar + test + + + org.apache.james + apache-james-mailbox-store + ${project.version} + test-jar + test + + + org.apache.james + apache-james-mailbox-lucene + ${project.version} + + + org.apache.james + apache-james-mailbox-lucene + ${project.version} + test-jar + test + + + org.apache.james + apache-james-mailbox-store + ${project.version} + + + org.apache.james + apache-james-mailbox-jpa + ${project.version} + + + org.apache.james + apache-james-mailbox-jcr + ${project.version} + + + org.apache.james + apache-james-mailbox-memory + ${project.version} + + + org.apache.james + apache-james-mailbox-maildir + ${project.version} + + + org.apache.james + apache-james-mailbox-hbase + ${project.version} + + + org.apache.james + apache-james-mailbox-tool + ${project.version} + + + + org.apache.lucene + lucene-core + ${lucene.version} + + + org.apache.lucene + lucene-analyzers + ${lucene.version} + + + org.apache.lucene + lucene-smartcn + ${lucene.version} + + + + + org.apache.james + apache-mime4j-core + ${apache-mime4j.version} + + + org.apache.james + apache-mime4j-dom + ${apache-mime4j.version} + + + + + javax.mail + mail + ${javax.mail.version} + + + javax.activation + activation + ${activation.version} + + + javax.inject + javax.inject + ${javax.inject.version} + + + org.apache.geronimo.specs + geronimo-activation_1.1_spec + ${geronimo-activation-spec.version} + + + org.apache.geronimo.javamail + geronimo-javamail_1.4_mail + ${geronimo-javamail-mail.version} + + + + + + org.apache.james + apache-mailet-api + ${apache-mailet.version} + + + javax.mail + mail + + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + + + + commons-lang + commons-lang + ${commons-lang.version} + + + commons-pool + commons-pool + ${commons-pool.version} + + + commons-dbcp + commons-dbcp + ${commons-dbcp.version} + + + xercesImpl + xerces + + + + + commons-configuration + commons-configuration + test + ${commons-configuration.version} + + + dom4j + dom4j + + + servletapi + servletapi + + + xerces + xerces + + + commons-digester + commons-digester + + + commons-beanutils-core + commons-beanutils + + + commons-beanutils-bean-collections + commons-beanutils + + + + + commons-beanutils + commons-beanutils-core + ${commons-beanutils-core.version} + + + + + + + junit + junit + ${junit.version} + test + + + org.jmock + jmock + ${jmock.version} + test + + + org.jmock + jmock-junit4 + ${jmock.version} + test + + + com.h2database + h2 + ${h2.version} + test + + + org.apache.derby + derby + ${derby.version} + test + + + commons-io + commons-io + ${commons-io.version} + + + + + + + + org.apache.openjpa + openjpa + ${openjpa.version} + + + org.apache.geronimo.specs + geronimo-jpa_2.0_spec + ${geronimo-jpa-spec.version} + + + org.jasypt + jasypt + ${jasypt.version} + + + + + + + javax.jcr + jcr + ${jcr.version} + + + org.apache.jackrabbit + jackrabbit-jcr-commons + ${jackrabbit.version} + + + org.apache.jackrabbit + jackrabbit-core + ${jackrabbit.version} + test + + + + xerces + xercesImpl + ${xercesImpl.version} + test + + + xml-apis + xml-apis + ${xml-apis.version} + test + + + + + + + + org.apache.geronimo.specs + geronimo-annotation_1.0_spec + ${geronimo-annotation-spec.version} + + + + + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-beans + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-orm + ${spring.version} + + + + + + + org.apache.hbase + hbase + ${hbase.version} + + + org.apache.hbase + hbase + ${hbase.version} + test-jar + test + + + org.apache.hadoop + hadoop-core + ${hadoop.version} + + + org.apache.hadoop + hadoop-test + ${hadoop.version} + test + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + bin + src + + + + + org.apache.felix + maven-bundle-plugin + true + + + + + + + geronimo + + org.apache.geronimo.javamail + geronimo-javamail_1.4_mail + org.apache.geronimo.specs + geronimo-activation_1.1_spec + 1.6 + 1.0.2 + + + + + diff --git a/james/apache-james-mailbox/LICENSE b/james/apache-james-mailbox/LICENSE new file mode 100644 index 0000000..fa09157 --- /dev/null +++ b/james/apache-james-mailbox/LICENSE @@ -0,0 +1,179 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/NOTICE b/james/apache-james-mailbox/NOTICE new file mode 100644 index 0000000..9229afd --- /dev/null +++ b/james/apache-james-mailbox/NOTICE @@ -0,0 +1,9 @@ + ========================================================================= + == NOTICE file for use with the Apache License, Version 2.0, == + ========================================================================= + + Apache JAMES Mailbox + Copyright 2009-2011 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). diff --git a/james/apache-james-mailbox/README.md b/james/apache-james-mailbox/README.md new file mode 100644 index 0000000..6b08d36 --- /dev/null +++ b/james/apache-james-mailbox/README.md @@ -0,0 +1,86 @@ +Apache James Mailbox project +============================ + +The James Mailbox project aims to provide an email (message) store. The main user of the Mailbox project is James Server +project. The implementations can be used standalone and do not depend on James Server. + +The project defines the Mailbox API and has several implementations that you can use. More details bellow. + +Overview +======== + +Apache James Mailbox has the following project (Maven) structure: + +~~~ +|-- api -- Mailbox API +|-- hbase -- Mailbox implementation over HBase +|-- jcr -- Mailbox implementation over Java Content Repository (JCR) +|-- jpa -- Database Mailbox implementation using Java Persistence API +|-- lucene -- Email indexing module with Apache Lucene +|-- maildir -- Email storage using Maildir format http://en.wikipedia.org/wiki/Maildir +|-- memory -- In memory Mailbox implementation - good for testing +|-- spring -- Spring module - starts a specific mailbox implementation +|-- store -- Common base/utility classes used in all mailbox implementations +|-- tool -- Database migration/mailbox export tool +|-- zoo-seq-provider -- Distributed unique ID generator using Zookeeper and Curator (Clustering James Mailbox) +~~~ + +Mailbox JPA +=========== + +Persist email messages inside any database that is supported by your Java Persistence Api provider. Currently James uses +OpenJPA (http://openjpa.apache.org/), but it's easy to implement your own. + +Mailbox 'In memory' message store +================================= + +In module **memory**, does not persist emails. It just keeps them in memory. Fast, and good for testing. +**Note:** Not to be used in production. + +Mailbox JCR +=========== + +Uses Java Content Repository as a persistence layer. Uses Jackrabbit as a provider (http://jackrabbit.apache.org/), +but you could swap in any provider. Comes with all the nice features that Jackrabbit has. + + +Mailbox Maildir +=============== + +Implements the Maildir standard for email storage (http://en.wikipedia.org/wiki/Maildir). Works only on GNU/Linux and other +*Nix systems. + + +Mailbox HBase +============= + +Uses Apache HBase (http://hbase.apache.org/) for storing email messages. Provides a scalable email storage. To have a fully +distributed email server you will also need, among others: + +* distributed UID generation, look at Zookeeper Sequence Provider (**zoo-seq-provider**) for distributed locking and Mailbox manipulation +* distributed SMTP/IMAP access +* other + +Zookeeper Sequence Provider +========================== + +Uses Zookeeper and Curator Framework for generating distributed unique ID's, needed for mailbox management from multiple +instances of James (IMAP) servers. + + +Building +======== + +The primary build tool for Apache James Mailbox is maven 3. + +On a new checkout start by running +~~~ + $ mvn clean package +~~~ + +This will compiled all modules + +For just building without running junit tests: +~~~ + $ mvn clean package -DskiTests=true +~~~ diff --git a/james/apache-james-mailbox/api/.svn/all-wcprops b/james/apache-james-mailbox/api/.svn/all-wcprops new file mode 100644 index 0000000..0b61e78 --- /dev/null +++ b/james/apache-james-mailbox/api/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 51 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 59 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/api/pom.xml +END diff --git a/james/apache-james-mailbox/api/.svn/dir-prop-base b/james/apache-james-mailbox/api/.svn/dir-prop-base new file mode 100644 index 0000000..4a09e9c --- /dev/null +++ b/james/apache-james-mailbox/api/.svn/dir-prop-base @@ -0,0 +1,8 @@ +K 10 +svn:ignore +V 22 +.* +target +coverage.ec + +END diff --git a/james/apache-james-mailbox/api/.svn/entries b/james/apache-james-mailbox/api/.svn/entries new file mode 100644 index 0000000..5847cc3 --- /dev/null +++ b/james/apache-james-mailbox/api/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:40.000000Z +44a7f4b20cd292f5dc9e54c022b5a28d +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +2431 + diff --git a/james/apache-james-mailbox/api/.svn/prop-base/pom.xml.svn-base b/james/apache-james-mailbox/api/.svn/prop-base/pom.xml.svn-base new file mode 100644 index 0000000..4a83b70 --- /dev/null +++ b/james/apache-james-mailbox/api/.svn/prop-base/pom.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/api/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..057d75d --- /dev/null +++ b/james/apache-james-mailbox/api/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,64 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-api + bundle + Apache James :: Mailbox :: API + + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + org.jmock + jmock + test + + + org.jmock + jmock-junit4 + test + + + diff --git a/james/apache-james-mailbox/api/pom.xml b/james/apache-james-mailbox/api/pom.xml new file mode 100644 index 0000000..057d75d --- /dev/null +++ b/james/apache-james-mailbox/api/pom.xml @@ -0,0 +1,64 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-api + bundle + Apache James :: Mailbox :: API + + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + org.jmock + jmock + test + + + org.jmock + jmock-junit4 + test + + + diff --git a/james/apache-james-mailbox/api/src/.svn/all-wcprops b/james/apache-james-mailbox/api/src/.svn/all-wcprops new file mode 100644 index 0000000..b304e8b --- /dev/null +++ b/james/apache-james-mailbox/api/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 55 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src +END diff --git a/james/apache-james-mailbox/api/src/.svn/entries b/james/apache-james-mailbox/api/src/.svn/entries new file mode 100644 index 0000000..d084c02 --- /dev/null +++ b/james/apache-james-mailbox/api/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/api/src/main/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/.svn/all-wcprops new file mode 100644 index 0000000..87687a6 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main +END diff --git a/james/apache-james-mailbox/api/src/main/.svn/entries b/james/apache-james-mailbox/api/src/main/.svn/entries new file mode 100644 index 0000000..ee0937e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/api/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..de0492a --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 65 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main/java +END diff --git a/james/apache-james-mailbox/api/src/main/java/.svn/entries b/james/apache-james-mailbox/api/src/main/java/.svn/entries new file mode 100644 index 0000000..97f4433 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/api/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..a226f42 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main/java/org +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/.svn/entries new file mode 100644 index 0000000..72b7fc8 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..41a6052 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 76 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..82623e0 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..990ab33 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..f7d9c1f --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..b1083e7 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,71 @@ +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox +END +MailboxSession.java +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/MailboxSession.java +END +MessageManager.java +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/MessageManager.java +END +SubscriptionManager.java +K 25 +svn:wc:ra_dav:version-url +V 115 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/SubscriptionManager.java +END +MailboxListener.java +K 25 +svn:wc:ra_dav:version-url +V 111 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/MailboxListener.java +END +MailboxSessionIdGenerator.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1179215/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/MailboxSessionIdGenerator.java +END +QuotaManager.java +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/QuotaManager.java +END +StandardMailboxMetaDataComparator.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/StandardMailboxMetaDataComparator.java +END +MailboxManager.java +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/MailboxManager.java +END +MailboxPathLocker.java +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/MailboxPathLocker.java +END +RequestAware.java +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1091296/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/RequestAware.java +END +MailboxListenerSupport.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/MailboxListenerSupport.java +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..fb63dc1 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,414 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MailboxSession.java +file + + + + +2013-09-02T02:54:40.000000Z +cb7675a3ffc764d08452e46bf7f2a239 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +4763 + +MailboxListener.java +file + + + + +2013-09-02T02:54:40.000000Z +54552467f028450060786f9e9399f549 +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +6155 + +StandardMailboxMetaDataComparator.java +file + + + + +2013-09-02T02:54:40.000000Z +078779e84e76f35c4f4b9cf71ac20252 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2647 + +RequestAware.java +file + + + + +2013-09-02T02:54:40.000000Z +38347644deb415697b0f25e980493be5 +2011-04-12T05:10:18.798553Z +1091296 +felixk + + + + + + + + + + + + + + + + + + + + + +1858 + +MailboxPathLocker.java +file + + + + +2013-09-02T02:54:40.000000Z +b63742685323388dfec666a33828b762 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2905 + +quota +dir + +exception +dir + +SubscriptionManager.java +file + + + + +2013-09-02T02:54:40.000000Z +30408d6b85c925a7d672dc2c9a8554de +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2638 + +MessageManager.java +file + + + + +2013-09-02T02:54:40.000000Z +5f3e7cf0193f0e6a7b42f535367fd2ef +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +13580 + +acl +dir + +model +dir + +MailboxSessionIdGenerator.java +file + + + + +2013-09-02T02:54:40.000000Z +fbc4d03d4b879667b984c9689cc597cc +2011-10-05T13:28:07.200119Z +1179215 +norman +has-props + + + + + + + + + + + + + + + + + + + + +1721 + +QuotaManager.java +file + + + + +2013-09-02T02:54:40.000000Z +073b8ef2b5a330fb72bbae8c26dcffe0 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2083 + +MailboxManager.java +file + + + + +2013-09-02T02:54:40.000000Z +1a619fd6c1ac1ca15150816b987a714c +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +10178 + +MailboxListenerSupport.java +file + + + + +2013-09-02T02:54:40.000000Z +2b563d6d12cab3ffd75e2005b98504c0 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2914 + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxListener.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxListener.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxListener.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxManager.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxManager.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxManager.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxSessionIdGenerator.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxSessionIdGenerator.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MailboxSessionIdGenerator.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MessageManager.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MessageManager.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/prop-base/MessageManager.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListener.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListener.java.svn-base new file mode 100644 index 0000000..5ee4a79 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListener.java.svn-base @@ -0,0 +1,227 @@ +/**************************************************************** + * 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.Serializable; +import java.util.List; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.UpdatedFlags; + + +/** + * Listens to Mailbox events.
+ * Note that listeners may be removed asynchronously. + */ +public interface MailboxListener { + + /** + * Informs this listener about the given event. + * + * @param event + * not null + */ + void event(final Event event); + + /** + * A mailbox event. + */ + @SuppressWarnings("serial") + public abstract class Event implements Serializable { + private final MailboxSession session; + private final MailboxPath path; + + public Event(final MailboxSession session, final MailboxPath path) { + this.session = session; + this.path = path; + } + + /** + * Gets the {@link MailboxSession} in which's context the {@link Event} + * happened + * + * @return session + */ + public MailboxSession getSession() { + return session; + } + + /** + * Return the path of the Mailbox this event belongs to. + * + * @return path + */ + public MailboxPath getMailboxPath() { + return path; + } + } + + /** + * Indicates that mailbox has been deleted. + */ + public class MailboxDeletion extends Event { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxDeletion(final MailboxSession session, MailboxPath path) { + super(session, path); + } + } + + /** + * Indicates that a mailbox has been Added. + */ + public class MailboxAdded extends Event { + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxAdded(final MailboxSession session, MailboxPath path) { + super(session, path); + } + } + + /** + * Indicates that a mailbox has been renamed. + */ + public abstract class MailboxRenamed extends Event { + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxRenamed(final MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Gets the new name for this mailbox. + * + * @return name, not null + */ + public abstract MailboxPath getNewPath(); + } + + + /** + * A mailbox event related to updated ACL + */ + public abstract class MailboxACLUpdated extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxACLUpdated(MailboxSession session, MailboxPath path) { + super(session, path); + } + + public abstract MailboxACL getUpdatedACL(); + } + + /** + * A mailbox event related to a message. + */ + public abstract class MessageEvent extends Event { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public MessageEvent(MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Gets the message UIDs for the subject of this event. + * + * @return message uids + */ + public abstract List getUids(); + } + + public abstract class Expunged extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public Expunged(MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Return the flags which were set for the added message + * + * @return flags + */ + public abstract MessageMetaData getMetaData(long uid); + } + + /** + * A mailbox event related to updated flags + */ + public abstract class FlagsUpdated extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public FlagsUpdated(MailboxSession session, MailboxPath path) { + super(session, path); + } + + public abstract List getUpdatedFlags(); + } + + /** + * A mailbox event related to added message + */ + public abstract class Added extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public Added(MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Return the flags which were set for the added message + * + * @return flags + */ + public abstract MessageMetaData getMetaData(long uid); + + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListenerSupport.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListenerSupport.java.svn-base new file mode 100644 index 0000000..1f5e35d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxListenerSupport.java.svn-base @@ -0,0 +1,81 @@ +/**************************************************************** + * 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 org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +/** + * Implementations of this interface supports {@link MailboxListener}. Its needed that the events get handled + * in the submitted order + * + * + */ +public interface MailboxListenerSupport { + + /** + *

+ * Implementations of Mailbox may interpret the fact that someone is + * listening and do some caching and even postpone persistence until + * everyone has removed itself. + *

+ * + * @param mailboxPath + * not null + * @param listener + * not null + * @param session + * not null + * @throws MailboxException + */ + void addListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException; + + /** + * Remove the {@link MailboxListener} + * + * @param mailboxPath + * @param listner + * @param session + * @throws MailboxException + */ + void removeListener(MailboxPath mailboxPath, MailboxListener listner, MailboxSession session) throws MailboxException; + + /** + * Add a {@link MailboxListener} which get fired for ever + * {@link MailboxPath} + * + * @param listener + * @param session + * @throws MailboxException + */ + void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException; + + + /** + * Remove the {@link MailboxListener} + * + * @param listner + * @param session + * @throws MailboxException + */ + void removeGlobalListener(MailboxListener listner, MailboxSession session) throws MailboxException; + + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxManager.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxManager.java.svn-base new file mode 100644 index 0000000..d375023 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxManager.java.svn-base @@ -0,0 +1,260 @@ +/**************************************************************** + * 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.util.List; + +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MailboxQuery; +import org.apache.james.mailbox.model.MessageRange; +import org.slf4j.Logger; + +/** + *

+ * Central MailboxManager which creates, lists, provides, renames and deletes + * Mailboxes + *

+ *

+ * An important goal is to be JavaMail feature compatible. That means JavaMail + * could be used in both directions: As a backend for e.g. accessing a Maildir + * JavaMail store or as a frontend to access a JDBC MailboxManager through + * JavaMail. This should be possible by not too complicated wrapper classes. Due + * to the complexity of JavaMail it might be impossible to avoid some + * limitations. + *

+ *

+ * Internally MailboxManager deals with named repositories that could have + * different implementations. E.g. JDBC connections to different hosts or + * Maildir / Mbox like stores. These repositories are identified by their names + * and maybe are configured in config.xml. The names of the mailboxes have to be + * mapped to the corresponding repository name. For user mailboxes this could be + * done by a "User.getRepositoryName()" property. It is imaginable that + * repositories lookup further properties from the user object like a path name + * for a file based storage method. Until Milestone 6 there is only one named + * repository: "default". + *

+ *

+ * The only operation that requires dealing with the named repositories directly + * is the quota management. It is probably really difficult to implement a quota + * system that spans multiple repository implementations. That is why quotas are + * created for a specific repository. To be able to administer, repositories and + * theier belonging mailboxes can be listet. + *

+ */ + +public interface MailboxManager extends RequestAware, MailboxListenerSupport { + + /** + * Return the delimiter to use for folders + * + * @return delimiter + */ + char getDelimiter(); + + /** + * Gets an session suitable for IMAP. + * + * @param mailboxPath + * the Path of the mailbox, not null + * @param session + * the context for this call, not null + * @return ImapMailboxSession, not null + * @throws MailboxException + * when the mailbox cannot be opened + * @throws MailboxNotFoundException + * when the given mailbox does not exist + */ + MessageManager getMailbox(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; + + /** + * Creates a new mailbox. Any intermediary mailboxes missing from the + * hierarchy should be created. + * + * @param mailboxPath + * @param mailboxSession + * the context for this call, not null + * @throws MailboxException + * when creation fails + */ + void createMailbox(MailboxPath mailboxPath, MailboxSession mailboxSession) throws MailboxException; + + /** + * Delete the mailbox with the name + * + * @param mailboxPath + * @param session + * @throws MailboxException + */ + void deleteMailbox(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; + + /** + * Renames a mailbox. + * + * @param from + * original mailbox + * @param to + * new mailbox + * @param session + * the context for this call, not nul + * @throws MailboxException + * otherwise + * @throws MailboxExistsException + * when the to mailbox exists + * @throws MailboxNotFound + * when the from mailbox does not exist + */ + void renameMailbox(MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; + + /** + * Copy the given {@link MessageRange} from one Mailbox to the other. + * + * Be aware that the copied Messages MUST get the \RECENT flag set! + * + * @param set + * messages to copy + * @param from + * name of the source mailbox + * @param to + * name of the destination mailbox + * @param session + * MailboxSession, not null + * @return a list of MessageRange - uids assigned to copied messages + */ + List copyMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; + + /** + * Move the given {@link MessageRange} from one Mailbox to the other. + * + * Be aware that the moved Messages MUST get the \RECENT flag set! + * + * @param set + * messages to move + * @param from + * name of the source mailbox + * @param to + * name of the destination mailbox + * @param session + * MailboxSession, not null + * @return a list of MessageRange - uids assigned to moved messages + */ + List moveMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; + + /** + * Searches for mailboxes matching the given query. + * + * @param expression + * not null + * @param session + * the context for this call, not null + * @throws MailboxException + */ + List search(MailboxQuery expression, MailboxSession session) throws MailboxException; + + /** + * Does the given mailbox exist? + * + * @param mailboxPath + * not null + * @param session + * the context for this call, not null + * @return true when the mailbox exists and is accessible for the given + * user, false otherwise + * @throws MailboxException + */ + boolean mailboxExists(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; + + /** + * Creates a new system session.
+ * A system session is intended to be used for programmatic access.
+ * Use {@link #login(String, String, Logger)} when accessing this API from a + * protocol. + * + * @param userName + * the name of the user whose session is being created + * @param log + * context sensitive log + * @return MailboxSession, not null + * @throws BadCredentialsException + * when system access is not allowed for the given user + * @throws MailboxException + * when the creation fails for other reasons + */ + MailboxSession createSystemSession(String userName, Logger log) throws BadCredentialsException, MailboxException; + + /** + * Autenticates the given user against the given password.
+ * When authentic and authorized, a session will be supplied + * + * @param userid + * user name + * @param passwd + * password supplied + * @param log + * context sensitive log + * @return a MailboxSession when the user is authentic and + * authorized to access + * @throws BadCredentialsException + * when system access is denighed for the given user + * @throws MailboxException + * when the creation fails for other reasons + */ + MailboxSession login(String userid, String passwd, Logger log) throws BadCredentialsException, MailboxException; + + /** + *

+ * Logs the session out, freeing any resources. Clients who open session + * should make best efforts to call this when the session is closed. + *

+ *

+ * Note that clients may not always be able to call logout (whether forced + * or not). Mailboxes that create sessions which are expensive to maintain + * MUST retain a reference and periodically check + * {@link MailboxSession#isOpen()}. + *

+ *

+ * Note that implementations much be aware that it is possible that this + * method may be called more than once with the same session. + *

+ * + * @param session + * not null + * @param force + * true when the session logout is forced by premature connection + * termination + * @throws MailboxException + * when logout fails + */ + void logout(MailboxSession session, boolean force) throws MailboxException; + + /** + * Return a unmodifiable {@link List} of {@link MailboxPath} objects + * + * @param session + * @return pathList + * @throws MailboxException + */ + List list(MailboxSession session) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxPathLocker.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxPathLocker.java.svn-base new file mode 100644 index 0000000..b4ec882 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxPathLocker.java.svn-base @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.util.concurrent.locks.ReadWriteLock; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +/** + * The {@link MailboxPathLocker} is responsible to help to synchronize the + * access to a {@link MailboxPath} and execute an given {@link LockAwareExecution} + * + * Implementations that are not able to handle read / write locks in a different way are needed to handle all locks as write lock. + */ +public interface MailboxPathLocker { + + /** + * @deprecated use {@link #executeWithLock(MailboxSession, MailboxPath, LockAwareExecution, boolean)} with argument true + */ + @Deprecated + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution) throws MailboxException; + + + /** + * Execute the {@link LockAwareExecution} while holding a lock on the + * {@link MailboxPath}. If writeLock is true the implementation need to make sure that no other threads can read and write while the lock + * is hold. The contract is the same as documented in {@link ReadWriteLock}. + * + * @param session + * @param path + * @param execution + * @param writeLock + * + * @throws MailboxException + */ + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution, boolean writeLock) throws MailboxException; + + /** + * Execute code while holding a lock + */ + public interface LockAwareExecution { + + /** + * Execute code block + * + * @throws MailboxException + */ + public T execute() throws MailboxException; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSession.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSession.java.svn-base new file mode 100644 index 0000000..4241017 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSession.java.svn-base @@ -0,0 +1,164 @@ +/**************************************************************** + * 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.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.slf4j.Logger; + +/** + * Mailbox session. + */ +public interface MailboxSession { + + + /** + * Id which will be used for a System session + */ + public final static long SYSTEM_SESSION_ID = 0L; + + public static enum SessionType { + /** + * Session was created via the System + */ + System, + + /** + * Session belongs to a specific user which was authenticated somehow + */ + User + } + + /** + * Return if the {@link MailboxSession} is of type {@link SessionType#User} or {@link SessionType#System} + * + * @return type + */ + SessionType getType(); + + /** + * Gets the session ID. + * + * @return session id + */ + long getSessionId(); + + /** + * Is this session open? + * + * @return true if the session is open, false otherwise + */ + boolean isOpen(); + + /** + * Closes this session. + */ + void close(); + + /** + * Gets the logger for this session context. + * + * @return not null + */ + Logger getLog(); + + /** + * Gets the user executing this session. + * + * @return not null + */ + User getUser(); + + /** + * A mailbox user. Useful for specialist mailbox implementation. + */ + public interface User { + /** + * Gets the name of the user. + * + * @return not null + */ + String getUserName(); + + /** + * Return the Password for the logged in user + * + * @return password + */ + String getPassword(); + + /** + * Gets acceptable localisation for this user in preference order.
+ * When localising a phrase, each Locale should be tried in + * order until an appropriate translation is obtained. + * + * @return not null, when empty the default local should be used + */ + List getLocalePreferences(); + } + + /** + * Gets the personal namespace for the current session.
+ * Note that though servers may offer multiple personal namespaces, support + * is not offered through this API. This decision may be revised if + * reasonable use cases emerge. + * + * @return Personal Namespace, not null + */ + String getPersonalSpace(); + + /** + * Gets the other users namespace for the current session.
+ * Note that though servers may offer multiple other users namespaces, + * support is not offered through this API. This decision may be revised if + * reasonable use cases emerge. + * + * @return Other Users Namespace or null when there is non available + */ + String getOtherUsersSpace(); + + /** + * Iterates the Shared Namespaces available for the current + * session. + * + * @return not null though possibly empty + */ + Collection getSharedSpaces(); + + /** + * Return the stored attributes for this {@link MailboxSession}. + * + * @return attributes + */ + Map getAttributes(); + + /** + * Return server side, folder path separator + * + * @return path separator + */ + char getPathDelimiter(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSessionIdGenerator.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSessionIdGenerator.java.svn-base new file mode 100644 index 0000000..a87ea77 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MailboxSessionIdGenerator.java.svn-base @@ -0,0 +1,39 @@ +/**************************************************************** + * 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; + + +/** + * Generator for id's which should used for new {@link MailboxSession} instances + * + * + */ +public interface MailboxSessionIdGenerator { + + /** + * Return the next id to use for a {@link MailboxSession}. The id must be unique + * while the server is running and can be any long except {@link MailboxSession#SYSTEM_SESSION_ID}. + * + * The returned ids can be in any specific order. + * + * @return id + */ + long nextId(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MessageManager.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MessageManager.java.svn-base new file mode 100644 index 0000000..28ad722 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/MessageManager.java.svn-base @@ -0,0 +1,382 @@ +/**************************************************************** + * 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.InputStream; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.UnsupportedCriteriaException; +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.EditMode; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResultIterator; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; + +/** + * Interface which represent a Mailbox + * + * A {@link MessageManager} should be valid for the whole {@link MailboxSession} + */ +public interface MessageManager { + + /** + * Return the count + * + * @param mailboxSession + * @return count + * @throws MailboxException + * @deprecated use + * {@link #getMetaData(boolean, MailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup)} + */ + @Deprecated + long getMessageCount(MailboxSession mailboxSession) throws MailboxException; + + /** + * Return if the Mailbox is writable + * + * @param session + * @return writable + * @throws MailboxException + * @deprecated use + * {@link #getMetaData(boolean, MailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup)} + */ + @Deprecated + boolean isWriteable(MailboxSession session) throws MailboxException; + + /** + * Return true if {@link MessageResult#getModSeq()} is stored in a permanent + * way. + * + * @param session + * @return modSeqPermanent + * @deprecated use + * {@link #getMetaData(boolean, MailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup)} + */ + boolean isModSeqPermanent(MailboxSession session); + + /** + * Searches for messages matching the given query. The result must be + * ordered + * + * @param mailboxSession + * not null + * @return uid iterator + * @throws UnsupportedCriteriaException + * when any of the search parameters are not supported by this + * mailbox + * @throws MailboxException + * when search fails for other reasons + */ + Iterator search(SearchQuery searchQuery, MailboxSession mailboxSession) throws MailboxException; + + /** + * Expunges messages in the given range from this mailbox. + * + * @param set + * not null + * @param mailboxSession + * not null + * @return uid iterator + * @throws MailboxException + * if anything went wrong + */ + Iterator expunge(MessageRange set, MailboxSession mailboxSession) throws MailboxException; + + /** + * Sets flags on messages within the given range. The new flags are returned + * for each message altered. + * + * @param flags + * Flags to be set + * @param value + * true = set, false = unset + * @param replace + * replace all Flags with this flags, value has to be true + * @param set + * the range of messages + * @param mailboxSession + * not null + * @return new flags indexed by UID + * @throws MailboxException + */ + Map setFlags(Flags flags, boolean value, boolean replace, MessageRange set, MailboxSession mailboxSession) throws MailboxException; + + /** + * Appends a message to this mailbox. This method must return a higher UID + * as the last call in every case which also needs to be unique for the + * lifetime of the mailbox. + * + * + * @param internalDate + * the time of addition to be set, not null + * @param mailboxSession + * not null + * @param isRecent + * true when the message should be marked recent, false otherwise + * @param flags + * optionally set these flags on created message, or null when no + * additional flags should be set + * @return uid for the newly added message + * @throws MailboxException + * when message cannot be appended + */ + long appendMessage(InputStream msgIn, Date internalDate, MailboxSession mailboxSession, boolean isRecent, Flags flags) throws MailboxException; + + /** + * Gets messages in the given range. The messages may get fetched under + * the-hood in batches so the caller should check if + * {@link MessageResultIterator#getException()} returns null + * after {@link MessageResultIterator#hasNext()} returns false. + * + * + * @param set + * @param fetchGroup + * data to fetch + * @param mailboxSession + * not null + * @return MessageResult with the fields defined by FetchGroup + * @throws MailboxException + */ + MessageResultIterator getMessages(MessageRange set, FetchGroup fetchGroup, MailboxSession mailboxSession) throws MailboxException; + + /** + * Tells whether the given {@link MailboxSession}'s user has the given + * {@link MailboxACLRight} for this {@link MessageManager}'s mailbox. + * + * @param right + * @param session + * @return true if the given {@link MailboxSession}'s user has the given + * {@link MailboxACLRight} for this {@link MessageManager}'s + * mailbox; false otherwise. + * @throws MailboxException + */ + public boolean hasRight(MailboxACLRight right, MailboxSession session) throws MailboxException; + + /** + * Returns the rights applicable to the user who has sent the current + * request. + * + * @param session + * @return the rights applicable to the user who has sent the request, + * returns {@link SimpleMailboxACL#NO_RIGHTS} if + * {@code session.getUser()} is null. + * @throws UnsupportedRightException + */ + public abstract MailboxACLRights myRights(MailboxSession session) throws MailboxException; + + /** + * Computes a result suitable for the LISTRIGHTS IMAP command. The result is + * computed for this mailbox and the given {@code identifier}. + * + * From RFC 4314 section 3.7: + * The first element of the resulting array contains the (possibly empty) + * set of rights the identifier will always be granted in the mailbox. + * Following this are zero or more right sets the identifier can be granted + * in the mailbox. Rights mentioned in the same set are tied together. The + * server MUST either grant all tied rights to the identifier in the mailbox + * or grant none. + * + * The same right MUST NOT be listed more than once in the LISTRIGHTS + * command. + * + * @param identifier + * the identifier from the LISTRIGHTS command. + * @param session + * @return + * @throws UnsupportedRightException + */ + public MailboxACLRights[] listRigths(MailboxACLEntryKey identifier, MailboxSession session) throws UnsupportedRightException; + + /** + * TODO setRights. + * + * @param identifier + * @param editMode + * @param mailboxAclRights + * @throws UnsupportedRightException + */ + void setRights(MailboxACLEntryKey identifier, EditMode editMode, MailboxACLRights mailboxAclRights) throws UnsupportedRightException; + + /** + * Gets current meta data for the mailbox.
+ * Consolidates common calls together to allow improved performance.
+ * The meta-data returned should be immutable and represent the current + * state of the mailbox. + * + * @param resetRecent + * true when recent flags should be reset, false otherwise + * @param mailboxSession + * context, not null + * @param fetchGroup + * describes which optional data should be returned + * @return meta data, not null + * @throws MailboxException + */ + MetaData getMetaData(boolean resetRecent, MailboxSession mailboxSession, MessageManager.MetaData.FetchGroup fetchGroup) throws MailboxException; + + /** + * Meta data about the current state of the mailbox. + */ + public interface MetaData { + + /** + * Describes the optional data types which will get set in the + * {@link MetaData}. + * + * These are always set: - HIGHESTMODSEQ - PERMANENTFLAGS - UIDNEXT - + * UIDVALIDITY - MODSEQPERMANET - WRITABLE + */ + public enum FetchGroup { + + /** + * Only include the message and recent count + */ + NO_UNSEEN, + + /** + * Only include the unseen message and recent count + */ + UNSEEN_COUNT, + + /** + * Only include the first unseen and the recent count + */ + FIRST_UNSEEN, + + /** + * Only return the "always set" metadata as documented above + */ + NO_COUNT + }; + + /** + * Gets the UIDs of recent messages if requested or an empty + * {@link List} otherwise. + * + * @return the uids flagged RECENT in this mailbox, + */ + List getRecent(); + + /** + * Gets the number of recent messages. + * + * @return the number of messages flagged RECENT in this mailbox + */ + long countRecent(); + + /** + * Gets the flags which can be stored by this mailbox. + * + * @return Flags that can be stored + */ + Flags getPermanentFlags(); + + /** + * Gets the UIDVALIDITY. + * + * @return UIDVALIDITY + */ + long getUidValidity(); + + /** + * Gets the next UID predicted. The returned UID is not guaranteed to be + * the one that is assigned to the next message. Its only guaranteed + * that it will be at least equals or bigger then the value + * + * @return the uid that will be assigned to the next appended message + */ + long getUidNext(); + + /** + * Return the highest mod-sequence for the mailbox. If this value has + * changed till the last check you can be sure that some changes where + * happen on the mailbox + * + * @return higestModSeq + */ + long getHighestModSeq(); + + /** + * Gets the number of messages that this mailbox contains. This is an + * optional property.
+ * + * @return number of messages contained or -1 when this optional data + * has not be requested + * + */ + long getMessageCount(); + + /** + * Gets the number of unseen messages contained in this mailbox. This is + * an optional property.
+ * + * @return number of unseen messages contained or zero when this + * optional data has not been requested + * @see FetchGroup#UNSEEN_COUNT + */ + long getUnseenCount(); + + /** + * Gets the UID of the first unseen message. This is an optional + * property.
+ * + * @return uid of the first unseen message, or null when there are no + * unseen messages + * @see FetchGroup#FIRST_UNSEEN + */ + Long getFirstUnseen(); + + /** + * Is this mailbox writable? + * + * @return true if read-write, false if read only + */ + boolean isWriteable(); + + /** + * Return true if the mailbox does store the mod-sequences in a + * permanent way + * + * @return permanent + */ + boolean isModSeqPermanent(); + + /** + * Returns the ACL concerning this mailbox. + * + * @return acl + */ + MailboxACL getACL(); + + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/QuotaManager.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/QuotaManager.java.svn-base new file mode 100644 index 0000000..6408dbd --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/QuotaManager.java.svn-base @@ -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; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Quota; + + +/** + * Allows to get quotas for {@link MailboxSession} which are bound to a user. + * + */ +public interface QuotaManager { + + /** + * Return the message count {@link Quota} for the given {@link MailboxSession} (which in fact is + * bound to a user) + * + * @param session + * @return quota + * @throws MailboxException + */ + public Quota getMessageQuota(MailboxSession session) throws MailboxException; + + + /** + * Return the message storage {@link Quota} for the given {@link MailboxSession} (which in fact is + * bound to a user) + * + * @param session + * @return quota + * @throws MailboxException + */ + public Quota getStorageQuota(MailboxSession session) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/RequestAware.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/RequestAware.java.svn-base new file mode 100644 index 0000000..3072331 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/RequestAware.java.svn-base @@ -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; + +/** + * Implementations of this interface are aware of processing requests + */ +public interface RequestAware { + + /** + * Start the processing of a request for the given MailboxSession. If the + * user is not logged in already then the MailboxSession will be null + * + * @param session + */ + public void startProcessingRequest(MailboxSession session); + + /** + * End the processing of a request for the given MailboxSession. If the user + * is not logged in already then the MailboxSession will be null + * + * @param session + */ + public void endProcessingRequest(MailboxSession session); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/StandardMailboxMetaDataComparator.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/StandardMailboxMetaDataComparator.java.svn-base new file mode 100644 index 0000000..a876c26 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/StandardMailboxMetaDataComparator.java.svn-base @@ -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 java.io.Serializable; +import java.util.Comparator; + +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxMetaData; + +/** + * Orders by name with INBOX first. + */ +public class StandardMailboxMetaDataComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Static comparison. + * + * @param one + * possibly null + * @param two + * possibly null + * @return {@link Comparator#compare(Object, Object)} + */ + public static int order(MailboxMetaData one, MailboxMetaData two) { + final String nameTwo = two.getPath().getName(); + final int result; + final String nameOne = one.getPath().getName(); + if (MailboxConstants.INBOX.equals(nameOne)) { + result = MailboxConstants.INBOX.equals(nameTwo) ? 0 : -1; + } else if (MailboxConstants.INBOX.equals(nameTwo)) { + result = 1; + } else if (nameOne == null) { + result = nameTwo == null ? 0 : 1; + } else if (nameTwo == null) { + result = -1; + } else { + result = nameOne.compareTo(nameTwo); + } + return result; + } + + /** + * @see Comparator#compare(Object, Object) + */ + public int compare(MailboxMetaData one, MailboxMetaData two) { + return order(one, two); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/SubscriptionManager.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/SubscriptionManager.java.svn-base new file mode 100644 index 0000000..ea66b48 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/.svn/text-base/SubscriptionManager.java.svn-base @@ -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 java.util.Collection; + +import org.apache.james.mailbox.exception.SubscriptionException; + +/** + * Subscribes mailboxes to users. This is only needed to implement if the Mailbox should be usable via + * IMAP. For POP3 only you don't need this at all. + * + */ +public interface SubscriptionManager extends RequestAware { + + /** + * Subscribes the user in the session to the given mailbox. + * + * @param session + * not null + * @param mailbox + * not null + * @throws SubscriptionException + * when subscription fails + */ + public void subscribe(MailboxSession session, String mailbox) throws SubscriptionException; + + /** + * Finds all subscriptions for the user in the session. + * + * @param session + * not null + * @return not null + * @throws SubscriptionException + * when subscriptions cannot be read + */ + public Collection subscriptions(MailboxSession session) throws SubscriptionException; + + /** + * Unsubscribes the user in the session from the given mailbox. + * + * @param session + * not null + * @param mailbox + * not null + * @throws SubscriptionException + * when subscriptions cannot be read + */ + public void unsubscribe(MailboxSession session, String mailbox) throws SubscriptionException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java new file mode 100644 index 0000000..5ee4a79 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListener.java @@ -0,0 +1,227 @@ +/**************************************************************** + * 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.Serializable; +import java.util.List; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.UpdatedFlags; + + +/** + * Listens to Mailbox events.
+ * Note that listeners may be removed asynchronously. + */ +public interface MailboxListener { + + /** + * Informs this listener about the given event. + * + * @param event + * not null + */ + void event(final Event event); + + /** + * A mailbox event. + */ + @SuppressWarnings("serial") + public abstract class Event implements Serializable { + private final MailboxSession session; + private final MailboxPath path; + + public Event(final MailboxSession session, final MailboxPath path) { + this.session = session; + this.path = path; + } + + /** + * Gets the {@link MailboxSession} in which's context the {@link Event} + * happened + * + * @return session + */ + public MailboxSession getSession() { + return session; + } + + /** + * Return the path of the Mailbox this event belongs to. + * + * @return path + */ + public MailboxPath getMailboxPath() { + return path; + } + } + + /** + * Indicates that mailbox has been deleted. + */ + public class MailboxDeletion extends Event { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxDeletion(final MailboxSession session, MailboxPath path) { + super(session, path); + } + } + + /** + * Indicates that a mailbox has been Added. + */ + public class MailboxAdded extends Event { + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxAdded(final MailboxSession session, MailboxPath path) { + super(session, path); + } + } + + /** + * Indicates that a mailbox has been renamed. + */ + public abstract class MailboxRenamed extends Event { + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxRenamed(final MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Gets the new name for this mailbox. + * + * @return name, not null + */ + public abstract MailboxPath getNewPath(); + } + + + /** + * A mailbox event related to updated ACL + */ + public abstract class MailboxACLUpdated extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public MailboxACLUpdated(MailboxSession session, MailboxPath path) { + super(session, path); + } + + public abstract MailboxACL getUpdatedACL(); + } + + /** + * A mailbox event related to a message. + */ + public abstract class MessageEvent extends Event { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public MessageEvent(MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Gets the message UIDs for the subject of this event. + * + * @return message uids + */ + public abstract List getUids(); + } + + public abstract class Expunged extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public Expunged(MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Return the flags which were set for the added message + * + * @return flags + */ + public abstract MessageMetaData getMetaData(long uid); + } + + /** + * A mailbox event related to updated flags + */ + public abstract class FlagsUpdated extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public FlagsUpdated(MailboxSession session, MailboxPath path) { + super(session, path); + } + + public abstract List getUpdatedFlags(); + } + + /** + * A mailbox event related to added message + */ + public abstract class Added extends MessageEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public Added(MailboxSession session, MailboxPath path) { + super(session, path); + } + + /** + * Return the flags which were set for the added message + * + * @return flags + */ + public abstract MessageMetaData getMetaData(long uid); + + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListenerSupport.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListenerSupport.java new file mode 100644 index 0000000..1f5e35d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxListenerSupport.java @@ -0,0 +1,81 @@ +/**************************************************************** + * 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 org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +/** + * Implementations of this interface supports {@link MailboxListener}. Its needed that the events get handled + * in the submitted order + * + * + */ +public interface MailboxListenerSupport { + + /** + *

+ * Implementations of Mailbox may interpret the fact that someone is + * listening and do some caching and even postpone persistence until + * everyone has removed itself. + *

+ * + * @param mailboxPath + * not null + * @param listener + * not null + * @param session + * not null + * @throws MailboxException + */ + void addListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException; + + /** + * Remove the {@link MailboxListener} + * + * @param mailboxPath + * @param listner + * @param session + * @throws MailboxException + */ + void removeListener(MailboxPath mailboxPath, MailboxListener listner, MailboxSession session) throws MailboxException; + + /** + * Add a {@link MailboxListener} which get fired for ever + * {@link MailboxPath} + * + * @param listener + * @param session + * @throws MailboxException + */ + void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException; + + + /** + * Remove the {@link MailboxListener} + * + * @param listner + * @param session + * @throws MailboxException + */ + void removeGlobalListener(MailboxListener listner, MailboxSession session) throws MailboxException; + + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java new file mode 100644 index 0000000..d375023 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java @@ -0,0 +1,260 @@ +/**************************************************************** + * 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.util.List; + +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MailboxQuery; +import org.apache.james.mailbox.model.MessageRange; +import org.slf4j.Logger; + +/** + *

+ * Central MailboxManager which creates, lists, provides, renames and deletes + * Mailboxes + *

+ *

+ * An important goal is to be JavaMail feature compatible. That means JavaMail + * could be used in both directions: As a backend for e.g. accessing a Maildir + * JavaMail store or as a frontend to access a JDBC MailboxManager through + * JavaMail. This should be possible by not too complicated wrapper classes. Due + * to the complexity of JavaMail it might be impossible to avoid some + * limitations. + *

+ *

+ * Internally MailboxManager deals with named repositories that could have + * different implementations. E.g. JDBC connections to different hosts or + * Maildir / Mbox like stores. These repositories are identified by their names + * and maybe are configured in config.xml. The names of the mailboxes have to be + * mapped to the corresponding repository name. For user mailboxes this could be + * done by a "User.getRepositoryName()" property. It is imaginable that + * repositories lookup further properties from the user object like a path name + * for a file based storage method. Until Milestone 6 there is only one named + * repository: "default". + *

+ *

+ * The only operation that requires dealing with the named repositories directly + * is the quota management. It is probably really difficult to implement a quota + * system that spans multiple repository implementations. That is why quotas are + * created for a specific repository. To be able to administer, repositories and + * theier belonging mailboxes can be listet. + *

+ */ + +public interface MailboxManager extends RequestAware, MailboxListenerSupport { + + /** + * Return the delimiter to use for folders + * + * @return delimiter + */ + char getDelimiter(); + + /** + * Gets an session suitable for IMAP. + * + * @param mailboxPath + * the Path of the mailbox, not null + * @param session + * the context for this call, not null + * @return ImapMailboxSession, not null + * @throws MailboxException + * when the mailbox cannot be opened + * @throws MailboxNotFoundException + * when the given mailbox does not exist + */ + MessageManager getMailbox(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; + + /** + * Creates a new mailbox. Any intermediary mailboxes missing from the + * hierarchy should be created. + * + * @param mailboxPath + * @param mailboxSession + * the context for this call, not null + * @throws MailboxException + * when creation fails + */ + void createMailbox(MailboxPath mailboxPath, MailboxSession mailboxSession) throws MailboxException; + + /** + * Delete the mailbox with the name + * + * @param mailboxPath + * @param session + * @throws MailboxException + */ + void deleteMailbox(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; + + /** + * Renames a mailbox. + * + * @param from + * original mailbox + * @param to + * new mailbox + * @param session + * the context for this call, not nul + * @throws MailboxException + * otherwise + * @throws MailboxExistsException + * when the to mailbox exists + * @throws MailboxNotFound + * when the from mailbox does not exist + */ + void renameMailbox(MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; + + /** + * Copy the given {@link MessageRange} from one Mailbox to the other. + * + * Be aware that the copied Messages MUST get the \RECENT flag set! + * + * @param set + * messages to copy + * @param from + * name of the source mailbox + * @param to + * name of the destination mailbox + * @param session + * MailboxSession, not null + * @return a list of MessageRange - uids assigned to copied messages + */ + List copyMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; + + /** + * Move the given {@link MessageRange} from one Mailbox to the other. + * + * Be aware that the moved Messages MUST get the \RECENT flag set! + * + * @param set + * messages to move + * @param from + * name of the source mailbox + * @param to + * name of the destination mailbox + * @param session + * MailboxSession, not null + * @return a list of MessageRange - uids assigned to moved messages + */ + List moveMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; + + /** + * Searches for mailboxes matching the given query. + * + * @param expression + * not null + * @param session + * the context for this call, not null + * @throws MailboxException + */ + List search(MailboxQuery expression, MailboxSession session) throws MailboxException; + + /** + * Does the given mailbox exist? + * + * @param mailboxPath + * not null + * @param session + * the context for this call, not null + * @return true when the mailbox exists and is accessible for the given + * user, false otherwise + * @throws MailboxException + */ + boolean mailboxExists(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; + + /** + * Creates a new system session.
+ * A system session is intended to be used for programmatic access.
+ * Use {@link #login(String, String, Logger)} when accessing this API from a + * protocol. + * + * @param userName + * the name of the user whose session is being created + * @param log + * context sensitive log + * @return MailboxSession, not null + * @throws BadCredentialsException + * when system access is not allowed for the given user + * @throws MailboxException + * when the creation fails for other reasons + */ + MailboxSession createSystemSession(String userName, Logger log) throws BadCredentialsException, MailboxException; + + /** + * Autenticates the given user against the given password.
+ * When authentic and authorized, a session will be supplied + * + * @param userid + * user name + * @param passwd + * password supplied + * @param log + * context sensitive log + * @return a MailboxSession when the user is authentic and + * authorized to access + * @throws BadCredentialsException + * when system access is denighed for the given user + * @throws MailboxException + * when the creation fails for other reasons + */ + MailboxSession login(String userid, String passwd, Logger log) throws BadCredentialsException, MailboxException; + + /** + *

+ * Logs the session out, freeing any resources. Clients who open session + * should make best efforts to call this when the session is closed. + *

+ *

+ * Note that clients may not always be able to call logout (whether forced + * or not). Mailboxes that create sessions which are expensive to maintain + * MUST retain a reference and periodically check + * {@link MailboxSession#isOpen()}. + *

+ *

+ * Note that implementations much be aware that it is possible that this + * method may be called more than once with the same session. + *

+ * + * @param session + * not null + * @param force + * true when the session logout is forced by premature connection + * termination + * @throws MailboxException + * when logout fails + */ + void logout(MailboxSession session, boolean force) throws MailboxException; + + /** + * Return a unmodifiable {@link List} of {@link MailboxPath} objects + * + * @param session + * @return pathList + * @throws MailboxException + */ + List list(MailboxSession session) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxPathLocker.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxPathLocker.java new file mode 100644 index 0000000..b4ec882 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxPathLocker.java @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.util.concurrent.locks.ReadWriteLock; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +/** + * The {@link MailboxPathLocker} is responsible to help to synchronize the + * access to a {@link MailboxPath} and execute an given {@link LockAwareExecution} + * + * Implementations that are not able to handle read / write locks in a different way are needed to handle all locks as write lock. + */ +public interface MailboxPathLocker { + + /** + * @deprecated use {@link #executeWithLock(MailboxSession, MailboxPath, LockAwareExecution, boolean)} with argument true + */ + @Deprecated + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution) throws MailboxException; + + + /** + * Execute the {@link LockAwareExecution} while holding a lock on the + * {@link MailboxPath}. If writeLock is true the implementation need to make sure that no other threads can read and write while the lock + * is hold. The contract is the same as documented in {@link ReadWriteLock}. + * + * @param session + * @param path + * @param execution + * @param writeLock + * + * @throws MailboxException + */ + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution, boolean writeLock) throws MailboxException; + + /** + * Execute code while holding a lock + */ + public interface LockAwareExecution { + + /** + * Execute code block + * + * @throws MailboxException + */ + public T execute() throws MailboxException; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java new file mode 100644 index 0000000..4241017 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSession.java @@ -0,0 +1,164 @@ +/**************************************************************** + * 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.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.slf4j.Logger; + +/** + * Mailbox session. + */ +public interface MailboxSession { + + + /** + * Id which will be used for a System session + */ + public final static long SYSTEM_SESSION_ID = 0L; + + public static enum SessionType { + /** + * Session was created via the System + */ + System, + + /** + * Session belongs to a specific user which was authenticated somehow + */ + User + } + + /** + * Return if the {@link MailboxSession} is of type {@link SessionType#User} or {@link SessionType#System} + * + * @return type + */ + SessionType getType(); + + /** + * Gets the session ID. + * + * @return session id + */ + long getSessionId(); + + /** + * Is this session open? + * + * @return true if the session is open, false otherwise + */ + boolean isOpen(); + + /** + * Closes this session. + */ + void close(); + + /** + * Gets the logger for this session context. + * + * @return not null + */ + Logger getLog(); + + /** + * Gets the user executing this session. + * + * @return not null + */ + User getUser(); + + /** + * A mailbox user. Useful for specialist mailbox implementation. + */ + public interface User { + /** + * Gets the name of the user. + * + * @return not null + */ + String getUserName(); + + /** + * Return the Password for the logged in user + * + * @return password + */ + String getPassword(); + + /** + * Gets acceptable localisation for this user in preference order.
+ * When localising a phrase, each Locale should be tried in + * order until an appropriate translation is obtained. + * + * @return not null, when empty the default local should be used + */ + List getLocalePreferences(); + } + + /** + * Gets the personal namespace for the current session.
+ * Note that though servers may offer multiple personal namespaces, support + * is not offered through this API. This decision may be revised if + * reasonable use cases emerge. + * + * @return Personal Namespace, not null + */ + String getPersonalSpace(); + + /** + * Gets the other users namespace for the current session.
+ * Note that though servers may offer multiple other users namespaces, + * support is not offered through this API. This decision may be revised if + * reasonable use cases emerge. + * + * @return Other Users Namespace or null when there is non available + */ + String getOtherUsersSpace(); + + /** + * Iterates the Shared Namespaces available for the current + * session. + * + * @return not null though possibly empty + */ + Collection getSharedSpaces(); + + /** + * Return the stored attributes for this {@link MailboxSession}. + * + * @return attributes + */ + Map getAttributes(); + + /** + * Return server side, folder path separator + * + * @return path separator + */ + char getPathDelimiter(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSessionIdGenerator.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSessionIdGenerator.java new file mode 100644 index 0000000..a87ea77 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxSessionIdGenerator.java @@ -0,0 +1,39 @@ +/**************************************************************** + * 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; + + +/** + * Generator for id's which should used for new {@link MailboxSession} instances + * + * + */ +public interface MailboxSessionIdGenerator { + + /** + * Return the next id to use for a {@link MailboxSession}. The id must be unique + * while the server is running and can be any long except {@link MailboxSession#SYSTEM_SESSION_ID}. + * + * The returned ids can be in any specific order. + * + * @return id + */ + long nextId(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java new file mode 100644 index 0000000..28ad722 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java @@ -0,0 +1,382 @@ +/**************************************************************** + * 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.InputStream; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.UnsupportedCriteriaException; +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.EditMode; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResultIterator; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; + +/** + * Interface which represent a Mailbox + * + * A {@link MessageManager} should be valid for the whole {@link MailboxSession} + */ +public interface MessageManager { + + /** + * Return the count + * + * @param mailboxSession + * @return count + * @throws MailboxException + * @deprecated use + * {@link #getMetaData(boolean, MailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup)} + */ + @Deprecated + long getMessageCount(MailboxSession mailboxSession) throws MailboxException; + + /** + * Return if the Mailbox is writable + * + * @param session + * @return writable + * @throws MailboxException + * @deprecated use + * {@link #getMetaData(boolean, MailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup)} + */ + @Deprecated + boolean isWriteable(MailboxSession session) throws MailboxException; + + /** + * Return true if {@link MessageResult#getModSeq()} is stored in a permanent + * way. + * + * @param session + * @return modSeqPermanent + * @deprecated use + * {@link #getMetaData(boolean, MailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup)} + */ + boolean isModSeqPermanent(MailboxSession session); + + /** + * Searches for messages matching the given query. The result must be + * ordered + * + * @param mailboxSession + * not null + * @return uid iterator + * @throws UnsupportedCriteriaException + * when any of the search parameters are not supported by this + * mailbox + * @throws MailboxException + * when search fails for other reasons + */ + Iterator search(SearchQuery searchQuery, MailboxSession mailboxSession) throws MailboxException; + + /** + * Expunges messages in the given range from this mailbox. + * + * @param set + * not null + * @param mailboxSession + * not null + * @return uid iterator + * @throws MailboxException + * if anything went wrong + */ + Iterator expunge(MessageRange set, MailboxSession mailboxSession) throws MailboxException; + + /** + * Sets flags on messages within the given range. The new flags are returned + * for each message altered. + * + * @param flags + * Flags to be set + * @param value + * true = set, false = unset + * @param replace + * replace all Flags with this flags, value has to be true + * @param set + * the range of messages + * @param mailboxSession + * not null + * @return new flags indexed by UID + * @throws MailboxException + */ + Map setFlags(Flags flags, boolean value, boolean replace, MessageRange set, MailboxSession mailboxSession) throws MailboxException; + + /** + * Appends a message to this mailbox. This method must return a higher UID + * as the last call in every case which also needs to be unique for the + * lifetime of the mailbox. + * + * + * @param internalDate + * the time of addition to be set, not null + * @param mailboxSession + * not null + * @param isRecent + * true when the message should be marked recent, false otherwise + * @param flags + * optionally set these flags on created message, or null when no + * additional flags should be set + * @return uid for the newly added message + * @throws MailboxException + * when message cannot be appended + */ + long appendMessage(InputStream msgIn, Date internalDate, MailboxSession mailboxSession, boolean isRecent, Flags flags) throws MailboxException; + + /** + * Gets messages in the given range. The messages may get fetched under + * the-hood in batches so the caller should check if + * {@link MessageResultIterator#getException()} returns null + * after {@link MessageResultIterator#hasNext()} returns false. + * + * + * @param set + * @param fetchGroup + * data to fetch + * @param mailboxSession + * not null + * @return MessageResult with the fields defined by FetchGroup + * @throws MailboxException + */ + MessageResultIterator getMessages(MessageRange set, FetchGroup fetchGroup, MailboxSession mailboxSession) throws MailboxException; + + /** + * Tells whether the given {@link MailboxSession}'s user has the given + * {@link MailboxACLRight} for this {@link MessageManager}'s mailbox. + * + * @param right + * @param session + * @return true if the given {@link MailboxSession}'s user has the given + * {@link MailboxACLRight} for this {@link MessageManager}'s + * mailbox; false otherwise. + * @throws MailboxException + */ + public boolean hasRight(MailboxACLRight right, MailboxSession session) throws MailboxException; + + /** + * Returns the rights applicable to the user who has sent the current + * request. + * + * @param session + * @return the rights applicable to the user who has sent the request, + * returns {@link SimpleMailboxACL#NO_RIGHTS} if + * {@code session.getUser()} is null. + * @throws UnsupportedRightException + */ + public abstract MailboxACLRights myRights(MailboxSession session) throws MailboxException; + + /** + * Computes a result suitable for the LISTRIGHTS IMAP command. The result is + * computed for this mailbox and the given {@code identifier}. + * + * From RFC 4314 section 3.7: + * The first element of the resulting array contains the (possibly empty) + * set of rights the identifier will always be granted in the mailbox. + * Following this are zero or more right sets the identifier can be granted + * in the mailbox. Rights mentioned in the same set are tied together. The + * server MUST either grant all tied rights to the identifier in the mailbox + * or grant none. + * + * The same right MUST NOT be listed more than once in the LISTRIGHTS + * command. + * + * @param identifier + * the identifier from the LISTRIGHTS command. + * @param session + * @return + * @throws UnsupportedRightException + */ + public MailboxACLRights[] listRigths(MailboxACLEntryKey identifier, MailboxSession session) throws UnsupportedRightException; + + /** + * TODO setRights. + * + * @param identifier + * @param editMode + * @param mailboxAclRights + * @throws UnsupportedRightException + */ + void setRights(MailboxACLEntryKey identifier, EditMode editMode, MailboxACLRights mailboxAclRights) throws UnsupportedRightException; + + /** + * Gets current meta data for the mailbox.
+ * Consolidates common calls together to allow improved performance.
+ * The meta-data returned should be immutable and represent the current + * state of the mailbox. + * + * @param resetRecent + * true when recent flags should be reset, false otherwise + * @param mailboxSession + * context, not null + * @param fetchGroup + * describes which optional data should be returned + * @return meta data, not null + * @throws MailboxException + */ + MetaData getMetaData(boolean resetRecent, MailboxSession mailboxSession, MessageManager.MetaData.FetchGroup fetchGroup) throws MailboxException; + + /** + * Meta data about the current state of the mailbox. + */ + public interface MetaData { + + /** + * Describes the optional data types which will get set in the + * {@link MetaData}. + * + * These are always set: - HIGHESTMODSEQ - PERMANENTFLAGS - UIDNEXT - + * UIDVALIDITY - MODSEQPERMANET - WRITABLE + */ + public enum FetchGroup { + + /** + * Only include the message and recent count + */ + NO_UNSEEN, + + /** + * Only include the unseen message and recent count + */ + UNSEEN_COUNT, + + /** + * Only include the first unseen and the recent count + */ + FIRST_UNSEEN, + + /** + * Only return the "always set" metadata as documented above + */ + NO_COUNT + }; + + /** + * Gets the UIDs of recent messages if requested or an empty + * {@link List} otherwise. + * + * @return the uids flagged RECENT in this mailbox, + */ + List getRecent(); + + /** + * Gets the number of recent messages. + * + * @return the number of messages flagged RECENT in this mailbox + */ + long countRecent(); + + /** + * Gets the flags which can be stored by this mailbox. + * + * @return Flags that can be stored + */ + Flags getPermanentFlags(); + + /** + * Gets the UIDVALIDITY. + * + * @return UIDVALIDITY + */ + long getUidValidity(); + + /** + * Gets the next UID predicted. The returned UID is not guaranteed to be + * the one that is assigned to the next message. Its only guaranteed + * that it will be at least equals or bigger then the value + * + * @return the uid that will be assigned to the next appended message + */ + long getUidNext(); + + /** + * Return the highest mod-sequence for the mailbox. If this value has + * changed till the last check you can be sure that some changes where + * happen on the mailbox + * + * @return higestModSeq + */ + long getHighestModSeq(); + + /** + * Gets the number of messages that this mailbox contains. This is an + * optional property.
+ * + * @return number of messages contained or -1 when this optional data + * has not be requested + * + */ + long getMessageCount(); + + /** + * Gets the number of unseen messages contained in this mailbox. This is + * an optional property.
+ * + * @return number of unseen messages contained or zero when this + * optional data has not been requested + * @see FetchGroup#UNSEEN_COUNT + */ + long getUnseenCount(); + + /** + * Gets the UID of the first unseen message. This is an optional + * property.
+ * + * @return uid of the first unseen message, or null when there are no + * unseen messages + * @see FetchGroup#FIRST_UNSEEN + */ + Long getFirstUnseen(); + + /** + * Is this mailbox writable? + * + * @return true if read-write, false if read only + */ + boolean isWriteable(); + + /** + * Return true if the mailbox does store the mod-sequences in a + * permanent way + * + * @return permanent + */ + boolean isModSeqPermanent(); + + /** + * Returns the ACL concerning this mailbox. + * + * @return acl + */ + MailboxACL getACL(); + + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/QuotaManager.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/QuotaManager.java new file mode 100644 index 0000000..6408dbd --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/QuotaManager.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; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Quota; + + +/** + * Allows to get quotas for {@link MailboxSession} which are bound to a user. + * + */ +public interface QuotaManager { + + /** + * Return the message count {@link Quota} for the given {@link MailboxSession} (which in fact is + * bound to a user) + * + * @param session + * @return quota + * @throws MailboxException + */ + public Quota getMessageQuota(MailboxSession session) throws MailboxException; + + + /** + * Return the message storage {@link Quota} for the given {@link MailboxSession} (which in fact is + * bound to a user) + * + * @param session + * @return quota + * @throws MailboxException + */ + public Quota getStorageQuota(MailboxSession session) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/RequestAware.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/RequestAware.java new file mode 100644 index 0000000..3072331 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/RequestAware.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; + +/** + * Implementations of this interface are aware of processing requests + */ +public interface RequestAware { + + /** + * Start the processing of a request for the given MailboxSession. If the + * user is not logged in already then the MailboxSession will be null + * + * @param session + */ + public void startProcessingRequest(MailboxSession session); + + /** + * End the processing of a request for the given MailboxSession. If the user + * is not logged in already then the MailboxSession will be null + * + * @param session + */ + public void endProcessingRequest(MailboxSession session); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/StandardMailboxMetaDataComparator.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/StandardMailboxMetaDataComparator.java new file mode 100644 index 0000000..a876c26 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/StandardMailboxMetaDataComparator.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 java.io.Serializable; +import java.util.Comparator; + +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxMetaData; + +/** + * Orders by name with INBOX first. + */ +public class StandardMailboxMetaDataComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Static comparison. + * + * @param one + * possibly null + * @param two + * possibly null + * @return {@link Comparator#compare(Object, Object)} + */ + public static int order(MailboxMetaData one, MailboxMetaData two) { + final String nameTwo = two.getPath().getName(); + final int result; + final String nameOne = one.getPath().getName(); + if (MailboxConstants.INBOX.equals(nameOne)) { + result = MailboxConstants.INBOX.equals(nameTwo) ? 0 : -1; + } else if (MailboxConstants.INBOX.equals(nameTwo)) { + result = 1; + } else if (nameOne == null) { + result = nameTwo == null ? 0 : 1; + } else if (nameTwo == null) { + result = -1; + } else { + result = nameOne.compareTo(nameTwo); + } + return result; + } + + /** + * @see Comparator#compare(Object, Object) + */ + public int compare(MailboxMetaData one, MailboxMetaData two) { + return order(one, two); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/SubscriptionManager.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/SubscriptionManager.java new file mode 100644 index 0000000..ea66b48 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/SubscriptionManager.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 java.util.Collection; + +import org.apache.james.mailbox.exception.SubscriptionException; + +/** + * Subscribes mailboxes to users. This is only needed to implement if the Mailbox should be usable via + * IMAP. For POP3 only you don't need this at all. + * + */ +public interface SubscriptionManager extends RequestAware { + + /** + * Subscribes the user in the session to the given mailbox. + * + * @param session + * not null + * @param mailbox + * not null + * @throws SubscriptionException + * when subscription fails + */ + public void subscribe(MailboxSession session, String mailbox) throws SubscriptionException; + + /** + * Finds all subscriptions for the user in the session. + * + * @param session + * not null + * @return not null + * @throws SubscriptionException + * when subscriptions cannot be read + */ + public Collection subscriptions(MailboxSession session) throws SubscriptionException; + + /** + * Unsubscribes the user in the session from the given mailbox. + * + * @param session + * not null + * @param mailbox + * not null + * @throws SubscriptionException + * when subscriptions cannot be read + */ + public void unsubscribe(MailboxSession session, String mailbox) throws SubscriptionException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/all-wcprops new file mode 100644 index 0000000..4b36e07 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/acl +END +SimpleGroupMembershipResolver.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/acl/SimpleGroupMembershipResolver.java +END +UnionMailboxACLResolver.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java +END +GroupMembershipResolver.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/acl/GroupMembershipResolver.java +END +MailboxACLResolver.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/entries new file mode 100644 index 0000000..8d93b55 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/entries @@ -0,0 +1,164 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/acl +http://svn.apache.org/repos/asf + + + +2012-02-21T21:05:02.067525Z +1292019 +ppalaga + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SimpleGroupMembershipResolver.java +file + + + + +2013-09-02T02:54:40.000000Z +b44ac189eb5c41157ee5385e2f95924e +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +2508 + +UnionMailboxACLResolver.java +file + + + + +2013-09-02T02:54:40.000000Z +ed6f457a1bf983e54befa85758cc0e15 +2012-02-21T21:05:02.067525Z +1292019 +ppalaga +has-props + + + + + + + + + + + + + + + + + + + + +21192 + +GroupMembershipResolver.java +file + + + + +2013-09-02T02:54:40.000000Z +263bd0d4db90f8f7cf062652c4cf8add +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1523 + +MailboxACLResolver.java +file + + + + +2013-09-02T02:54:40.000000Z +6480d0b6110328544dea8916b5606fbe +2012-02-21T21:05:02.067525Z +1292019 +ppalaga +has-props + + + + + + + + + + + + + + + + + + + + +8685 + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/MailboxACLResolver.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/MailboxACLResolver.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/MailboxACLResolver.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/SimpleGroupMembershipResolver.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/SimpleGroupMembershipResolver.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/SimpleGroupMembershipResolver.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolver.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolver.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolver.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/GroupMembershipResolver.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/GroupMembershipResolver.java.svn-base new file mode 100644 index 0000000..6896a93 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/GroupMembershipResolver.java.svn-base @@ -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.acl; + +/** + * An interface for querying group memberships. + */ +public interface GroupMembershipResolver { + + /** + * Tests if the given user is a member of the given group. + * + * @param user + * @param group + * @return + */ + boolean isMember(String user, String group); + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/MailboxACLResolver.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/MailboxACLResolver.java.svn-base new file mode 100644 index 0000000..8ce3bf4 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/MailboxACLResolver.java.svn-base @@ -0,0 +1,178 @@ +/* + * 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.acl; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; + +/** + * Implements the interpretation of ACLs. + * + * From RFC4314: It is possible for multiple identifiers in an access + * control list to apply to a given user. For example, an ACL may include rights + * to be granted to the identifier matching the user, one or more + * implementation-defined identifiers matching groups that include the user, + * and/or the identifier "anyone". How these rights are combined to determine + * the users access is implementation defined. An implementation may choose, for + * example, to use the union of the rights granted to the applicable + * identifiers. An implementation may instead choose, for example, to use only + * those rights granted to the most specific identifier present in the ACL. A + * client can determine the set of rights granted to the logged-in user for a + * given mailbox name by using the MYRIGHTS command. + * + */ +public interface MailboxACLResolver { + + /** + * Applies global ACL to the given resourceACL. From RFC 4314: + * An implementation [...] MAY force rights to always or never be granted to + * particular identifiers. + * + * @param resourceACL + * @param resourceOwnerIsGroup + * @return + * @throws UnsupportedRightException + */ + public MailboxACL applyGlobalACL(MailboxACL resourceACL, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + + /** + * Tells whether the given user has the given right granted on the basis of + * the given resourceACL. Global ACL (if there is any) should be applied + * within this method. + * + * @param requestUser + * the user for whom the given right is tested, possibly + * null when there is no authenticated user in the + * given context. + * @param groupMembershipResolver + * this resolver is used when checking whether any group rights + * contained in resourceACL are applicable for the requestUser. + * @param right + * the right which will be proven to apply for the given + * requestUser. + * @param resourceACL + * the ACL defining the access right for the resource in + * question. + * @param resourceOwner + * this user name is used as a replacement for the "owner" place + * holder in the resourceACL. + * @param resourceOwnerIsGroup + * true if the resourceOwner is a group of users, false + * otherwise. + * @return true if the given user has the given right for the given + * resource; false otherwise. + * @throws UnsupportedRightException + */ + boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + + /** + * Maps the given {@code mailboxACLRights} to READ-WRITE and READ-ONLY + * response codes. + * + * From RFC 4314 section 5.2: + * + * The server SHOULD include a READ-WRITE response code in the tagged OK + * response if at least one of the "i", "e", or "shared flag rights"(***) is + * granted to the current user. + * + * The server MUST include a READ-ONLY response code in the tagged OK + * response to a SELECT command if none of the following rights is granted + * to the current user: "i", "e", and "shared flag rights"(***). + * + * @param mailboxACLRights + * the rights applicable to the user and resource in question. + * This method supposes that any global ACLs were already applied + * to the {@code mailboxACLRights} parameter before this method + * is called. + * @param sharedFlags + * From RFC 4314 section 5.2: If the ACL server implements some + * flags as shared for a mailbox (i.e., the ACL for the mailbox + * MAY be set up so that changes to those flags are visible to + * another user), let’s call the set of rights associated with + * these flags (as described in Section 4) for that mailbox + * collectively as "shared flag rights". Note that the + * "shared flag rights" set MAY be different for different + * mailboxes. + * + * If the server doesn’t support "shared multiuser write access" + * to a mailbox or doesn’t implement shared flags on the mailbox, + * "shared flag rights" for the mailbox is defined to be the + * empty set. + * + * @return + * @throws UnsupportedRightException + */ + public abstract boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException; + + /** + * Computes a result suitable for the LISTRIGHTS IMAP command. The result is + * computed regardless of mailbox. Therefore it should be viewed as a + * general default which may be further customised depending on the given + * mailbox. + * + * @param key + * the identifier from the LISTRIGHTS command + * @param groupMembershipResolver + * @param resourceOwner + * the owner of the mailbox named in the LISTRIGHTS command. User + * name or group name. + * @param resourceOwnerIsGroup + * true if the {@code resourceOwner} is a group of users, false + * otherwise. + * @return an array of {@link MailboxACLRights}. The first element is the + * set of implicit (global) rights which does not need to be set + * explicitly for the given identifier. Further elements are groups + * of rights which can be set for the given identifier and resource. + * @throws UnsupportedRightException + */ + public MailboxACLRights[] listRights(final MailboxACLEntryKey key, final GroupMembershipResolver groupMembershipResolver, final String resourceOwner, final boolean resourceOwnerIsGroup) throws UnsupportedRightException; + + /** + * Computes the rights which apply to the given user and resource. Global + * ACL (if there is any) should be applied within this method. + * + * @param requestUser + * the user for whom the rights are computed, possibly + * null when there is no authenticated user in the + * given context. + * @param groupMembershipResolver + * this resolver is used when checking whether any group rights + * contained in resourceACL are applicable for the requestUser. + * @param resourceACL + * the ACL defining the access right for the resource in + * question. + * @param resourceOwner + * this user name is used as a replacement for the "owner" place + * holder in the resourceACL. + * @param resourceOwnerIsGroup + * true if the resourceOwner is a group of users, false + * otherwise. + * @return the rights applicable for the given user and resource. + * @throws UnsupportedRightException + */ + public abstract MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/SimpleGroupMembershipResolver.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/SimpleGroupMembershipResolver.java.svn-base new file mode 100644 index 0000000..51fe11c --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/SimpleGroupMembershipResolver.java.svn-base @@ -0,0 +1,81 @@ +/* + * 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.acl; + +import java.util.HashSet; +import java.util.Set; + + +/** + * In memory {@link GroupMembershipResolver} implementation. There is no + * persistence. You will get only what you add. + * + */ +public class SimpleGroupMembershipResolver implements GroupMembershipResolver { + + private static class Membership { + private final String group; + private final int hash; + private final String user; + + public Membership(String user, String group) { + super(); + this.group = group; + this.user = user; + + final int PRIME = 31; + this.hash = PRIME * this.group.hashCode() + this.user.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Membership) { + Membership other = (Membership) o; + return this.group == other.group || (this.group != null && this.group.equals(other.group)) && this.user == other.user || (this.user != null && this.user.equals(other.user)); + + } + return false; + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public String toString() { + return group + ": " + user; + } + + } + + private Set memberships = new HashSet(32); + + public void addMembership(String group, String user) { + memberships.add(new Membership(user, group)); + } + + @Override + public boolean isMember(String user, String group) { + return memberships.contains(new Membership(user, group)); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolver.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolver.java.svn-base new file mode 100644 index 0000000..df25db4 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolver.java.svn-base @@ -0,0 +1,442 @@ +/* + * 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.acl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.MailboxACL.NameType; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; + +import com.sun.mail.mbox.Mailbox; + +/** + * An implementation which works with the union of the rights granted to the + * applicable identifiers. Inspired by RFC 4314 Section 2. + * + * In + * {@link UnionMailboxACLResolver#resolveRights(String, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver, MailboxACL, String, boolean)} + * all applicable negative and non-negative rights are union-ed separately and + * the result is computed afterwards with + * nonNegativeUnion.except(negativeUnion). + * + * Allows for setting distinct global ACL for users' mailboxes on one hand and + * group (a.k.a shared) mailboxes on the other hand. E.g. the zero parameter + * constructor uses full rights for user mailboxes and + * full-except-administration rights for group mailboxes. + * + */ +public class UnionMailboxACLResolver implements MailboxACLResolver { + public static final MailboxACL DEFAULT_GLOBAL_GROUP_ACL = SimpleMailboxACL.OWNER_FULL_EXCEPT_ADMINISTRATION_ACL; + + /** + * Nothing else than full rights for the owner. + */ + public static final MailboxACL DEFAULT_GLOBAL_USER_ACL = SimpleMailboxACL.OWNER_FULL_ACL; + + private static final int POSITIVE_INDEX = 0; + private static final int NEGATIVE_INDEX = 1; + + private final MailboxACL groupGlobalACL; + /** + * Stores global ACL which is merged with ACL of every mailbox when + * computing + * {@link #rightsOf(String, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver, Mailbox)} + * and + * {@link #hasRight(String, Mailbox, MailboxACLRight, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver)} + * . + */ + private final MailboxACL userGlobalACL; + + /** + * Creates a new instance of UnionMailboxACLResolver with + * {@link #DEFAULT_GLOBAL_USER_ACL} as {@link #userGlobalACL} and + * {@link #DEFAULT_GLOBAL_USER_ACL} as {@link #groupGlobalACL}. + */ + public UnionMailboxACLResolver() { + super(); + this.userGlobalACL = DEFAULT_GLOBAL_USER_ACL; + this.groupGlobalACL = DEFAULT_GLOBAL_GROUP_ACL; + } + + /** + * Creates a new instance of UnionMailboxACLResolver with the given + * globalACL. + * + * @param groupGlobalACL + * + * @param globalACL + * see {@link #userGlobalACL}, cannot be null. + * @throws NullPointerException + * when globalACL is null. + */ + public UnionMailboxACLResolver(MailboxACL userGlobalACL, MailboxACL groupGlobalACL) { + super(); + if (userGlobalACL == null) { + throw new NullPointerException("Missing userGlobalACL."); + } + if (groupGlobalACL == null) { + throw new NullPointerException("Missing groupGlobalACL."); + } + this.userGlobalACL = userGlobalACL; + this.groupGlobalACL = groupGlobalACL; + } + + /** + * Tells whether the given {@code aclKey} {@link MailboxACLEntryKey} is + * applicable for the given {@code queryKey}. + * + * There are two use cases for which this method was designed and tested: + * + * (1) Calls from + * {@link #hasRight(String, GroupMembershipResolver, MailboxACLRight, MailboxACL, String, boolean)} + * and + * {@link #resolveRights(String, GroupMembershipResolver, MailboxACL, String, boolean)} + * in which the {@code queryKey} is a {@link NameType#user}. + * + * (2) Calls from + * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)} + * where {@code queryKey} can be anything including {@link NameType#user}, + * {@link NameType#group} and all {@link NameType#special} identifiers. + * + * Clearly the set of cases which this method has to handle in (1) is a + * proper subset of the cases handled in (2). See the javadoc on + * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)} + * for more details. + * + * @param aclKey + * @param queryKey + * @param groupMembershipResolver + * @param resourceOwner + * @param resourceOwnerIsGroup + * @return + */ + protected static boolean applies(MailboxACLEntryKey aclKey, MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) { + final String aclKeyName = aclKey.getName(); + final NameType aclKeyNameType = aclKey.getNameType(); + if (MailboxACL.SpecialName.anybody.name().equals(aclKeyName)) { + /* this works also for unauthenticated users */ + return true; + } else if (queryKey != null) { + String queryUserOrGroupName = queryKey.getName(); + switch (queryKey.getNameType()) { + case user: + /* Authenticated users */ + switch (aclKeyNameType) { + case special: + if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + /* non null query user is viewed as authenticated */ + return true; + } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) { + return (!resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner)) || (resourceOwnerIsGroup && groupMembershipResolver.isMember(queryUserOrGroupName, resourceOwner)); + } else { + /* should not happen unless the parent if is changed */ + throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName); + } + case user: + return aclKeyName.equals(queryUserOrGroupName); + case group: + return groupMembershipResolver.isMember(queryUserOrGroupName, aclKeyName); + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + aclKeyNameType); + } + case group: + /* query is a group */ + switch (aclKeyNameType) { + case special: + if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + /* + * see the javadoc comment on listRights() + */ + return true; + } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) { + return resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner); + } else { + /* should not happen unless the parent if is changed */ + throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName); + } + case user: + /* query groups cannot match ACL users */ + return false; + case group: + return aclKeyName.equals(queryUserOrGroupName); + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + aclKeyNameType); + } + case special: + /* query is a special name */ + switch (aclKeyNameType) { + case special: + if (aclKeyName.equals(queryUserOrGroupName)) { + /* + * authenticated matches authenticated and owner matches + * owner + */ + return true; + } else if (MailboxACL.SpecialName.owner.name().equals(queryUserOrGroupName) && MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + /* + * query owner matches authenticated because owner will + * be resolved only if the user is authenticated + */ + return true; + } else { + return false; + } + case user: + case group: + /* query specials cannot match ACL users or groups */ + return false; + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + aclKeyNameType); + } + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + queryKey.getNameType()); + } + } else { + /* non-anybody ACL keys do not match non-authenticated queries */ + return false; + } + } + + /** + * @see org.apache.james.mailbox.MailboxACLResolver#applyGlobalACL(org.apache + * .james.mailbox.MailboxACL, boolean) + */ + @Override + public MailboxACL applyGlobalACL(MailboxACL resourceACL, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + return resourceOwnerIsGroup ? resourceACL.union(groupGlobalACL) : resourceACL.union(userGlobalACL); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxACLResolver#hasRight(java. + * lang.String, org.apache.james.mailbox.store.mail.MailboxACLResolver. + * GroupMembershipResolver, + * org.apache.james.mailbox.MailboxACL.MailboxACLRight, + * org.apache.james.mailbox.MailboxACL, java.lang.String) + */ + @Override + public boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false); + boolean result = false; + Map entries = resourceOwnerIsGroup ? groupGlobalACL.getEntries() : userGlobalACL.getEntries(); + if (entries != null) { + for (Iterator> it = entries.entrySet().iterator(); it.hasNext();) { + final Entry entry = it.next(); + final MailboxACLEntryKey key = entry.getKey(); + if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) { + if (key.isNegative()) { + return false; + } else { + result = true; + } + } + } + } + + if (resourceACL != null) { + entries = resourceACL.getEntries(); + if (entries != null) { + for (Iterator> it = entries.entrySet().iterator(); it.hasNext();) { + final Entry entry = it.next(); + final MailboxACLEntryKey key = entry.getKey(); + if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) { + if (key.isNegative()) { + return false; + } else { + result = true; + } + } + } + } + } + + return result; + } + + /** + * @see org.apache.james.mailbox.acl.MailboxACLResolver#isReadWrite(org.apache.james.mailbox.model.MailboxACL.MailboxACLRights, + * javax.mail.Flags) + */ + @Override + public boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException { + /* the two fast cases first */ + if (mailboxACLRights.contains(Rfc4314Rights.i_Insert_RIGHT) || mailboxACLRights.contains(Rfc4314Rights.e_PerformExpunge_RIGHT)) { + return true; + } + /* + * then go through shared flags. RFC 4314 section 4: + * + * Changing flags: STORE + * + * - the server MUST check if the user has "t" right + * + * - when the user modifies \Deleted flag "s" right + * + * - when the user modifies \Seen flag "w" right - for all other message + * flags. + */ + else if (sharedFlags != null) { + if (sharedFlags.contains(Flag.DELETED) && mailboxACLRights.contains(Rfc4314Rights.t_DeleteMessages_RIGHT)) { + return true; + } else if (sharedFlags.contains(Flag.SEEN) && mailboxACLRights.contains(Rfc4314Rights.s_WriteSeenFlag_RIGHT)) { + return true; + } else { + boolean hasWriteRight = mailboxACLRights.contains(Rfc4314Rights.w_Write_RIGHT); + return hasWriteRight && (sharedFlags.contains(Flag.ANSWERED) || sharedFlags.contains(Flag.DRAFT) || sharedFlags.contains(Flag.FLAGGED) || sharedFlags.contains(Flag.RECENT) || sharedFlags.contains(Flag.USER)); + } + } + return false; + } + + /** + * The key point of this implementation is that it resolves everything what + * can be resolved. Let us explain what it means in particular for the + * implicit (global) rights included in the result: + * + * (1) if {@code queryKey} is a user key, the rights included come from the + * following ACL entries: + *
    + *
  • the entry literally matching the given user name
  • + *
  • the entries of the groups of which the given user is a member
  • + *
  • if the given user is the owner of the given mailbox also the "owner" + * entry is included
  • + *
  • the "authenticated" entry
  • + *
  • the "anybody" entry
  • + *
+ * + * (2) if {@code queryKey} is a group key, the rights included come from the + * following ACL entries: + *
    + *
  • the entry literally matching the given group name
  • + *
  • if the given group is the owner of the given mailbox also the "owner" + * entry is included
  • + *
  • the "authenticated" entry (*)
  • + *
  • the "anybody" entry
  • + *
+ * + * (3) if {@code queryKey} is a special key, the rights included come from + * the following ACL entries: + *
    + *
  • the entry literally matching the given special name
  • + *
  • the "authenticated" entry if the {@code queryKey} is the "owner" + * query key (*)
  • + *
  • the "anybody" entry
  • + *
+ * + * (*) This is the most questionable case: should "authenticated" ACL + * entries hold for group name queries? We say yes. Firstly, listing + * implicit rights for, say "group1", should inform which rights do not need + * to be set explicitly for the members of "group1". And secondly the group + * rights are actually queried and applied only for authenticated users. To + * put it in other words, the hasRight(user, right, ...) call can be + * performed only either with user == null (only "anybody" rights will + * apply) or with a user name which is there only after the user was + * authenticated. + * + * @see org.apache.james.mailbox.acl.MailboxACLResolver#listRightsDefault(boolean) + */ + @Override + public MailboxACLRights[] listRights(final MailboxACLEntryKey queryKey, final GroupMembershipResolver groupMembershipResolver, final String resourceOwner, final boolean resourceOwnerIsGroup) throws UnsupportedRightException { + MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS }; + + MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL; + resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); + + if (queryKey.isNegative()) { + return toListRightsArray(positiveNegativePair[NEGATIVE_INDEX]); + } else { + return toListRightsArray(positiveNegativePair[POSITIVE_INDEX].except(positiveNegativePair[NEGATIVE_INDEX])); + } + } + + private static MailboxACLRights[] toListRightsArray(MailboxACLRights implicitRights) throws UnsupportedRightException { + List result = new ArrayList(Rfc4314Rights.FIELD_COUNT); + result.add(implicitRights); + for (MailboxACLRight right : SimpleMailboxACL.FULL_RIGHTS) { + if (!implicitRights.contains(right)) { + result.add(new Rfc4314Rights(right)); + } + } + return result.toArray(new MailboxACLRights[result.size()]); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxACLResolver#rightsOf(java. + * lang.String, org.apache.james.mailbox.store.mail.MailboxACLResolver. + * GroupMembershipResolver, org.apache.james.mailbox.MailboxACL, + * java.lang.String) + */ + @Override + public MailboxACL.MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS }; + final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false); + MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL; + resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); + + if (resourceACL != null) { + resolveRights(queryKey, groupMembershipResolver, resourceACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); + } + + return positiveNegativePair[POSITIVE_INDEX].except(positiveNegativePair[NEGATIVE_INDEX]); + } + + /** + * What needs to be done for both global ACL and the given mailboxe's ACL. + * + * @param requestUser + * @param groupMembershipResolver + * @param entries + * @param resourceOwner + * @param resourceOwnerIsGroup + * @param positiveNegativePair + * @throws UnsupportedRightException + */ + private void resolveRights(MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, final Map entries, String resourceOwner, boolean resourceOwnerIsGroup, MailboxACL.MailboxACLRights[] positiveNegativePair) + throws UnsupportedRightException { + if (entries != null) { + for (Iterator> it = entries.entrySet().iterator(); it.hasNext();) { + final Entry entry = it.next(); + final MailboxACLEntryKey key = entry.getKey(); + if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup)) { + if (key.isNegative()) { + positiveNegativePair[NEGATIVE_INDEX] = positiveNegativePair[NEGATIVE_INDEX].union(entry.getValue()); + } else { + positiveNegativePair[POSITIVE_INDEX] = positiveNegativePair[POSITIVE_INDEX].union(entry.getValue()); + } + } + } + } + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/GroupMembershipResolver.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/GroupMembershipResolver.java new file mode 100644 index 0000000..6896a93 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/GroupMembershipResolver.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.acl; + +/** + * An interface for querying group memberships. + */ +public interface GroupMembershipResolver { + + /** + * Tests if the given user is a member of the given group. + * + * @param user + * @param group + * @return + */ + boolean isMember(String user, String group); + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java new file mode 100644 index 0000000..8ce3bf4 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java @@ -0,0 +1,178 @@ +/* + * 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.acl; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; + +/** + * Implements the interpretation of ACLs. + * + * From RFC4314: It is possible for multiple identifiers in an access + * control list to apply to a given user. For example, an ACL may include rights + * to be granted to the identifier matching the user, one or more + * implementation-defined identifiers matching groups that include the user, + * and/or the identifier "anyone". How these rights are combined to determine + * the users access is implementation defined. An implementation may choose, for + * example, to use the union of the rights granted to the applicable + * identifiers. An implementation may instead choose, for example, to use only + * those rights granted to the most specific identifier present in the ACL. A + * client can determine the set of rights granted to the logged-in user for a + * given mailbox name by using the MYRIGHTS command. + * + */ +public interface MailboxACLResolver { + + /** + * Applies global ACL to the given resourceACL. From RFC 4314: + * An implementation [...] MAY force rights to always or never be granted to + * particular identifiers. + * + * @param resourceACL + * @param resourceOwnerIsGroup + * @return + * @throws UnsupportedRightException + */ + public MailboxACL applyGlobalACL(MailboxACL resourceACL, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + + /** + * Tells whether the given user has the given right granted on the basis of + * the given resourceACL. Global ACL (if there is any) should be applied + * within this method. + * + * @param requestUser + * the user for whom the given right is tested, possibly + * null when there is no authenticated user in the + * given context. + * @param groupMembershipResolver + * this resolver is used when checking whether any group rights + * contained in resourceACL are applicable for the requestUser. + * @param right + * the right which will be proven to apply for the given + * requestUser. + * @param resourceACL + * the ACL defining the access right for the resource in + * question. + * @param resourceOwner + * this user name is used as a replacement for the "owner" place + * holder in the resourceACL. + * @param resourceOwnerIsGroup + * true if the resourceOwner is a group of users, false + * otherwise. + * @return true if the given user has the given right for the given + * resource; false otherwise. + * @throws UnsupportedRightException + */ + boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + + /** + * Maps the given {@code mailboxACLRights} to READ-WRITE and READ-ONLY + * response codes. + * + * From RFC 4314 section 5.2: + * + * The server SHOULD include a READ-WRITE response code in the tagged OK + * response if at least one of the "i", "e", or "shared flag rights"(***) is + * granted to the current user. + * + * The server MUST include a READ-ONLY response code in the tagged OK + * response to a SELECT command if none of the following rights is granted + * to the current user: "i", "e", and "shared flag rights"(***). + * + * @param mailboxACLRights + * the rights applicable to the user and resource in question. + * This method supposes that any global ACLs were already applied + * to the {@code mailboxACLRights} parameter before this method + * is called. + * @param sharedFlags + * From RFC 4314 section 5.2: If the ACL server implements some + * flags as shared for a mailbox (i.e., the ACL for the mailbox + * MAY be set up so that changes to those flags are visible to + * another user), let’s call the set of rights associated with + * these flags (as described in Section 4) for that mailbox + * collectively as "shared flag rights". Note that the + * "shared flag rights" set MAY be different for different + * mailboxes. + * + * If the server doesn’t support "shared multiuser write access" + * to a mailbox or doesn’t implement shared flags on the mailbox, + * "shared flag rights" for the mailbox is defined to be the + * empty set. + * + * @return + * @throws UnsupportedRightException + */ + public abstract boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException; + + /** + * Computes a result suitable for the LISTRIGHTS IMAP command. The result is + * computed regardless of mailbox. Therefore it should be viewed as a + * general default which may be further customised depending on the given + * mailbox. + * + * @param key + * the identifier from the LISTRIGHTS command + * @param groupMembershipResolver + * @param resourceOwner + * the owner of the mailbox named in the LISTRIGHTS command. User + * name or group name. + * @param resourceOwnerIsGroup + * true if the {@code resourceOwner} is a group of users, false + * otherwise. + * @return an array of {@link MailboxACLRights}. The first element is the + * set of implicit (global) rights which does not need to be set + * explicitly for the given identifier. Further elements are groups + * of rights which can be set for the given identifier and resource. + * @throws UnsupportedRightException + */ + public MailboxACLRights[] listRights(final MailboxACLEntryKey key, final GroupMembershipResolver groupMembershipResolver, final String resourceOwner, final boolean resourceOwnerIsGroup) throws UnsupportedRightException; + + /** + * Computes the rights which apply to the given user and resource. Global + * ACL (if there is any) should be applied within this method. + * + * @param requestUser + * the user for whom the rights are computed, possibly + * null when there is no authenticated user in the + * given context. + * @param groupMembershipResolver + * this resolver is used when checking whether any group rights + * contained in resourceACL are applicable for the requestUser. + * @param resourceACL + * the ACL defining the access right for the resource in + * question. + * @param resourceOwner + * this user name is used as a replacement for the "owner" place + * holder in the resourceACL. + * @param resourceOwnerIsGroup + * true if the resourceOwner is a group of users, false + * otherwise. + * @return the rights applicable for the given user and resource. + * @throws UnsupportedRightException + */ + public abstract MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/SimpleGroupMembershipResolver.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/SimpleGroupMembershipResolver.java new file mode 100644 index 0000000..51fe11c --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/SimpleGroupMembershipResolver.java @@ -0,0 +1,81 @@ +/* + * 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.acl; + +import java.util.HashSet; +import java.util.Set; + + +/** + * In memory {@link GroupMembershipResolver} implementation. There is no + * persistence. You will get only what you add. + * + */ +public class SimpleGroupMembershipResolver implements GroupMembershipResolver { + + private static class Membership { + private final String group; + private final int hash; + private final String user; + + public Membership(String user, String group) { + super(); + this.group = group; + this.user = user; + + final int PRIME = 31; + this.hash = PRIME * this.group.hashCode() + this.user.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Membership) { + Membership other = (Membership) o; + return this.group == other.group || (this.group != null && this.group.equals(other.group)) && this.user == other.user || (this.user != null && this.user.equals(other.user)); + + } + return false; + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public String toString() { + return group + ": " + user; + } + + } + + private Set memberships = new HashSet(32); + + public void addMembership(String group, String user) { + memberships.add(new Membership(user, group)); + } + + @Override + public boolean isMember(String user, String group) { + return memberships.contains(new Membership(user, group)); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java new file mode 100644 index 0000000..df25db4 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java @@ -0,0 +1,442 @@ +/* + * 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.acl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.MailboxACL.NameType; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; + +import com.sun.mail.mbox.Mailbox; + +/** + * An implementation which works with the union of the rights granted to the + * applicable identifiers. Inspired by RFC 4314 Section 2. + * + * In + * {@link UnionMailboxACLResolver#resolveRights(String, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver, MailboxACL, String, boolean)} + * all applicable negative and non-negative rights are union-ed separately and + * the result is computed afterwards with + * nonNegativeUnion.except(negativeUnion). + * + * Allows for setting distinct global ACL for users' mailboxes on one hand and + * group (a.k.a shared) mailboxes on the other hand. E.g. the zero parameter + * constructor uses full rights for user mailboxes and + * full-except-administration rights for group mailboxes. + * + */ +public class UnionMailboxACLResolver implements MailboxACLResolver { + public static final MailboxACL DEFAULT_GLOBAL_GROUP_ACL = SimpleMailboxACL.OWNER_FULL_EXCEPT_ADMINISTRATION_ACL; + + /** + * Nothing else than full rights for the owner. + */ + public static final MailboxACL DEFAULT_GLOBAL_USER_ACL = SimpleMailboxACL.OWNER_FULL_ACL; + + private static final int POSITIVE_INDEX = 0; + private static final int NEGATIVE_INDEX = 1; + + private final MailboxACL groupGlobalACL; + /** + * Stores global ACL which is merged with ACL of every mailbox when + * computing + * {@link #rightsOf(String, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver, Mailbox)} + * and + * {@link #hasRight(String, Mailbox, MailboxACLRight, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver)} + * . + */ + private final MailboxACL userGlobalACL; + + /** + * Creates a new instance of UnionMailboxACLResolver with + * {@link #DEFAULT_GLOBAL_USER_ACL} as {@link #userGlobalACL} and + * {@link #DEFAULT_GLOBAL_USER_ACL} as {@link #groupGlobalACL}. + */ + public UnionMailboxACLResolver() { + super(); + this.userGlobalACL = DEFAULT_GLOBAL_USER_ACL; + this.groupGlobalACL = DEFAULT_GLOBAL_GROUP_ACL; + } + + /** + * Creates a new instance of UnionMailboxACLResolver with the given + * globalACL. + * + * @param groupGlobalACL + * + * @param globalACL + * see {@link #userGlobalACL}, cannot be null. + * @throws NullPointerException + * when globalACL is null. + */ + public UnionMailboxACLResolver(MailboxACL userGlobalACL, MailboxACL groupGlobalACL) { + super(); + if (userGlobalACL == null) { + throw new NullPointerException("Missing userGlobalACL."); + } + if (groupGlobalACL == null) { + throw new NullPointerException("Missing groupGlobalACL."); + } + this.userGlobalACL = userGlobalACL; + this.groupGlobalACL = groupGlobalACL; + } + + /** + * Tells whether the given {@code aclKey} {@link MailboxACLEntryKey} is + * applicable for the given {@code queryKey}. + * + * There are two use cases for which this method was designed and tested: + * + * (1) Calls from + * {@link #hasRight(String, GroupMembershipResolver, MailboxACLRight, MailboxACL, String, boolean)} + * and + * {@link #resolveRights(String, GroupMembershipResolver, MailboxACL, String, boolean)} + * in which the {@code queryKey} is a {@link NameType#user}. + * + * (2) Calls from + * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)} + * where {@code queryKey} can be anything including {@link NameType#user}, + * {@link NameType#group} and all {@link NameType#special} identifiers. + * + * Clearly the set of cases which this method has to handle in (1) is a + * proper subset of the cases handled in (2). See the javadoc on + * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)} + * for more details. + * + * @param aclKey + * @param queryKey + * @param groupMembershipResolver + * @param resourceOwner + * @param resourceOwnerIsGroup + * @return + */ + protected static boolean applies(MailboxACLEntryKey aclKey, MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) { + final String aclKeyName = aclKey.getName(); + final NameType aclKeyNameType = aclKey.getNameType(); + if (MailboxACL.SpecialName.anybody.name().equals(aclKeyName)) { + /* this works also for unauthenticated users */ + return true; + } else if (queryKey != null) { + String queryUserOrGroupName = queryKey.getName(); + switch (queryKey.getNameType()) { + case user: + /* Authenticated users */ + switch (aclKeyNameType) { + case special: + if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + /* non null query user is viewed as authenticated */ + return true; + } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) { + return (!resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner)) || (resourceOwnerIsGroup && groupMembershipResolver.isMember(queryUserOrGroupName, resourceOwner)); + } else { + /* should not happen unless the parent if is changed */ + throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName); + } + case user: + return aclKeyName.equals(queryUserOrGroupName); + case group: + return groupMembershipResolver.isMember(queryUserOrGroupName, aclKeyName); + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + aclKeyNameType); + } + case group: + /* query is a group */ + switch (aclKeyNameType) { + case special: + if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + /* + * see the javadoc comment on listRights() + */ + return true; + } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) { + return resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner); + } else { + /* should not happen unless the parent if is changed */ + throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName); + } + case user: + /* query groups cannot match ACL users */ + return false; + case group: + return aclKeyName.equals(queryUserOrGroupName); + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + aclKeyNameType); + } + case special: + /* query is a special name */ + switch (aclKeyNameType) { + case special: + if (aclKeyName.equals(queryUserOrGroupName)) { + /* + * authenticated matches authenticated and owner matches + * owner + */ + return true; + } else if (MailboxACL.SpecialName.owner.name().equals(queryUserOrGroupName) && MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + /* + * query owner matches authenticated because owner will + * be resolved only if the user is authenticated + */ + return true; + } else { + return false; + } + case user: + case group: + /* query specials cannot match ACL users or groups */ + return false; + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + aclKeyNameType); + } + default: + throw new IllegalStateException("Unexpected " + NameType.class.getName() + "." + queryKey.getNameType()); + } + } else { + /* non-anybody ACL keys do not match non-authenticated queries */ + return false; + } + } + + /** + * @see org.apache.james.mailbox.MailboxACLResolver#applyGlobalACL(org.apache + * .james.mailbox.MailboxACL, boolean) + */ + @Override + public MailboxACL applyGlobalACL(MailboxACL resourceACL, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + return resourceOwnerIsGroup ? resourceACL.union(groupGlobalACL) : resourceACL.union(userGlobalACL); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxACLResolver#hasRight(java. + * lang.String, org.apache.james.mailbox.store.mail.MailboxACLResolver. + * GroupMembershipResolver, + * org.apache.james.mailbox.MailboxACL.MailboxACLRight, + * org.apache.james.mailbox.MailboxACL, java.lang.String) + */ + @Override + public boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false); + boolean result = false; + Map entries = resourceOwnerIsGroup ? groupGlobalACL.getEntries() : userGlobalACL.getEntries(); + if (entries != null) { + for (Iterator> it = entries.entrySet().iterator(); it.hasNext();) { + final Entry entry = it.next(); + final MailboxACLEntryKey key = entry.getKey(); + if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) { + if (key.isNegative()) { + return false; + } else { + result = true; + } + } + } + } + + if (resourceACL != null) { + entries = resourceACL.getEntries(); + if (entries != null) { + for (Iterator> it = entries.entrySet().iterator(); it.hasNext();) { + final Entry entry = it.next(); + final MailboxACLEntryKey key = entry.getKey(); + if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) { + if (key.isNegative()) { + return false; + } else { + result = true; + } + } + } + } + } + + return result; + } + + /** + * @see org.apache.james.mailbox.acl.MailboxACLResolver#isReadWrite(org.apache.james.mailbox.model.MailboxACL.MailboxACLRights, + * javax.mail.Flags) + */ + @Override + public boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException { + /* the two fast cases first */ + if (mailboxACLRights.contains(Rfc4314Rights.i_Insert_RIGHT) || mailboxACLRights.contains(Rfc4314Rights.e_PerformExpunge_RIGHT)) { + return true; + } + /* + * then go through shared flags. RFC 4314 section 4: + * + * Changing flags: STORE + * + * - the server MUST check if the user has "t" right + * + * - when the user modifies \Deleted flag "s" right + * + * - when the user modifies \Seen flag "w" right - for all other message + * flags. + */ + else if (sharedFlags != null) { + if (sharedFlags.contains(Flag.DELETED) && mailboxACLRights.contains(Rfc4314Rights.t_DeleteMessages_RIGHT)) { + return true; + } else if (sharedFlags.contains(Flag.SEEN) && mailboxACLRights.contains(Rfc4314Rights.s_WriteSeenFlag_RIGHT)) { + return true; + } else { + boolean hasWriteRight = mailboxACLRights.contains(Rfc4314Rights.w_Write_RIGHT); + return hasWriteRight && (sharedFlags.contains(Flag.ANSWERED) || sharedFlags.contains(Flag.DRAFT) || sharedFlags.contains(Flag.FLAGGED) || sharedFlags.contains(Flag.RECENT) || sharedFlags.contains(Flag.USER)); + } + } + return false; + } + + /** + * The key point of this implementation is that it resolves everything what + * can be resolved. Let us explain what it means in particular for the + * implicit (global) rights included in the result: + * + * (1) if {@code queryKey} is a user key, the rights included come from the + * following ACL entries: + *
    + *
  • the entry literally matching the given user name
  • + *
  • the entries of the groups of which the given user is a member
  • + *
  • if the given user is the owner of the given mailbox also the "owner" + * entry is included
  • + *
  • the "authenticated" entry
  • + *
  • the "anybody" entry
  • + *
+ * + * (2) if {@code queryKey} is a group key, the rights included come from the + * following ACL entries: + *
    + *
  • the entry literally matching the given group name
  • + *
  • if the given group is the owner of the given mailbox also the "owner" + * entry is included
  • + *
  • the "authenticated" entry (*)
  • + *
  • the "anybody" entry
  • + *
+ * + * (3) if {@code queryKey} is a special key, the rights included come from + * the following ACL entries: + *
    + *
  • the entry literally matching the given special name
  • + *
  • the "authenticated" entry if the {@code queryKey} is the "owner" + * query key (*)
  • + *
  • the "anybody" entry
  • + *
+ * + * (*) This is the most questionable case: should "authenticated" ACL + * entries hold for group name queries? We say yes. Firstly, listing + * implicit rights for, say "group1", should inform which rights do not need + * to be set explicitly for the members of "group1". And secondly the group + * rights are actually queried and applied only for authenticated users. To + * put it in other words, the hasRight(user, right, ...) call can be + * performed only either with user == null (only "anybody" rights will + * apply) or with a user name which is there only after the user was + * authenticated. + * + * @see org.apache.james.mailbox.acl.MailboxACLResolver#listRightsDefault(boolean) + */ + @Override + public MailboxACLRights[] listRights(final MailboxACLEntryKey queryKey, final GroupMembershipResolver groupMembershipResolver, final String resourceOwner, final boolean resourceOwnerIsGroup) throws UnsupportedRightException { + MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS }; + + MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL; + resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); + + if (queryKey.isNegative()) { + return toListRightsArray(positiveNegativePair[NEGATIVE_INDEX]); + } else { + return toListRightsArray(positiveNegativePair[POSITIVE_INDEX].except(positiveNegativePair[NEGATIVE_INDEX])); + } + } + + private static MailboxACLRights[] toListRightsArray(MailboxACLRights implicitRights) throws UnsupportedRightException { + List result = new ArrayList(Rfc4314Rights.FIELD_COUNT); + result.add(implicitRights); + for (MailboxACLRight right : SimpleMailboxACL.FULL_RIGHTS) { + if (!implicitRights.contains(right)) { + result.add(new Rfc4314Rights(right)); + } + } + return result.toArray(new MailboxACLRights[result.size()]); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxACLResolver#rightsOf(java. + * lang.String, org.apache.james.mailbox.store.mail.MailboxACLResolver. + * GroupMembershipResolver, org.apache.james.mailbox.MailboxACL, + * java.lang.String) + */ + @Override + public MailboxACL.MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS }; + final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false); + MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL; + resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); + + if (resourceACL != null) { + resolveRights(queryKey, groupMembershipResolver, resourceACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); + } + + return positiveNegativePair[POSITIVE_INDEX].except(positiveNegativePair[NEGATIVE_INDEX]); + } + + /** + * What needs to be done for both global ACL and the given mailboxe's ACL. + * + * @param requestUser + * @param groupMembershipResolver + * @param entries + * @param resourceOwner + * @param resourceOwnerIsGroup + * @param positiveNegativePair + * @throws UnsupportedRightException + */ + private void resolveRights(MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, final Map entries, String resourceOwner, boolean resourceOwnerIsGroup, MailboxACL.MailboxACLRights[] positiveNegativePair) + throws UnsupportedRightException { + if (entries != null) { + for (Iterator> it = entries.entrySet().iterator(); it.hasNext();) { + final Entry entry = it.next(); + final MailboxACLEntryKey key = entry.getKey(); + if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup)) { + if (key.isNegative()) { + positiveNegativePair[NEGATIVE_INDEX] = positiveNegativePair[NEGATIVE_INDEX].union(entry.getValue()); + } else { + positiveNegativePair[POSITIVE_INDEX] = positiveNegativePair[POSITIVE_INDEX].union(entry.getValue()); + } + } + } + } + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/all-wcprops new file mode 100644 index 0000000..43582e2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/all-wcprops @@ -0,0 +1,89 @@ +K 25 +svn:wc:ra_dav:version-url +V 100 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception +END +MessageRangeException.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MessageRangeException.java +END +MailboxSecurityException.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MailboxSecurityException.java +END +BadCredentialsException.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/BadCredentialsException.java +END +SubscriptionException.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/SubscriptionException.java +END +OverQuotaException.java +K 25 +svn:wc:ra_dav:version-url +V 124 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/OverQuotaException.java +END +UnsupportedRightException.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java +END +InsufficientRightsException.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/InsufficientRightsException.java +END +MailboxNotFoundException.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MailboxNotFoundException.java +END +UnsupportedOperationException.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedOperationException.java +END +MailboxException.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MailboxException.java +END +MailboxExistsException.java +K 25 +svn:wc:ra_dav:version-url +V 128 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MailboxExistsException.java +END +UnsupportedCriteriaException.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedCriteriaException.java +END +ReadOnlyException.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/ReadOnlyException.java +END +UnsupportedSearchException.java +K 25 +svn:wc:ra_dav:version-url +V 132 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedSearchException.java +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/entries new file mode 100644 index 0000000..7314853 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/entries @@ -0,0 +1,504 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MessageRangeException.java +file + + + + +2013-09-02T02:54:40.000000Z +f0c9ea5bb31bcb6db25f4dd419852c42 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1554 + +MailboxSecurityException.java +file + + + + +2013-09-02T02:54:40.000000Z +70321ac39e7e95afe3b11cc47780aa0e +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1355 + +BadCredentialsException.java +file + + + + +2013-09-02T02:54:40.000000Z +4b7e886773f91b38245101b6ec5c4ddf +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1521 + +SubscriptionException.java +file + + + + +2013-09-02T02:54:40.000000Z +517bf3db7a1cadd1a74f7e9930895ef0 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1584 + +OverQuotaException.java +file + + + + +2013-09-02T02:54:40.000000Z +2388eb21e2c5962805ecbad4a8d3f9d2 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1837 + +UnsupportedRightException.java +file + + + + +2013-09-02T02:54:40.000000Z +e885294bc958c67b8d081e0b0d6557ae +2012-02-21T21:05:02.067525Z +1292019 +ppalaga +has-props + + + + + + + + + + + + + + + + + + + + +1711 + +InsufficientRightsException.java +file + + + + +2013-09-02T02:54:40.000000Z +117cb044bfaf25180cec1468545f62fd +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1438 + +MailboxNotFoundException.java +file + + + + +2013-09-02T02:54:40.000000Z +74d5a243fbf1f7d19f1bcaf74d9967d5 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +2193 + +UnsupportedOperationException.java +file + + + + +2013-09-02T02:54:40.000000Z +e96da94593a7fe4c7cb8fe1eb1d5526c +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1553 + +MailboxException.java +file + + + + +2013-09-02T02:54:40.000000Z +2c12374bc0838c033084e4cd1bdf532b +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +1686 + +MailboxExistsException.java +file + + + + +2013-09-02T02:54:40.000000Z +257c98299f6683e7c50d12e854fc95c4 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1937 + +UnsupportedCriteriaException.java +file + + + + +2013-09-02T02:54:40.000000Z +e73cb98d5eb377f3cf7d9d657900e6dd +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1510 + +ReadOnlyException.java +file + + + + +2013-09-02T02:54:40.000000Z +e3d78d70a8b997800af020a7501eb0fd +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1809 + +UnsupportedSearchException.java +file + + + + +2013-09-02T02:54:40.000000Z +4ccdb45a6d22e168e46f5ffe33ac82cd +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1532 + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/BadCredentialsException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/BadCredentialsException.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/BadCredentialsException.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/InsufficientRightsException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/InsufficientRightsException.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/InsufficientRightsException.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxException.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxException.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxSecurityException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxSecurityException.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/MailboxSecurityException.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedOperationException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedOperationException.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedOperationException.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedRightException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedRightException.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/prop-base/UnsupportedRightException.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/BadCredentialsException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/BadCredentialsException.java.svn-base new file mode 100644 index 0000000..461ab84 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/BadCredentialsException.java.svn-base @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates that the credentials for this operation were not acceptable. + */ +public class BadCredentialsException extends MailboxException { + + private static final long serialVersionUID = -8055692887730696513L; + + public BadCredentialsException() { + super(); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/InsufficientRightsException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/InsufficientRightsException.java.svn-base new file mode 100644 index 0000000..ebb2429 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/InsufficientRightsException.java.svn-base @@ -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.exception; + + +/** + * Thrown when the rights granted to the given authenticated user do not suffice + * for the given action. + * + */ +public class InsufficientRightsException extends MailboxSecurityException { + + /** + * + */ + private static final long serialVersionUID = 7310501567640913596L; + + public InsufficientRightsException() { + super(); + } + + public InsufficientRightsException(String message) { + super(message); + } + + public InsufficientRightsException(String msg, Exception cause) { + super(msg, cause); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxException.java.svn-base new file mode 100644 index 0000000..fa857c0 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxException.java.svn-base @@ -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.exception; + +/** + * Mailbox services should throw this exception in case of unsuccessfull + * operation. + */ +public class MailboxException extends Exception { + + private static final long serialVersionUID = 4612761817238115904L; + + public MailboxException() { + super(); + } + + public MailboxException(final String message) { + super(message); + } + + public MailboxException(String msg, Exception cause) { + super(msg, cause); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxExistsException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxExistsException.java.svn-base new file mode 100644 index 0000000..d029324 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxExistsException.java.svn-base @@ -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.exception; + + +/** + * Indicates that the operation failed since the mailbox already exists. + */ +public class MailboxExistsException extends MailboxException { + + private static final long serialVersionUID = -486951759505030166L; + + private final String mailboxName; + + public MailboxExistsException(String mailboxName) { + super("Mailbox with name=" + mailboxName + " already exists."); + this.mailboxName = mailboxName; + } + + /** + * Gets the name of the mailbox which already exists. + * + * @return the mailboxName, not null + */ + public final String getMailboxName() { + return mailboxName; + } + + public String toString() { + return getMessage(); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxNotFoundException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxNotFoundException.java.svn-base new file mode 100644 index 0000000..8301d76 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxNotFoundException.java.svn-base @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.exception; + +import org.apache.james.mailbox.model.MailboxPath; + +/** + * Indicates that the failure is caused by a reference to a mailbox which does + * not exist. + */ +public class MailboxNotFoundException extends MailboxException { + + private static final long serialVersionUID = -8493370806722264915L; + + private final String mailboxName; + + /** + * @param mailboxName + * name of the mailbox, not null + */ + public MailboxNotFoundException(String mailboxName) { + this.mailboxName = mailboxName; + } + + /** + * @param mailboxPath + * name of the mailbox, not null + */ + public MailboxNotFoundException(MailboxPath mailboxPath) { + this.mailboxName = mailboxPath.toString(); + } + + /** + * Gets the name of the mailbox which cannot be found. + * + * @return name or null when only mailbox ID is known + */ + public final String getMailboxName() { + return mailboxName; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxSecurityException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxSecurityException.java.svn-base new file mode 100644 index 0000000..2923a08 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MailboxSecurityException.java.svn-base @@ -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.exception; + + +/** + * Parent for security related exceptions. + * + */ +public class MailboxSecurityException extends MailboxException { + + /** + * + */ + private static final long serialVersionUID = 4186633460326902649L; + + public MailboxSecurityException() { + super(); + } + + public MailboxSecurityException(String message) { + super(message); + } + + public MailboxSecurityException(String msg, Exception cause) { + super(msg, cause); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MessageRangeException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MessageRangeException.java.svn-base new file mode 100644 index 0000000..57baa6d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/MessageRangeException.java.svn-base @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Exception which should get thrown if an invalid message set was specified via + * an IMAP command + */ +public class MessageRangeException extends MailboxException { + + private static final long serialVersionUID = 5016914557908202117L; + + public MessageRangeException(String msg) { + super(msg); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/OverQuotaException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/OverQuotaException.java.svn-base new file mode 100644 index 0000000..56bdfc8 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/OverQuotaException.java.svn-base @@ -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.exception; + + +/** + * {@link MailboxException} which identicate that a user was over-quota + * + * + */ +public class OverQuotaException extends MailboxException{ + + /** + * + */ + private static final long serialVersionUID = 532673188582481689L; + + private long used; + private long max; + + public OverQuotaException(String msg, long max, long used) { + super(msg); + } + public OverQuotaException(long max, long used) { + this(null, max, used); + } + + public long getUsed() { + return used; + } + + public long getMax() { + return max; + } + + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/ReadOnlyException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/ReadOnlyException.java.svn-base new file mode 100644 index 0000000..0db3c1c --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/ReadOnlyException.java.svn-base @@ -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.exception; + +import org.apache.james.mailbox.model.MailboxPath; + +/** + * {@link MailboxException} which should get thrown if someone tries to modify a READ-ONLY Mailbox + * + * + */ +public class ReadOnlyException extends MailboxException{ + + + /** + * + */ + private static final long serialVersionUID = 5016914557908202113L; + + + + public ReadOnlyException(MailboxPath path, char delimiter) { + super(path.getFullName(delimiter)); + } + + + public ReadOnlyException(MailboxPath path, char delimiter, Exception e) { + super(path.getFullName(delimiter), e); + } +} + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/SubscriptionException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/SubscriptionException.java.svn-base new file mode 100644 index 0000000..b27f68a --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/SubscriptionException.java.svn-base @@ -0,0 +1,37 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates exception during subscription processing. + */ +public class SubscriptionException extends MailboxException { + + private static final long serialVersionUID = -4512372322774311468L; + + public SubscriptionException() { + super(); + } + + public SubscriptionException(Exception cause) { + super(null, cause); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedCriteriaException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedCriteriaException.java.svn-base new file mode 100644 index 0000000..4411d28 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedCriteriaException.java.svn-base @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates that a search criteria is not supported. + */ +public class UnsupportedCriteriaException extends MailboxException { + + private static final long serialVersionUID = 3791907285083231285L; + + public UnsupportedCriteriaException() { + super(); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedOperationException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedOperationException.java.svn-base new file mode 100644 index 0000000..f1baa63 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedOperationException.java.svn-base @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates that an operation required is not supported by this mailbox. + */ +public class UnsupportedOperationException extends MailboxException { + + private static final long serialVersionUID = 1943118588115772317L; + + public UnsupportedOperationException(String message) { + super(message); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedRightException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedRightException.java.svn-base new file mode 100644 index 0000000..eb99b42 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedRightException.java.svn-base @@ -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.exception; + +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; + +/** + * Thrown when the current system does not support the given right. + * + */ +public class UnsupportedRightException extends MailboxSecurityException { + + private static final char INVALID_RIGHT = 0; + private static final long serialVersionUID = 2959248897018370078L; + private char unsupportedRight = INVALID_RIGHT; + + public UnsupportedRightException() { + super(); + } + + public UnsupportedRightException(char right) { + super("Unsupported right flag '"+ right +"'."); + this.unsupportedRight = right; + } + + public UnsupportedRightException(MailboxACLRight unsupportedRight) { + this(unsupportedRight.getValue()); + } + + public char getUnsupportedRight() { + return unsupportedRight; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedSearchException.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedSearchException.java.svn-base new file mode 100644 index 0000000..261828e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/.svn/text-base/UnsupportedSearchException.java.svn-base @@ -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.exception; + + +/** + * Indicates that the requested search is not supported by this implementation. + */ +public class UnsupportedSearchException extends MailboxException { + + private static final long serialVersionUID = -7442949630563672557L; + + public UnsupportedSearchException() { + super(); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/BadCredentialsException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/BadCredentialsException.java new file mode 100644 index 0000000..461ab84 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/BadCredentialsException.java @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates that the credentials for this operation were not acceptable. + */ +public class BadCredentialsException extends MailboxException { + + private static final long serialVersionUID = -8055692887730696513L; + + public BadCredentialsException() { + super(); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/InsufficientRightsException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/InsufficientRightsException.java new file mode 100644 index 0000000..ebb2429 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/InsufficientRightsException.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.exception; + + +/** + * Thrown when the rights granted to the given authenticated user do not suffice + * for the given action. + * + */ +public class InsufficientRightsException extends MailboxSecurityException { + + /** + * + */ + private static final long serialVersionUID = 7310501567640913596L; + + public InsufficientRightsException() { + super(); + } + + public InsufficientRightsException(String message) { + super(message); + } + + public InsufficientRightsException(String msg, Exception cause) { + super(msg, cause); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxException.java new file mode 100644 index 0000000..fa857c0 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxException.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.exception; + +/** + * Mailbox services should throw this exception in case of unsuccessfull + * operation. + */ +public class MailboxException extends Exception { + + private static final long serialVersionUID = 4612761817238115904L; + + public MailboxException() { + super(); + } + + public MailboxException(final String message) { + super(message); + } + + public MailboxException(String msg, Exception cause) { + super(msg, cause); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxExistsException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxExistsException.java new file mode 100644 index 0000000..d029324 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxExistsException.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.exception; + + +/** + * Indicates that the operation failed since the mailbox already exists. + */ +public class MailboxExistsException extends MailboxException { + + private static final long serialVersionUID = -486951759505030166L; + + private final String mailboxName; + + public MailboxExistsException(String mailboxName) { + super("Mailbox with name=" + mailboxName + " already exists."); + this.mailboxName = mailboxName; + } + + /** + * Gets the name of the mailbox which already exists. + * + * @return the mailboxName, not null + */ + public final String getMailboxName() { + return mailboxName; + } + + public String toString() { + return getMessage(); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxNotFoundException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxNotFoundException.java new file mode 100644 index 0000000..8301d76 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxNotFoundException.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.exception; + +import org.apache.james.mailbox.model.MailboxPath; + +/** + * Indicates that the failure is caused by a reference to a mailbox which does + * not exist. + */ +public class MailboxNotFoundException extends MailboxException { + + private static final long serialVersionUID = -8493370806722264915L; + + private final String mailboxName; + + /** + * @param mailboxName + * name of the mailbox, not null + */ + public MailboxNotFoundException(String mailboxName) { + this.mailboxName = mailboxName; + } + + /** + * @param mailboxPath + * name of the mailbox, not null + */ + public MailboxNotFoundException(MailboxPath mailboxPath) { + this.mailboxName = mailboxPath.toString(); + } + + /** + * Gets the name of the mailbox which cannot be found. + * + * @return name or null when only mailbox ID is known + */ + public final String getMailboxName() { + return mailboxName; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxSecurityException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxSecurityException.java new file mode 100644 index 0000000..2923a08 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MailboxSecurityException.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.exception; + + +/** + * Parent for security related exceptions. + * + */ +public class MailboxSecurityException extends MailboxException { + + /** + * + */ + private static final long serialVersionUID = 4186633460326902649L; + + public MailboxSecurityException() { + super(); + } + + public MailboxSecurityException(String message) { + super(message); + } + + public MailboxSecurityException(String msg, Exception cause) { + super(msg, cause); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MessageRangeException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MessageRangeException.java new file mode 100644 index 0000000..57baa6d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/MessageRangeException.java @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Exception which should get thrown if an invalid message set was specified via + * an IMAP command + */ +public class MessageRangeException extends MailboxException { + + private static final long serialVersionUID = 5016914557908202117L; + + public MessageRangeException(String msg) { + super(msg); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/OverQuotaException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/OverQuotaException.java new file mode 100644 index 0000000..56bdfc8 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/OverQuotaException.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.exception; + + +/** + * {@link MailboxException} which identicate that a user was over-quota + * + * + */ +public class OverQuotaException extends MailboxException{ + + /** + * + */ + private static final long serialVersionUID = 532673188582481689L; + + private long used; + private long max; + + public OverQuotaException(String msg, long max, long used) { + super(msg); + } + public OverQuotaException(long max, long used) { + this(null, max, used); + } + + public long getUsed() { + return used; + } + + public long getMax() { + return max; + } + + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/ReadOnlyException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/ReadOnlyException.java new file mode 100644 index 0000000..0db3c1c --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/ReadOnlyException.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.exception; + +import org.apache.james.mailbox.model.MailboxPath; + +/** + * {@link MailboxException} which should get thrown if someone tries to modify a READ-ONLY Mailbox + * + * + */ +public class ReadOnlyException extends MailboxException{ + + + /** + * + */ + private static final long serialVersionUID = 5016914557908202113L; + + + + public ReadOnlyException(MailboxPath path, char delimiter) { + super(path.getFullName(delimiter)); + } + + + public ReadOnlyException(MailboxPath path, char delimiter, Exception e) { + super(path.getFullName(delimiter), e); + } +} + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/SubscriptionException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/SubscriptionException.java new file mode 100644 index 0000000..b27f68a --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/SubscriptionException.java @@ -0,0 +1,37 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates exception during subscription processing. + */ +public class SubscriptionException extends MailboxException { + + private static final long serialVersionUID = -4512372322774311468L; + + public SubscriptionException() { + super(); + } + + public SubscriptionException(Exception cause) { + super(null, cause); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedCriteriaException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedCriteriaException.java new file mode 100644 index 0000000..4411d28 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedCriteriaException.java @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates that a search criteria is not supported. + */ +public class UnsupportedCriteriaException extends MailboxException { + + private static final long serialVersionUID = 3791907285083231285L; + + public UnsupportedCriteriaException() { + super(); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedOperationException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedOperationException.java new file mode 100644 index 0000000..f1baa63 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedOperationException.java @@ -0,0 +1,34 @@ +/**************************************************************** + * 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.exception; + + +/** + * Indicates that an operation required is not supported by this mailbox. + */ +public class UnsupportedOperationException extends MailboxException { + + private static final long serialVersionUID = 1943118588115772317L; + + public UnsupportedOperationException(String message) { + super(message); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java new file mode 100644 index 0000000..eb99b42 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.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.exception; + +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; + +/** + * Thrown when the current system does not support the given right. + * + */ +public class UnsupportedRightException extends MailboxSecurityException { + + private static final char INVALID_RIGHT = 0; + private static final long serialVersionUID = 2959248897018370078L; + private char unsupportedRight = INVALID_RIGHT; + + public UnsupportedRightException() { + super(); + } + + public UnsupportedRightException(char right) { + super("Unsupported right flag '"+ right +"'."); + this.unsupportedRight = right; + } + + public UnsupportedRightException(MailboxACLRight unsupportedRight) { + this(unsupportedRight.getValue()); + } + + public char getUnsupportedRight() { + return unsupportedRight; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedSearchException.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedSearchException.java new file mode 100644 index 0000000..261828e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedSearchException.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.exception; + + +/** + * Indicates that the requested search is not supported by this implementation. + */ +public class UnsupportedSearchException extends MailboxException { + + private static final long serialVersionUID = -7442949630563672557L; + + public UnsupportedSearchException() { + super(); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/all-wcprops new file mode 100644 index 0000000..8a86995 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/all-wcprops @@ -0,0 +1,119 @@ +K 25 +svn:wc:ra_dav:version-url +V 96 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model +END +MessageRange.java +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MessageRange.java +END +UpdatedFlags.java +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java +END +InputStreamContent.java +K 25 +svn:wc:ra_dav:version-url +V 120 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/InputStreamContent.java +END +MailboxPath.java +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java +END +Content.java +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/Content.java +END +MailboxACL.java +K 25 +svn:wc:ra_dav:version-url +V 112 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java +END +Headers.java +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/Headers.java +END +MailboxQuery.java +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MailboxQuery.java +END +PartContentDescriptorImpl.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/PartContentDescriptorImpl.java +END +MessageResult.java +K 25 +svn:wc:ra_dav:version-url +V 115 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java +END +MessageMetaData.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java +END +SimpleMailboxACL.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/SimpleMailboxACL.java +END +MimeDescriptor.java +K 25 +svn:wc:ra_dav:version-url +V 116 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MimeDescriptor.java +END +MailboxConstants.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MailboxConstants.java +END +FetchGroupImpl.java +K 25 +svn:wc:ra_dav:version-url +V 116 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/FetchGroupImpl.java +END +Quota.java +K 25 +svn:wc:ra_dav:version-url +V 107 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/Quota.java +END +SearchQuery.java +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1469272/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java +END +MailboxMetaData.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MailboxMetaData.java +END +MessageResultIterator.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model/MessageResultIterator.java +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/entries new file mode 100644 index 0000000..416131b --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/entries @@ -0,0 +1,674 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/model +http://svn.apache.org/repos/asf + + + +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MessageRange.java +file + + + + +2013-09-02T02:54:40.000000Z +16f92008e4a828fc6697ca184e556e14 +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +8826 + +UpdatedFlags.java +file + + + + +2013-09-02T02:54:40.000000Z +f6b689589172737528ec001d2b2584dc +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +5778 + +InputStreamContent.java +file + + + + +2013-09-02T02:54:40.000000Z +7762f3f7585f745384da918f2627806c +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1448 + +MailboxPath.java +file + + + + +2013-09-02T02:54:40.000000Z +581f2182869511439c63255728576441 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +7048 + +Content.java +file + + + + +2013-09-02T02:54:40.000000Z +c95581d34434f9324450d50b71c2d4a5 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1901 + +MailboxACL.java +file + + + + +2013-09-02T02:54:40.000000Z +1c4658840f68b2609e6389342f647fe4 +2012-02-21T21:05:02.067525Z +1292019 +ppalaga +has-props + + + + + + + + + + + + + + + + + + + + +11802 + +Headers.java +file + + + + +2013-09-02T02:54:40.000000Z +13cd6bab09b6e64cbc24768869d5339b +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1742 + +MailboxQuery.java +file + + + + +2013-09-02T02:54:40.000000Z +8c0588770ea13381df8023186a69da5d +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +6490 + +PartContentDescriptorImpl.java +file + + + + +2013-09-02T02:54:40.000000Z +55c6d4fa44819f3bd1be2e66cd0b75ed +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +2655 + +MessageResult.java +file + + + + +2013-09-02T02:54:40.000000Z +91ac302e625c61b5c10b632aa4633dbb +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +8717 + +MessageMetaData.java +file + + + + +2013-09-02T02:54:40.000000Z +02a3461dda0437cc635be39090451c1a +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2263 + +SimpleMailboxACL.java +file + + + + +2013-09-02T02:54:40.000000Z +995c289dbb780b9d20a60c606126abb4 +2012-02-21T21:05:02.067525Z +1292019 +ppalaga +has-props + + + + + + + + + + + + + + + + + + + + +40218 + +MimeDescriptor.java +file + + + + +2013-09-02T02:54:40.000000Z +6a4151cc7e8884dc2853c9247db3a020 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4283 + +MailboxConstants.java +file + + + + +2013-09-02T02:54:40.000000Z +beb94c83a71f4afe7f92de53f8a0ffea +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +1829 + +FetchGroupImpl.java +file + + + + +2013-09-02T02:54:40.000000Z +fb61327dd999d563812482460dd1205f +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +4037 + +Quota.java +file + + + + +2013-09-02T02:54:40.000000Z +9f12e85b0c1543876bdbd2a0c0117ff4 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1750 + +SearchQuery.java +file + + + + +2013-09-02T02:54:40.000000Z +c3e18d714573b8aabc2a431f1f663a57 +2013-04-18T10:45:04.570973Z +1469272 +eric + + + + + + + + + + + + + + + + + + + + + +63738 + +MailboxMetaData.java +file + + + + +2013-09-02T02:54:40.000000Z +0540377db784ad49f1045bdc4422c38a +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +2533 + +MessageResultIterator.java +file + + + + +2013-09-02T02:54:40.000000Z +339c0f0777da1915965d6748a22c6beb +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1974 + diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxACL.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxACL.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxACL.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxConstants.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxConstants.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxConstants.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxMetaData.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxMetaData.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MailboxMetaData.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageRange.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageRange.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageRange.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageResult.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageResult.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/MessageResult.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/SimpleMailboxACL.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/SimpleMailboxACL.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/prop-base/SimpleMailboxACL.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Content.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Content.java.svn-base new file mode 100644 index 0000000..b39b60e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Content.java.svn-base @@ -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.model; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * IMAP needs to know the size of the content before it starts to write it out. + * This interface allows direct writing whilst exposing total size. + */ +public interface Content { + + + /** + * Return the content as {@link InputStream} + * + * @return content + * @throws IOException + */ + InputStream getInputStream() throws IOException; + + /** + * Size (in octets) of the content. + * + * @return number of octets to be written + * @throws MessagingException + */ + long size() throws MailboxException; +} \ No newline at end of file diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/FetchGroupImpl.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/FetchGroupImpl.java.svn-base new file mode 100644 index 0000000..6e4c7d2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/FetchGroupImpl.java.svn-base @@ -0,0 +1,109 @@ +/**************************************************************** + * 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.model; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.james.mailbox.model.MessageResult.MimePath; + +/** + * Specifies a fetch group. + */ +public class FetchGroupImpl implements MessageResult.FetchGroup { + + public static final MessageResult.FetchGroup MINIMAL = new FetchGroupImpl(MessageResult.FetchGroup.MINIMAL); + + public static final MessageResult.FetchGroup HEADERS = new FetchGroupImpl(MessageResult.FetchGroup.HEADERS); + + public static final MessageResult.FetchGroup FULL_CONTENT = new FetchGroupImpl(MessageResult.FetchGroup.FULL_CONTENT); + + public static final MessageResult.FetchGroup BODY_CONTENT = new FetchGroupImpl(MessageResult.FetchGroup.BODY_CONTENT); + + private int content = MessageResult.FetchGroup.MINIMAL; + + private Set partContentDescriptors; + + public FetchGroupImpl() { + super(); + } + + public FetchGroupImpl(int content) { + super(); + this.content = content; + } + + public FetchGroupImpl(int content, Set partContentDescriptors) { + super(); + this.content = content; + this.partContentDescriptors = partContentDescriptors; + } + + public int content() { + return content; + } + + public void or(int content) { + this.content = this.content | content; + } + + public String toString() { + return "Fetch " + content; + } + + /** + * Gets content descriptors for the parts to be fetched. + * + * @return Set of {@link org.apache.james.mailbox.MessageResult.FetchGroup.PartContentDescriptor}, + * possibly null + */ + public Set getPartContentDescriptors() { + return partContentDescriptors; + } + + /** + * Adds content for the particular part. + * + * @param path + * MimePath, not null + * @param content + * bitwise content constant + */ + public void addPartContent(MimePath path, int content) { + if (partContentDescriptors == null) { + partContentDescriptors = new HashSet(); + } + PartContentDescriptorImpl currentDescriptor = null; + for (Iterator it = partContentDescriptors.iterator(); it.hasNext();) { + PartContentDescriptor descriptor = (PartContentDescriptor) it.next(); + if (path.equals(descriptor.path())) { + currentDescriptor = (PartContentDescriptorImpl) descriptor; + break; + } + } + if (currentDescriptor == null) { + currentDescriptor = new PartContentDescriptorImpl(path); + partContentDescriptors.add(currentDescriptor); + } + + currentDescriptor.or(content); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Headers.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Headers.java.svn-base new file mode 100644 index 0000000..6eebb9d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Headers.java.svn-base @@ -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.model; + +import java.util.Iterator; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.MessageResult.Header; + +public interface Headers extends Content{ + /** + * Gets headers for the message. + * + * @return Header Iterator, or null if + * {@link FetchGroup#HEADERS} was not fetched + */ + Iterator
headers() throws MailboxException; + + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/InputStreamContent.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/InputStreamContent.java.svn-base new file mode 100644 index 0000000..62da0eb --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/InputStreamContent.java.svn-base @@ -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.model; + +import java.io.InputStream; + + +/** + * {@link Content} which offers the content via {@link InputStream} too + * + * @deprecated use {@link Content} + */ +@Deprecated +public interface InputStreamContent extends Content { + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxACL.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxACL.java.svn-base new file mode 100644 index 0000000..2709ee2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxACL.java.svn-base @@ -0,0 +1,333 @@ +/**************************************************************** + * 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.model; + +import java.util.Map; + +import org.apache.james.mailbox.exception.UnsupportedRightException; + +/** + * Stores an Access Control List (ACL) applicable to a mailbox. Inspired by + * RFC4314 IMAP4 Access Control List (ACL) Extension. + * + * Implementations must be immutable. Implementations should override + * {@link #hashCode()} and {@link #equals(Object)}. + * + */ +public interface MailboxACL { + + /** + * SETACL command mode. + */ + enum EditMode { + ADD, REMOVE, REPLACE + } + + /** + * The key used in {@link MailboxACL#getEntries()}. Implementations should + * override {@link #hashCode()} and {@link #equals(Object)} in such a way + * that all of {@link #getName()}, {@link #getNameType()} and + * {@link #isNegative()} are significant. + * + */ + public interface MailboxACLEntryKey { + /** + * Returns the name of a user or of a group to which this + * {@link MailboxACLEntryKey} applies. + * + * @return User name, group name or special name. + */ + String getName(); + + /** + * Tells of what type is the name returned by {@link #getName()}. + * + * @return type of the name returned by {@link #getName()} + */ + NameType getNameType(); + + /** + * If true the {@link MailboxACLRights} returned by + * {@link MailboxACLEntry#getRights()} should be interpreted as + * "negative rights" as described in RFC4314: If the identifier "-fred" + * is granted the "w" right, that indicates that the "w" right is to be + * removed from users matching the identifier "fred", even though the + * user "fred" might have the "w" right as a consequence of some other + * identifier in the ACL. + * + * Note that {@link MailboxACLEntry#getName()} does not start with "-" + * when {@link MailboxACLEntry#getRights()} returns true. + * + * @return + */ + boolean isNegative(); + + /** + * Returns a serialized form of this {@link MailboxACLEntryKey} as a + * {@link String}. Implementations should choose a consistent way how + * all of {@link #getName()}, {@link #getNameType()} and + * {@link #isNegative()} get serialized. + * + * RFC4314 sction 2. states: All user name strings accepted by the LOGIN + * or AUTHENTICATE commands to authenticate to the IMAP server are + * reserved as identifiers for the corresponding users. Identifiers + * starting with a dash ("-") are reserved for "negative rights", + * described below. All other identifier strings are interpreted in an + * implementation-defined manner. + * + * Dovecot and Cyrus mark groups with '$' prefix. See http://wiki2.dovecot.org/SharedMailboxes/Shared: + * + * The $group syntax is not a standard, but it is mentioned in RFC + * 4314 examples and is also understood by at least Cyrus IMAP. Having + * '-' before the identifier specifies negative rights. + * + * @see MailboxACL#DEFAULT_GROUP_MARKER + * @see MailboxACL#DEFAULT_NEGATIVE_MARKER + * + * @return serialized form as a {@link String} + */ + String serialize(); + } + + /** + * Single right applicable to a mailbox. + */ + public interface MailboxACLRight { + /** + * Returns the char representation of this right. + * + * @return char representation of this right + */ + char getValue(); + } + + /** + * Iterable set of {@link MailboxACLRight}s. + * + * Implementations may decide to support only a specific range of rights, + * e.g. the Standard Rights of RFC 4314 section 2.1. + * + * Implementations must not allow adding or removing of elements once this + * MailboxACLRights are initialized. + */ + public interface MailboxACLRights extends Iterable { + + /** + * Tells whether this contains the given right. + * + * @param right + * @return + * @throws UnsupportedRightException + * iff the given right is not supported. + */ + boolean contains(MailboxACLRight right) throws UnsupportedRightException; + + /** + * Performs the set theoretic operation of relative complement of + * toRemove MailboxACLRights in this MailboxACLRights. + * + * A schematic example: "lrw".except("w") returns "lr". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACLRights}. However, implementations may decide to + * return this or toRemove parameter value in case the result would be + * equal to the respective one of those. + * + * @param toRemove + * @return + * @throws UnsupportedRightException + */ + public MailboxACLRights except(MailboxACLRights toRemove) throws UnsupportedRightException; + + /** + * Tells if this set of rights is empty. + * + * @return true if there are no rights in this set; false otherwise. + */ + public boolean isEmpty(); + + /** + * Tells whether the implementation supports the given right. + * + * @param right + * @return true if this supports the given right. + */ + boolean isSupported(MailboxACLRight right); + + /** + * Returns a serialized form of this {@link MailboxACLRights} as + * {@link String}. + * + * @return a {@link String} + */ + String serialize(); + + /** + * Performs the set theoretic operation of union of this + * MailboxACLRights and toAdd MailboxACLRights. + * + * A schematic example: "lr".union("rw") returns "lrw". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACLRights}. However, implementations may decide to + * return this or toAdd parameter value in case the result would be + * equal to the respective one of those. + * + * @param toAdd + * @return union of this and toAdd + * @throws UnsupportedRightException + * + */ + public abstract MailboxACLRights union(MailboxACLRights toAdd) throws UnsupportedRightException; + + }; + + /** + * Allows distinguishing between users, groups and special names (see + * {@link SpecialName}). + */ + enum NameType { + group, special, user + }; + + /** + * Special name literals. + */ + enum SpecialName { + anybody, authenticated, owner + }; + + /** + * SETACL third argument prefix + */ + public static final char ADD_RIGHTS_MARKER = '+'; + + /** + * Marks groups when (de)serializing {@link MailboxACLEntryKey}s. + * + * @see MailboxACLEntryKey#serialize() + */ + public static final char DEFAULT_GROUP_MARKER = '$'; + + /** + * Marks negative when (de)serializing {@link MailboxACLEntryKey}s. + * + * @see MailboxACLEntryKey#serialize() + */ + public static final char DEFAULT_NEGATIVE_MARKER = '-'; + + /** + * SETACL third argument prefix + */ + public static final char REMOVE_RIGHTS_MARKER = '-'; + + /** + * Performs the set theoretic operation of relative complement of toRemove + * {@link MailboxACL} in this {@link MailboxACL}. + * + * A schematic example: "user1:lr;user2:lrwt".except("user1:w;user2:t") + * returns "user1:lr;user2:lrw". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACL}. However, implementations may decide to return this or + * toRemove parameter value in case the result would be equal to the + * respective one of those. + * + * Implementations must ensure that the result does not contain entries with + * empty rigths. E.g. "user1:lr;user2:lrwt".except("user1:lr") should return + * "user2:lrwt" rather than "user1:;user2:lrwt" + * + * @param toRemove + * @return + * @throws UnsupportedRightException + */ + MailboxACL except(MailboxACL toRemove) throws UnsupportedRightException; + + /** + * TODO except. + * + * @param key + * @param toRemove + * @return + * @throws UnsupportedRightException + */ + MailboxACL except(MailboxACLEntryKey key, MailboxACLRights toRemove) throws UnsupportedRightException; + + /** + * {@link Map} of entries. + * + * @return the entries. + */ + Map getEntries(); + + /** + * Replaces the entry corresponding to the given {@code key} with + * {@code toAdd}link MailboxACLRights}. + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACL}. However, implementations may decide to return this in + * case the result would be equal to it. + * + * Implementations must ensure that the result does not contain entries with + * empty rigths. E.g. "user1:lr;user2:lrwt".replace("user1", + * MailboxACLRights.EMPTY) should return "user2:lrwt" rather than + * "user1:;user2:lrwt". The same result should be returned by + * "user1:lr;user2:lrwt".replace("user1", null). + * + * @param key + * @param toAdd + * @return + * @throws UnsupportedRightException + */ + MailboxACL replace(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException; + + /** + * Performs the set theoretic operation of union of this {@link MailboxACL} + * and toAdd {@link MailboxACL}. + * + * A schematic example: + * "user1:lr;user2:lrwt".union("user1:at;-$group1:lrwt") returns + * "user1:alrt;user2:lrwt;-$group1:lrwt". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACL}. However, implementations may decide to return this or + * toAdd parameter value in case the result would be equal to the respective + * one of those. + * + * + * @param toAdd + * @return + * @throws UnsupportedRightException + */ + MailboxACL union(MailboxACL toAdd) throws UnsupportedRightException; + + /** + * TODO union. + * + * @param key + * @param toAdd + * @return + * @throws UnsupportedRightException + */ + MailboxACL union(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxConstants.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxConstants.java.svn-base new file mode 100644 index 0000000..fa53e13 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxConstants.java.svn-base @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.model; + +/** + * Constants which are used within the mailbox api and implementations + * + * + */ +public interface MailboxConstants { + + /** + * The char which is used to prefix a namespace + */ + public static final char NAMESPACE_PREFIX_CHAR = '#'; + + /** The namespace used for store user inboxes */ + public static final String USER_NAMESPACE = NAMESPACE_PREFIX_CHAR + "private"; + + /** The default delimiter used to seperated parent/child folders */ + public static final char DEFAULT_DELIMITER = '.'; + + /** The name of the INBOX */ + public static final String INBOX = "INBOX"; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxMetaData.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxMetaData.java.svn-base new file mode 100644 index 0000000..d78fc0e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxMetaData.java.svn-base @@ -0,0 +1,82 @@ +/**************************************************************** + * 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.model; + +/** + * Returned by the list method of MailboxRepository and others + */ +public interface MailboxMetaData { + + /** RFC3501 Selectability flag */ + public enum Selectability { + NONE, MARKED, UNMARKED, NOSELECT + } + + /** + * Indicates whether this mailbox allows children and - if so - whether it + * has any. + */ + public enum Children { + /** + * No children allowed. + */ + NO_INFERIORS, + /** + * Children allowed by this mailbox but it is unknown whether this + * mailbox has children. + */ + CHILDREN_ALLOWED_BUT_UNKNOWN, + /** + * Indicates that this mailbox has children. + */ + HAS_CHILDREN, + /** + * Indicates that this mailbox allows interiors but currently has no + * children. + */ + HAS_NO_CHILDREN + } + + /** + * Gets the inferiors status of this mailbox. + * + * @return not null + */ + Children inferiors(); + + /** + * Gets the RFC3501 Selectability flag. + */ + Selectability getSelectability(); + + /** + * Return the delimiter + * + * @return delimiter + */ + char getHierarchyDelimiter(); + + /** + * Return the MailboxPath + * + * @return path + */ + MailboxPath getPath(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxPath.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxPath.java.svn-base new file mode 100644 index 0000000..9bedf42 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxPath.java.svn-base @@ -0,0 +1,230 @@ +/**************************************************************** + * 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.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.james.mailbox.MailboxSession; + +/** + * The path to a mailbox. + */ +public class MailboxPath { + + private String namespace; + private String user; + private String name; + + + + public MailboxPath(String namespace, String user, String name) { + if (namespace == null || namespace.equals("")) { + this.namespace = MailboxConstants.USER_NAMESPACE; + } else { + this.namespace = namespace; + } + this.user = user; + this.name = name; + } + + public MailboxPath(MailboxPath mailboxPath) { + this(mailboxPath.getNamespace(), mailboxPath.getUser(), mailboxPath.getName()); + } + + public MailboxPath(MailboxPath mailboxPath, String name) { + this(mailboxPath.getNamespace(), mailboxPath.getUser(), name); + } + + /** + * Get the namespace this mailbox is in + * + * @return The namespace + */ + public String getNamespace() { + return namespace; + } + + /** + * Set the namespace this mailbox is in + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Get the name of the user who owns the mailbox. This can be null e.g. for + * shared mailboxes. + * + * @return The username + */ + public String getUser() { + return user; + } + + /** + * Set the name of the user who owns the mailbox. + */ + public void setUser(String user) { + this.user = user; + } + + /** + * Get the name of the mailbox. This is the pure name without user or + * namespace, so this is what a user would see in his client. + * + * @return The name string + */ + public String getName() { + return name; + } + + /** + * Set the name of the mailbox. This is the pure name without user or + * namespace, so this is what a user would see in his client. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Return a list of MailboxPath representing the hierarchy levels of this + * MailboxPath. E.g. INBOX.main.sub would yield + * + *
+     * INBOX
+     * INBOX.main
+     * INBOX.main.sub
+     * 
+ * + * @param delimiter + * @return list of hierarchy levels + */ + public List getHierarchyLevels(char delimiter) { + ArrayList levels = new ArrayList(); + int index = name.indexOf(delimiter); + while (index >= 0) { + final String levelname = name.substring(0, index); + levels.add(new MailboxPath(namespace, user, levelname)); + index = name.indexOf(delimiter, ++index); + } + levels.add(this); + return levels; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return namespace + ":" + user + ":" + name; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object mailboxPath) { + if (this == mailboxPath) + return true; + + if (!(mailboxPath instanceof MailboxPath)) + return false; + MailboxPath mp = (MailboxPath) mailboxPath; + if (namespace == null) { + if (mp.getNamespace() != null) + return false; + } else if (!namespace.equals(mp.getNamespace())) + return false; + if (user == null) { + if (mp.getUser() != null) + return false; + } else if (!user.equals(mp.getUser())) + return false; + if (name == null) { + if (mp.getName() != null) + return false; + } else if (!name.equals(mp.getName())) + return false; + return true; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + if (getName() != null) + result = PRIME * result + getName().hashCode(); + if (getUser() != null) + result = PRIME * result + getUser().hashCode(); + if (getNamespace() != null) + result = PRIME * result + getNamespace().hashCode(); + return result; + } + + /** + * Return the full name of the {@link MailboxPath}, which is constructed via the {@link #namespace} and {@link #name} + * + * @param delimiter + * @return fullName + */ + public String getFullName(char delimiter) { + return namespace + delimiter + name; + } + + /** + * Return a {@link MailboxPath} which represent the INBOX of the given + * session + * + * @param session + * @return inbox + */ + public static MailboxPath inbox(MailboxSession session) { + return new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), MailboxConstants.INBOX); + } + + /** + * Create a {@link MailboxPath} by parsing the given full mailboxname (which included the namespace) + * + * @param session + * @param fullmailboxname + * @return path + */ + public static MailboxPath parse(MailboxSession session, String fullmailboxname) { + char delimiter = session.getPathDelimiter(); + int i = fullmailboxname.indexOf(delimiter); + String namespace = fullmailboxname.substring(0, i); + String mailbox = fullmailboxname.substring(i + 1, fullmailboxname.length()); + String username = null; + if (namespace == null || namespace.trim().equals("")) { + namespace = MailboxConstants.USER_NAMESPACE; + } + if (namespace.equals(session.getPersonalSpace())) { + username = session.getUser().getUserName(); + } + return new MailboxPath(namespace, username, mailbox); + + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxQuery.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxQuery.java.svn-base new file mode 100644 index 0000000..0df382d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MailboxQuery.java.svn-base @@ -0,0 +1,192 @@ +/**************************************************************** + * 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.model; + +import java.util.regex.Pattern; + + +/** + * Expresses select criteria for mailboxes. + */ +public final class MailboxQuery { + + private final MailboxPath base; + + private final String expression; + + private final char pathDelimiter; + + private final Pattern pattern; + + /** + * Use this wildcard to match every char including the hierarchy delimiter + */ + public final static char FREEWILDCARD = '*'; + + + /** + * Use this wildcard to match every char except the hierarchy delimiter + */ + public final static char LOCALWILDCARD = '%'; + + /** + * Constructs an expression determining a set of mailbox names. + * + * @param base + * base reference name, not null + * @param expression + * mailbox match expression, not null + * @param pathDelimiter + * path delimiter to use + */ + public MailboxQuery(final MailboxPath base, final String expression, final char pathDelimiter) { + super(); + this.base = base; + if (base.getName() == null) + this.base.setName(""); + if (expression == null) { + this.expression = ""; + } else { + this.expression = expression; + } + this.pathDelimiter = pathDelimiter; + + // Compile some pattern which is used later + pattern = Pattern.compile(this.expression.replaceAll("\\" + pathDelimiter ,"\\\\" + pathDelimiter).replaceAll("\\*", "\\.\\*").replaceAll("\\%", "[^\\" + pathDelimiter + "]*")); + } + + /** + * Gets the base reference for the search. + * + * @return the base + */ + public final MailboxPath getBase() { + return base; + } + + /** + * Gets the name search expression. This may contain wildcards. + * + * @return the expression + */ + public final String getExpression() { + return expression; + } + + /** + * Gets wildcard character that matches any series of characters. + * + * @return the freeWildcard + */ + public final char getFreeWildcard() { + return FREEWILDCARD; + } + + /** + * Gets wildcard character that matches any series of characters excluding + * hierarchy delimiters. Effectively, this means that it matches any + * sequence within a name part. + * + * @return the localWildcard + */ + public final char getLocalWildcard() { + return LOCALWILDCARD; + } + + /** + * Is the given name a match for {@link #getExpression()}? + * + * @param name + * name to be matched + * @return true if the given name matches this expression, false otherwise + */ + public final boolean isExpressionMatch(String name) { + final boolean result; + if (isWild()) { + if (name == null) { + result = false; + } else { + result = pattern.matcher(name).matches(); + } + } else { + result = expression.equals(name); + } + return result; + } + + /** + * Get combined name formed by adding the expression to the base using the + * given hierarchy delimiter. Note that the wildcards are retained in the + * combined name. + * + * @return {@link #getBase()} combined with {@link #getExpression()}, + * notnull + */ + public String getCombinedName() { + final String result; + if (base != null && base.getName() != null && base.getName().length() > 0) { + final int baseLength = base.getName().length(); + if (base.getName().charAt(baseLength - 1) == pathDelimiter) { + if (expression != null && expression.length() > 0) { + if (expression.charAt(0) == pathDelimiter) { + result = base.getName() + expression.substring(1); + } else { + result = base.getName() + expression; + } + } else { + result = base.getName(); + } + } else { + if (expression != null && expression.length() > 0) { + if (expression.charAt(0) == pathDelimiter) { + result = base.getName() + expression; + } else { + result = base.getName() + pathDelimiter + expression; + } + } else { + result = base.getName(); + } + } + } else { + result = expression; + } + return result; + } + + /** + * Is this expression wild? + * + * @return true if wildcard contained, false otherwise + */ + public boolean isWild() { + return expression != null && (expression.indexOf(getFreeWildcard()) >= 0 || expression.indexOf(getLocalWildcard()) >= 0); + } + + /** + * Renders a string suitable for logging. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + return "MailboxExpression [ " + "base = " + this.base + TAB + "expression = " + this.expression + TAB + "freeWildcard = " + this.getFreeWildcard() + TAB + "localWildcard = " + this.getLocalWildcard() + TAB + " ]"; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageMetaData.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageMetaData.java.svn-base new file mode 100644 index 0000000..504ef16 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageMetaData.java.svn-base @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.model; + +import java.util.Date; + +import javax.mail.Flags; + +/** + * Represent the {@link MessageMetaData} for a message + * + * + */ +public interface MessageMetaData { + + /** + * Return the uid of the message which the MessageResult belongs to + * + * @return uid + */ + long getUid(); + + + /** + * Return the modify-sequence number of the message. This is kind of optional and the mailbox + * implementation may not support this. If so it will return -1 + * + * @return modSeq + */ + long getModSeq(); + + /** + * Return the {@link Flags} + * + * @return flags + */ + Flags getFlags(); + + /** + * Return the size in bytes + * + * @return size + */ + long getSize(); + + /** + *

+ * IMAP defines this as the time when the message has arrived to the server + * (by smtp). Clients are also allowed to set the internalDate on append. + *

+ */ + Date getInternalDate(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageRange.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageRange.java.svn-base new file mode 100644 index 0000000..1de7de2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageRange.java.svn-base @@ -0,0 +1,301 @@ +/**************************************************************** + * 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.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Used to define a range of messages by uid.
+ * The type of the set should be defined by using an appropriate constructor. + */ +public class MessageRange implements Iterable{ + + public enum Type { + /** All messages */ + ALL, + /** A sigle message */ + ONE, + /** All messages with a uid equal or higher than */ + FROM, + /** All messagse within the given range of uids (inclusive) */ + RANGE + } + + public static final long NOT_A_UID = -1; + + public static final long MAX_UID = Long.MAX_VALUE; + + /** + * Constructs a range consisting of a single message only. + * + * @param uid + * UID of the message + * @return not null + */ + public static MessageRange one(long uid) { + final MessageRange result = new MessageRange(Type.ONE, uid, uid); + return result; + } + + /** + * Constructs a range consisting of all messages. + * + * @return not null + */ + public static MessageRange all() { + final MessageRange result = new MessageRange(Type.ALL, NOT_A_UID, MAX_UID); + return result; + } + + /** + * Constructs an inclusive ranges of messages. The parameters will be + * checked and {@link #from(long)} used where appropriate. + * + * @param from + * first message UID + * @param to + * last message UID + * @return not null + */ + public static MessageRange range(long from, long to) { + final MessageRange result; + if (to == Long.MAX_VALUE || to < from) { + to = NOT_A_UID; + result = from(from); + } else if (from == to) { + // from and to is the same so no need to construct a real range + result = one(from); + } else { + result = new MessageRange(Type.RANGE, from, to); + } + return result; + } + + + /** + * Constructs an inclusive, open ended range of messages. + * + * @param from + * first message UID in range + * @return not null + */ + public static MessageRange from(long from) { + final MessageRange result = new MessageRange(Type.FROM, from, NOT_A_UID); + return result; + } + + + private final Type type; + + private final long uidFrom; + + private final long uidTo; + + protected MessageRange(final Type type, final long uidFrom, final long uidTo) { + super(); + this.type = type; + this.uidFrom = uidFrom; + this.uidTo = uidTo; + } + + public Type getType() { + return type; + } + + public long getUidFrom() { + return uidFrom; + } + + public long getUidTo() { + return uidTo; + } + + + /** + * Return true if the uid is within the range + * + * @param uid + * @return withinRange + */ + public boolean includes(long uid) { + switch (type) { + case ALL: + return true; + case FROM: + if (uid > getUidFrom()) { + return true; + } + case RANGE: + if (uid >= getUidFrom() && uid <= getUidTo()) { + return true; + } + case ONE: + if (getUidFrom() == uid) { + return true; + } + default: + break; + } + return false; + } + + public String toString() { + return "TYPE: " + type + " UID: " + uidFrom + ":" + uidTo; + } + + + /** + * Converts the given {@link Collection} of uids to a {@link List} of {@link MessageRange} instances + * + * @param uidsCol + * collection of uids to convert + * @return ranges + */ + public static List toRanges(Collection uidsCol) { + List ranges = new ArrayList(); + List uids = new ArrayList(uidsCol); + Collections.sort(uids); + + long firstUid = 0; + int a = 0; + for (int i = 0; i < uids.size(); i++) { + long u = uids.get(i); + if (i == 0) { + firstUid = u; + if (uids.size() == 1) { + ranges.add(MessageRange.one(firstUid)); + } + } else { + if ((firstUid + a +1) != u) { + ranges.add(MessageRange.range(firstUid, firstUid + a)); + + // set the next first uid and reset the counter + firstUid = u; + a = 0; + if (uids.size() <= i +1) { + ranges.add(MessageRange.one(firstUid)); + } + } else { + a++; + // Handle uids which are in sequence. See MAILBOX-56 + if (uids.size() <= i +1) { + ranges.add(MessageRange.range(firstUid, firstUid +a)); + break; + } + } + } + } + return ranges; + } + + + /** + * Return a read-only {@link Iterator} which contains all uid which fail in the specified range. + * + * @return rangeIt + */ + @Override + public Iterator iterator() { + long from = getUidFrom(); + if (from == NOT_A_UID) { + from = 1; + } + long to = getUidTo(); + if (to == NOT_A_UID) { + to = Long.MAX_VALUE; + } + return new RangeIterator(from, to); + } + + /** + * {@link Iterator} of a range of msn/uid + * + */ + private final class RangeIterator implements Iterator { + + private long to; + private long current; + + public RangeIterator(long from, long to) { + this.to = to; + this.current = from; + } + + @Override + public boolean hasNext() { + return current <= to; + } + + @Override + public Long next() { + if (hasNext()) { + return current++; + } else { + throw new NoSuchElementException("Max uid of " + to + " was reached before"); + } + } + + @Override + public void remove() { + throw new java.lang.UnsupportedOperationException("Read-Only"); + } + + } + + + /** + * Tries to split the given {@link MessageRange} to a {@link List} of {@link MessageRange}'s which + * select only a max amount of items. This only work for {@link MessageRange}'s with {@link Type} of + * {@link Type#RANGE}. + * + * @param maxItems + * @return ranges + */ + public List split( int maxItems) { + List ranges = new ArrayList(); + if (getType() == Type.RANGE) { + long from = getUidFrom(); + long to = getUidTo(); + long realTo = to; + while(from <= realTo) { + if (from + maxItems -1 < realTo) { + to = from + maxItems -1; + } else { + to = realTo; + } + if (from == to) { + ranges.add(MessageRange.one(from)); + } else { + ranges.add(MessageRange.range(from, to)); + } + + from = to + 1; + } + } else { + ranges.add(this); + } + return ranges; + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResult.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResult.java.svn-base new file mode 100644 index 0000000..4b84fae --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResult.java.svn-base @@ -0,0 +1,260 @@ +/**************************************************************** + * 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.model; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Set; + +import org.apache.james.mailbox.exception.MailboxException; + + +/** + *

+ * Used to get specific informations about a Message without dealing with a + * MimeMessage instance. Demanded information can be requested by binary + * combining the constants. + *

+ * + *

+ * I came to the Idea of the MessageResult because there are many possible + * combinations of different requests (uid, msn, MimeMessage, Flags). + *

+ *

+ * e.g. I want to have all uids, msns and flags of all messages. (a common IMAP + * operation) Javamail would do it that way: + *

    + *
  1. get all Message objects (Message[])
  2. + *
  3. call Message.getMessageNumber()
  4. + *
  5. call Message.getFlags()
  6. + *
  7. call Folder.getUid(Message)
  8. + *
+ *

+ * This means creating a lazy-loading MimeMessage instance.
So why don't + * call getMessages(MessageResult.UID | MessageResult.MSN | + * MessageResult.FLAGS)? This would leave a lot of room for the implementation + * to optimize + *

+ */ + +public interface MessageResult extends Comparable, MessageMetaData { + + /** + * Indicates the results fetched. + */ + public interface FetchGroup { + + /** + * For example: could have best performance when doing store and then + * forget. UIDs are always returned + */ + public static final int MINIMAL = 0x00; + + /** + * + */ + public static final int MIME_DESCRIPTOR = 0x01; + + public static final int HEADERS = 0x100; + + public static final int FULL_CONTENT = 0x200; + + public static final int BODY_CONTENT = 0x400; + + public static final int MIME_HEADERS = 0x800; + + public static final int MIME_CONTENT = 0x1000; + + /** + * Contents to be fetched. Composed bitwise. + * + * @return bitwise description + * @see #MINIMAL + * @see #MIME_DESCRIPTOR + * @see #HEADERS + * @see #FULL_CONTENT + * @see #BODY_CONTENT + * @see #MIME_HEADERS + * @see #MIME_CONTENT + */ + int content(); + + /** + * Gets contents to be fetched for contained parts. For each part to be + * contained, only one descriptor should be contained. + * + * @return Set of {@link PartContentDescriptor}, or null if + * there is no part content to be fetched + */ + Set getPartContentDescriptors(); + + /** + * Describes the contents to be fetched for a mail part. All + * implementations MUST implement equals. Two implementations are equal + * if and only if their paths are equal. + */ + public interface PartContentDescriptor { + /** + * Contents to be fetched. Composed bitwise. + * + * @return bitwise descripion + * @see #MINIMAL + * @see #MIME_DESCRIPTOR + * @see #HEADERS + * @see #FULL_CONTENT + * @see #BODY_CONTENT + * @see #MIME_HEADERS + * @see #MIME_CONTENT + */ + int content(); + + /** + * Path describing the part to be fetched. + * + * @return path describing the part, not null + */ + MimePath path(); + } + } + + MimeDescriptor getMimeDescriptor() throws MailboxException; + + /** + * Iterates the message headers for the given part in a multipart message. + * + * @param path + * describing the part's position within a multipart message + * @return Header Iterator, or null when + * {@link FetchGroup#content()} does not include the index and + * when the mime part cannot be found + * @throws MailboxException + */ + Iterator
iterateHeaders(MimePath path) throws MailboxException; + + /** + * Iterates the MIME headers for the given part in a multipart message. + * + * @param path + * describing the part's position within a multipart message + * @return Header Iterator, or null when + * {@link FetchGroup#content()} does not include the index and + * when the mime part cannot be found + * @throws MailboxException + */ + Iterator
iterateMimeHeaders(MimePath path) throws MailboxException; + + /** + * A header. + */ + public interface Header extends Content { + + /** + * Gets the name of this header. + * + * @return name of this header + * @throws MessagingException + */ + String getName() throws MailboxException; + + /** + * Gets the (unparsed) value of this header. + * + * @return value of this header + * @throws MessagingException + */ + String getValue() throws MailboxException; + } + + /** + * Gets the full message including headers and body. The message data should + * have normalised line endings (CRLF). + * + * @return Content, or or null if + * {@link FetchGroup#FULL_CONTENT} has not been included in the + * results + * @throws IOException + */ + Content getFullContent() throws MailboxException, IOException; + + /** + * Gets the full content of the given mime part. + * + * @param path + * describes the part + * @return Content, or null when + * {@link FetchGroup#content()} did not been include the given + * index and when the mime part cannot be found + * @throws MailboxException + */ + Content getFullContent(MimePath path) throws MailboxException; + + /** + * Gets the body of the message excluding headers. The message data should + * have normalised line endings (CRLF). + * + * @return Content, or or null if + * {@link FetchGroup#FULL_CONTENT} has not been included in the + * results + * @throws IOException + */ + Content getBody() throws MailboxException, IOException; + + /** + * Gets the body of the given mime part. + * + * @param path + * describes the part + * @return Content, or null when + * {@link FetchGroup#content()} did not been include the given + * index and when the mime part cannot be found + * @throws MailboxException + */ + Content getBody(MimePath path) throws MailboxException; + + /** + * Gets the body of the given mime part. + * + * @param path + * describes the part + * @return Content, or null when + * {@link FetchGroup#content()} did not been include the given + * index and when the mime part cannot be found + * @throws MailboxException + */ + Content getMimeBody(MimePath path) throws MailboxException; + + + Headers getHeaders() throws MailboxException; + + /** + * Describes a path within a multipart MIME message. All implementations + * must implement equals. Two paths are equal if and only if each position + * is identical. + */ + public interface MimePath { + + /** + * Gets the positions of each part in the path. + * + * @return part positions describing the path + */ + int[] getPositions(); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResultIterator.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResultIterator.java.svn-base new file mode 100644 index 0000000..68ef98e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MessageResultIterator.java.svn-base @@ -0,0 +1,44 @@ +/**************************************************************** + * 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.model; + +import java.util.Iterator; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * A special {@link Iterator} which allows to access the {@link MailboxException} if one was thrown while try to lazy fetch the {@link MessageResult}'s in batches + * + * + */ +public interface MessageResultIterator extends Iterator { + + /** + * This method should get called after the {@link #hasNext()} method returns + * false. + * + * If it does not return null an error was thrown before while + * try to lazy fetch th next batch of {@link MessageResult}'s + * + * @return exception + */ + public MailboxException getException(); + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MimeDescriptor.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MimeDescriptor.java.svn-base new file mode 100644 index 0000000..6c58ab0 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/MimeDescriptor.java.svn-base @@ -0,0 +1,142 @@ +/**************************************************************** + * 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.model; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +public interface MimeDescriptor extends Headers{ + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + String getMimeType(); + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + String getMimeSubType(); + + /** + * Gets the MIME Content-ID header value. + * + * @return MIME Content-ID, possibly null + */ + String getContentID(); + + /** + * Gets MIME Content-Description header value. + * + * @return MIME Content-Description, possibly null + */ + String getContentDescription(); + + /** + * Gets MIME Content-Location header value. + * + * @return parsed MIME Content-Location, possibly null + */ + String getContentLocation(); + + /** + * Gets MIME Content-MD5 header value. + * + * @return parsed MIME Content-MD5, possibly null + */ + String getContentMD5(); + + /** + * Gets the MIME content transfer encoding. + * + * @return MIME Content-Transfer-Encoding, possibly null + */ + String getTransferContentEncoding(); + + /** + * Gets the languages, From the MIME Content-Language header + * value. + * + * @return List of String names + */ + List getLanguages(); + + /** + * Gets MIME Content-Disposition. + * + * @return Content-Disposition, or null if no disposition + * header exists + */ + String getDisposition(); + + /** + * Gets MIME Content-Disposition parameters. + * + * @return Content-Disposition values indexed by names + */ + Map getDispositionParams(); + + /** + * Gets the number of lines of text in a part of type TEXT when + * transfer encoded. + * + * @return CRLF count when a TEXT type, otherwise + * -1 + */ + long getLines(); + + /** + * The number of octets contained in the body of this part. + * + * @return number of octets + */ + long getBodyOctets(); + + /** + * Gets parts. + * + * @return MimeDescriptor Iterator when a + * composite top level MIME media type, null otherwise + */ + Iterator parts(); + + /** + * Gets embedded message. + * + * @return MimeDescriptor when top level MIME type is + * message, null otherwise + */ + MimeDescriptor embeddedMessage(); + + /** + * Gets MIME body parameters parsed from Content-Type. + * + * @return Header Iterator, not null + */ + Map contentTypeParameters(); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/PartContentDescriptorImpl.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/PartContentDescriptorImpl.java.svn-base new file mode 100644 index 0000000..7320dfb --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/PartContentDescriptorImpl.java.svn-base @@ -0,0 +1,78 @@ +/**************************************************************** + * 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.model; + +import org.apache.james.mailbox.model.MessageResult.FetchGroup.PartContentDescriptor; +import org.apache.james.mailbox.model.MessageResult.MimePath; + + +public class PartContentDescriptorImpl implements PartContentDescriptor { + + private int content = 0; + + private final MimePath path; + + public PartContentDescriptorImpl(final MimePath path) { + super(); + this.path = path; + } + + public PartContentDescriptorImpl(int content, final MimePath path) { + super(); + this.content = content; + this.path = path; + } + + public void or(int content) { + this.content = this.content | content; + } + + public int content() { + return content; + } + + public MimePath path() { + return path; + } + + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((path == null) ? 0 : path.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final PartContentDescriptor other = (PartContentDescriptor) obj; + if (path == null) { + if (other.path() != null) + return false; + } else if (!path.equals(other.path())) + return false; + return true; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Quota.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Quota.java.svn-base new file mode 100644 index 0000000..9323886 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/Quota.java.svn-base @@ -0,0 +1,54 @@ +/**************************************************************** + * 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.model; + +/** + * A {@link Quota} restriction + * + * + * + */ +public interface Quota { + + /** + * Unlimited value + */ + public final static long UNLIMITED = -1; + + + /** + * Value not known + */ + public final static long UNKNOWN = Long.MIN_VALUE; + + /** + * Return the maximum value for the {@link Quota} + * + * @return max + */ + long getMax(); + + /** + * Return the currently used for the {@link Quota} + * + * @return used + */ + long getUsed(); + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SearchQuery.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SearchQuery.java.svn-base new file mode 100644 index 0000000..f2c8d31 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SearchQuery.java.svn-base @@ -0,0 +1,2096 @@ +/**************************************************************** + * 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.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +/** + *

+ * Models a query used to search for messages. A query is the logical + * AND of the contained criteria. + *

+ *

+ * Each Criterion is composed of an Operator + * (combining value and operation) together with field information (optional + * since the criteria type may imply a particular field). Factory methods are + * provided for criteria. + *

+ */ +public class SearchQuery implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * The Resolution which should get used for {@link Date} searches + */ + public static enum DateResolution { + Second, Minute, Hour, Day, Month, Year + } + + public static enum AddressType { + From, To, Cc, Bcc + } + + /** + * Allow to sort a {@link SearchQuery} response in different ways. + */ + public static final class Sort implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * Specify on what to sort + */ + public static enum SortClause { + + /** + * Internal date and time of the message (internaldate) + */ + Arrival, + + /** + * addr-mailbox of the first "cc" address. + * + * This MUST BE converted to uppercase before doing the sort + */ + MailboxCc, + + /** + * addr-mailbox of the first "from" address. + * + * This MUST BE converted to uppercase before doing the sort + */ + MailboxFrom, + + /** + * addr-mailbox of the first "To" address + * + * This MUST BE converted to uppercase before doing the sort + */ + MailboxTo, + + /** + * Base subject text. + * + * This MUST BE converted to uppercase before doing the sort + */ + BaseSubject, + + /** + * Size of the message in octets. + */ + Size, + + /** + *

+ * As used in this document, the term "sent date" refers to the date + * and time from the Date: header, adjusted by time zone to + * normalize to UTC. For example, "31 Dec 2000 16:01:33 -0800" is + * equivalent to the UTC date and time of + * "1 Jan 2001 00:01:33 +0000". If the time zone is invalid, the + * date and time SHOULD be treated as UTC. If the time is also + * invalid, the time SHOULD be treated as 00:00:00. If there is no + * valid date or time, the date and time SHOULD be treated as + * 00:00:00 on the earliest possible date. + * + * If the sent date cannot be determined (a Date: header is missing + * or cannot be parsed), the INTERNALDATE for that message is used + * as the sent date. + *

+ */ + SentDate, + + /** + * addr-name of the first "From" address + * + * This MUST BE converted to uppercase before doing the sort + */ + DisplayFrom, + + /** + * addr-name of the first "To" address + * + * This MUST BE converted to uppercase before doing the sort + */ + DisplayTo, + + /** + * Uid of the message. This is the DEFAULT if no other is specified + */ + Uid + } + + private final boolean reverse; + private final SortClause sortClause; + + public Sort(SortClause sortClause, boolean reverse) { + this.reverse = reverse; + this.sortClause = sortClause; + } + + /** + * Create a new {@link Sort} which is NOT {@link #reverse} + * + * @param sortClause + */ + public Sort(SortClause sortClause) { + this(sortClause, false); + } + + /** + * Return true if the sort should be in reverse order + * + * @return reverse + */ + public boolean isReverse() { + return reverse; + } + + /** + * Return the {@link SortClause} + * + * @return clause + */ + public SortClause getSortClause() { + return sortClause; + } + } + + /** + * Creates a filter for message size less than the given value + * + * @param value + * messages with size less than this value will be selected by + * the returned criterion + * @return Criterion, not null + */ + public static final Criterion sizeLessThan(long value) { + return new SizeCriterion(new NumericOperator(value, NumericComparator.LESS_THAN)); + } + + /** + * Creates a filter for message size greater than the given value + * + * @param value + * messages with size greater than this value will be selected by + * the returned criterion + * @return Criterion, not null + */ + public static final Criterion sizeGreaterThan(long value) { + return new SizeCriterion(new NumericOperator(value, NumericComparator.GREATER_THAN)); + } + + /** + * Creates a filter for message size equal to the given value + * + * @param value + * messages with size equal to this value will be selected by the + * returned criterion + * @return Criterion, not null + */ + public static final Criterion sizeEquals(long value) { + return new SizeCriterion(new NumericOperator(value, NumericComparator.EQUALS)); + } + + /** + * Creates a filter for message mod-sequence less than the given value + * + * @param value + * messages with mod-sequence less than this value will be + * selected by the returned criterion + * @return Criterion, not null + */ + public static final Criterion modSeqLessThan(long value) { + return new ModSeqCriterion(new NumericOperator(value, NumericComparator.LESS_THAN)); + } + + /** + * Creates a filter for message mod-sequence greater than the given value + * + * @param value + * messages with mod-sequence greater than this value will be + * selected by the returned criterion + * @return Criterion, not null + */ + public static final Criterion modSeqGreaterThan(long value) { + return new ModSeqCriterion(new NumericOperator(value, NumericComparator.GREATER_THAN)); + } + + /** + * Creates a filter for message mod-sequence equal to the given value + * + * @param value + * messages with mod-sequence equal to this value will be + * selected by the returned criterion + * @return Criterion, not null + */ + public static final Criterion modSeqEquals(long value) { + return new ModSeqCriterion(new NumericOperator(value, NumericComparator.EQUALS)); + } + + /** + * Creates a filter matching messages with internal date after the given + * date. + * + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion internalDateAfter(Date date, DateResolution res) { + return new InternalDateCriterion(new DateOperator(DateComparator.AFTER, date, res)); + } + + /** + * Creates a filter matching messages with internal date on the given date. + * + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion internalDateOn(Date date, DateResolution res) { + return new InternalDateCriterion(new DateOperator(DateComparator.ON, date, res)); + } + + /** + * Creates a filter matching messages with internal date before the given + * date. + * + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion internalDateBefore(Date date, DateResolution res) { + return new InternalDateCriterion(new DateOperator(DateComparator.BEFORE, date, res)); + } + + /** + * Creates a filter matching messages with the date of the given header + * after the given date. If the header's value is not a date then it will + * not be included. + * + * @param headerName + * name of the header whose value will be compared, not null + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion headerDateAfter(String headerName, Date date, DateResolution res) { + return new HeaderCriterion(headerName, new DateOperator(DateComparator.AFTER, date, res)); + } + + /** + * Creates a filter matching messages with the date of the given header on + * the given date. If the header's value is not a date then it will not be + * included. + * + * @param headerName + * name of the header whose value will be compared, not null + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion headerDateOn(String headerName, Date date, DateResolution res) { + return new HeaderCriterion(headerName, new DateOperator(DateComparator.ON, date, res)); + } + + /** + * Creates a filter matching messages with the date of the given header + * before the given date. If the header's value is not a date then it will + * not be included. + * + * @param headerName + * name of the header whose value will be compared, not null + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion headerDateBefore(String headerName, Date date, DateResolution res) { + return new HeaderCriterion(headerName, new DateOperator(DateComparator.BEFORE, date, res)); + } + + /** + * Creates a filter matching messages whose Address header contains the + * given address. The address header of the message MUST get canonicalized + * before try to match it. + * + * @param type + * @param address + * @return Criterion + */ + public static final Criterion address(AddressType type, String address) { + return new HeaderCriterion(type.name(), new AddressOperator(address)); + } + + /** + * Creates a filter matching messages whose header value contains the given + * value. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param headerName + * name of the header whose value will be compared, not null + * @param value + * when null or empty the existance of the header will be + * checked, otherwise contained value + * @return Criterion, not null + */ + public static final Criterion headerContains(String headerName, String value) { + if (value == null || value.length() == 0) { + return headerExists(headerName); + } else { + return new HeaderCriterion(headerName, new ContainsOperator(value)); + } + } + + /** + * Creates a filter matching messages with a header matching the given name. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param headerName + * name of the header whose value will be compared, not null + * @return Criterion, not null + */ + public static final Criterion headerExists(String headerName) { + return new HeaderCriterion(headerName, ExistsOperator.exists()); + } + + /** + * Creates a filter matching messages which contains the given text either + * within the body or in the headers. Implementations may choose to ignore + * mime parts which cannot be decoded to text. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param value + * search value + * @return Criterion, not null + */ + public static final Criterion mailContains(String value) { + return new TextCriterion(value, Scope.FULL); + } + + /** + * Creates a filter matching messages which contains the given text within + * the body. Implementations may choose to ignore mime parts which cannot be + * decoded to text. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param value + * search value + * @return Criterion, not null + */ + public static final Criterion bodyContains(String value) { + return new TextCriterion(value, Scope.BODY); + } + + /** + * Creates a filter matching messages within any of the given ranges. + * + * @param range + * NumericRange's, not null + * @return Criterion, not null + */ + public static final Criterion uid(NumericRange[] range) { + return new UidCriterion(range); + } + + /** + * Creates a filter composing the two different criteria. + * + * @param one + * Criterion, not null + * @param two + * Criterion, not null + * @return Criterion, not null + */ + public static final Criterion or(Criterion one, Criterion two) { + final List criteria = new ArrayList(); + criteria.add(one); + criteria.add(two); + return new ConjunctionCriterion(Conjunction.OR, criteria); + } + + /** + * Creates a filter composing the two different criteria. + * + * @param one + * Criterion, not null + * @param two + * Criterion, not null + * @return Criterion, not null + */ + public static final Criterion and(Criterion one, Criterion two) { + final List criteria = new ArrayList(); + criteria.add(one); + criteria.add(two); + return new ConjunctionCriterion(Conjunction.AND, criteria); + } + + /** + * Creates a filter composing the listed criteria. + * + * @param criteria + * List of {@link Criterion} + * @return Criterion, not null + */ + public static final Criterion and(List criteria) { + return new ConjunctionCriterion(Conjunction.AND, criteria); + } + + /** + * Creates a filter inverting the given criteria. + * + * @param criterion + * Criterion, not null + * @return Criterion, not null + */ + public static final Criterion not(Criterion criterion) { + final List criteria = new ArrayList(); + criteria.add(criterion); + return new ConjunctionCriterion(Conjunction.NOR, criteria); + } + + /** + * Creates a filter on the given flag. + * + * @param flag + * Flag, not null + * @param isSet + * true if the messages with the flag set should be matched, + * false otherwise + * @return Criterion, not null + */ + public static final Criterion flagSet(final Flag flag, final boolean isSet) { + final Criterion result; + if (isSet) { + result = flagIsSet(flag); + } else { + result = flagIsUnSet(flag); + } + return result; + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsSet(final Flag flag) { + return new FlagCriterion(flag, BooleanOperator.set()); + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is not selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsUnSet(final Flag flag) { + return new FlagCriterion(flag, BooleanOperator.unset()); + } + + /** + * Creates a filter on the given flag. + * + * @param flag + * Flag, not null + * @param isSet + * true if the messages with the flag set should be matched, + * false otherwise + * @return Criterion, not null + */ + public static final Criterion flagSet(final String flag, final boolean isSet) { + final Criterion result; + if (isSet) { + result = flagIsSet(flag); + } else { + result = flagIsUnSet(flag); + } + return result; + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsSet(final String flag) { + return new CustomFlagCriterion(flag, BooleanOperator.set()); + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is not selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsUnSet(final String flag) { + return new CustomFlagCriterion(flag, BooleanOperator.unset()); + } + + /** + * Creates a filter matching all messages. + * + * @return Criterion, not null + */ + public static final Criterion all() { + return AllCriterion.all(); + } + + private final Set recentMessageUids = new HashSet(); + + private final List criterias = new ArrayList(); + + private List sorts = new ArrayList(Arrays.asList(new Sort(Sort.SortClause.Uid, false))); + + public void andCriteria(Criterion crit) { + criterias.add(crit); + } + + public List getCriterias() { + return criterias; + } + + /** + * Set the {@link Sort}'s to use + * + * @param sorts + */ + public void setSorts(List sorts) { + if (sorts == null || sorts.isEmpty()) + throw new IllegalArgumentException("There must be at least one Sort"); + this.sorts = sorts; + } + + /** + * Return the {@link Sort}'s which should get used for sorting the result. + * They get "executed" in a chain, if the first does not give an result the + * second will get executed etc. + * + * If not set via {@link #setSorts(List)} it will sort via + * {@link Sort.SortClause#Uid} + * + * @return sorts + */ + public List getSorts() { + return sorts; + } + + /** + * Gets the UIDS of messages which are recent for this client session. The + * list of recent mail is maintained in the protocol layer since the + * mechanics are protocol specific. + * + * @return mutable Set of Long UIDS + */ + public Set getRecentMessageUids() { + return recentMessageUids; + } + + /** + * Adds all the uids to the collection of recents. + * + * @param uids + * not null + */ + public void addRecentMessageUids(final Collection uids) { + recentMessageUids.addAll(uids); + } + + @Override + public String toString() { + return "Search:" + criterias.toString(); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((criterias == null) ? 0 : criterias.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SearchQuery other = (SearchQuery) obj; + if (criterias == null) { + if (other.criterias != null) + return false; + } else if (!criterias.equals(other.criterias)) + return false; + return true; + } + + /** + * Numbers within a particular range. Range includes both high and low + * boundaries. May be a single value. {@link Long#MAX_VALUE} represents + * unlimited in either direction. + */ + public static final class NumericRange implements Serializable { + private static final long serialVersionUID = 1L; + + private final long lowValue; + + private final long highValue; + + public NumericRange(final long value) { + super(); + this.lowValue = value; + this.highValue = value; + } + + public NumericRange(final long lowValue, final long highValue) { + super(); + this.lowValue = lowValue; + this.highValue = highValue; + } + + public long getHighValue() { + return highValue; + } + + public long getLowValue() { + return lowValue; + } + + /** + * Is the given value in this range? + * + * @param value + * value to be tested + * @return true if the value is in range, false otherwise + */ + public boolean isIn(long value) { + if (lowValue == Long.MAX_VALUE) { + return highValue >= value; + } + return lowValue <= value && highValue >= value; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (highValue ^ (highValue >>> 32)); + result = PRIME * result + (int) (lowValue ^ (lowValue >>> 32)); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final NumericRange other = (NumericRange) obj; + if (highValue != other.highValue) + return false; + if (lowValue != other.lowValue) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + return new StringBuffer().append(this.lowValue).append("->").append(this.highValue).toString(); + } + + } + + /** + * Marker superclass for criteria. + */ + public static abstract class Criterion implements Serializable { + private static final long serialVersionUID = 1L; + + } + + public enum Conjunction { + AND, OR, NOR + } + + /** + * Conjunction applying to the contained criteria. {@link #getType} + * indicates how the conjoined criteria should be related. + */ + public static final class ConjunctionCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final Conjunction type; + + private final List criteria; + + public ConjunctionCriterion(final Conjunction type, final List criteria) { + super(); + this.type = type; + this.criteria = criteria; + } + + /** + * Gets the criteria related through this conjunction. + * + * @return List of {@link Criterion} + */ + public List getCriteria() { + return criteria; + } + + /** + * Gets the type of conjunction. + * + * @return not null + */ + public Conjunction getType() { + return type; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((criteria == null) ? 0 : criteria.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final ConjunctionCriterion other = (ConjunctionCriterion) obj; + if (criteria == null) { + if (other.criteria != null) + return false; + } else if (!criteria.equals(other.criteria)) + return false; + if (type != other.type) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("ConjunctionCriterion ( ").append("criteria = ").append(this.criteria).append(TAB) + .append("type = ").append(this.type).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Any message. + */ + public static final class AllCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private static final AllCriterion ALL = new AllCriterion(); + + private static Criterion all() { + return ALL; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + return obj instanceof AllCriterion; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 1729; + } + + public String toString() { + return "AllCriterion"; + } + } + + public enum Scope { + /** Only message body content */ + BODY, + + /** Full message content including headers */ + FULL + } + + /** + * Message text. + */ + public static final class TextCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final Scope type; + + private final ContainsOperator operator; + + private TextCriterion(final String value, final Scope type) { + super(); + this.operator = new ContainsOperator(value); + this.type = type; + } + + /** + * Gets the type of text to be searched. + * + * @return not null + */ + public Scope getType() { + return type; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the Operator, not null + */ + public ContainsOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final TextCriterion other = (TextCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + if (type != other.type) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("TextCriterion ( ").append("operator = ").append(this.operator).append(TAB) + .append("type = ").append(this.type).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Header value content search. + */ + public static final class HeaderCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final HeaderOperator operator; + + private final String headerName; + + private HeaderCriterion(final String headerName, final HeaderOperator operator) { + super(); + this.operator = operator; + this.headerName = headerName; + } + + /** + * Gets the name of the header whose value is to be searched. + * + * @return the headerName + */ + public String getHeaderName() { + return headerName; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the Operator, not null + */ + public HeaderOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((headerName == null) ? 0 : headerName.hashCode()); + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final HeaderCriterion other = (HeaderCriterion) obj; + if (headerName == null) { + if (other.headerName != null) + return false; + } else if (!headerName.equals(other.headerName)) + return false; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("HeaderCriterion ( ").append("headerName = ").append(this.headerName).append(TAB) + .append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Filters on the internal date. + */ + public static final class InternalDateCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final DateOperator operator; + + public InternalDateCriterion(final DateOperator operator) { + super(); + this.operator = operator; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the Operator, not null + */ + public DateOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final InternalDateCriterion other = (InternalDateCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("InternalDateCriterion ( ").append("operator = ").append(this.operator).append(TAB) + .append(" )"); + + return retValue.toString(); + } + } + + /** + * Filters on the mod-sequence of the messages. + */ + public static final class ModSeqCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final NumericOperator operator; + + private ModSeqCriterion(final NumericOperator operator) { + super(); + this.operator = operator; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the NumericOperator, not null + */ + public NumericOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final ModSeqCriterion other = (ModSeqCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("SizeCriterion ( ").append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + public static final class SizeCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final NumericOperator operator; + + private SizeCriterion(final NumericOperator operator) { + super(); + this.operator = operator; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the NumericOperator, not null + */ + public NumericOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SizeCriterion other = (SizeCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("SizeCriterion ( ").append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Filters on a custom flag valuation. + */ + public static final class CustomFlagCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final String flag; + + private final BooleanOperator operator; + + private CustomFlagCriterion(final String flag, final BooleanOperator operator) { + super(); + this.flag = flag; + this.operator = operator; + } + + /** + * Gets the custom flag to be search. + * + * @return the flag name, not null + */ + public String getFlag() { + return flag; + } + + /** + * Gets the value to be tested. + * + * @return the BooleanOperator, not null + */ + public BooleanOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((flag == null) ? 0 : flag.hashCode()); + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final CustomFlagCriterion other = (CustomFlagCriterion) obj; + if (flag == null) { + if (other.flag != null) + return false; + } else if (!flag.equals(other.flag)) + return false; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("CustomFlagCriterion ( ").append("flag = ").append(this.flag).append(TAB) + .append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Filters on a standard flag. + */ + public static final class FlagCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + // Flags not Flag because Flags are serializable and Flag is not + private final Flags flag; + private final BooleanOperator operator; + + private FlagCriterion(final Flag flag, final BooleanOperator operator) { + super(); + this.flag = new Flags(flag); + this.operator = operator; + } + + /** + * Gets the flag filtered on. + * + * @return the flag, not null + */ + public Flag getFlag() { + // safe because the Flags(Flag) does system flags, + // and James code also constructs FlagCriterion only with system flags + return flag.getSystemFlags()[0]; + } + + /** + * Gets the test to be preformed. + * + * @return the BooleanOperator, not null + */ + public BooleanOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((flag == null) ? 0 : flag.hashCode()); + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final FlagCriterion other = (FlagCriterion) obj; + if (flag == null) { + if (other.flag != null) + return false; + } else if (!flag.equals(other.flag)) + return false; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("FlagCriterion ( ").append("flag = ").append(this.flag).append(TAB).append("operator = ") + .append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Filters on message identity. + */ + public static final class UidCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final InOperator operator; + + public UidCriterion(final NumericRange[] ranges) { + super(); + this.operator = new InOperator(ranges); + } + + /** + * Gets the filtering operation. + * + * @return the InOperator + */ + public InOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final UidCriterion other = (UidCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("UidCriterion ( ").append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Search operator. + */ + public interface Operator extends Serializable { + } + + /** + * Marks operator as suitable for header value searching. + */ + public interface HeaderOperator extends Operator { + } + + public static final class AddressOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + private final String address; + + public AddressOperator(final String address) { + super(); + this.address = address; + } + + /** + * Gets the value to be searched for. + * + * @return the value + */ + public String getAddress() { + return address; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((address == null) ? 0 : address.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final AddressOperator other = (AddressOperator) obj; + if (address == null) { + if (other.address != null) + return false; + } else if (!address.equals(other.address)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("AdressOperator ( ").append("address = ").append(this.address).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Contained value search. + */ + public static final class ContainsOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + private final String value; + + public ContainsOperator(final String value) { + super(); + this.value = value; + } + + /** + * Gets the value to be searched for. + * + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final ContainsOperator other = (ContainsOperator) obj; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("ContainsOperator ( ").append("value = ").append(this.value).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Existance search. + */ + public static final class ExistsOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + private static final ExistsOperator EXISTS = new ExistsOperator(); + + public static ExistsOperator exists() { + return EXISTS; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + return obj instanceof ExistsOperator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 42; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ExistsCriterion"; + } + + } + + /** + * Boolean value search. + */ + public static final class BooleanOperator implements Operator { + private static final long serialVersionUID = 1L; + + private static final BooleanOperator SET = new BooleanOperator(true); + + private static final BooleanOperator UNSET = new BooleanOperator(false); + + public static BooleanOperator set() { + return SET; + } + + public static BooleanOperator unset() { + return UNSET; + } + + private final boolean set; + + private BooleanOperator(final boolean set) { + super(); + this.set = set; + } + + /** + * Is the search for set? + * + * @return true indicates that set values should be selected, false + * indicates that unset values should be selected + */ + public boolean isSet() { + return set; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (set ? 1231 : 1237); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final BooleanOperator other = (BooleanOperator) obj; + if (set != other.set) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("BooleanOperator ( ").append("set = ").append(this.set).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + public enum NumericComparator { + EQUALS, LESS_THAN, GREATER_THAN + } + + /** + * Searches numeric values. + */ + public static final class NumericOperator implements Operator { + private static final long serialVersionUID = 1L; + + public static final int EQUALS = 1; + + public static final int LESS_THAN = 2; + + public static final int GREATER_THAN = 3; + + private final long value; + + private final NumericComparator type; + + private NumericOperator(final long value, final NumericComparator type) { + super(); + this.value = value; + this.type = type; + } + + /** + * Gets the operation type + * + * @return not null + */ + public NumericComparator getType() { + return type; + } + + /** + * Gets the value to be compared. + * + * @return the value + */ + public long getValue() { + return value; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (value ^ (value >>> 32)); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final NumericOperator other = (NumericOperator) obj; + if (type != other.type) + return false; + if (value != other.value) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("NumericOperator ( ").append("type = ").append(this.type).append(TAB).append("value = ") + .append(this.value).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + public enum DateComparator { + BEFORE, AFTER, ON + } + + /** + * Operates on a date. + */ + public static final class DateOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + public static final int BEFORE = 1; + + public static final int AFTER = 2; + + public static final int ON = 3; + + private final DateComparator type; + + private final Date date; + + private final DateResolution res; + + public DateOperator(final DateComparator type, final Date date, final DateResolution res) { + super(); + this.type = type; + this.date = date; + this.res = res; + } + + public Date getDate() { + return date; + } + + public DateResolution getDateResultion() { + return res; + } + + /** + * Gets the operator type. + * + * @return the type, either {@link DateComparator#BEFORE}, + * {@link DateComparator#AFTER} or {@link DateComparator#ON} + */ + public DateComparator getType() { + return type; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + // result = PRIME * result + (int)date.getTime(); + result = PRIME * result + type.hashCode(); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final DateOperator other = (DateOperator) obj; + // if (date != other.date) + // return false; + if (res != other.res) + return false; + if (type != other.type) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("DateOperator ( ").append("date = ").append(date.toString()).append(TAB).append("res = ") + .append(this.res.name()).append(TAB).append("type = ").append(this.type).append(TAB).append(TAB) + .append(" )"); + + return retValue.toString(); + } + + } + + /** + * Search for numbers within set of ranges. + */ + public static final class InOperator implements Operator { + private static final long serialVersionUID = 1L; + + private final NumericRange[] range; + + public InOperator(final NumericRange[] range) { + super(); + this.range = range; + } + + /** + * Gets the filtering ranges. Values falling within these ranges will be + * selected. + * + * @return the NumericRange's search on, not null + */ + public NumericRange[] getRange() { + return range; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return range.length; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final InOperator other = (InOperator) obj; + if (!Arrays.equals(range, other.range)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("InOperator ( ").append("range = ").append(Arrays.toString(this.range)).append(TAB) + .append(" )"); + + return retValue.toString(); + } + + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACL.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACL.java.svn-base new file mode 100644 index 0000000..28b200c --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACL.java.svn-base @@ -0,0 +1,1069 @@ +/**************************************************************** + * 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.model; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; + +/** + * Default implementation of {@link MailboxACL}. + * + */ +public class SimpleMailboxACL implements MailboxACL { + + /** + * Supports only the Standard Rights of RFC 4314 section 2.1. The rights are + * stored as single bits in 32 bit int {@link #value} field. + */ + public static class Rfc4314Rights implements MailboxACLRights { + /** + * See RFC 4314 section 2.1.1. Obsolete Rights. + */ + public enum CompatibilityMode { + ck_detx, ckx_det, NO_COMPATIBILITY + } + + private class Rfc4314RightsIterator implements Iterator { + + int position = 0; + + public Rfc4314RightsIterator() { + super(); + nextPostion(); + } + + @Override + public boolean hasNext() { + return position < FIELD_COUNT; + } + + @Override + public MailboxACLRight next() { + if (!hasNext()) { + throw new IndexOutOfBoundsException("No next element at position " + position + " from " + FIELD_COUNT + " in " + Rfc4314RightsIterator.class.getName()); + } + MailboxACLRight result = indexRightLookup[position]; + position++; + nextPostion(); + return result; + } + + /** + */ + private void nextPostion() { + while (position < FIELD_COUNT && (value & (1 << position)) == 0) { + position++; + } + } + + @Override + public void remove() { + throw new java.lang.UnsupportedOperationException("Cannot remove rights through this " + Rfc4314RightsIterator.class.getName()); + } + + } + + /** + * a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) + * + */ + public static final char a_Administer = 'a'; + + static final int a_Administer_MASK = 1; + public static final MailboxACLRight a_Administer_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(a_Administer); + public static final char c_ObsoleteCreate = 'c'; + public static final char d_ObsoleteDelete = 'd'; + /** + * e - perform EXPUNGE and expunge as a part of CLOSE + * + */ + public static final char e_PerformExpunge = 'e'; + static final int e_PerformExpunge_MASK = 1 << 1; + public static final MailboxACLRight e_PerformExpunge_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(e_PerformExpunge); + public static final int EMPTY_MASK = 0; + public static final int FIELD_COUNT = 11; + /** + * i - insert (perform APPEND, COPY into mailbox) + * + */ + public static final char i_Insert = 'i'; + static final int i_Insert_MASK = 1 << 2; + + public static final MailboxACLRight i_Insert_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(i_Insert); + private static final char[] indexFlagLookup; + private static final MailboxACLRight[] indexRightLookup; + /** + * k - create mailboxes (CREATE new sub-mailboxes in any + * implementation-defined hierarchy, parent mailbox for the new mailbox + * name in RENAME) + * + */ + public static final char k_CreateMailbox = 'k'; + static final int k_CreateMailbox_MASK = 1 << 3; + public static final MailboxACLRight k_CreateMailbox_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(k_CreateMailbox); + /** + * l - lookup (mailbox is visible to LIST/LSUB commands, SUBSCRIBE + * mailbox) + * + */ + public static final char l_Lookup = 'l'; + static final int l_Lookup_MASK = 1 << 4; + public static final MailboxACLRight l_Lookup_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(l_Lookup); + /** + * p - post (send mail to submission address for mailbox, not enforced + * by IMAP4 itself) + * + */ + public static final char p_Post = 'p'; + static final int p_Post_MASK = 1 << 5; + public static final MailboxACLRight p_Post_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(p_Post); + + /** + * r - read (SELECT the mailbox, perform STATUS) + * + */ + public static final char r_Read = 'r'; + static final int r_Read_MASK = 1 << 6; + + public static final MailboxACLRight r_Read_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(r_Read); + /** + * s - keep seen/unseen information across sessions (set or clear \SEEN + * flag via STORE, also set \SEEN during APPEND/COPY/ FETCH BODY[...]) + * + */ + public static final char s_WriteSeenFlag = 's'; + + static final int s_WriteSeenFlag_MASK = 1 << 7; + + public static final MailboxACLRight s_WriteSeenFlag_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(s_WriteSeenFlag); + + public static final char t_DeleteMessages = 't'; + + /** + * t - delete messages (set or clear \DELETED flag via STORE, set + * \DELETED flag during APPEND/COPY) + * + */ + static final int t_DeleteMessages_MASK = 1 << 8; + public static final MailboxACLRight t_DeleteMessages_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(t_DeleteMessages); + /** + * w - write (set or clear flags other than \SEEN and \DELETED via + * STORE, also set them during APPEND/COPY) + * + */ + public static final char w_Write = 'w'; + static final int w_Write_MASK = 1 << 9; + public static final MailboxACLRight w_Write_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(w_Write); + /** + * x - delete mailbox (DELETE mailbox, old mailbox name in RENAME) + * + */ + public static final char x_DeleteMailbox = 'x'; + static final int x_DeleteMailbox_MASK = 1 << 10; + public static final MailboxACLRight x_DeleteMailbox_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(x_DeleteMailbox); + static { + indexFlagLookup = new char[] { a_Administer, e_PerformExpunge, i_Insert, k_CreateMailbox, l_Lookup, p_Post, r_Read, s_WriteSeenFlag, t_DeleteMessages, w_Write, x_DeleteMailbox }; + indexRightLookup = new MailboxACLRight[] { a_Administer_RIGHT, e_PerformExpunge_RIGHT, i_Insert_RIGHT, k_CreateMailbox_RIGHT, l_Lookup_RIGHT, p_Post_RIGHT, r_Read_RIGHT, s_WriteSeenFlag_RIGHT, t_DeleteMessages_RIGHT, w_Write_RIGHT, x_DeleteMailbox_RIGHT }; + } + + private static int flagMaskLookup(char flag) throws UnsupportedRightException { + switch (flag) { + case a_Administer: + return a_Administer_MASK; + case e_PerformExpunge: + return e_PerformExpunge_MASK; + case i_Insert: + return i_Insert_MASK; + case k_CreateMailbox: + return k_CreateMailbox_MASK; + case l_Lookup: + return l_Lookup_MASK; + case p_Post: + return p_Post_MASK; + case r_Read: + return r_Read_MASK; + case s_WriteSeenFlag: + return s_WriteSeenFlag_MASK; + case t_DeleteMessages: + return t_DeleteMessages_MASK; + case w_Write: + return w_Write_MASK; + case x_DeleteMailbox: + return x_DeleteMailbox_MASK; + default: + throw new UnsupportedRightException(flag); + } + } + + /** + * See RFC 4314 section 2.1.1. Obsolete Rights. + */ + private CompatibilityMode compatibilityMode = CompatibilityMode.ckx_det; + + /** + * 32 bit int to store the rights. + */ + private final int value; + + private Rfc4314Rights() { + this.value = EMPTY_MASK; + } + + public Rfc4314Rights(boolean canAdminister, boolean canCreateMailbox, boolean canDeleteMailbox, boolean canDeleteMessages, boolean canInsert, boolean canLookup, boolean canPerformExpunge, boolean canPost, boolean canRead, boolean canWrite, boolean canWriteSeenFlag) { + super(); + int v = 0; + + if (canAdminister) { + v |= a_Administer_MASK; + } + if (canCreateMailbox) { + v |= k_CreateMailbox_MASK; + } + if (canDeleteMailbox) { + v |= x_DeleteMailbox_MASK; + } + if (canDeleteMessages) { + v |= t_DeleteMessages_MASK; + } + if (canInsert) { + v |= i_Insert_MASK; + } + if (canLookup) { + v |= l_Lookup_MASK; + } + if (canPerformExpunge) { + v |= e_PerformExpunge_MASK; + } + if (canPost) { + v |= p_Post_MASK; + } + if (canRead) { + v |= r_Read_MASK; + } + if (canWrite) { + v |= w_Write_MASK; + } + if (canWriteSeenFlag) { + v |= s_WriteSeenFlag_MASK; + } + + this.value = v; + + } + + public Rfc4314Rights(int value) throws UnsupportedRightException { + if ((value >> FIELD_COUNT) != 0) { + throw new UnsupportedRightException(); + } + this.value = value; + } + + public Rfc4314Rights(MailboxACLRight right) throws UnsupportedRightException { + this.value = flagMaskLookup(right.getValue()); + } + + public Rfc4314Rights(String serializedRfc4314Rights) throws UnsupportedRightException { + int v = 0; + + for (int i = 0; i < serializedRfc4314Rights.length(); i++) { + char flag = serializedRfc4314Rights.charAt(i); + switch (flag) { + case c_ObsoleteCreate: + switch (compatibilityMode) { + case ck_detx: + v |= k_CreateMailbox_MASK; + break; + case ckx_det: + v |= k_CreateMailbox_MASK; + v |= x_DeleteMailbox_MASK; + break; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + break; + case d_ObsoleteDelete: + switch (compatibilityMode) { + case ck_detx: + v |= e_PerformExpunge_MASK; + v |= t_DeleteMessages_MASK; + v |= x_DeleteMailbox_MASK; + break; + case ckx_det: + v |= e_PerformExpunge_MASK; + v |= t_DeleteMessages_MASK; + break; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + break; + default: + v |= flagMaskLookup(flag); + } + } + this.value = v; + + } + + public boolean contains(char flag) throws UnsupportedRightException { + + switch (flag) { + case c_ObsoleteCreate: + switch (compatibilityMode) { + case ck_detx: + return (value & k_CreateMailbox_MASK) != 0; + case ckx_det: + return (value & (k_CreateMailbox_MASK | x_DeleteMailbox_MASK)) != 0; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + case d_ObsoleteDelete: + switch (compatibilityMode) { + case ck_detx: + return (value & (e_PerformExpunge_MASK | t_DeleteMessages_MASK | x_DeleteMailbox_MASK)) != 0; + case ckx_det: + return (value & (e_PerformExpunge_MASK | t_DeleteMessages_MASK)) != 0; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + default: + return (value | flagMaskLookup(flag)) != 0; + } + } + + /** + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#contains(org + * .apache.james.mailbox.MailboxACL.MailboxACLRight) + */ + @Override + public boolean contains(MailboxACLRight right) throws UnsupportedRightException { + return contains(right.getValue()); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Rfc4314Rights) { + return this.value == ((Rfc4314Rights) o).value; + } else if (o instanceof MailboxACLRights) { + try { + return this.value == new Rfc4314Rights(((MailboxACLRights) o).serialize()).value; + } catch (UnsupportedRightException e) { + throw new RuntimeException(e); + } + } else { + return false; + } + } + + /** + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#except(org.apache + * .james.mailbox.MailboxACL.MailboxACLRights) + */ + @Override + public MailboxACLRights except(MailboxACLRights toRemove) throws UnsupportedRightException { + if (this.value == EMPTY_MASK || toRemove == null || toRemove.isEmpty()) { + /* nothing to remove */ + return this; + } else if (toRemove instanceof Rfc4314Rights) { + Rfc4314Rights other = (Rfc4314Rights) toRemove; + if (other.value == EMPTY_MASK) { + /* toRemove is an identity element */ + return this; + } else { + return new Rfc4314Rights(this.value & (~((other).value))); + } + } else { + return new Rfc4314Rights(this.value & (~(new Rfc4314Rights(toRemove.serialize()).value))); + } + } + + public int getValue() { + return value; + } + + /** + * Returns {@link #value}. + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value; + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL.MailboxACLRights#isEmpty() + */ + @Override + public boolean isEmpty() { + return value == EMPTY_MASK; + } + + /** + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#isSupported( + * org.apache.james.mailbox.MailboxACL.MailboxACLRight) + */ + @Override + public boolean isSupported(MailboxACLRight right) { + try { + contains(right.getValue()); + return true; + } catch (UnsupportedRightException e) { + return false; + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return new Rfc4314RightsIterator(); + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MailboxACL.MailboxACLRights#serialize() + */ + @Override + public String serialize() { + StringBuilder result = new StringBuilder(FIELD_COUNT); + for (int i = 0; i < FIELD_COUNT; i++) { + if ((value & (1 << i)) != 0) { + result.append(indexFlagLookup[i]); + } + } + return result.toString(); + } + + /** + * Returns {@link #serialize()} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return serialize(); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#union(org.apache + * .james.mailbox.MailboxACL.MailboxACLRights) + */ + @Override + public MailboxACLRights union(MailboxACLRights toAdd) throws UnsupportedRightException { + if (this.value == EMPTY_MASK) { + /* this is an identity element */ + return toAdd; + } else if (toAdd instanceof Rfc4314Rights) { + Rfc4314Rights other = (Rfc4314Rights) toAdd; + if (other.value == EMPTY_MASK) { + /* toAdd is an identity element */ + return this; + } else { + return new Rfc4314Rights(this.value | other.value); + } + } else { + return new Rfc4314Rights(this.value | new Rfc4314Rights(toAdd.serialize()).value); + } + } + + } + + /** + * A utility implementation of + * {@code Map.Entry}. + */ + public static class SimpleMailboxACLEntry implements Map.Entry { + private final MailboxACLEntryKey key; + + private final MailboxACLRights value; + + public SimpleMailboxACLEntry(MailboxACLEntryKey key, MailboxACLRights value) { + super(); + this.key = key; + this.value = value; + } + public SimpleMailboxACLEntry(String key, String value) throws UnsupportedRightException { + this(new SimpleMailboxACLEntryKey(key), new Rfc4314Rights(value)); + } + + /* + * (non-Javadoc) + * + * @see java.util.Map.Entry#getKey() + */ + @Override + public MailboxACLEntryKey getKey() { + return key; + } + + /* + * (non-Javadoc) + * + * @see java.util.Map.Entry#getValue() + */ + @Override + public MailboxACLRights getValue() { + return value; + } + + /** + * Unsupported. + * + * @see java.util.Map.Entry#setValue(java.lang.Object) + */ + @Override + public MailboxACLRights setValue(MailboxACLRights value) { + throw new java.lang.UnsupportedOperationException("Fields of " + MailboxACLRights.class.getName() + " are read only."); + } + + } + + /** + * Default implementation of {@link MailboxACLEntryKey}. + */ + public static class SimpleMailboxACLEntryKey implements MailboxACLEntryKey { + public static SimpleMailboxACLEntryKey createGroup(String name) { + return new SimpleMailboxACLEntryKey(name, NameType.group, false); + } + + public static SimpleMailboxACLEntryKey createGroup(String name, boolean negative) { + return new SimpleMailboxACLEntryKey(name, NameType.group, negative); + } + + public static SimpleMailboxACLEntryKey createUser(String name) { + return new SimpleMailboxACLEntryKey(name, NameType.user, false); + } + + public static SimpleMailboxACLEntryKey createUser(String name, boolean negative) { + return new SimpleMailboxACLEntryKey(name, NameType.user, negative); + } + + private final int hash; + private final String name; + private final NameType nameType; + private final boolean negative; + + /** + * Creates a new instance of SimpleMailboxACLEntryKey from the given + * serialized {@link String}. It supposes that negative rights are + * marked with {@link MailboxACL#DEFAULT_NEGATIVE_MARKER} and that + * groups are marked with {@link MailboxACL#DEFAULT_GROUP_MARKER}. + * + * @param serialized + */ + public SimpleMailboxACLEntryKey(String serialized) { + + if (serialized == null) { + throw new IllegalStateException("Cannot parse null to a " + getClass().getName()); + } + if (serialized.length() == 0) { + throw new IllegalStateException("Cannot parse an empty string to a " + getClass().getName()); + } + int start = 0; + if (serialized.charAt(start) == DEFAULT_NEGATIVE_MARKER) { + negative = true; + start++; + } else { + negative = false; + } + if (serialized.charAt(start) == DEFAULT_GROUP_MARKER) { + nameType = NameType.group; + start++; + name = serialized.substring(start); + if (name.length() == 0) { + throw new IllegalStateException("Cannot parse a string with empty name to a " + getClass().getName()); + } + } else { + name = serialized.substring(start); + if (name.length() == 0) { + throw new IllegalStateException("Cannot parse a string with empty name to a " + getClass().getName()); + } + NameType nt = NameType.user; + for (SpecialName specialName : SpecialName.values()) { + if (specialName.name().equals(name)) { + nt = NameType.special; + break; + } + } + this.nameType = nt; + } + + this.hash = hash(); + + } + + public SimpleMailboxACLEntryKey(String name, NameType nameType, boolean negative) { + super(); + if (name == null) { + throw new NullPointerException("Provide a name for this " + getClass().getName()); + } + if (nameType == null) { + throw new NullPointerException("Provide a nameType for this " + getClass().getName()); + } + this.name = name; + this.nameType = nameType; + this.negative = negative; + this.hash = hash(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof MailboxACLEntryKey) { + MailboxACLEntryKey other = (MailboxACLEntryKey) o; + return this.name.equals(other.getName()) && this.nameType.equals(other.getNameType()) && this.negative == other.isNegative(); + } else { + return false; + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MailboxACL.MailboxACLEntryKey#getName() + */ + public String getName() { + return name; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLEntryKey#getNameType() + */ + public NameType getNameType() { + return nameType; + } + + private int hash() { + final int PRIME = 31; + int hash = negative ? 1 : 0; + hash = PRIME * hash + nameType.hashCode(); + hash = PRIME * hash + name.hashCode(); + return hash; + } + + @Override + public int hashCode() { + return hash; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLEntryKey#isNegative() + */ + public boolean isNegative() { + return negative; + } + + /** + * Serializes this {@link SimpleMailboxACLEntryKey} using + * {@link MailboxACL#DEFAULT_NEGATIVE_MARKER} and + * {@link MailboxACL#DEFAULT_GROUP_MARKER}. + * + * @see org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey#serialize() + */ + @Override + public String serialize() { + if (!negative) { + switch (nameType) { + case special: + case user: + return name; + case group: + return new StringBuilder(name.length() + 1).append(DEFAULT_GROUP_MARKER).append(name).toString(); + default: + throw new IllegalStateException(); + } + } else { + StringBuilder result = new StringBuilder(name.length() + 2).append(DEFAULT_NEGATIVE_MARKER); + switch (nameType) { + case special: + case user: + break; + case group: + result.append(DEFAULT_GROUP_MARKER); + break; + default: + throw new IllegalStateException(); + } + return result.append(name).toString(); + } + } + + @Override + public String toString() { + return serialize(); + } + + } + + /** + * Default implementation of {@link MailboxACLRight}. + */ + public static final class SimpleMailboxACLRight implements MailboxACLRight { + private final char value; + + public SimpleMailboxACLRight(char value) { + super(); + this.value = value; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (o instanceof MailboxACLRight) { + return ((MailboxACLRight) o).getValue() == this.value; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MailboxACL.MailboxACLRight#getValue() + */ + @Override + public char getValue() { + return value; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return (int) value; + } + + /** + * Returns String.valueOf(value). + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.valueOf(value); + } + + } + + public static final MailboxACLEntryKey ANYBODY_KEY; + public static final MailboxACLEntryKey ANYBODY_NEGATIVE_KEY; + public static final MailboxACLEntryKey AUTHENTICATED_KEY; + public static final MailboxACLEntryKey AUTHENTICATED_NEGATIVE_KEY; + public static final MailboxACL EMPTY; + + public static final MailboxACLRights FULL_RIGHTS; + + public static final MailboxACLRights NO_RIGHTS; + public static final MailboxACL OWNER_FULL_ACL; + public static final MailboxACL OWNER_FULL_EXCEPT_ADMINISTRATION_ACL; + + public static final MailboxACLEntryKey OWNER_KEY; + public static final MailboxACLEntryKey OWNER_NEGATIVE_KEY; + + static { + try { + ANYBODY_KEY = new SimpleMailboxACLEntryKey(SpecialName.anybody.name(), NameType.special, false); + ANYBODY_NEGATIVE_KEY = new SimpleMailboxACLEntryKey(SpecialName.anybody.name(), NameType.special, true); + AUTHENTICATED_KEY = new SimpleMailboxACLEntryKey(SpecialName.authenticated.name(), NameType.special, false); + AUTHENTICATED_NEGATIVE_KEY = new SimpleMailboxACLEntryKey(SpecialName.authenticated.name(), NameType.special, true); + EMPTY = new SimpleMailboxACL(); + FULL_RIGHTS = new Rfc4314Rights(true, true, true, true, true, true, true, true, true, true, true); + NO_RIGHTS = new Rfc4314Rights(); + OWNER_KEY = new SimpleMailboxACLEntryKey(SpecialName.owner.name(), NameType.special, false); + OWNER_NEGATIVE_KEY = new SimpleMailboxACLEntryKey(SpecialName.owner.name(), NameType.special, true); + OWNER_FULL_ACL = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS) }); + OWNER_FULL_EXCEPT_ADMINISTRATION_ACL = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS.except(new Rfc4314Rights(Rfc4314Rights.a_Administer_MASK))) }); + } catch (UnsupportedRightException e) { + throw new RuntimeException(e); + } + } + + private final Map entries; + + /** + * Creates a new instance of SimpleMailboxACL containing no entries. + * + */ + public SimpleMailboxACL() { + this.entries = Collections.emptyMap(); + } + + /** + * Creates a new instance of SimpleMailboxACL from the given array of + * entries. + * + * @param entries + */ + public SimpleMailboxACL(Map.Entry[] entries) { + if (entries != null) { + Map m = new HashMap(entries.length + entries.length / 2 + 1); + for (Entry en : entries) { + m.put(en.getKey(), en.getValue()); + } + this.entries = Collections.unmodifiableMap(m); + } else { + this.entries = Collections.emptyMap(); + } + } + + /** + * Creates a new instance of SimpleMailboxACL from the given {@link Map} of + * entries. + * + * @param entries + */ + public SimpleMailboxACL(Map entries) { + if (entries != null && entries.size() > 0) { + Map m = new HashMap(entries.size() + entries.size() / 2 + 1); + for (Entry en : entries.entrySet()) { + m.put(en.getKey(), en.getValue()); + } + this.entries = Collections.unmodifiableMap(m); + } else { + this.entries = Collections.emptyMap(); + } + } + + /** + * Creates a new instance of SimpleMailboxACL. + * unmodifiableEntries parameter is supposed to be umodifiable + * already. + * + * @param unmodifiableEntries + * @param dummy + * just to be different from {@link #SimpleMailboxACL(Map)}. + */ + private SimpleMailboxACL(Map unmodifiableEntries, boolean dummy) { + this.entries = unmodifiableEntries; + } + + /** + * Creates a new instance of SimpleMailboxACL from {@link Properties}. The + * keys and values from the props parameter are parsed by the + * {@link String} constructors of {@link SimpleMailboxACLEntryKey} and + * {@link Rfc4314Rights} respectively. + * + * @param props + * @throws UnsupportedRightException + */ + public SimpleMailboxACL(Properties props) throws UnsupportedRightException { + super(); + + Map m = new HashMap(props.size() + props.size() / 2 + 1); + + if (props != null) { + for (Map.Entry prop : props.entrySet()) { + m.put(new SimpleMailboxACLEntryKey((String) prop.getKey()), new Rfc4314Rights((String) prop.getValue())); + } + } + + entries = Collections.unmodifiableMap(m); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (o instanceof MailboxACL) { + MailboxACL acl = (MailboxACL) o; + Map ens = acl.getEntries(); + return entries == ens || (entries != null && entries.equals(ens)); + } + return false; + } + + /** + * @see org.apache.james.mailbox.MailboxACL#except(org.apache.james.mailbox.MailboxACL) + */ + @Override + public MailboxACL except(MailboxACL other) throws UnsupportedRightException { + if (entries.size() == 0) { + return this; + } else { + Map otherEntries = other.getEntries(); + Map resultEntries = new HashMap(this.entries); + for (Entry otherEntry : otherEntries.entrySet()) { + MailboxACLEntryKey key = otherEntry.getKey(); + MailboxACLRights thisRights = resultEntries.get(key); + if (thisRights == null) { + /* nothing to diff */ + } else { + /* diff */ + MailboxACLRights resultRights = thisRights.except(otherEntry.getValue()); + if (!resultRights.isEmpty()) { + resultEntries.put(key, resultRights); + } + else { + resultEntries.remove(key); + } + } + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL#except(org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + public MailboxACL except(MailboxACLEntryKey key, MailboxACLRights mailboxACLRights) throws UnsupportedRightException { + Map resultEntries = new HashMap(this.entries); + MailboxACLRights thisRights = resultEntries.get(key); + if (thisRights == null) { + /* nothing to diff */ + } else { + /* diff */ + MailboxACLRights resultRights = thisRights.except(mailboxACLRights); + if (!resultRights.isEmpty()) { + resultEntries.put(key, resultRights); + } + else { + resultEntries.remove(key); + } + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + + /** + * @see org.apache.james.mailbox.MailboxACL#getEntries() + */ + @Override + public Map getEntries() { + return entries; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return entries == null ? 0 : entries.hashCode(); + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL#replace(org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + @Override + public MailboxACL replace(MailboxACLEntryKey key, MailboxACLRights replacement) throws UnsupportedRightException { + Map resultEntries = new HashMap(this.entries); + if (replacement == null || replacement.isEmpty()) { + resultEntries.remove(key); + } else { + resultEntries.put(key, replacement); + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return entries == null ? "" : entries.toString(); + } + + /** + * @see org.apache.james.mailbox.MailboxACL#union(org.apache.james.mailbox.MailboxACL) + */ + @Override + public MailboxACL union(MailboxACL other) throws UnsupportedRightException { + Map otherEntries = other.getEntries(); + if (otherEntries.size() == 0) { + return this; + } else if (entries.size() == 0) { + return other; + } else { + int cnt = otherEntries.size() + entries.size(); + Map resultEntries = new HashMap(cnt + cnt / 2 + 1); + for (Entry otherEntry : otherEntries.entrySet()) { + MailboxACLEntryKey key = otherEntry.getKey(); + MailboxACLRights thisRights = entries.get(key); + if (thisRights == null) { + /* nothing to union */ + resultEntries.put(key, otherEntry.getValue()); + } else { + /* union */ + resultEntries.put(key, otherEntry.getValue().union(thisRights)); + } + } + /* let us check what we have missed in the previous loop */ + for (Entry thisEntry : entries.entrySet()) { + MailboxACLEntryKey key = thisEntry.getKey(); + if (!resultEntries.containsKey(key)) { + resultEntries.put(key, thisEntry.getValue()); + } + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL#union(org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + public MailboxACL union(MailboxACLEntryKey key, MailboxACLRights mailboxACLRights) throws UnsupportedRightException { + Map resultEntries = new HashMap(this.entries); + MailboxACLRights thisRights = resultEntries.get(key); + if (thisRights == null) { + /* nothing to union */ + resultEntries.put(key, mailboxACLRights); + } else { + /* union */ + resultEntries.put(key, thisRights.union(mailboxACLRights)); + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/UpdatedFlags.java.svn-base b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/UpdatedFlags.java.svn-base new file mode 100644 index 0000000..c3a746b --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/.svn/text-base/UpdatedFlags.java.svn-base @@ -0,0 +1,172 @@ +/**************************************************************** + * 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.model; + +import java.util.Arrays; +import java.util.Iterator; + +import javax.mail.Flags; + +/** + * Represent a Flag update for a message + * + * + */ +public class UpdatedFlags { + + private final long uid; + private final Flags oldFlags; + private final Flags newFlags; + private final Flags modifiedFlags; + private long modSeq; + + public UpdatedFlags(long uid, long modSeq, Flags oldFlags, Flags newFlags) { + this.uid = uid; + this.modSeq = modSeq; + this.oldFlags = oldFlags; + this.newFlags = newFlags; + this.modifiedFlags = new Flags(); + addModifiedSystemFlags(oldFlags, newFlags, modifiedFlags); + addModifiedUserFlags(oldFlags, newFlags, modifiedFlags); + } + + private static void addModifiedSystemFlags(Flags oldFlags, Flags newFlags, Flags modifiedFlags) { + if (isChanged(oldFlags, newFlags, Flags.Flag.ANSWERED)) { + modifiedFlags.add(Flags.Flag.ANSWERED); + } + if(isChanged(oldFlags, newFlags, Flags.Flag.DELETED)) { + modifiedFlags.add(Flags.Flag.DELETED); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.DRAFT)) { + modifiedFlags.add(Flags.Flag.DRAFT); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.FLAGGED)) { + modifiedFlags.add(Flags.Flag.FLAGGED); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.RECENT)) { + modifiedFlags.add(Flags.Flag.RECENT); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.SEEN)) { + modifiedFlags.add(Flags.Flag.SEEN); + } + } + + private static void addModifiedUserFlags(Flags oldFlags, Flags newFlags, Flags modifiedFlags) { + addModifiedUserFlags(oldFlags, newFlags, oldFlags.getUserFlags(), modifiedFlags); + addModifiedUserFlags(oldFlags, newFlags, newFlags.getUserFlags(), modifiedFlags); + + } + + + private static void addModifiedUserFlags(Flags oldFlags, Flags newFlags, String[] userflags, Flags modifiedFlags) { + for (int i = 0; i < userflags.length; i++) { + String userFlag = userflags[i]; + if (isChanged(oldFlags, newFlags, userFlag)) { + modifiedFlags.add(userFlag); + + } + } + } + private static boolean isChanged(final Flags original, final Flags updated, Flags.Flag flag) { + return original != null && updated != null && (original.contains(flag) ^ updated.contains(flag)); + } + + private static boolean isChanged(final Flags original, final Flags updated, String userFlag) { + return original != null && updated != null && (original.contains(userFlag) ^ updated.contains(userFlag)); + } + + + /** + * Return the old {@link Flags} for the message + * + * @return oldFlags + */ + public Flags getOldFlags() { + return oldFlags; + } + + /** + * Return the new {@link Flags} for the message + * + * @return newFlags + */ + public Flags getNewFlags() { + return newFlags; + } + + /** + * Return the uid for the message whichs {@link Flags} was updated + * + * @return uid + */ + public long getUid() { + return uid; + } + + + /** + * Gets an iterator for the system flags changed. + * + * @return Flags.Flag Iterator, not null + */ + + public Iterator systemFlagIterator() { + return Arrays.asList(modifiedFlags.getSystemFlags()).iterator(); + } + + /** + * Gets an iterator for the users flags changed. + * + * @return String Iterator, not null + */ + + public Iterator userFlagIterator() { + return Arrays.asList(modifiedFlags.getUserFlags()).iterator(); + } + + + /** + * Return the new mod-sequence for the message + * + * @return mod-seq + */ + public long getModSeq() { + return modSeq; + } + + public static boolean flagsChanged(Flags flagsOld, Flags flagsNew) { + Flags modifiedFlags = new Flags(); + addModifiedSystemFlags(flagsOld, flagsNew, modifiedFlags); + addModifiedUserFlags(flagsOld, flagsNew, modifiedFlags); + if (modifiedFlags.getSystemFlags().length > 0 || modifiedFlags.getUserFlags().length > 0) { + return true; + } else { + return false; + } + } + + public boolean flagsChanged() { + if (modifiedFlags.getSystemFlags().length > 0 || modifiedFlags.getUserFlags().length > 0) { + return true; + } else { + return false; + } + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Content.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Content.java new file mode 100644 index 0000000..b39b60e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Content.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.model; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * IMAP needs to know the size of the content before it starts to write it out. + * This interface allows direct writing whilst exposing total size. + */ +public interface Content { + + + /** + * Return the content as {@link InputStream} + * + * @return content + * @throws IOException + */ + InputStream getInputStream() throws IOException; + + /** + * Size (in octets) of the content. + * + * @return number of octets to be written + * @throws MessagingException + */ + long size() throws MailboxException; +} \ No newline at end of file diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/FetchGroupImpl.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/FetchGroupImpl.java new file mode 100644 index 0000000..6e4c7d2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/FetchGroupImpl.java @@ -0,0 +1,109 @@ +/**************************************************************** + * 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.model; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.james.mailbox.model.MessageResult.MimePath; + +/** + * Specifies a fetch group. + */ +public class FetchGroupImpl implements MessageResult.FetchGroup { + + public static final MessageResult.FetchGroup MINIMAL = new FetchGroupImpl(MessageResult.FetchGroup.MINIMAL); + + public static final MessageResult.FetchGroup HEADERS = new FetchGroupImpl(MessageResult.FetchGroup.HEADERS); + + public static final MessageResult.FetchGroup FULL_CONTENT = new FetchGroupImpl(MessageResult.FetchGroup.FULL_CONTENT); + + public static final MessageResult.FetchGroup BODY_CONTENT = new FetchGroupImpl(MessageResult.FetchGroup.BODY_CONTENT); + + private int content = MessageResult.FetchGroup.MINIMAL; + + private Set partContentDescriptors; + + public FetchGroupImpl() { + super(); + } + + public FetchGroupImpl(int content) { + super(); + this.content = content; + } + + public FetchGroupImpl(int content, Set partContentDescriptors) { + super(); + this.content = content; + this.partContentDescriptors = partContentDescriptors; + } + + public int content() { + return content; + } + + public void or(int content) { + this.content = this.content | content; + } + + public String toString() { + return "Fetch " + content; + } + + /** + * Gets content descriptors for the parts to be fetched. + * + * @return Set of {@link org.apache.james.mailbox.MessageResult.FetchGroup.PartContentDescriptor}, + * possibly null + */ + public Set getPartContentDescriptors() { + return partContentDescriptors; + } + + /** + * Adds content for the particular part. + * + * @param path + * MimePath, not null + * @param content + * bitwise content constant + */ + public void addPartContent(MimePath path, int content) { + if (partContentDescriptors == null) { + partContentDescriptors = new HashSet(); + } + PartContentDescriptorImpl currentDescriptor = null; + for (Iterator it = partContentDescriptors.iterator(); it.hasNext();) { + PartContentDescriptor descriptor = (PartContentDescriptor) it.next(); + if (path.equals(descriptor.path())) { + currentDescriptor = (PartContentDescriptorImpl) descriptor; + break; + } + } + if (currentDescriptor == null) { + currentDescriptor = new PartContentDescriptorImpl(path); + partContentDescriptors.add(currentDescriptor); + } + + currentDescriptor.or(content); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Headers.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Headers.java new file mode 100644 index 0000000..6eebb9d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Headers.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.model; + +import java.util.Iterator; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.MessageResult.Header; + +public interface Headers extends Content{ + /** + * Gets headers for the message. + * + * @return Header Iterator, or null if + * {@link FetchGroup#HEADERS} was not fetched + */ + Iterator
headers() throws MailboxException; + + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/InputStreamContent.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/InputStreamContent.java new file mode 100644 index 0000000..62da0eb --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/InputStreamContent.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.model; + +import java.io.InputStream; + + +/** + * {@link Content} which offers the content via {@link InputStream} too + * + * @deprecated use {@link Content} + */ +@Deprecated +public interface InputStreamContent extends Content { + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java new file mode 100644 index 0000000..2709ee2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java @@ -0,0 +1,333 @@ +/**************************************************************** + * 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.model; + +import java.util.Map; + +import org.apache.james.mailbox.exception.UnsupportedRightException; + +/** + * Stores an Access Control List (ACL) applicable to a mailbox. Inspired by + * RFC4314 IMAP4 Access Control List (ACL) Extension. + * + * Implementations must be immutable. Implementations should override + * {@link #hashCode()} and {@link #equals(Object)}. + * + */ +public interface MailboxACL { + + /** + * SETACL command mode. + */ + enum EditMode { + ADD, REMOVE, REPLACE + } + + /** + * The key used in {@link MailboxACL#getEntries()}. Implementations should + * override {@link #hashCode()} and {@link #equals(Object)} in such a way + * that all of {@link #getName()}, {@link #getNameType()} and + * {@link #isNegative()} are significant. + * + */ + public interface MailboxACLEntryKey { + /** + * Returns the name of a user or of a group to which this + * {@link MailboxACLEntryKey} applies. + * + * @return User name, group name or special name. + */ + String getName(); + + /** + * Tells of what type is the name returned by {@link #getName()}. + * + * @return type of the name returned by {@link #getName()} + */ + NameType getNameType(); + + /** + * If true the {@link MailboxACLRights} returned by + * {@link MailboxACLEntry#getRights()} should be interpreted as + * "negative rights" as described in RFC4314: If the identifier "-fred" + * is granted the "w" right, that indicates that the "w" right is to be + * removed from users matching the identifier "fred", even though the + * user "fred" might have the "w" right as a consequence of some other + * identifier in the ACL. + * + * Note that {@link MailboxACLEntry#getName()} does not start with "-" + * when {@link MailboxACLEntry#getRights()} returns true. + * + * @return + */ + boolean isNegative(); + + /** + * Returns a serialized form of this {@link MailboxACLEntryKey} as a + * {@link String}. Implementations should choose a consistent way how + * all of {@link #getName()}, {@link #getNameType()} and + * {@link #isNegative()} get serialized. + * + * RFC4314 sction 2. states: All user name strings accepted by the LOGIN + * or AUTHENTICATE commands to authenticate to the IMAP server are + * reserved as identifiers for the corresponding users. Identifiers + * starting with a dash ("-") are reserved for "negative rights", + * described below. All other identifier strings are interpreted in an + * implementation-defined manner. + * + * Dovecot and Cyrus mark groups with '$' prefix. See http://wiki2.dovecot.org/SharedMailboxes/Shared: + * + * The $group syntax is not a standard, but it is mentioned in RFC + * 4314 examples and is also understood by at least Cyrus IMAP. Having + * '-' before the identifier specifies negative rights. + * + * @see MailboxACL#DEFAULT_GROUP_MARKER + * @see MailboxACL#DEFAULT_NEGATIVE_MARKER + * + * @return serialized form as a {@link String} + */ + String serialize(); + } + + /** + * Single right applicable to a mailbox. + */ + public interface MailboxACLRight { + /** + * Returns the char representation of this right. + * + * @return char representation of this right + */ + char getValue(); + } + + /** + * Iterable set of {@link MailboxACLRight}s. + * + * Implementations may decide to support only a specific range of rights, + * e.g. the Standard Rights of RFC 4314 section 2.1. + * + * Implementations must not allow adding or removing of elements once this + * MailboxACLRights are initialized. + */ + public interface MailboxACLRights extends Iterable { + + /** + * Tells whether this contains the given right. + * + * @param right + * @return + * @throws UnsupportedRightException + * iff the given right is not supported. + */ + boolean contains(MailboxACLRight right) throws UnsupportedRightException; + + /** + * Performs the set theoretic operation of relative complement of + * toRemove MailboxACLRights in this MailboxACLRights. + * + * A schematic example: "lrw".except("w") returns "lr". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACLRights}. However, implementations may decide to + * return this or toRemove parameter value in case the result would be + * equal to the respective one of those. + * + * @param toRemove + * @return + * @throws UnsupportedRightException + */ + public MailboxACLRights except(MailboxACLRights toRemove) throws UnsupportedRightException; + + /** + * Tells if this set of rights is empty. + * + * @return true if there are no rights in this set; false otherwise. + */ + public boolean isEmpty(); + + /** + * Tells whether the implementation supports the given right. + * + * @param right + * @return true if this supports the given right. + */ + boolean isSupported(MailboxACLRight right); + + /** + * Returns a serialized form of this {@link MailboxACLRights} as + * {@link String}. + * + * @return a {@link String} + */ + String serialize(); + + /** + * Performs the set theoretic operation of union of this + * MailboxACLRights and toAdd MailboxACLRights. + * + * A schematic example: "lr".union("rw") returns "lrw". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACLRights}. However, implementations may decide to + * return this or toAdd parameter value in case the result would be + * equal to the respective one of those. + * + * @param toAdd + * @return union of this and toAdd + * @throws UnsupportedRightException + * + */ + public abstract MailboxACLRights union(MailboxACLRights toAdd) throws UnsupportedRightException; + + }; + + /** + * Allows distinguishing between users, groups and special names (see + * {@link SpecialName}). + */ + enum NameType { + group, special, user + }; + + /** + * Special name literals. + */ + enum SpecialName { + anybody, authenticated, owner + }; + + /** + * SETACL third argument prefix + */ + public static final char ADD_RIGHTS_MARKER = '+'; + + /** + * Marks groups when (de)serializing {@link MailboxACLEntryKey}s. + * + * @see MailboxACLEntryKey#serialize() + */ + public static final char DEFAULT_GROUP_MARKER = '$'; + + /** + * Marks negative when (de)serializing {@link MailboxACLEntryKey}s. + * + * @see MailboxACLEntryKey#serialize() + */ + public static final char DEFAULT_NEGATIVE_MARKER = '-'; + + /** + * SETACL third argument prefix + */ + public static final char REMOVE_RIGHTS_MARKER = '-'; + + /** + * Performs the set theoretic operation of relative complement of toRemove + * {@link MailboxACL} in this {@link MailboxACL}. + * + * A schematic example: "user1:lr;user2:lrwt".except("user1:w;user2:t") + * returns "user1:lr;user2:lrw". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACL}. However, implementations may decide to return this or + * toRemove parameter value in case the result would be equal to the + * respective one of those. + * + * Implementations must ensure that the result does not contain entries with + * empty rigths. E.g. "user1:lr;user2:lrwt".except("user1:lr") should return + * "user2:lrwt" rather than "user1:;user2:lrwt" + * + * @param toRemove + * @return + * @throws UnsupportedRightException + */ + MailboxACL except(MailboxACL toRemove) throws UnsupportedRightException; + + /** + * TODO except. + * + * @param key + * @param toRemove + * @return + * @throws UnsupportedRightException + */ + MailboxACL except(MailboxACLEntryKey key, MailboxACLRights toRemove) throws UnsupportedRightException; + + /** + * {@link Map} of entries. + * + * @return the entries. + */ + Map getEntries(); + + /** + * Replaces the entry corresponding to the given {@code key} with + * {@code toAdd}link MailboxACLRights}. + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACL}. However, implementations may decide to return this in + * case the result would be equal to it. + * + * Implementations must ensure that the result does not contain entries with + * empty rigths. E.g. "user1:lr;user2:lrwt".replace("user1", + * MailboxACLRights.EMPTY) should return "user2:lrwt" rather than + * "user1:;user2:lrwt". The same result should be returned by + * "user1:lr;user2:lrwt".replace("user1", null). + * + * @param key + * @param toAdd + * @return + * @throws UnsupportedRightException + */ + MailboxACL replace(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException; + + /** + * Performs the set theoretic operation of union of this {@link MailboxACL} + * and toAdd {@link MailboxACL}. + * + * A schematic example: + * "user1:lr;user2:lrwt".union("user1:at;-$group1:lrwt") returns + * "user1:alrt;user2:lrwt;-$group1:lrwt". + * + * Implementations must return a new unmodifiable instance of + * {@link MailboxACL}. However, implementations may decide to return this or + * toAdd parameter value in case the result would be equal to the respective + * one of those. + * + * + * @param toAdd + * @return + * @throws UnsupportedRightException + */ + MailboxACL union(MailboxACL toAdd) throws UnsupportedRightException; + + /** + * TODO union. + * + * @param key + * @param toAdd + * @return + * @throws UnsupportedRightException + */ + MailboxACL union(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxConstants.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxConstants.java new file mode 100644 index 0000000..fa53e13 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxConstants.java @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.model; + +/** + * Constants which are used within the mailbox api and implementations + * + * + */ +public interface MailboxConstants { + + /** + * The char which is used to prefix a namespace + */ + public static final char NAMESPACE_PREFIX_CHAR = '#'; + + /** The namespace used for store user inboxes */ + public static final String USER_NAMESPACE = NAMESPACE_PREFIX_CHAR + "private"; + + /** The default delimiter used to seperated parent/child folders */ + public static final char DEFAULT_DELIMITER = '.'; + + /** The name of the INBOX */ + public static final String INBOX = "INBOX"; + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxMetaData.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxMetaData.java new file mode 100644 index 0000000..d78fc0e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxMetaData.java @@ -0,0 +1,82 @@ +/**************************************************************** + * 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.model; + +/** + * Returned by the list method of MailboxRepository and others + */ +public interface MailboxMetaData { + + /** RFC3501 Selectability flag */ + public enum Selectability { + NONE, MARKED, UNMARKED, NOSELECT + } + + /** + * Indicates whether this mailbox allows children and - if so - whether it + * has any. + */ + public enum Children { + /** + * No children allowed. + */ + NO_INFERIORS, + /** + * Children allowed by this mailbox but it is unknown whether this + * mailbox has children. + */ + CHILDREN_ALLOWED_BUT_UNKNOWN, + /** + * Indicates that this mailbox has children. + */ + HAS_CHILDREN, + /** + * Indicates that this mailbox allows interiors but currently has no + * children. + */ + HAS_NO_CHILDREN + } + + /** + * Gets the inferiors status of this mailbox. + * + * @return not null + */ + Children inferiors(); + + /** + * Gets the RFC3501 Selectability flag. + */ + Selectability getSelectability(); + + /** + * Return the delimiter + * + * @return delimiter + */ + char getHierarchyDelimiter(); + + /** + * Return the MailboxPath + * + * @return path + */ + MailboxPath getPath(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java new file mode 100644 index 0000000..9bedf42 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java @@ -0,0 +1,230 @@ +/**************************************************************** + * 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.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.james.mailbox.MailboxSession; + +/** + * The path to a mailbox. + */ +public class MailboxPath { + + private String namespace; + private String user; + private String name; + + + + public MailboxPath(String namespace, String user, String name) { + if (namespace == null || namespace.equals("")) { + this.namespace = MailboxConstants.USER_NAMESPACE; + } else { + this.namespace = namespace; + } + this.user = user; + this.name = name; + } + + public MailboxPath(MailboxPath mailboxPath) { + this(mailboxPath.getNamespace(), mailboxPath.getUser(), mailboxPath.getName()); + } + + public MailboxPath(MailboxPath mailboxPath, String name) { + this(mailboxPath.getNamespace(), mailboxPath.getUser(), name); + } + + /** + * Get the namespace this mailbox is in + * + * @return The namespace + */ + public String getNamespace() { + return namespace; + } + + /** + * Set the namespace this mailbox is in + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Get the name of the user who owns the mailbox. This can be null e.g. for + * shared mailboxes. + * + * @return The username + */ + public String getUser() { + return user; + } + + /** + * Set the name of the user who owns the mailbox. + */ + public void setUser(String user) { + this.user = user; + } + + /** + * Get the name of the mailbox. This is the pure name without user or + * namespace, so this is what a user would see in his client. + * + * @return The name string + */ + public String getName() { + return name; + } + + /** + * Set the name of the mailbox. This is the pure name without user or + * namespace, so this is what a user would see in his client. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Return a list of MailboxPath representing the hierarchy levels of this + * MailboxPath. E.g. INBOX.main.sub would yield + * + *
+     * INBOX
+     * INBOX.main
+     * INBOX.main.sub
+     * 
+ * + * @param delimiter + * @return list of hierarchy levels + */ + public List getHierarchyLevels(char delimiter) { + ArrayList levels = new ArrayList(); + int index = name.indexOf(delimiter); + while (index >= 0) { + final String levelname = name.substring(0, index); + levels.add(new MailboxPath(namespace, user, levelname)); + index = name.indexOf(delimiter, ++index); + } + levels.add(this); + return levels; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return namespace + ":" + user + ":" + name; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object mailboxPath) { + if (this == mailboxPath) + return true; + + if (!(mailboxPath instanceof MailboxPath)) + return false; + MailboxPath mp = (MailboxPath) mailboxPath; + if (namespace == null) { + if (mp.getNamespace() != null) + return false; + } else if (!namespace.equals(mp.getNamespace())) + return false; + if (user == null) { + if (mp.getUser() != null) + return false; + } else if (!user.equals(mp.getUser())) + return false; + if (name == null) { + if (mp.getName() != null) + return false; + } else if (!name.equals(mp.getName())) + return false; + return true; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + if (getName() != null) + result = PRIME * result + getName().hashCode(); + if (getUser() != null) + result = PRIME * result + getUser().hashCode(); + if (getNamespace() != null) + result = PRIME * result + getNamespace().hashCode(); + return result; + } + + /** + * Return the full name of the {@link MailboxPath}, which is constructed via the {@link #namespace} and {@link #name} + * + * @param delimiter + * @return fullName + */ + public String getFullName(char delimiter) { + return namespace + delimiter + name; + } + + /** + * Return a {@link MailboxPath} which represent the INBOX of the given + * session + * + * @param session + * @return inbox + */ + public static MailboxPath inbox(MailboxSession session) { + return new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), MailboxConstants.INBOX); + } + + /** + * Create a {@link MailboxPath} by parsing the given full mailboxname (which included the namespace) + * + * @param session + * @param fullmailboxname + * @return path + */ + public static MailboxPath parse(MailboxSession session, String fullmailboxname) { + char delimiter = session.getPathDelimiter(); + int i = fullmailboxname.indexOf(delimiter); + String namespace = fullmailboxname.substring(0, i); + String mailbox = fullmailboxname.substring(i + 1, fullmailboxname.length()); + String username = null; + if (namespace == null || namespace.trim().equals("")) { + namespace = MailboxConstants.USER_NAMESPACE; + } + if (namespace.equals(session.getPersonalSpace())) { + username = session.getUser().getUserName(); + } + return new MailboxPath(namespace, username, mailbox); + + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxQuery.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxQuery.java new file mode 100644 index 0000000..0df382d --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxQuery.java @@ -0,0 +1,192 @@ +/**************************************************************** + * 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.model; + +import java.util.regex.Pattern; + + +/** + * Expresses select criteria for mailboxes. + */ +public final class MailboxQuery { + + private final MailboxPath base; + + private final String expression; + + private final char pathDelimiter; + + private final Pattern pattern; + + /** + * Use this wildcard to match every char including the hierarchy delimiter + */ + public final static char FREEWILDCARD = '*'; + + + /** + * Use this wildcard to match every char except the hierarchy delimiter + */ + public final static char LOCALWILDCARD = '%'; + + /** + * Constructs an expression determining a set of mailbox names. + * + * @param base + * base reference name, not null + * @param expression + * mailbox match expression, not null + * @param pathDelimiter + * path delimiter to use + */ + public MailboxQuery(final MailboxPath base, final String expression, final char pathDelimiter) { + super(); + this.base = base; + if (base.getName() == null) + this.base.setName(""); + if (expression == null) { + this.expression = ""; + } else { + this.expression = expression; + } + this.pathDelimiter = pathDelimiter; + + // Compile some pattern which is used later + pattern = Pattern.compile(this.expression.replaceAll("\\" + pathDelimiter ,"\\\\" + pathDelimiter).replaceAll("\\*", "\\.\\*").replaceAll("\\%", "[^\\" + pathDelimiter + "]*")); + } + + /** + * Gets the base reference for the search. + * + * @return the base + */ + public final MailboxPath getBase() { + return base; + } + + /** + * Gets the name search expression. This may contain wildcards. + * + * @return the expression + */ + public final String getExpression() { + return expression; + } + + /** + * Gets wildcard character that matches any series of characters. + * + * @return the freeWildcard + */ + public final char getFreeWildcard() { + return FREEWILDCARD; + } + + /** + * Gets wildcard character that matches any series of characters excluding + * hierarchy delimiters. Effectively, this means that it matches any + * sequence within a name part. + * + * @return the localWildcard + */ + public final char getLocalWildcard() { + return LOCALWILDCARD; + } + + /** + * Is the given name a match for {@link #getExpression()}? + * + * @param name + * name to be matched + * @return true if the given name matches this expression, false otherwise + */ + public final boolean isExpressionMatch(String name) { + final boolean result; + if (isWild()) { + if (name == null) { + result = false; + } else { + result = pattern.matcher(name).matches(); + } + } else { + result = expression.equals(name); + } + return result; + } + + /** + * Get combined name formed by adding the expression to the base using the + * given hierarchy delimiter. Note that the wildcards are retained in the + * combined name. + * + * @return {@link #getBase()} combined with {@link #getExpression()}, + * notnull + */ + public String getCombinedName() { + final String result; + if (base != null && base.getName() != null && base.getName().length() > 0) { + final int baseLength = base.getName().length(); + if (base.getName().charAt(baseLength - 1) == pathDelimiter) { + if (expression != null && expression.length() > 0) { + if (expression.charAt(0) == pathDelimiter) { + result = base.getName() + expression.substring(1); + } else { + result = base.getName() + expression; + } + } else { + result = base.getName(); + } + } else { + if (expression != null && expression.length() > 0) { + if (expression.charAt(0) == pathDelimiter) { + result = base.getName() + expression; + } else { + result = base.getName() + pathDelimiter + expression; + } + } else { + result = base.getName(); + } + } + } else { + result = expression; + } + return result; + } + + /** + * Is this expression wild? + * + * @return true if wildcard contained, false otherwise + */ + public boolean isWild() { + return expression != null && (expression.indexOf(getFreeWildcard()) >= 0 || expression.indexOf(getLocalWildcard()) >= 0); + } + + /** + * Renders a string suitable for logging. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + return "MailboxExpression [ " + "base = " + this.base + TAB + "expression = " + this.expression + TAB + "freeWildcard = " + this.getFreeWildcard() + TAB + "localWildcard = " + this.getLocalWildcard() + TAB + " ]"; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java new file mode 100644 index 0000000..504ef16 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.model; + +import java.util.Date; + +import javax.mail.Flags; + +/** + * Represent the {@link MessageMetaData} for a message + * + * + */ +public interface MessageMetaData { + + /** + * Return the uid of the message which the MessageResult belongs to + * + * @return uid + */ + long getUid(); + + + /** + * Return the modify-sequence number of the message. This is kind of optional and the mailbox + * implementation may not support this. If so it will return -1 + * + * @return modSeq + */ + long getModSeq(); + + /** + * Return the {@link Flags} + * + * @return flags + */ + Flags getFlags(); + + /** + * Return the size in bytes + * + * @return size + */ + long getSize(); + + /** + *

+ * IMAP defines this as the time when the message has arrived to the server + * (by smtp). Clients are also allowed to set the internalDate on append. + *

+ */ + Date getInternalDate(); +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageRange.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageRange.java new file mode 100644 index 0000000..1de7de2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageRange.java @@ -0,0 +1,301 @@ +/**************************************************************** + * 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.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Used to define a range of messages by uid.
+ * The type of the set should be defined by using an appropriate constructor. + */ +public class MessageRange implements Iterable{ + + public enum Type { + /** All messages */ + ALL, + /** A sigle message */ + ONE, + /** All messages with a uid equal or higher than */ + FROM, + /** All messagse within the given range of uids (inclusive) */ + RANGE + } + + public static final long NOT_A_UID = -1; + + public static final long MAX_UID = Long.MAX_VALUE; + + /** + * Constructs a range consisting of a single message only. + * + * @param uid + * UID of the message + * @return not null + */ + public static MessageRange one(long uid) { + final MessageRange result = new MessageRange(Type.ONE, uid, uid); + return result; + } + + /** + * Constructs a range consisting of all messages. + * + * @return not null + */ + public static MessageRange all() { + final MessageRange result = new MessageRange(Type.ALL, NOT_A_UID, MAX_UID); + return result; + } + + /** + * Constructs an inclusive ranges of messages. The parameters will be + * checked and {@link #from(long)} used where appropriate. + * + * @param from + * first message UID + * @param to + * last message UID + * @return not null + */ + public static MessageRange range(long from, long to) { + final MessageRange result; + if (to == Long.MAX_VALUE || to < from) { + to = NOT_A_UID; + result = from(from); + } else if (from == to) { + // from and to is the same so no need to construct a real range + result = one(from); + } else { + result = new MessageRange(Type.RANGE, from, to); + } + return result; + } + + + /** + * Constructs an inclusive, open ended range of messages. + * + * @param from + * first message UID in range + * @return not null + */ + public static MessageRange from(long from) { + final MessageRange result = new MessageRange(Type.FROM, from, NOT_A_UID); + return result; + } + + + private final Type type; + + private final long uidFrom; + + private final long uidTo; + + protected MessageRange(final Type type, final long uidFrom, final long uidTo) { + super(); + this.type = type; + this.uidFrom = uidFrom; + this.uidTo = uidTo; + } + + public Type getType() { + return type; + } + + public long getUidFrom() { + return uidFrom; + } + + public long getUidTo() { + return uidTo; + } + + + /** + * Return true if the uid is within the range + * + * @param uid + * @return withinRange + */ + public boolean includes(long uid) { + switch (type) { + case ALL: + return true; + case FROM: + if (uid > getUidFrom()) { + return true; + } + case RANGE: + if (uid >= getUidFrom() && uid <= getUidTo()) { + return true; + } + case ONE: + if (getUidFrom() == uid) { + return true; + } + default: + break; + } + return false; + } + + public String toString() { + return "TYPE: " + type + " UID: " + uidFrom + ":" + uidTo; + } + + + /** + * Converts the given {@link Collection} of uids to a {@link List} of {@link MessageRange} instances + * + * @param uidsCol + * collection of uids to convert + * @return ranges + */ + public static List toRanges(Collection uidsCol) { + List ranges = new ArrayList(); + List uids = new ArrayList(uidsCol); + Collections.sort(uids); + + long firstUid = 0; + int a = 0; + for (int i = 0; i < uids.size(); i++) { + long u = uids.get(i); + if (i == 0) { + firstUid = u; + if (uids.size() == 1) { + ranges.add(MessageRange.one(firstUid)); + } + } else { + if ((firstUid + a +1) != u) { + ranges.add(MessageRange.range(firstUid, firstUid + a)); + + // set the next first uid and reset the counter + firstUid = u; + a = 0; + if (uids.size() <= i +1) { + ranges.add(MessageRange.one(firstUid)); + } + } else { + a++; + // Handle uids which are in sequence. See MAILBOX-56 + if (uids.size() <= i +1) { + ranges.add(MessageRange.range(firstUid, firstUid +a)); + break; + } + } + } + } + return ranges; + } + + + /** + * Return a read-only {@link Iterator} which contains all uid which fail in the specified range. + * + * @return rangeIt + */ + @Override + public Iterator iterator() { + long from = getUidFrom(); + if (from == NOT_A_UID) { + from = 1; + } + long to = getUidTo(); + if (to == NOT_A_UID) { + to = Long.MAX_VALUE; + } + return new RangeIterator(from, to); + } + + /** + * {@link Iterator} of a range of msn/uid + * + */ + private final class RangeIterator implements Iterator { + + private long to; + private long current; + + public RangeIterator(long from, long to) { + this.to = to; + this.current = from; + } + + @Override + public boolean hasNext() { + return current <= to; + } + + @Override + public Long next() { + if (hasNext()) { + return current++; + } else { + throw new NoSuchElementException("Max uid of " + to + " was reached before"); + } + } + + @Override + public void remove() { + throw new java.lang.UnsupportedOperationException("Read-Only"); + } + + } + + + /** + * Tries to split the given {@link MessageRange} to a {@link List} of {@link MessageRange}'s which + * select only a max amount of items. This only work for {@link MessageRange}'s with {@link Type} of + * {@link Type#RANGE}. + * + * @param maxItems + * @return ranges + */ + public List split( int maxItems) { + List ranges = new ArrayList(); + if (getType() == Type.RANGE) { + long from = getUidFrom(); + long to = getUidTo(); + long realTo = to; + while(from <= realTo) { + if (from + maxItems -1 < realTo) { + to = from + maxItems -1; + } else { + to = realTo; + } + if (from == to) { + ranges.add(MessageRange.one(from)); + } else { + ranges.add(MessageRange.range(from, to)); + } + + from = to + 1; + } + } else { + ranges.add(this); + } + return ranges; + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java new file mode 100644 index 0000000..4b84fae --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java @@ -0,0 +1,260 @@ +/**************************************************************** + * 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.model; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Set; + +import org.apache.james.mailbox.exception.MailboxException; + + +/** + *

+ * Used to get specific informations about a Message without dealing with a + * MimeMessage instance. Demanded information can be requested by binary + * combining the constants. + *

+ * + *

+ * I came to the Idea of the MessageResult because there are many possible + * combinations of different requests (uid, msn, MimeMessage, Flags). + *

+ *

+ * e.g. I want to have all uids, msns and flags of all messages. (a common IMAP + * operation) Javamail would do it that way: + *

    + *
  1. get all Message objects (Message[])
  2. + *
  3. call Message.getMessageNumber()
  4. + *
  5. call Message.getFlags()
  6. + *
  7. call Folder.getUid(Message)
  8. + *
+ *

+ * This means creating a lazy-loading MimeMessage instance.
So why don't + * call getMessages(MessageResult.UID | MessageResult.MSN | + * MessageResult.FLAGS)? This would leave a lot of room for the implementation + * to optimize + *

+ */ + +public interface MessageResult extends Comparable, MessageMetaData { + + /** + * Indicates the results fetched. + */ + public interface FetchGroup { + + /** + * For example: could have best performance when doing store and then + * forget. UIDs are always returned + */ + public static final int MINIMAL = 0x00; + + /** + * + */ + public static final int MIME_DESCRIPTOR = 0x01; + + public static final int HEADERS = 0x100; + + public static final int FULL_CONTENT = 0x200; + + public static final int BODY_CONTENT = 0x400; + + public static final int MIME_HEADERS = 0x800; + + public static final int MIME_CONTENT = 0x1000; + + /** + * Contents to be fetched. Composed bitwise. + * + * @return bitwise description + * @see #MINIMAL + * @see #MIME_DESCRIPTOR + * @see #HEADERS + * @see #FULL_CONTENT + * @see #BODY_CONTENT + * @see #MIME_HEADERS + * @see #MIME_CONTENT + */ + int content(); + + /** + * Gets contents to be fetched for contained parts. For each part to be + * contained, only one descriptor should be contained. + * + * @return Set of {@link PartContentDescriptor}, or null if + * there is no part content to be fetched + */ + Set getPartContentDescriptors(); + + /** + * Describes the contents to be fetched for a mail part. All + * implementations MUST implement equals. Two implementations are equal + * if and only if their paths are equal. + */ + public interface PartContentDescriptor { + /** + * Contents to be fetched. Composed bitwise. + * + * @return bitwise descripion + * @see #MINIMAL + * @see #MIME_DESCRIPTOR + * @see #HEADERS + * @see #FULL_CONTENT + * @see #BODY_CONTENT + * @see #MIME_HEADERS + * @see #MIME_CONTENT + */ + int content(); + + /** + * Path describing the part to be fetched. + * + * @return path describing the part, not null + */ + MimePath path(); + } + } + + MimeDescriptor getMimeDescriptor() throws MailboxException; + + /** + * Iterates the message headers for the given part in a multipart message. + * + * @param path + * describing the part's position within a multipart message + * @return Header Iterator, or null when + * {@link FetchGroup#content()} does not include the index and + * when the mime part cannot be found + * @throws MailboxException + */ + Iterator
iterateHeaders(MimePath path) throws MailboxException; + + /** + * Iterates the MIME headers for the given part in a multipart message. + * + * @param path + * describing the part's position within a multipart message + * @return Header Iterator, or null when + * {@link FetchGroup#content()} does not include the index and + * when the mime part cannot be found + * @throws MailboxException + */ + Iterator
iterateMimeHeaders(MimePath path) throws MailboxException; + + /** + * A header. + */ + public interface Header extends Content { + + /** + * Gets the name of this header. + * + * @return name of this header + * @throws MessagingException + */ + String getName() throws MailboxException; + + /** + * Gets the (unparsed) value of this header. + * + * @return value of this header + * @throws MessagingException + */ + String getValue() throws MailboxException; + } + + /** + * Gets the full message including headers and body. The message data should + * have normalised line endings (CRLF). + * + * @return Content, or or null if + * {@link FetchGroup#FULL_CONTENT} has not been included in the + * results + * @throws IOException + */ + Content getFullContent() throws MailboxException, IOException; + + /** + * Gets the full content of the given mime part. + * + * @param path + * describes the part + * @return Content, or null when + * {@link FetchGroup#content()} did not been include the given + * index and when the mime part cannot be found + * @throws MailboxException + */ + Content getFullContent(MimePath path) throws MailboxException; + + /** + * Gets the body of the message excluding headers. The message data should + * have normalised line endings (CRLF). + * + * @return Content, or or null if + * {@link FetchGroup#FULL_CONTENT} has not been included in the + * results + * @throws IOException + */ + Content getBody() throws MailboxException, IOException; + + /** + * Gets the body of the given mime part. + * + * @param path + * describes the part + * @return Content, or null when + * {@link FetchGroup#content()} did not been include the given + * index and when the mime part cannot be found + * @throws MailboxException + */ + Content getBody(MimePath path) throws MailboxException; + + /** + * Gets the body of the given mime part. + * + * @param path + * describes the part + * @return Content, or null when + * {@link FetchGroup#content()} did not been include the given + * index and when the mime part cannot be found + * @throws MailboxException + */ + Content getMimeBody(MimePath path) throws MailboxException; + + + Headers getHeaders() throws MailboxException; + + /** + * Describes a path within a multipart MIME message. All implementations + * must implement equals. Two paths are equal if and only if each position + * is identical. + */ + public interface MimePath { + + /** + * Gets the positions of each part in the path. + * + * @return part positions describing the path + */ + int[] getPositions(); + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResultIterator.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResultIterator.java new file mode 100644 index 0000000..68ef98e --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResultIterator.java @@ -0,0 +1,44 @@ +/**************************************************************** + * 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.model; + +import java.util.Iterator; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * A special {@link Iterator} which allows to access the {@link MailboxException} if one was thrown while try to lazy fetch the {@link MessageResult}'s in batches + * + * + */ +public interface MessageResultIterator extends Iterator { + + /** + * This method should get called after the {@link #hasNext()} method returns + * false. + * + * If it does not return null an error was thrown before while + * try to lazy fetch th next batch of {@link MessageResult}'s + * + * @return exception + */ + public MailboxException getException(); + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MimeDescriptor.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MimeDescriptor.java new file mode 100644 index 0000000..6c58ab0 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/MimeDescriptor.java @@ -0,0 +1,142 @@ +/**************************************************************** + * 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.model; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +public interface MimeDescriptor extends Headers{ + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + String getMimeType(); + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + String getMimeSubType(); + + /** + * Gets the MIME Content-ID header value. + * + * @return MIME Content-ID, possibly null + */ + String getContentID(); + + /** + * Gets MIME Content-Description header value. + * + * @return MIME Content-Description, possibly null + */ + String getContentDescription(); + + /** + * Gets MIME Content-Location header value. + * + * @return parsed MIME Content-Location, possibly null + */ + String getContentLocation(); + + /** + * Gets MIME Content-MD5 header value. + * + * @return parsed MIME Content-MD5, possibly null + */ + String getContentMD5(); + + /** + * Gets the MIME content transfer encoding. + * + * @return MIME Content-Transfer-Encoding, possibly null + */ + String getTransferContentEncoding(); + + /** + * Gets the languages, From the MIME Content-Language header + * value. + * + * @return List of String names + */ + List getLanguages(); + + /** + * Gets MIME Content-Disposition. + * + * @return Content-Disposition, or null if no disposition + * header exists + */ + String getDisposition(); + + /** + * Gets MIME Content-Disposition parameters. + * + * @return Content-Disposition values indexed by names + */ + Map getDispositionParams(); + + /** + * Gets the number of lines of text in a part of type TEXT when + * transfer encoded. + * + * @return CRLF count when a TEXT type, otherwise + * -1 + */ + long getLines(); + + /** + * The number of octets contained in the body of this part. + * + * @return number of octets + */ + long getBodyOctets(); + + /** + * Gets parts. + * + * @return MimeDescriptor Iterator when a + * composite top level MIME media type, null otherwise + */ + Iterator parts(); + + /** + * Gets embedded message. + * + * @return MimeDescriptor when top level MIME type is + * message, null otherwise + */ + MimeDescriptor embeddedMessage(); + + /** + * Gets MIME body parameters parsed from Content-Type. + * + * @return Header Iterator, not null + */ + Map contentTypeParameters(); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/PartContentDescriptorImpl.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/PartContentDescriptorImpl.java new file mode 100644 index 0000000..7320dfb --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/PartContentDescriptorImpl.java @@ -0,0 +1,78 @@ +/**************************************************************** + * 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.model; + +import org.apache.james.mailbox.model.MessageResult.FetchGroup.PartContentDescriptor; +import org.apache.james.mailbox.model.MessageResult.MimePath; + + +public class PartContentDescriptorImpl implements PartContentDescriptor { + + private int content = 0; + + private final MimePath path; + + public PartContentDescriptorImpl(final MimePath path) { + super(); + this.path = path; + } + + public PartContentDescriptorImpl(int content, final MimePath path) { + super(); + this.content = content; + this.path = path; + } + + public void or(int content) { + this.content = this.content | content; + } + + public int content() { + return content; + } + + public MimePath path() { + return path; + } + + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((path == null) ? 0 : path.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final PartContentDescriptor other = (PartContentDescriptor) obj; + if (path == null) { + if (other.path() != null) + return false; + } else if (!path.equals(other.path())) + return false; + return true; + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java new file mode 100644 index 0000000..9323886 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/Quota.java @@ -0,0 +1,54 @@ +/**************************************************************** + * 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.model; + +/** + * A {@link Quota} restriction + * + * + * + */ +public interface Quota { + + /** + * Unlimited value + */ + public final static long UNLIMITED = -1; + + + /** + * Value not known + */ + public final static long UNKNOWN = Long.MIN_VALUE; + + /** + * Return the maximum value for the {@link Quota} + * + * @return max + */ + long getMax(); + + /** + * Return the currently used for the {@link Quota} + * + * @return used + */ + long getUsed(); + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java new file mode 100644 index 0000000..f2c8d31 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java @@ -0,0 +1,2096 @@ +/**************************************************************** + * 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.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +/** + *

+ * Models a query used to search for messages. A query is the logical + * AND of the contained criteria. + *

+ *

+ * Each Criterion is composed of an Operator + * (combining value and operation) together with field information (optional + * since the criteria type may imply a particular field). Factory methods are + * provided for criteria. + *

+ */ +public class SearchQuery implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * The Resolution which should get used for {@link Date} searches + */ + public static enum DateResolution { + Second, Minute, Hour, Day, Month, Year + } + + public static enum AddressType { + From, To, Cc, Bcc + } + + /** + * Allow to sort a {@link SearchQuery} response in different ways. + */ + public static final class Sort implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * Specify on what to sort + */ + public static enum SortClause { + + /** + * Internal date and time of the message (internaldate) + */ + Arrival, + + /** + * addr-mailbox of the first "cc" address. + * + * This MUST BE converted to uppercase before doing the sort + */ + MailboxCc, + + /** + * addr-mailbox of the first "from" address. + * + * This MUST BE converted to uppercase before doing the sort + */ + MailboxFrom, + + /** + * addr-mailbox of the first "To" address + * + * This MUST BE converted to uppercase before doing the sort + */ + MailboxTo, + + /** + * Base subject text. + * + * This MUST BE converted to uppercase before doing the sort + */ + BaseSubject, + + /** + * Size of the message in octets. + */ + Size, + + /** + *

+ * As used in this document, the term "sent date" refers to the date + * and time from the Date: header, adjusted by time zone to + * normalize to UTC. For example, "31 Dec 2000 16:01:33 -0800" is + * equivalent to the UTC date and time of + * "1 Jan 2001 00:01:33 +0000". If the time zone is invalid, the + * date and time SHOULD be treated as UTC. If the time is also + * invalid, the time SHOULD be treated as 00:00:00. If there is no + * valid date or time, the date and time SHOULD be treated as + * 00:00:00 on the earliest possible date. + * + * If the sent date cannot be determined (a Date: header is missing + * or cannot be parsed), the INTERNALDATE for that message is used + * as the sent date. + *

+ */ + SentDate, + + /** + * addr-name of the first "From" address + * + * This MUST BE converted to uppercase before doing the sort + */ + DisplayFrom, + + /** + * addr-name of the first "To" address + * + * This MUST BE converted to uppercase before doing the sort + */ + DisplayTo, + + /** + * Uid of the message. This is the DEFAULT if no other is specified + */ + Uid + } + + private final boolean reverse; + private final SortClause sortClause; + + public Sort(SortClause sortClause, boolean reverse) { + this.reverse = reverse; + this.sortClause = sortClause; + } + + /** + * Create a new {@link Sort} which is NOT {@link #reverse} + * + * @param sortClause + */ + public Sort(SortClause sortClause) { + this(sortClause, false); + } + + /** + * Return true if the sort should be in reverse order + * + * @return reverse + */ + public boolean isReverse() { + return reverse; + } + + /** + * Return the {@link SortClause} + * + * @return clause + */ + public SortClause getSortClause() { + return sortClause; + } + } + + /** + * Creates a filter for message size less than the given value + * + * @param value + * messages with size less than this value will be selected by + * the returned criterion + * @return Criterion, not null + */ + public static final Criterion sizeLessThan(long value) { + return new SizeCriterion(new NumericOperator(value, NumericComparator.LESS_THAN)); + } + + /** + * Creates a filter for message size greater than the given value + * + * @param value + * messages with size greater than this value will be selected by + * the returned criterion + * @return Criterion, not null + */ + public static final Criterion sizeGreaterThan(long value) { + return new SizeCriterion(new NumericOperator(value, NumericComparator.GREATER_THAN)); + } + + /** + * Creates a filter for message size equal to the given value + * + * @param value + * messages with size equal to this value will be selected by the + * returned criterion + * @return Criterion, not null + */ + public static final Criterion sizeEquals(long value) { + return new SizeCriterion(new NumericOperator(value, NumericComparator.EQUALS)); + } + + /** + * Creates a filter for message mod-sequence less than the given value + * + * @param value + * messages with mod-sequence less than this value will be + * selected by the returned criterion + * @return Criterion, not null + */ + public static final Criterion modSeqLessThan(long value) { + return new ModSeqCriterion(new NumericOperator(value, NumericComparator.LESS_THAN)); + } + + /** + * Creates a filter for message mod-sequence greater than the given value + * + * @param value + * messages with mod-sequence greater than this value will be + * selected by the returned criterion + * @return Criterion, not null + */ + public static final Criterion modSeqGreaterThan(long value) { + return new ModSeqCriterion(new NumericOperator(value, NumericComparator.GREATER_THAN)); + } + + /** + * Creates a filter for message mod-sequence equal to the given value + * + * @param value + * messages with mod-sequence equal to this value will be + * selected by the returned criterion + * @return Criterion, not null + */ + public static final Criterion modSeqEquals(long value) { + return new ModSeqCriterion(new NumericOperator(value, NumericComparator.EQUALS)); + } + + /** + * Creates a filter matching messages with internal date after the given + * date. + * + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion internalDateAfter(Date date, DateResolution res) { + return new InternalDateCriterion(new DateOperator(DateComparator.AFTER, date, res)); + } + + /** + * Creates a filter matching messages with internal date on the given date. + * + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion internalDateOn(Date date, DateResolution res) { + return new InternalDateCriterion(new DateOperator(DateComparator.ON, date, res)); + } + + /** + * Creates a filter matching messages with internal date before the given + * date. + * + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion internalDateBefore(Date date, DateResolution res) { + return new InternalDateCriterion(new DateOperator(DateComparator.BEFORE, date, res)); + } + + /** + * Creates a filter matching messages with the date of the given header + * after the given date. If the header's value is not a date then it will + * not be included. + * + * @param headerName + * name of the header whose value will be compared, not null + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion headerDateAfter(String headerName, Date date, DateResolution res) { + return new HeaderCriterion(headerName, new DateOperator(DateComparator.AFTER, date, res)); + } + + /** + * Creates a filter matching messages with the date of the given header on + * the given date. If the header's value is not a date then it will not be + * included. + * + * @param headerName + * name of the header whose value will be compared, not null + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion headerDateOn(String headerName, Date date, DateResolution res) { + return new HeaderCriterion(headerName, new DateOperator(DateComparator.ON, date, res)); + } + + /** + * Creates a filter matching messages with the date of the given header + * before the given date. If the header's value is not a date then it will + * not be included. + * + * @param headerName + * name of the header whose value will be compared, not null + * @param date + * given date + * @param res + * the date resolution, either {@link DateResolution#Year}, + * {@link DateResolution#Month}, {@link DateResolution#Day}, + * {@link DateResolution#Hour}, {@link DateResolution#Minute} or + * {@link DateResolution#Second} + * @return Criterion, not null + */ + public static final Criterion headerDateBefore(String headerName, Date date, DateResolution res) { + return new HeaderCriterion(headerName, new DateOperator(DateComparator.BEFORE, date, res)); + } + + /** + * Creates a filter matching messages whose Address header contains the + * given address. The address header of the message MUST get canonicalized + * before try to match it. + * + * @param type + * @param address + * @return Criterion + */ + public static final Criterion address(AddressType type, String address) { + return new HeaderCriterion(type.name(), new AddressOperator(address)); + } + + /** + * Creates a filter matching messages whose header value contains the given + * value. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param headerName + * name of the header whose value will be compared, not null + * @param value + * when null or empty the existance of the header will be + * checked, otherwise contained value + * @return Criterion, not null + */ + public static final Criterion headerContains(String headerName, String value) { + if (value == null || value.length() == 0) { + return headerExists(headerName); + } else { + return new HeaderCriterion(headerName, new ContainsOperator(value)); + } + } + + /** + * Creates a filter matching messages with a header matching the given name. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param headerName + * name of the header whose value will be compared, not null + * @return Criterion, not null + */ + public static final Criterion headerExists(String headerName) { + return new HeaderCriterion(headerName, ExistsOperator.exists()); + } + + /** + * Creates a filter matching messages which contains the given text either + * within the body or in the headers. Implementations may choose to ignore + * mime parts which cannot be decoded to text. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param value + * search value + * @return Criterion, not null + */ + public static final Criterion mailContains(String value) { + return new TextCriterion(value, Scope.FULL); + } + + /** + * Creates a filter matching messages which contains the given text within + * the body. Implementations may choose to ignore mime parts which cannot be + * decoded to text. + * + * All to-compared Strings MUST BE converted to uppercase before doing so + * (this also include the search value) + * + * @param value + * search value + * @return Criterion, not null + */ + public static final Criterion bodyContains(String value) { + return new TextCriterion(value, Scope.BODY); + } + + /** + * Creates a filter matching messages within any of the given ranges. + * + * @param range + * NumericRange's, not null + * @return Criterion, not null + */ + public static final Criterion uid(NumericRange[] range) { + return new UidCriterion(range); + } + + /** + * Creates a filter composing the two different criteria. + * + * @param one + * Criterion, not null + * @param two + * Criterion, not null + * @return Criterion, not null + */ + public static final Criterion or(Criterion one, Criterion two) { + final List criteria = new ArrayList(); + criteria.add(one); + criteria.add(two); + return new ConjunctionCriterion(Conjunction.OR, criteria); + } + + /** + * Creates a filter composing the two different criteria. + * + * @param one + * Criterion, not null + * @param two + * Criterion, not null + * @return Criterion, not null + */ + public static final Criterion and(Criterion one, Criterion two) { + final List criteria = new ArrayList(); + criteria.add(one); + criteria.add(two); + return new ConjunctionCriterion(Conjunction.AND, criteria); + } + + /** + * Creates a filter composing the listed criteria. + * + * @param criteria + * List of {@link Criterion} + * @return Criterion, not null + */ + public static final Criterion and(List criteria) { + return new ConjunctionCriterion(Conjunction.AND, criteria); + } + + /** + * Creates a filter inverting the given criteria. + * + * @param criterion + * Criterion, not null + * @return Criterion, not null + */ + public static final Criterion not(Criterion criterion) { + final List criteria = new ArrayList(); + criteria.add(criterion); + return new ConjunctionCriterion(Conjunction.NOR, criteria); + } + + /** + * Creates a filter on the given flag. + * + * @param flag + * Flag, not null + * @param isSet + * true if the messages with the flag set should be matched, + * false otherwise + * @return Criterion, not null + */ + public static final Criterion flagSet(final Flag flag, final boolean isSet) { + final Criterion result; + if (isSet) { + result = flagIsSet(flag); + } else { + result = flagIsUnSet(flag); + } + return result; + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsSet(final Flag flag) { + return new FlagCriterion(flag, BooleanOperator.set()); + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is not selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsUnSet(final Flag flag) { + return new FlagCriterion(flag, BooleanOperator.unset()); + } + + /** + * Creates a filter on the given flag. + * + * @param flag + * Flag, not null + * @param isSet + * true if the messages with the flag set should be matched, + * false otherwise + * @return Criterion, not null + */ + public static final Criterion flagSet(final String flag, final boolean isSet) { + final Criterion result; + if (isSet) { + result = flagIsSet(flag); + } else { + result = flagIsUnSet(flag); + } + return result; + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsSet(final String flag) { + return new CustomFlagCriterion(flag, BooleanOperator.set()); + } + + /** + * Creates a filter on the given flag selecting messages where the given + * flag is not selected. + * + * @param flag + * Flag, not null + * @return Criterion, not null + */ + public static final Criterion flagIsUnSet(final String flag) { + return new CustomFlagCriterion(flag, BooleanOperator.unset()); + } + + /** + * Creates a filter matching all messages. + * + * @return Criterion, not null + */ + public static final Criterion all() { + return AllCriterion.all(); + } + + private final Set recentMessageUids = new HashSet(); + + private final List criterias = new ArrayList(); + + private List sorts = new ArrayList(Arrays.asList(new Sort(Sort.SortClause.Uid, false))); + + public void andCriteria(Criterion crit) { + criterias.add(crit); + } + + public List getCriterias() { + return criterias; + } + + /** + * Set the {@link Sort}'s to use + * + * @param sorts + */ + public void setSorts(List sorts) { + if (sorts == null || sorts.isEmpty()) + throw new IllegalArgumentException("There must be at least one Sort"); + this.sorts = sorts; + } + + /** + * Return the {@link Sort}'s which should get used for sorting the result. + * They get "executed" in a chain, if the first does not give an result the + * second will get executed etc. + * + * If not set via {@link #setSorts(List)} it will sort via + * {@link Sort.SortClause#Uid} + * + * @return sorts + */ + public List getSorts() { + return sorts; + } + + /** + * Gets the UIDS of messages which are recent for this client session. The + * list of recent mail is maintained in the protocol layer since the + * mechanics are protocol specific. + * + * @return mutable Set of Long UIDS + */ + public Set getRecentMessageUids() { + return recentMessageUids; + } + + /** + * Adds all the uids to the collection of recents. + * + * @param uids + * not null + */ + public void addRecentMessageUids(final Collection uids) { + recentMessageUids.addAll(uids); + } + + @Override + public String toString() { + return "Search:" + criterias.toString(); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((criterias == null) ? 0 : criterias.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SearchQuery other = (SearchQuery) obj; + if (criterias == null) { + if (other.criterias != null) + return false; + } else if (!criterias.equals(other.criterias)) + return false; + return true; + } + + /** + * Numbers within a particular range. Range includes both high and low + * boundaries. May be a single value. {@link Long#MAX_VALUE} represents + * unlimited in either direction. + */ + public static final class NumericRange implements Serializable { + private static final long serialVersionUID = 1L; + + private final long lowValue; + + private final long highValue; + + public NumericRange(final long value) { + super(); + this.lowValue = value; + this.highValue = value; + } + + public NumericRange(final long lowValue, final long highValue) { + super(); + this.lowValue = lowValue; + this.highValue = highValue; + } + + public long getHighValue() { + return highValue; + } + + public long getLowValue() { + return lowValue; + } + + /** + * Is the given value in this range? + * + * @param value + * value to be tested + * @return true if the value is in range, false otherwise + */ + public boolean isIn(long value) { + if (lowValue == Long.MAX_VALUE) { + return highValue >= value; + } + return lowValue <= value && highValue >= value; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (highValue ^ (highValue >>> 32)); + result = PRIME * result + (int) (lowValue ^ (lowValue >>> 32)); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final NumericRange other = (NumericRange) obj; + if (highValue != other.highValue) + return false; + if (lowValue != other.lowValue) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + return new StringBuffer().append(this.lowValue).append("->").append(this.highValue).toString(); + } + + } + + /** + * Marker superclass for criteria. + */ + public static abstract class Criterion implements Serializable { + private static final long serialVersionUID = 1L; + + } + + public enum Conjunction { + AND, OR, NOR + } + + /** + * Conjunction applying to the contained criteria. {@link #getType} + * indicates how the conjoined criteria should be related. + */ + public static final class ConjunctionCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final Conjunction type; + + private final List criteria; + + public ConjunctionCriterion(final Conjunction type, final List criteria) { + super(); + this.type = type; + this.criteria = criteria; + } + + /** + * Gets the criteria related through this conjunction. + * + * @return List of {@link Criterion} + */ + public List getCriteria() { + return criteria; + } + + /** + * Gets the type of conjunction. + * + * @return not null + */ + public Conjunction getType() { + return type; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((criteria == null) ? 0 : criteria.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final ConjunctionCriterion other = (ConjunctionCriterion) obj; + if (criteria == null) { + if (other.criteria != null) + return false; + } else if (!criteria.equals(other.criteria)) + return false; + if (type != other.type) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("ConjunctionCriterion ( ").append("criteria = ").append(this.criteria).append(TAB) + .append("type = ").append(this.type).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Any message. + */ + public static final class AllCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private static final AllCriterion ALL = new AllCriterion(); + + private static Criterion all() { + return ALL; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + return obj instanceof AllCriterion; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 1729; + } + + public String toString() { + return "AllCriterion"; + } + } + + public enum Scope { + /** Only message body content */ + BODY, + + /** Full message content including headers */ + FULL + } + + /** + * Message text. + */ + public static final class TextCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final Scope type; + + private final ContainsOperator operator; + + private TextCriterion(final String value, final Scope type) { + super(); + this.operator = new ContainsOperator(value); + this.type = type; + } + + /** + * Gets the type of text to be searched. + * + * @return not null + */ + public Scope getType() { + return type; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the Operator, not null + */ + public ContainsOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final TextCriterion other = (TextCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + if (type != other.type) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("TextCriterion ( ").append("operator = ").append(this.operator).append(TAB) + .append("type = ").append(this.type).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Header value content search. + */ + public static final class HeaderCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final HeaderOperator operator; + + private final String headerName; + + private HeaderCriterion(final String headerName, final HeaderOperator operator) { + super(); + this.operator = operator; + this.headerName = headerName; + } + + /** + * Gets the name of the header whose value is to be searched. + * + * @return the headerName + */ + public String getHeaderName() { + return headerName; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the Operator, not null + */ + public HeaderOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((headerName == null) ? 0 : headerName.hashCode()); + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final HeaderCriterion other = (HeaderCriterion) obj; + if (headerName == null) { + if (other.headerName != null) + return false; + } else if (!headerName.equals(other.headerName)) + return false; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("HeaderCriterion ( ").append("headerName = ").append(this.headerName).append(TAB) + .append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Filters on the internal date. + */ + public static final class InternalDateCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final DateOperator operator; + + public InternalDateCriterion(final DateOperator operator) { + super(); + this.operator = operator; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the Operator, not null + */ + public DateOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final InternalDateCriterion other = (InternalDateCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("InternalDateCriterion ( ").append("operator = ").append(this.operator).append(TAB) + .append(" )"); + + return retValue.toString(); + } + } + + /** + * Filters on the mod-sequence of the messages. + */ + public static final class ModSeqCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final NumericOperator operator; + + private ModSeqCriterion(final NumericOperator operator) { + super(); + this.operator = operator; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the NumericOperator, not null + */ + public NumericOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final ModSeqCriterion other = (ModSeqCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("SizeCriterion ( ").append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + public static final class SizeCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final NumericOperator operator; + + private SizeCriterion(final NumericOperator operator) { + super(); + this.operator = operator; + } + + /** + * Gets the search operation and value to be evaluated. + * + * @return the NumericOperator, not null + */ + public NumericOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SizeCriterion other = (SizeCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("SizeCriterion ( ").append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Filters on a custom flag valuation. + */ + public static final class CustomFlagCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final String flag; + + private final BooleanOperator operator; + + private CustomFlagCriterion(final String flag, final BooleanOperator operator) { + super(); + this.flag = flag; + this.operator = operator; + } + + /** + * Gets the custom flag to be search. + * + * @return the flag name, not null + */ + public String getFlag() { + return flag; + } + + /** + * Gets the value to be tested. + * + * @return the BooleanOperator, not null + */ + public BooleanOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((flag == null) ? 0 : flag.hashCode()); + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final CustomFlagCriterion other = (CustomFlagCriterion) obj; + if (flag == null) { + if (other.flag != null) + return false; + } else if (!flag.equals(other.flag)) + return false; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("CustomFlagCriterion ( ").append("flag = ").append(this.flag).append(TAB) + .append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Filters on a standard flag. + */ + public static final class FlagCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + // Flags not Flag because Flags are serializable and Flag is not + private final Flags flag; + private final BooleanOperator operator; + + private FlagCriterion(final Flag flag, final BooleanOperator operator) { + super(); + this.flag = new Flags(flag); + this.operator = operator; + } + + /** + * Gets the flag filtered on. + * + * @return the flag, not null + */ + public Flag getFlag() { + // safe because the Flags(Flag) does system flags, + // and James code also constructs FlagCriterion only with system flags + return flag.getSystemFlags()[0]; + } + + /** + * Gets the test to be preformed. + * + * @return the BooleanOperator, not null + */ + public BooleanOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((flag == null) ? 0 : flag.hashCode()); + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final FlagCriterion other = (FlagCriterion) obj; + if (flag == null) { + if (other.flag != null) + return false; + } else if (!flag.equals(other.flag)) + return false; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("FlagCriterion ( ").append("flag = ").append(this.flag).append(TAB).append("operator = ") + .append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Filters on message identity. + */ + public static final class UidCriterion extends Criterion { + private static final long serialVersionUID = 1L; + + private final InOperator operator; + + public UidCriterion(final NumericRange[] ranges) { + super(); + this.operator = new InOperator(ranges); + } + + /** + * Gets the filtering operation. + * + * @return the InOperator + */ + public InOperator getOperator() { + return operator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((operator == null) ? 0 : operator.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final UidCriterion other = (UidCriterion) obj; + if (operator == null) { + if (other.operator != null) + return false; + } else if (!operator.equals(other.operator)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("UidCriterion ( ").append("operator = ").append(this.operator).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + /** + * Search operator. + */ + public interface Operator extends Serializable { + } + + /** + * Marks operator as suitable for header value searching. + */ + public interface HeaderOperator extends Operator { + } + + public static final class AddressOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + private final String address; + + public AddressOperator(final String address) { + super(); + this.address = address; + } + + /** + * Gets the value to be searched for. + * + * @return the value + */ + public String getAddress() { + return address; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((address == null) ? 0 : address.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final AddressOperator other = (AddressOperator) obj; + if (address == null) { + if (other.address != null) + return false; + } else if (!address.equals(other.address)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("AdressOperator ( ").append("address = ").append(this.address).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Contained value search. + */ + public static final class ContainsOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + private final String value; + + public ContainsOperator(final String value) { + super(); + this.value = value; + } + + /** + * Gets the value to be searched for. + * + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final ContainsOperator other = (ContainsOperator) obj; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("ContainsOperator ( ").append("value = ").append(this.value).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + /** + * Existance search. + */ + public static final class ExistsOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + private static final ExistsOperator EXISTS = new ExistsOperator(); + + public static ExistsOperator exists() { + return EXISTS; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + return obj instanceof ExistsOperator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 42; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ExistsCriterion"; + } + + } + + /** + * Boolean value search. + */ + public static final class BooleanOperator implements Operator { + private static final long serialVersionUID = 1L; + + private static final BooleanOperator SET = new BooleanOperator(true); + + private static final BooleanOperator UNSET = new BooleanOperator(false); + + public static BooleanOperator set() { + return SET; + } + + public static BooleanOperator unset() { + return UNSET; + } + + private final boolean set; + + private BooleanOperator(final boolean set) { + super(); + this.set = set; + } + + /** + * Is the search for set? + * + * @return true indicates that set values should be selected, false + * indicates that unset values should be selected + */ + public boolean isSet() { + return set; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (set ? 1231 : 1237); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final BooleanOperator other = (BooleanOperator) obj; + if (set != other.set) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("BooleanOperator ( ").append("set = ").append(this.set).append(TAB).append(" )"); + + return retValue.toString(); + } + + } + + public enum NumericComparator { + EQUALS, LESS_THAN, GREATER_THAN + } + + /** + * Searches numeric values. + */ + public static final class NumericOperator implements Operator { + private static final long serialVersionUID = 1L; + + public static final int EQUALS = 1; + + public static final int LESS_THAN = 2; + + public static final int GREATER_THAN = 3; + + private final long value; + + private final NumericComparator type; + + private NumericOperator(final long value, final NumericComparator type) { + super(); + this.value = value; + this.type = type; + } + + /** + * Gets the operation type + * + * @return not null + */ + public NumericComparator getType() { + return type; + } + + /** + * Gets the value to be compared. + * + * @return the value + */ + public long getValue() { + return value; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (value ^ (value >>> 32)); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final NumericOperator other = (NumericOperator) obj; + if (type != other.type) + return false; + if (value != other.value) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("NumericOperator ( ").append("type = ").append(this.type).append(TAB).append("value = ") + .append(this.value).append(TAB).append(" )"); + + return retValue.toString(); + } + } + + public enum DateComparator { + BEFORE, AFTER, ON + } + + /** + * Operates on a date. + */ + public static final class DateOperator implements HeaderOperator { + private static final long serialVersionUID = 1L; + + public static final int BEFORE = 1; + + public static final int AFTER = 2; + + public static final int ON = 3; + + private final DateComparator type; + + private final Date date; + + private final DateResolution res; + + public DateOperator(final DateComparator type, final Date date, final DateResolution res) { + super(); + this.type = type; + this.date = date; + this.res = res; + } + + public Date getDate() { + return date; + } + + public DateResolution getDateResultion() { + return res; + } + + /** + * Gets the operator type. + * + * @return the type, either {@link DateComparator#BEFORE}, + * {@link DateComparator#AFTER} or {@link DateComparator#ON} + */ + public DateComparator getType() { + return type; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + // result = PRIME * result + (int)date.getTime(); + result = PRIME * result + type.hashCode(); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final DateOperator other = (DateOperator) obj; + // if (date != other.date) + // return false; + if (res != other.res) + return false; + if (type != other.type) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("DateOperator ( ").append("date = ").append(date.toString()).append(TAB).append("res = ") + .append(this.res.name()).append(TAB).append("type = ").append(this.type).append(TAB).append(TAB) + .append(" )"); + + return retValue.toString(); + } + + } + + /** + * Search for numbers within set of ranges. + */ + public static final class InOperator implements Operator { + private static final long serialVersionUID = 1L; + + private final NumericRange[] range; + + public InOperator(final NumericRange[] range) { + super(); + this.range = range; + } + + /** + * Gets the filtering ranges. Values falling within these ranges will be + * selected. + * + * @return the NumericRange's search on, not null + */ + public NumericRange[] getRange() { + return range; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return range.length; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final InOperator other = (InOperator) obj; + if (!Arrays.equals(range, other.range)) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + StringBuffer retValue = new StringBuffer(); + + retValue.append("InOperator ( ").append("range = ").append(Arrays.toString(this.range)).append(TAB) + .append(" )"); + + return retValue.toString(); + } + + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SimpleMailboxACL.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SimpleMailboxACL.java new file mode 100644 index 0000000..28b200c --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/SimpleMailboxACL.java @@ -0,0 +1,1069 @@ +/**************************************************************** + * 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.model; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; + +/** + * Default implementation of {@link MailboxACL}. + * + */ +public class SimpleMailboxACL implements MailboxACL { + + /** + * Supports only the Standard Rights of RFC 4314 section 2.1. The rights are + * stored as single bits in 32 bit int {@link #value} field. + */ + public static class Rfc4314Rights implements MailboxACLRights { + /** + * See RFC 4314 section 2.1.1. Obsolete Rights. + */ + public enum CompatibilityMode { + ck_detx, ckx_det, NO_COMPATIBILITY + } + + private class Rfc4314RightsIterator implements Iterator { + + int position = 0; + + public Rfc4314RightsIterator() { + super(); + nextPostion(); + } + + @Override + public boolean hasNext() { + return position < FIELD_COUNT; + } + + @Override + public MailboxACLRight next() { + if (!hasNext()) { + throw new IndexOutOfBoundsException("No next element at position " + position + " from " + FIELD_COUNT + " in " + Rfc4314RightsIterator.class.getName()); + } + MailboxACLRight result = indexRightLookup[position]; + position++; + nextPostion(); + return result; + } + + /** + */ + private void nextPostion() { + while (position < FIELD_COUNT && (value & (1 << position)) == 0) { + position++; + } + } + + @Override + public void remove() { + throw new java.lang.UnsupportedOperationException("Cannot remove rights through this " + Rfc4314RightsIterator.class.getName()); + } + + } + + /** + * a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) + * + */ + public static final char a_Administer = 'a'; + + static final int a_Administer_MASK = 1; + public static final MailboxACLRight a_Administer_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(a_Administer); + public static final char c_ObsoleteCreate = 'c'; + public static final char d_ObsoleteDelete = 'd'; + /** + * e - perform EXPUNGE and expunge as a part of CLOSE + * + */ + public static final char e_PerformExpunge = 'e'; + static final int e_PerformExpunge_MASK = 1 << 1; + public static final MailboxACLRight e_PerformExpunge_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(e_PerformExpunge); + public static final int EMPTY_MASK = 0; + public static final int FIELD_COUNT = 11; + /** + * i - insert (perform APPEND, COPY into mailbox) + * + */ + public static final char i_Insert = 'i'; + static final int i_Insert_MASK = 1 << 2; + + public static final MailboxACLRight i_Insert_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(i_Insert); + private static final char[] indexFlagLookup; + private static final MailboxACLRight[] indexRightLookup; + /** + * k - create mailboxes (CREATE new sub-mailboxes in any + * implementation-defined hierarchy, parent mailbox for the new mailbox + * name in RENAME) + * + */ + public static final char k_CreateMailbox = 'k'; + static final int k_CreateMailbox_MASK = 1 << 3; + public static final MailboxACLRight k_CreateMailbox_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(k_CreateMailbox); + /** + * l - lookup (mailbox is visible to LIST/LSUB commands, SUBSCRIBE + * mailbox) + * + */ + public static final char l_Lookup = 'l'; + static final int l_Lookup_MASK = 1 << 4; + public static final MailboxACLRight l_Lookup_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(l_Lookup); + /** + * p - post (send mail to submission address for mailbox, not enforced + * by IMAP4 itself) + * + */ + public static final char p_Post = 'p'; + static final int p_Post_MASK = 1 << 5; + public static final MailboxACLRight p_Post_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(p_Post); + + /** + * r - read (SELECT the mailbox, perform STATUS) + * + */ + public static final char r_Read = 'r'; + static final int r_Read_MASK = 1 << 6; + + public static final MailboxACLRight r_Read_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(r_Read); + /** + * s - keep seen/unseen information across sessions (set or clear \SEEN + * flag via STORE, also set \SEEN during APPEND/COPY/ FETCH BODY[...]) + * + */ + public static final char s_WriteSeenFlag = 's'; + + static final int s_WriteSeenFlag_MASK = 1 << 7; + + public static final MailboxACLRight s_WriteSeenFlag_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(s_WriteSeenFlag); + + public static final char t_DeleteMessages = 't'; + + /** + * t - delete messages (set or clear \DELETED flag via STORE, set + * \DELETED flag during APPEND/COPY) + * + */ + static final int t_DeleteMessages_MASK = 1 << 8; + public static final MailboxACLRight t_DeleteMessages_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(t_DeleteMessages); + /** + * w - write (set or clear flags other than \SEEN and \DELETED via + * STORE, also set them during APPEND/COPY) + * + */ + public static final char w_Write = 'w'; + static final int w_Write_MASK = 1 << 9; + public static final MailboxACLRight w_Write_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(w_Write); + /** + * x - delete mailbox (DELETE mailbox, old mailbox name in RENAME) + * + */ + public static final char x_DeleteMailbox = 'x'; + static final int x_DeleteMailbox_MASK = 1 << 10; + public static final MailboxACLRight x_DeleteMailbox_RIGHT = new SimpleMailboxACL.SimpleMailboxACLRight(x_DeleteMailbox); + static { + indexFlagLookup = new char[] { a_Administer, e_PerformExpunge, i_Insert, k_CreateMailbox, l_Lookup, p_Post, r_Read, s_WriteSeenFlag, t_DeleteMessages, w_Write, x_DeleteMailbox }; + indexRightLookup = new MailboxACLRight[] { a_Administer_RIGHT, e_PerformExpunge_RIGHT, i_Insert_RIGHT, k_CreateMailbox_RIGHT, l_Lookup_RIGHT, p_Post_RIGHT, r_Read_RIGHT, s_WriteSeenFlag_RIGHT, t_DeleteMessages_RIGHT, w_Write_RIGHT, x_DeleteMailbox_RIGHT }; + } + + private static int flagMaskLookup(char flag) throws UnsupportedRightException { + switch (flag) { + case a_Administer: + return a_Administer_MASK; + case e_PerformExpunge: + return e_PerformExpunge_MASK; + case i_Insert: + return i_Insert_MASK; + case k_CreateMailbox: + return k_CreateMailbox_MASK; + case l_Lookup: + return l_Lookup_MASK; + case p_Post: + return p_Post_MASK; + case r_Read: + return r_Read_MASK; + case s_WriteSeenFlag: + return s_WriteSeenFlag_MASK; + case t_DeleteMessages: + return t_DeleteMessages_MASK; + case w_Write: + return w_Write_MASK; + case x_DeleteMailbox: + return x_DeleteMailbox_MASK; + default: + throw new UnsupportedRightException(flag); + } + } + + /** + * See RFC 4314 section 2.1.1. Obsolete Rights. + */ + private CompatibilityMode compatibilityMode = CompatibilityMode.ckx_det; + + /** + * 32 bit int to store the rights. + */ + private final int value; + + private Rfc4314Rights() { + this.value = EMPTY_MASK; + } + + public Rfc4314Rights(boolean canAdminister, boolean canCreateMailbox, boolean canDeleteMailbox, boolean canDeleteMessages, boolean canInsert, boolean canLookup, boolean canPerformExpunge, boolean canPost, boolean canRead, boolean canWrite, boolean canWriteSeenFlag) { + super(); + int v = 0; + + if (canAdminister) { + v |= a_Administer_MASK; + } + if (canCreateMailbox) { + v |= k_CreateMailbox_MASK; + } + if (canDeleteMailbox) { + v |= x_DeleteMailbox_MASK; + } + if (canDeleteMessages) { + v |= t_DeleteMessages_MASK; + } + if (canInsert) { + v |= i_Insert_MASK; + } + if (canLookup) { + v |= l_Lookup_MASK; + } + if (canPerformExpunge) { + v |= e_PerformExpunge_MASK; + } + if (canPost) { + v |= p_Post_MASK; + } + if (canRead) { + v |= r_Read_MASK; + } + if (canWrite) { + v |= w_Write_MASK; + } + if (canWriteSeenFlag) { + v |= s_WriteSeenFlag_MASK; + } + + this.value = v; + + } + + public Rfc4314Rights(int value) throws UnsupportedRightException { + if ((value >> FIELD_COUNT) != 0) { + throw new UnsupportedRightException(); + } + this.value = value; + } + + public Rfc4314Rights(MailboxACLRight right) throws UnsupportedRightException { + this.value = flagMaskLookup(right.getValue()); + } + + public Rfc4314Rights(String serializedRfc4314Rights) throws UnsupportedRightException { + int v = 0; + + for (int i = 0; i < serializedRfc4314Rights.length(); i++) { + char flag = serializedRfc4314Rights.charAt(i); + switch (flag) { + case c_ObsoleteCreate: + switch (compatibilityMode) { + case ck_detx: + v |= k_CreateMailbox_MASK; + break; + case ckx_det: + v |= k_CreateMailbox_MASK; + v |= x_DeleteMailbox_MASK; + break; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + break; + case d_ObsoleteDelete: + switch (compatibilityMode) { + case ck_detx: + v |= e_PerformExpunge_MASK; + v |= t_DeleteMessages_MASK; + v |= x_DeleteMailbox_MASK; + break; + case ckx_det: + v |= e_PerformExpunge_MASK; + v |= t_DeleteMessages_MASK; + break; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + break; + default: + v |= flagMaskLookup(flag); + } + } + this.value = v; + + } + + public boolean contains(char flag) throws UnsupportedRightException { + + switch (flag) { + case c_ObsoleteCreate: + switch (compatibilityMode) { + case ck_detx: + return (value & k_CreateMailbox_MASK) != 0; + case ckx_det: + return (value & (k_CreateMailbox_MASK | x_DeleteMailbox_MASK)) != 0; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + case d_ObsoleteDelete: + switch (compatibilityMode) { + case ck_detx: + return (value & (e_PerformExpunge_MASK | t_DeleteMessages_MASK | x_DeleteMailbox_MASK)) != 0; + case ckx_det: + return (value & (e_PerformExpunge_MASK | t_DeleteMessages_MASK)) != 0; + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + default: + return (value | flagMaskLookup(flag)) != 0; + } + } + + /** + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#contains(org + * .apache.james.mailbox.MailboxACL.MailboxACLRight) + */ + @Override + public boolean contains(MailboxACLRight right) throws UnsupportedRightException { + return contains(right.getValue()); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Rfc4314Rights) { + return this.value == ((Rfc4314Rights) o).value; + } else if (o instanceof MailboxACLRights) { + try { + return this.value == new Rfc4314Rights(((MailboxACLRights) o).serialize()).value; + } catch (UnsupportedRightException e) { + throw new RuntimeException(e); + } + } else { + return false; + } + } + + /** + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#except(org.apache + * .james.mailbox.MailboxACL.MailboxACLRights) + */ + @Override + public MailboxACLRights except(MailboxACLRights toRemove) throws UnsupportedRightException { + if (this.value == EMPTY_MASK || toRemove == null || toRemove.isEmpty()) { + /* nothing to remove */ + return this; + } else if (toRemove instanceof Rfc4314Rights) { + Rfc4314Rights other = (Rfc4314Rights) toRemove; + if (other.value == EMPTY_MASK) { + /* toRemove is an identity element */ + return this; + } else { + return new Rfc4314Rights(this.value & (~((other).value))); + } + } else { + return new Rfc4314Rights(this.value & (~(new Rfc4314Rights(toRemove.serialize()).value))); + } + } + + public int getValue() { + return value; + } + + /** + * Returns {@link #value}. + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value; + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL.MailboxACLRights#isEmpty() + */ + @Override + public boolean isEmpty() { + return value == EMPTY_MASK; + } + + /** + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#isSupported( + * org.apache.james.mailbox.MailboxACL.MailboxACLRight) + */ + @Override + public boolean isSupported(MailboxACLRight right) { + try { + contains(right.getValue()); + return true; + } catch (UnsupportedRightException e) { + return false; + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return new Rfc4314RightsIterator(); + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MailboxACL.MailboxACLRights#serialize() + */ + @Override + public String serialize() { + StringBuilder result = new StringBuilder(FIELD_COUNT); + for (int i = 0; i < FIELD_COUNT; i++) { + if ((value & (1 << i)) != 0) { + result.append(indexFlagLookup[i]); + } + } + return result.toString(); + } + + /** + * Returns {@link #serialize()} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return serialize(); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLRights#union(org.apache + * .james.mailbox.MailboxACL.MailboxACLRights) + */ + @Override + public MailboxACLRights union(MailboxACLRights toAdd) throws UnsupportedRightException { + if (this.value == EMPTY_MASK) { + /* this is an identity element */ + return toAdd; + } else if (toAdd instanceof Rfc4314Rights) { + Rfc4314Rights other = (Rfc4314Rights) toAdd; + if (other.value == EMPTY_MASK) { + /* toAdd is an identity element */ + return this; + } else { + return new Rfc4314Rights(this.value | other.value); + } + } else { + return new Rfc4314Rights(this.value | new Rfc4314Rights(toAdd.serialize()).value); + } + } + + } + + /** + * A utility implementation of + * {@code Map.Entry}. + */ + public static class SimpleMailboxACLEntry implements Map.Entry { + private final MailboxACLEntryKey key; + + private final MailboxACLRights value; + + public SimpleMailboxACLEntry(MailboxACLEntryKey key, MailboxACLRights value) { + super(); + this.key = key; + this.value = value; + } + public SimpleMailboxACLEntry(String key, String value) throws UnsupportedRightException { + this(new SimpleMailboxACLEntryKey(key), new Rfc4314Rights(value)); + } + + /* + * (non-Javadoc) + * + * @see java.util.Map.Entry#getKey() + */ + @Override + public MailboxACLEntryKey getKey() { + return key; + } + + /* + * (non-Javadoc) + * + * @see java.util.Map.Entry#getValue() + */ + @Override + public MailboxACLRights getValue() { + return value; + } + + /** + * Unsupported. + * + * @see java.util.Map.Entry#setValue(java.lang.Object) + */ + @Override + public MailboxACLRights setValue(MailboxACLRights value) { + throw new java.lang.UnsupportedOperationException("Fields of " + MailboxACLRights.class.getName() + " are read only."); + } + + } + + /** + * Default implementation of {@link MailboxACLEntryKey}. + */ + public static class SimpleMailboxACLEntryKey implements MailboxACLEntryKey { + public static SimpleMailboxACLEntryKey createGroup(String name) { + return new SimpleMailboxACLEntryKey(name, NameType.group, false); + } + + public static SimpleMailboxACLEntryKey createGroup(String name, boolean negative) { + return new SimpleMailboxACLEntryKey(name, NameType.group, negative); + } + + public static SimpleMailboxACLEntryKey createUser(String name) { + return new SimpleMailboxACLEntryKey(name, NameType.user, false); + } + + public static SimpleMailboxACLEntryKey createUser(String name, boolean negative) { + return new SimpleMailboxACLEntryKey(name, NameType.user, negative); + } + + private final int hash; + private final String name; + private final NameType nameType; + private final boolean negative; + + /** + * Creates a new instance of SimpleMailboxACLEntryKey from the given + * serialized {@link String}. It supposes that negative rights are + * marked with {@link MailboxACL#DEFAULT_NEGATIVE_MARKER} and that + * groups are marked with {@link MailboxACL#DEFAULT_GROUP_MARKER}. + * + * @param serialized + */ + public SimpleMailboxACLEntryKey(String serialized) { + + if (serialized == null) { + throw new IllegalStateException("Cannot parse null to a " + getClass().getName()); + } + if (serialized.length() == 0) { + throw new IllegalStateException("Cannot parse an empty string to a " + getClass().getName()); + } + int start = 0; + if (serialized.charAt(start) == DEFAULT_NEGATIVE_MARKER) { + negative = true; + start++; + } else { + negative = false; + } + if (serialized.charAt(start) == DEFAULT_GROUP_MARKER) { + nameType = NameType.group; + start++; + name = serialized.substring(start); + if (name.length() == 0) { + throw new IllegalStateException("Cannot parse a string with empty name to a " + getClass().getName()); + } + } else { + name = serialized.substring(start); + if (name.length() == 0) { + throw new IllegalStateException("Cannot parse a string with empty name to a " + getClass().getName()); + } + NameType nt = NameType.user; + for (SpecialName specialName : SpecialName.values()) { + if (specialName.name().equals(name)) { + nt = NameType.special; + break; + } + } + this.nameType = nt; + } + + this.hash = hash(); + + } + + public SimpleMailboxACLEntryKey(String name, NameType nameType, boolean negative) { + super(); + if (name == null) { + throw new NullPointerException("Provide a name for this " + getClass().getName()); + } + if (nameType == null) { + throw new NullPointerException("Provide a nameType for this " + getClass().getName()); + } + this.name = name; + this.nameType = nameType; + this.negative = negative; + this.hash = hash(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof MailboxACLEntryKey) { + MailboxACLEntryKey other = (MailboxACLEntryKey) o; + return this.name.equals(other.getName()) && this.nameType.equals(other.getNameType()) && this.negative == other.isNegative(); + } else { + return false; + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MailboxACL.MailboxACLEntryKey#getName() + */ + public String getName() { + return name; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLEntryKey#getNameType() + */ + public NameType getNameType() { + return nameType; + } + + private int hash() { + final int PRIME = 31; + int hash = negative ? 1 : 0; + hash = PRIME * hash + nameType.hashCode(); + hash = PRIME * hash + name.hashCode(); + return hash; + } + + @Override + public int hashCode() { + return hash; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.MailboxACL.MailboxACLEntryKey#isNegative() + */ + public boolean isNegative() { + return negative; + } + + /** + * Serializes this {@link SimpleMailboxACLEntryKey} using + * {@link MailboxACL#DEFAULT_NEGATIVE_MARKER} and + * {@link MailboxACL#DEFAULT_GROUP_MARKER}. + * + * @see org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey#serialize() + */ + @Override + public String serialize() { + if (!negative) { + switch (nameType) { + case special: + case user: + return name; + case group: + return new StringBuilder(name.length() + 1).append(DEFAULT_GROUP_MARKER).append(name).toString(); + default: + throw new IllegalStateException(); + } + } else { + StringBuilder result = new StringBuilder(name.length() + 2).append(DEFAULT_NEGATIVE_MARKER); + switch (nameType) { + case special: + case user: + break; + case group: + result.append(DEFAULT_GROUP_MARKER); + break; + default: + throw new IllegalStateException(); + } + return result.append(name).toString(); + } + } + + @Override + public String toString() { + return serialize(); + } + + } + + /** + * Default implementation of {@link MailboxACLRight}. + */ + public static final class SimpleMailboxACLRight implements MailboxACLRight { + private final char value; + + public SimpleMailboxACLRight(char value) { + super(); + this.value = value; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (o instanceof MailboxACLRight) { + return ((MailboxACLRight) o).getValue() == this.value; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MailboxACL.MailboxACLRight#getValue() + */ + @Override + public char getValue() { + return value; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return (int) value; + } + + /** + * Returns String.valueOf(value). + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.valueOf(value); + } + + } + + public static final MailboxACLEntryKey ANYBODY_KEY; + public static final MailboxACLEntryKey ANYBODY_NEGATIVE_KEY; + public static final MailboxACLEntryKey AUTHENTICATED_KEY; + public static final MailboxACLEntryKey AUTHENTICATED_NEGATIVE_KEY; + public static final MailboxACL EMPTY; + + public static final MailboxACLRights FULL_RIGHTS; + + public static final MailboxACLRights NO_RIGHTS; + public static final MailboxACL OWNER_FULL_ACL; + public static final MailboxACL OWNER_FULL_EXCEPT_ADMINISTRATION_ACL; + + public static final MailboxACLEntryKey OWNER_KEY; + public static final MailboxACLEntryKey OWNER_NEGATIVE_KEY; + + static { + try { + ANYBODY_KEY = new SimpleMailboxACLEntryKey(SpecialName.anybody.name(), NameType.special, false); + ANYBODY_NEGATIVE_KEY = new SimpleMailboxACLEntryKey(SpecialName.anybody.name(), NameType.special, true); + AUTHENTICATED_KEY = new SimpleMailboxACLEntryKey(SpecialName.authenticated.name(), NameType.special, false); + AUTHENTICATED_NEGATIVE_KEY = new SimpleMailboxACLEntryKey(SpecialName.authenticated.name(), NameType.special, true); + EMPTY = new SimpleMailboxACL(); + FULL_RIGHTS = new Rfc4314Rights(true, true, true, true, true, true, true, true, true, true, true); + NO_RIGHTS = new Rfc4314Rights(); + OWNER_KEY = new SimpleMailboxACLEntryKey(SpecialName.owner.name(), NameType.special, false); + OWNER_NEGATIVE_KEY = new SimpleMailboxACLEntryKey(SpecialName.owner.name(), NameType.special, true); + OWNER_FULL_ACL = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS) }); + OWNER_FULL_EXCEPT_ADMINISTRATION_ACL = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS.except(new Rfc4314Rights(Rfc4314Rights.a_Administer_MASK))) }); + } catch (UnsupportedRightException e) { + throw new RuntimeException(e); + } + } + + private final Map entries; + + /** + * Creates a new instance of SimpleMailboxACL containing no entries. + * + */ + public SimpleMailboxACL() { + this.entries = Collections.emptyMap(); + } + + /** + * Creates a new instance of SimpleMailboxACL from the given array of + * entries. + * + * @param entries + */ + public SimpleMailboxACL(Map.Entry[] entries) { + if (entries != null) { + Map m = new HashMap(entries.length + entries.length / 2 + 1); + for (Entry en : entries) { + m.put(en.getKey(), en.getValue()); + } + this.entries = Collections.unmodifiableMap(m); + } else { + this.entries = Collections.emptyMap(); + } + } + + /** + * Creates a new instance of SimpleMailboxACL from the given {@link Map} of + * entries. + * + * @param entries + */ + public SimpleMailboxACL(Map entries) { + if (entries != null && entries.size() > 0) { + Map m = new HashMap(entries.size() + entries.size() / 2 + 1); + for (Entry en : entries.entrySet()) { + m.put(en.getKey(), en.getValue()); + } + this.entries = Collections.unmodifiableMap(m); + } else { + this.entries = Collections.emptyMap(); + } + } + + /** + * Creates a new instance of SimpleMailboxACL. + * unmodifiableEntries parameter is supposed to be umodifiable + * already. + * + * @param unmodifiableEntries + * @param dummy + * just to be different from {@link #SimpleMailboxACL(Map)}. + */ + private SimpleMailboxACL(Map unmodifiableEntries, boolean dummy) { + this.entries = unmodifiableEntries; + } + + /** + * Creates a new instance of SimpleMailboxACL from {@link Properties}. The + * keys and values from the props parameter are parsed by the + * {@link String} constructors of {@link SimpleMailboxACLEntryKey} and + * {@link Rfc4314Rights} respectively. + * + * @param props + * @throws UnsupportedRightException + */ + public SimpleMailboxACL(Properties props) throws UnsupportedRightException { + super(); + + Map m = new HashMap(props.size() + props.size() / 2 + 1); + + if (props != null) { + for (Map.Entry prop : props.entrySet()) { + m.put(new SimpleMailboxACLEntryKey((String) prop.getKey()), new Rfc4314Rights((String) prop.getValue())); + } + } + + entries = Collections.unmodifiableMap(m); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (o instanceof MailboxACL) { + MailboxACL acl = (MailboxACL) o; + Map ens = acl.getEntries(); + return entries == ens || (entries != null && entries.equals(ens)); + } + return false; + } + + /** + * @see org.apache.james.mailbox.MailboxACL#except(org.apache.james.mailbox.MailboxACL) + */ + @Override + public MailboxACL except(MailboxACL other) throws UnsupportedRightException { + if (entries.size() == 0) { + return this; + } else { + Map otherEntries = other.getEntries(); + Map resultEntries = new HashMap(this.entries); + for (Entry otherEntry : otherEntries.entrySet()) { + MailboxACLEntryKey key = otherEntry.getKey(); + MailboxACLRights thisRights = resultEntries.get(key); + if (thisRights == null) { + /* nothing to diff */ + } else { + /* diff */ + MailboxACLRights resultRights = thisRights.except(otherEntry.getValue()); + if (!resultRights.isEmpty()) { + resultEntries.put(key, resultRights); + } + else { + resultEntries.remove(key); + } + } + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL#except(org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + public MailboxACL except(MailboxACLEntryKey key, MailboxACLRights mailboxACLRights) throws UnsupportedRightException { + Map resultEntries = new HashMap(this.entries); + MailboxACLRights thisRights = resultEntries.get(key); + if (thisRights == null) { + /* nothing to diff */ + } else { + /* diff */ + MailboxACLRights resultRights = thisRights.except(mailboxACLRights); + if (!resultRights.isEmpty()) { + resultEntries.put(key, resultRights); + } + else { + resultEntries.remove(key); + } + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + + /** + * @see org.apache.james.mailbox.MailboxACL#getEntries() + */ + @Override + public Map getEntries() { + return entries; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return entries == null ? 0 : entries.hashCode(); + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL#replace(org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + @Override + public MailboxACL replace(MailboxACLEntryKey key, MailboxACLRights replacement) throws UnsupportedRightException { + Map resultEntries = new HashMap(this.entries); + if (replacement == null || replacement.isEmpty()) { + resultEntries.remove(key); + } else { + resultEntries.put(key, replacement); + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return entries == null ? "" : entries.toString(); + } + + /** + * @see org.apache.james.mailbox.MailboxACL#union(org.apache.james.mailbox.MailboxACL) + */ + @Override + public MailboxACL union(MailboxACL other) throws UnsupportedRightException { + Map otherEntries = other.getEntries(); + if (otherEntries.size() == 0) { + return this; + } else if (entries.size() == 0) { + return other; + } else { + int cnt = otherEntries.size() + entries.size(); + Map resultEntries = new HashMap(cnt + cnt / 2 + 1); + for (Entry otherEntry : otherEntries.entrySet()) { + MailboxACLEntryKey key = otherEntry.getKey(); + MailboxACLRights thisRights = entries.get(key); + if (thisRights == null) { + /* nothing to union */ + resultEntries.put(key, otherEntry.getValue()); + } else { + /* union */ + resultEntries.put(key, otherEntry.getValue().union(thisRights)); + } + } + /* let us check what we have missed in the previous loop */ + for (Entry thisEntry : entries.entrySet()) { + MailboxACLEntryKey key = thisEntry.getKey(); + if (!resultEntries.containsKey(key)) { + resultEntries.put(key, thisEntry.getValue()); + } + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + } + + /** + * @see org.apache.james.mailbox.model.MailboxACL#union(org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + public MailboxACL union(MailboxACLEntryKey key, MailboxACLRights mailboxACLRights) throws UnsupportedRightException { + Map resultEntries = new HashMap(this.entries); + MailboxACLRights thisRights = resultEntries.get(key); + if (thisRights == null) { + /* nothing to union */ + resultEntries.put(key, mailboxACLRights); + } else { + /* union */ + resultEntries.put(key, thisRights.union(mailboxACLRights)); + } + return new SimpleMailboxACL(Collections.unmodifiableMap(resultEntries), true); + } + +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java new file mode 100644 index 0000000..c3a746b --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java @@ -0,0 +1,172 @@ +/**************************************************************** + * 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.model; + +import java.util.Arrays; +import java.util.Iterator; + +import javax.mail.Flags; + +/** + * Represent a Flag update for a message + * + * + */ +public class UpdatedFlags { + + private final long uid; + private final Flags oldFlags; + private final Flags newFlags; + private final Flags modifiedFlags; + private long modSeq; + + public UpdatedFlags(long uid, long modSeq, Flags oldFlags, Flags newFlags) { + this.uid = uid; + this.modSeq = modSeq; + this.oldFlags = oldFlags; + this.newFlags = newFlags; + this.modifiedFlags = new Flags(); + addModifiedSystemFlags(oldFlags, newFlags, modifiedFlags); + addModifiedUserFlags(oldFlags, newFlags, modifiedFlags); + } + + private static void addModifiedSystemFlags(Flags oldFlags, Flags newFlags, Flags modifiedFlags) { + if (isChanged(oldFlags, newFlags, Flags.Flag.ANSWERED)) { + modifiedFlags.add(Flags.Flag.ANSWERED); + } + if(isChanged(oldFlags, newFlags, Flags.Flag.DELETED)) { + modifiedFlags.add(Flags.Flag.DELETED); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.DRAFT)) { + modifiedFlags.add(Flags.Flag.DRAFT); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.FLAGGED)) { + modifiedFlags.add(Flags.Flag.FLAGGED); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.RECENT)) { + modifiedFlags.add(Flags.Flag.RECENT); + } + if (isChanged(oldFlags, newFlags, Flags.Flag.SEEN)) { + modifiedFlags.add(Flags.Flag.SEEN); + } + } + + private static void addModifiedUserFlags(Flags oldFlags, Flags newFlags, Flags modifiedFlags) { + addModifiedUserFlags(oldFlags, newFlags, oldFlags.getUserFlags(), modifiedFlags); + addModifiedUserFlags(oldFlags, newFlags, newFlags.getUserFlags(), modifiedFlags); + + } + + + private static void addModifiedUserFlags(Flags oldFlags, Flags newFlags, String[] userflags, Flags modifiedFlags) { + for (int i = 0; i < userflags.length; i++) { + String userFlag = userflags[i]; + if (isChanged(oldFlags, newFlags, userFlag)) { + modifiedFlags.add(userFlag); + + } + } + } + private static boolean isChanged(final Flags original, final Flags updated, Flags.Flag flag) { + return original != null && updated != null && (original.contains(flag) ^ updated.contains(flag)); + } + + private static boolean isChanged(final Flags original, final Flags updated, String userFlag) { + return original != null && updated != null && (original.contains(userFlag) ^ updated.contains(userFlag)); + } + + + /** + * Return the old {@link Flags} for the message + * + * @return oldFlags + */ + public Flags getOldFlags() { + return oldFlags; + } + + /** + * Return the new {@link Flags} for the message + * + * @return newFlags + */ + public Flags getNewFlags() { + return newFlags; + } + + /** + * Return the uid for the message whichs {@link Flags} was updated + * + * @return uid + */ + public long getUid() { + return uid; + } + + + /** + * Gets an iterator for the system flags changed. + * + * @return Flags.Flag Iterator, not null + */ + + public Iterator systemFlagIterator() { + return Arrays.asList(modifiedFlags.getSystemFlags()).iterator(); + } + + /** + * Gets an iterator for the users flags changed. + * + * @return String Iterator, not null + */ + + public Iterator userFlagIterator() { + return Arrays.asList(modifiedFlags.getUserFlags()).iterator(); + } + + + /** + * Return the new mod-sequence for the message + * + * @return mod-seq + */ + public long getModSeq() { + return modSeq; + } + + public static boolean flagsChanged(Flags flagsOld, Flags flagsNew) { + Flags modifiedFlags = new Flags(); + addModifiedSystemFlags(flagsOld, flagsNew, modifiedFlags); + addModifiedUserFlags(flagsOld, flagsNew, modifiedFlags); + if (modifiedFlags.getSystemFlags().length > 0 || modifiedFlags.getUserFlags().length > 0) { + return true; + } else { + return false; + } + } + + public boolean flagsChanged() { + if (modifiedFlags.getSystemFlags().length > 0 || modifiedFlags.getUserFlags().length > 0) { + return true; + } else { + return false; + } + } +} diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/all-wcprops b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/all-wcprops new file mode 100644 index 0000000..04e65de --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 96 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/quota +END diff --git a/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/entries b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/entries new file mode 100644 index 0000000..2fcbef6 --- /dev/null +++ b/james/apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/quota/.svn/entries @@ -0,0 +1,28 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/quota +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + diff --git a/james/apache-james-mailbox/api/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/api/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..31de497 --- /dev/null +++ b/james/apache-james-mailbox/api/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/api/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/api/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/api/src/reporting-site/.svn/entries b/james/apache-james-mailbox/api/src/reporting-site/.svn/entries new file mode 100644 index 0000000..fe2c82f --- /dev/null +++ b/james/apache-james-mailbox/api/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-03-05T11:38:38.771020Z +1078275 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:40.000000Z +5bde8261948ff1881960902a322aa196 +2011-03-05T11:38:38.771020Z +1078275 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/api/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/api/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/api/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/api/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/api/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/api/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/api/src/reporting-site/site.xml b/james/apache-james-mailbox/api/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/api/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/api/src/test/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/.svn/all-wcprops new file mode 100644 index 0000000..6af0686 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test +END diff --git a/james/apache-james-mailbox/api/src/test/.svn/entries b/james/apache-james-mailbox/api/src/test/.svn/entries new file mode 100644 index 0000000..b211a1a --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/api/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..b40a916 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 65 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java +END diff --git a/james/apache-james-mailbox/api/src/test/java/.svn/entries b/james/apache-james-mailbox/api/src/test/java/.svn/entries new file mode 100644 index 0000000..1469203 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/api/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..a2dd579 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/.svn/entries new file mode 100644 index 0000000..0b1d0f2 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..cd152dc --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 76 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..593a4a8 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..4f6bcd1 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..b7f81e5 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..deb4619 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,41 @@ +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox +END +MessageRangeTest.java +K 25 +svn:wc:ra_dav:version-url +V 112 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/MessageRangeTest.java +END +AbstractSubscriptionManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1051400/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/AbstractSubscriptionManagerTest.java +END +AbstractStressTest.java +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java +END +AbstractMailboxManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/AbstractMailboxManagerTest.java +END +MailboxExceptionTest.java +K 25 +svn:wc:ra_dav:version-url +V 116 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/MailboxExceptionTest.java +END +MailboxExpressionTest.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/MailboxExpressionTest.java +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..25ff4ef --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,244 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +acl +dir + +model +dir + +MessageRangeTest.java +file + + + + +2013-09-02T02:54:40.000000Z +6d7020e814777418cc8af4017a0bffb5 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3755 + +AbstractSubscriptionManagerTest.java +file + + + + +2013-09-02T02:54:40.000000Z +f01d423585131a4fc276743099bad98a +2010-12-21T07:49:30.029651Z +1051400 +eric + + + + + + + + + + + + + + + + + + + + + +3291 + +AbstractStressTest.java +file + + + + +2013-09-02T02:54:40.000000Z +b432415b15ae0868b8e7e10d15b0095d +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +5112 + +mock +dir + +AbstractMailboxManagerTest.java +file + + + + +2013-09-02T02:54:40.000000Z +819c8ac03e0ac1ef52aedb5b82f01673 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +6620 + +MailboxExceptionTest.java +file + + + + +2013-09-02T02:54:40.000000Z +0acf8e229f1c142ac599026592eb8edf +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2105 + +MailboxExpressionTest.java +file + + + + +2013-09-02T02:54:40.000000Z +ca5bedcca6f8797cbf45e6bcb50ec302 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +14505 + +util +dir + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractMailboxManagerTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractMailboxManagerTest.java.svn-base new file mode 100644 index 0000000..839a736 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractMailboxManagerTest.java.svn-base @@ -0,0 +1,158 @@ +/**************************************************************** + * 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.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; + +import javax.mail.Flags; + +import junit.framework.Assert; + +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.mock.MockMailboxManager; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +/** + * Test the {@link StoreMailboxManager} methods that + * are not covered by the protocol-tester suite. + * + * This class needs to be extended by the different mailbox + * implementations which are responsible to setup and + * implement the test methods. + * + */ +public abstract class AbstractMailboxManagerTest { + + private final static String USER_1 = "USER_1"; + private final static String USER_2 = "USER_2"; + + /** + * The mailboxManager that needs to get instanciated + * by the mailbox implementations. + */ + protected MailboxManager mailboxManager; + + @Test + public void testBasicOperations() throws BadCredentialsException, MailboxException, UnsupportedEncodingException { + + setMailboxManager(new MockMailboxManager(getMailboxManager()).getMockMailboxManager()); + + MailboxSession session = getMailboxManager().createSystemSession(USER_1, LoggerFactory.getLogger("Mock")); + Assert.assertEquals(USER_1, session.getUser().getUserName()); + + getMailboxManager().startProcessingRequest(session); + + MailboxPath inbox = MailboxPath.inbox(session); + Assert.assertFalse(getMailboxManager().mailboxExists(inbox, session)); + + getMailboxManager().createMailbox(inbox, session); + Assert.assertTrue(getMailboxManager().mailboxExists(inbox, session)); + + try { + getMailboxManager().createMailbox(inbox, session); + Assert.fail(); + } catch (MailboxException e) { + // mailbox already exists! + } + + MailboxPath inboxSubMailbox = new MailboxPath(inbox, "INBOX.Test"); + Assert.assertFalse(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().createMailbox(inboxSubMailbox, session); + Assert.assertTrue(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().deleteMailbox(inbox, session); + Assert.assertFalse(getMailboxManager().mailboxExists(inbox, session)); + + Assert.assertTrue(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().deleteMailbox(inboxSubMailbox, session); + Assert.assertFalse(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().logout(session, false); + getMailboxManager().endProcessingRequest(session); + + Assert.assertFalse(session.isOpen()); + + } + + /** + * Create some INBOXes and their sub mailboxes and assert list() method. + * + * @throws UnsupportedEncodingException + * @throws MailboxException + */ + @Test + public void testList() throws MailboxException, UnsupportedEncodingException { + + setMailboxManager(new MockMailboxManager(getMailboxManager()).getMockMailboxManager()); + + MailboxSession mailboxSession = getMailboxManager().createSystemSession("manager", LoggerFactory.getLogger("testList")); + getMailboxManager().startProcessingRequest(mailboxSession); + Assert.assertEquals(MockMailboxManager.EXPECTED_MAILBOXES_COUNT, getMailboxManager().list(mailboxSession).size()); + + } + + + @Test + public void testCreateSubFolderDirectly() throws BadCredentialsException, MailboxException { + + MailboxSession session = getMailboxManager().createSystemSession(USER_2, LoggerFactory.getLogger("Test")); + getMailboxManager().createMailbox(new MailboxPath(MailboxConstants.USER_NAMESPACE, USER_2, "Trash"), session); + getMailboxManager().createMailbox(new MailboxPath(MailboxConstants.USER_NAMESPACE, USER_2, "INBOX.testfolder"), session); + + getMailboxManager().getMailbox(MailboxPath.inbox(session), session).appendMessage(new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), session, false, new Flags()); + + } + + /** + * Implement this method to create the mailboxManager. + * + * @return + * @throws MailboxException + */ + protected abstract void createMailboxManager() throws MailboxException; + + /** + * Setter to inject the mailboxManager. + */ + protected void setMailboxManager(MailboxManager mailboxManager) { + this.mailboxManager = mailboxManager; + } + + /** + * Accessor to the mailboxManager. + * + * @return the mailboxManager instance. + * @throws IllegalStateException in case of null mailboxManager + */ + protected MailboxManager getMailboxManager() { + if (mailboxManager == null) { + throw new IllegalStateException("Please setMailboxManager with a non null value before requesting getMailboxManager()"); + } + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractStressTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractStressTest.java.svn-base new file mode 100644 index 0000000..f533482 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractStressTest.java.svn-base @@ -0,0 +1,124 @@ +/**************************************************************** + * 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 org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import javax.mail.Flags; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class AbstractStressTest { + + private final static int APPEND_OPERATIONS = 200; + + + protected abstract MailboxManager getMailboxManager(); + + @Test + public void testStessTest() throws InterruptedException, MailboxException { + + final CountDownLatch latch = new CountDownLatch(APPEND_OPERATIONS); + final ExecutorService pool = Executors.newFixedThreadPool(APPEND_OPERATIONS / 2); + final List uList = new ArrayList(); + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + getMailboxManager().startProcessingRequest(session); + final MailboxPath path = new MailboxPath(MailboxConstants.USER_NAMESPACE, "username", "INBOX"); + getMailboxManager().createMailbox(path, session); + getMailboxManager().addListener(path, new MailboxListener() { + + + @Override + public void event(Event event) { + long u = ((Added) event).getUids().get(0); + uList.add(u); + } + }, session); + getMailboxManager().endProcessingRequest(session); + getMailboxManager().logout(session, false); + + final AtomicBoolean fail = new AtomicBoolean(false); + final ConcurrentHashMap uids = new ConcurrentHashMap(); + + // fire of 1000 append operations + for (int i = 0; i < APPEND_OPERATIONS; i++) { + pool.execute(new Runnable() { + + public void run() { + if (fail.get()) { + latch.countDown(); + return; + } + + + try { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + + getMailboxManager().startProcessingRequest(session); + MessageManager m = getMailboxManager().getMailbox(path, session); + Long uid = m.appendMessage(new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), session, false, new Flags()); + + System.out.println("Append message with uid=" + uid); + if (uids.put(uid, new Object()) != null) { + fail.set(true); + } + getMailboxManager().endProcessingRequest(session); + getMailboxManager().logout(session, false); + } catch (MailboxException e) { + e.printStackTrace(); + fail.set(true); + } finally { + latch.countDown(); + } + + + } + }); + } + + latch.await(); + + // check if the uids were higher on each append. See MAILBOX-131 + long last = 0; + for (int i = 0; i < uList.size(); i++) { + long l = uList.get(i); + if (l <= last) { + fail(l + "->" + last); + } else { + last = l; + } + + } + assertFalse("Unable to append all messages", fail.get()); + pool.shutdown(); + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractSubscriptionManagerTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractSubscriptionManagerTest.java.svn-base new file mode 100644 index 0000000..b545d3a --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/AbstractSubscriptionManagerTest.java.svn-base @@ -0,0 +1,76 @@ +/**************************************************************** + * 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.util.Collection; + +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.junit.Assert; +import org.junit.Test; + +/** + * Abstract base class to test {@link SubscriptionManager} implementations + * + * + */ +public abstract class AbstractSubscriptionManagerTest { + + private final static String USER1 = "test"; + private final static String MAILBOX1 = "test1"; + private final static String MAILBOX2 = "test2"; + + public abstract SubscriptionManager createSubscriptionManager(); + + @Test + public void testSubscriptionManager() throws Exception { + SubscriptionManager manager = createSubscriptionManager(); + MailboxSession session = new MockMailboxSession(USER1); + manager.startProcessingRequest(session); + + Assert.assertTrue(manager.subscriptions(session).isEmpty()); + + manager.subscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX1, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + + manager.subscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX1, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + manager.subscribe(session, MAILBOX2); + Collection col = manager.subscriptions(session); + + Assert.assertTrue(col.contains(MAILBOX2)); + Assert.assertTrue(col.contains(MAILBOX1)); + Assert.assertEquals(2, col.size()); + + + manager.unsubscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX2, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + manager.unsubscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX2, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + + manager.endProcessingRequest(session); + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExceptionTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExceptionTest.java.svn-base new file mode 100644 index 0000000..5d9ed19 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExceptionTest.java.svn-base @@ -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; + +import junit.framework.Assert; + +import org.apache.james.mailbox.exception.MailboxException; +import org.junit.Test; + +/** + * Ensure that {@link MailboxException} construction is correct. + */ +public class MailboxExceptionTest { + + private static final String EXCEPTION_MESSAGE = "this is an exception message"; + + private static final Exception EXCEPTION_CAUSE = new Exception("this is a cause"); + + @Test + public void testMailboxExceptionMessage() { + MailboxException mbe = new MailboxException(EXCEPTION_MESSAGE); + Assert.assertEquals(EXCEPTION_MESSAGE, mbe.getMessage()); + } + + @Test + public void testMailboxExceptionCause() { + MailboxException mbe = new MailboxException(EXCEPTION_MESSAGE, EXCEPTION_CAUSE); + Assert.assertEquals(EXCEPTION_MESSAGE, mbe.getMessage()); + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExpressionTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExpressionTest.java.svn-base new file mode 100644 index 0000000..c87bcc8 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MailboxExpressionTest.java.svn-base @@ -0,0 +1,317 @@ +/**************************************************************** + * 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.junit.Assert.*; + +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MailboxQuery; +import org.junit.Test; + +public class MailboxExpressionTest { + + private static final String PART = "mailbox"; + + private static final String SECOND_PART = "sub"; + + private static final String BASE = "BASE"; + private static final MailboxPath BASE_PATH = new MailboxPath(null, null, BASE); + + + private MailboxQuery create(String expression) { + return new MailboxQuery(BASE_PATH, expression, '.'); + } + + @Test + public void testIsWild() throws Exception { + assertTrue(create("*").isWild()); + assertTrue(create("%").isWild()); + assertTrue(create("One*").isWild()); + assertTrue(create("*One").isWild()); + assertTrue(create("A*A").isWild()); + assertTrue(create("One%").isWild()); + assertTrue(create("%One").isWild()); + assertTrue(create("A%A").isWild()); + assertFalse(create("").isWild()); + assertFalse(create(null).isWild()); + assertFalse(create("ONE").isWild()); + } + + @Test + public void testCombinedNameEmptyPart() throws Exception { + MailboxQuery expression = new MailboxQuery(BASE_PATH, "", '.'); + assertEquals(BASE, expression.getCombinedName()); + } + + @Test + public void testNullCombinedName() throws Exception { + MailboxQuery expression = new MailboxQuery(new MailboxPath(null, null, null), null, '.'); + assertNotNull(expression.getCombinedName()); + } + + @Test + public void testSimpleCombinedName() throws Exception { + MailboxQuery expression = create(PART); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testCombinedNamePartStartsWithDelimiter() throws Exception { + MailboxQuery expression = create("." + PART); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testCombinedNameBaseEndsWithDelimiter() throws Exception { + MailboxQuery expression = new MailboxQuery(new MailboxPath(null, null, BASE + '.'), PART, '.'); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testCombinedNameBaseEndsWithDelimiterPartStarts() + throws Exception { + MailboxQuery expression = new MailboxQuery(new MailboxPath(null, null, BASE + '.'), '.' + PART, '.'); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testSimpleExpression() throws Exception { + MailboxQuery expression = create(PART); + assertTrue(expression.isExpressionMatch(PART)); + assertFalse(expression.isExpressionMatch('.' + PART)); + assertFalse(expression.isExpressionMatch(PART + '.')); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + } + + @Test + public void testEmptyExpression() throws Exception { + MailboxQuery expression = create(""); + assertTrue(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch("whatever")); + assertFalse(expression.isExpressionMatch(BASE + '.' + "whatever")); + assertFalse(expression.isExpressionMatch(BASE + "whatever")); + } + + @Test + public void testOnlyLocalWildcard() throws Exception { + MailboxQuery expression = create("%"); + assertTrue(expression.isExpressionMatch("")); + assertTrue(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testOnlyFreeWildcard() throws Exception { + MailboxQuery expression = create("*"); + assertTrue(expression.isExpressionMatch("")); + assertTrue(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testEndsWithLocalWildcard() throws Exception { + MailboxQuery expression = create(PART + '%'); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testStartsWithLocalWildcard() throws Exception { + MailboxQuery expression = create('%' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART + '.' + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + } + + @Test + public void testContainsLocalWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + '%' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testEndsWithFreeWildcard() throws Exception { + MailboxQuery expression = create(PART + '*'); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testStartsWithFreeWildcard() throws Exception { + MailboxQuery expression = create('*' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + } + + @Test + public void testContainsFreeWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + '*' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testDoubleFreeWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + "**" + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testFreeLocalWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + "*%" + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testLocalFreeWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + "%*" + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testMultipleFreeWildcards() throws Exception { + MailboxQuery expression = create(SECOND_PART + '*' + PART + '*' + + SECOND_PART + "**"); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + '.' + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "tosh.bosh" + + PART + "tosh.bosh" + SECOND_PART + "boshtosh")); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + + PART.substring(1) + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + + PART.substring(1) + '.' + SECOND_PART + PART + '.' + + SECOND_PART + "toshbosh")); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + + PART.substring(1) + '.' + SECOND_PART + PART + '.' + + SECOND_PART.substring(1))); + assertTrue(expression.isExpressionMatch(SECOND_PART + "tosh.bosh" + + PART + "tosh.bosh" + PART + SECOND_PART + "boshtosh" + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART.substring(1) + + "tosh.bosh" + PART + "tosh.bosh" + SECOND_PART + + PART.substring(1) + SECOND_PART + "boshtosh" + PART + + SECOND_PART.substring(1))); + } + + @Test + public void testSimpleMixedWildcards() throws Exception { + MailboxQuery expression = create(SECOND_PART + '%' + PART + '*' + + SECOND_PART); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + '.' + + SECOND_PART)); + + // Disable this tests as these are wrong. See MAILBOX-65 + //assertTrue(expression.isExpressionMatch(SECOND_PART + PART + // + SECOND_PART + "Whatever")); + //assertTrue(expression.isExpressionMatch(SECOND_PART + PART + // + SECOND_PART + ".Whatever.")); + } + + @Test + public void testFreeLocalMixedWildcards() throws Exception { + MailboxQuery expression = create(SECOND_PART + '*' + PART + '%' + + SECOND_PART); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + PART + '.' + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + "Whatever" + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART + ".Whatever.")); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + '.' + + SECOND_PART + PART + SECOND_PART)); + } + + @Test + public void testTwoLocalWildcardsShouldMatchMailboxs() throws Exception { + MailboxQuery expression = create("%.%"); + assertFalse(expression.isExpressionMatch(PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testMailbox65() throws Exception { + MailboxQuery expression = create("*.test"); + assertTrue(expression.isExpressionMatch("blah.test")); + assertFalse(expression.isExpressionMatch("blah.test.go")); + + assertFalse(expression.isExpressionMatch("blah.test3")); + + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MessageRangeTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MessageRangeTest.java.svn-base new file mode 100644 index 0000000..6207350 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/.svn/text-base/MessageRangeTest.java.svn-base @@ -0,0 +1,95 @@ +/**************************************************************** + * 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.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +import org.apache.james.mailbox.model.MessageRange; +import org.junit.Test; + +public class MessageRangeTest { + + @Test + public void testToRanges() { + List ranges = MessageRange.toRanges(Arrays.asList(1L,2L,3L,5L,6L,9L)); + assertEquals(3, ranges.size()); + checkRange(1, 3, ranges.get(0)); + checkRange(5, 6, ranges.get(1)); + checkRange(9, 9, ranges.get(2)); + + } + + @Test + public void testOneUidToRange() { + List ranges = MessageRange.toRanges(Arrays.asList(1L)); + assertEquals(1, ranges.size()); + checkRange(1, 1, ranges.get(0)); + } + + // Test for MAILBOX-56 + @Test + public void testTwoSeqUidToRange() { + List ranges = MessageRange.toRanges(Arrays.asList(1L,2L)); + assertEquals(1, ranges.size()); + checkRange(1, 2, ranges.get(0)); + + } + + + private void checkRange(long from, long to, MessageRange range) { + assertEquals(from, range.getUidFrom()); + assertEquals(to, range.getUidTo()); + } + + @Test + public void testSplitOne() { + MessageRange one = MessageRange.one(1); + List ranges = one.split(2); + assertEquals(1, ranges.size()); + checkRange(1, 1, ranges.get(0)); + assertEquals(MessageRange.Type.ONE, ranges.get(0).getType()); + } + + @Test + public void testSplitFrom() { + MessageRange from = MessageRange.from(1); + List ranges = from.split(2); + assertEquals(1, ranges.size()); + checkRange(1, MessageRange.NOT_A_UID, ranges.get(0)); + assertEquals(MessageRange.Type.FROM, ranges.get(0).getType()); + } + + @Test + public void testSplitRange() { + MessageRange range = MessageRange.range(1,10); + List ranges = range.split(3); + assertEquals(4, ranges.size()); + checkRange(1, 3, ranges.get(0)); + assertEquals(MessageRange.Type.RANGE, ranges.get(0).getType()); + checkRange(4, 6, ranges.get(1)); + assertEquals(MessageRange.Type.RANGE, ranges.get(1).getType()); + checkRange(7, 9, ranges.get(2)); + assertEquals(MessageRange.Type.RANGE, ranges.get(2).getType()); + checkRange(10, 10, ranges.get(3)); + assertEquals(MessageRange.Type.ONE, ranges.get(3).getType()); + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractMailboxManagerTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractMailboxManagerTest.java new file mode 100644 index 0000000..839a736 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractMailboxManagerTest.java @@ -0,0 +1,158 @@ +/**************************************************************** + * 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.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; + +import javax.mail.Flags; + +import junit.framework.Assert; + +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.mock.MockMailboxManager; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +/** + * Test the {@link StoreMailboxManager} methods that + * are not covered by the protocol-tester suite. + * + * This class needs to be extended by the different mailbox + * implementations which are responsible to setup and + * implement the test methods. + * + */ +public abstract class AbstractMailboxManagerTest { + + private final static String USER_1 = "USER_1"; + private final static String USER_2 = "USER_2"; + + /** + * The mailboxManager that needs to get instanciated + * by the mailbox implementations. + */ + protected MailboxManager mailboxManager; + + @Test + public void testBasicOperations() throws BadCredentialsException, MailboxException, UnsupportedEncodingException { + + setMailboxManager(new MockMailboxManager(getMailboxManager()).getMockMailboxManager()); + + MailboxSession session = getMailboxManager().createSystemSession(USER_1, LoggerFactory.getLogger("Mock")); + Assert.assertEquals(USER_1, session.getUser().getUserName()); + + getMailboxManager().startProcessingRequest(session); + + MailboxPath inbox = MailboxPath.inbox(session); + Assert.assertFalse(getMailboxManager().mailboxExists(inbox, session)); + + getMailboxManager().createMailbox(inbox, session); + Assert.assertTrue(getMailboxManager().mailboxExists(inbox, session)); + + try { + getMailboxManager().createMailbox(inbox, session); + Assert.fail(); + } catch (MailboxException e) { + // mailbox already exists! + } + + MailboxPath inboxSubMailbox = new MailboxPath(inbox, "INBOX.Test"); + Assert.assertFalse(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().createMailbox(inboxSubMailbox, session); + Assert.assertTrue(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().deleteMailbox(inbox, session); + Assert.assertFalse(getMailboxManager().mailboxExists(inbox, session)); + + Assert.assertTrue(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().deleteMailbox(inboxSubMailbox, session); + Assert.assertFalse(getMailboxManager().mailboxExists(inboxSubMailbox, session)); + + getMailboxManager().logout(session, false); + getMailboxManager().endProcessingRequest(session); + + Assert.assertFalse(session.isOpen()); + + } + + /** + * Create some INBOXes and their sub mailboxes and assert list() method. + * + * @throws UnsupportedEncodingException + * @throws MailboxException + */ + @Test + public void testList() throws MailboxException, UnsupportedEncodingException { + + setMailboxManager(new MockMailboxManager(getMailboxManager()).getMockMailboxManager()); + + MailboxSession mailboxSession = getMailboxManager().createSystemSession("manager", LoggerFactory.getLogger("testList")); + getMailboxManager().startProcessingRequest(mailboxSession); + Assert.assertEquals(MockMailboxManager.EXPECTED_MAILBOXES_COUNT, getMailboxManager().list(mailboxSession).size()); + + } + + + @Test + public void testCreateSubFolderDirectly() throws BadCredentialsException, MailboxException { + + MailboxSession session = getMailboxManager().createSystemSession(USER_2, LoggerFactory.getLogger("Test")); + getMailboxManager().createMailbox(new MailboxPath(MailboxConstants.USER_NAMESPACE, USER_2, "Trash"), session); + getMailboxManager().createMailbox(new MailboxPath(MailboxConstants.USER_NAMESPACE, USER_2, "INBOX.testfolder"), session); + + getMailboxManager().getMailbox(MailboxPath.inbox(session), session).appendMessage(new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), session, false, new Flags()); + + } + + /** + * Implement this method to create the mailboxManager. + * + * @return + * @throws MailboxException + */ + protected abstract void createMailboxManager() throws MailboxException; + + /** + * Setter to inject the mailboxManager. + */ + protected void setMailboxManager(MailboxManager mailboxManager) { + this.mailboxManager = mailboxManager; + } + + /** + * Accessor to the mailboxManager. + * + * @return the mailboxManager instance. + * @throws IllegalStateException in case of null mailboxManager + */ + protected MailboxManager getMailboxManager() { + if (mailboxManager == null) { + throw new IllegalStateException("Please setMailboxManager with a non null value before requesting getMailboxManager()"); + } + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java new file mode 100644 index 0000000..f533482 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractStressTest.java @@ -0,0 +1,124 @@ +/**************************************************************** + * 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 org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import javax.mail.Flags; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class AbstractStressTest { + + private final static int APPEND_OPERATIONS = 200; + + + protected abstract MailboxManager getMailboxManager(); + + @Test + public void testStessTest() throws InterruptedException, MailboxException { + + final CountDownLatch latch = new CountDownLatch(APPEND_OPERATIONS); + final ExecutorService pool = Executors.newFixedThreadPool(APPEND_OPERATIONS / 2); + final List uList = new ArrayList(); + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + getMailboxManager().startProcessingRequest(session); + final MailboxPath path = new MailboxPath(MailboxConstants.USER_NAMESPACE, "username", "INBOX"); + getMailboxManager().createMailbox(path, session); + getMailboxManager().addListener(path, new MailboxListener() { + + + @Override + public void event(Event event) { + long u = ((Added) event).getUids().get(0); + uList.add(u); + } + }, session); + getMailboxManager().endProcessingRequest(session); + getMailboxManager().logout(session, false); + + final AtomicBoolean fail = new AtomicBoolean(false); + final ConcurrentHashMap uids = new ConcurrentHashMap(); + + // fire of 1000 append operations + for (int i = 0; i < APPEND_OPERATIONS; i++) { + pool.execute(new Runnable() { + + public void run() { + if (fail.get()) { + latch.countDown(); + return; + } + + + try { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + + getMailboxManager().startProcessingRequest(session); + MessageManager m = getMailboxManager().getMailbox(path, session); + Long uid = m.appendMessage(new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), session, false, new Flags()); + + System.out.println("Append message with uid=" + uid); + if (uids.put(uid, new Object()) != null) { + fail.set(true); + } + getMailboxManager().endProcessingRequest(session); + getMailboxManager().logout(session, false); + } catch (MailboxException e) { + e.printStackTrace(); + fail.set(true); + } finally { + latch.countDown(); + } + + + } + }); + } + + latch.await(); + + // check if the uids were higher on each append. See MAILBOX-131 + long last = 0; + for (int i = 0; i < uList.size(); i++) { + long l = uList.get(i); + if (l <= last) { + fail(l + "->" + last); + } else { + last = l; + } + + } + assertFalse("Unable to append all messages", fail.get()); + pool.shutdown(); + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractSubscriptionManagerTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractSubscriptionManagerTest.java new file mode 100644 index 0000000..b545d3a --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/AbstractSubscriptionManagerTest.java @@ -0,0 +1,76 @@ +/**************************************************************** + * 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.util.Collection; + +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.junit.Assert; +import org.junit.Test; + +/** + * Abstract base class to test {@link SubscriptionManager} implementations + * + * + */ +public abstract class AbstractSubscriptionManagerTest { + + private final static String USER1 = "test"; + private final static String MAILBOX1 = "test1"; + private final static String MAILBOX2 = "test2"; + + public abstract SubscriptionManager createSubscriptionManager(); + + @Test + public void testSubscriptionManager() throws Exception { + SubscriptionManager manager = createSubscriptionManager(); + MailboxSession session = new MockMailboxSession(USER1); + manager.startProcessingRequest(session); + + Assert.assertTrue(manager.subscriptions(session).isEmpty()); + + manager.subscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX1, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + + manager.subscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX1, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + manager.subscribe(session, MAILBOX2); + Collection col = manager.subscriptions(session); + + Assert.assertTrue(col.contains(MAILBOX2)); + Assert.assertTrue(col.contains(MAILBOX1)); + Assert.assertEquals(2, col.size()); + + + manager.unsubscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX2, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + manager.unsubscribe(session, MAILBOX1); + Assert.assertEquals(MAILBOX2, manager.subscriptions(session).iterator().next()); + Assert.assertEquals(1, manager.subscriptions(session).size()); + + + manager.endProcessingRequest(session); + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExceptionTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExceptionTest.java new file mode 100644 index 0000000..5d9ed19 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExceptionTest.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; + +import junit.framework.Assert; + +import org.apache.james.mailbox.exception.MailboxException; +import org.junit.Test; + +/** + * Ensure that {@link MailboxException} construction is correct. + */ +public class MailboxExceptionTest { + + private static final String EXCEPTION_MESSAGE = "this is an exception message"; + + private static final Exception EXCEPTION_CAUSE = new Exception("this is a cause"); + + @Test + public void testMailboxExceptionMessage() { + MailboxException mbe = new MailboxException(EXCEPTION_MESSAGE); + Assert.assertEquals(EXCEPTION_MESSAGE, mbe.getMessage()); + } + + @Test + public void testMailboxExceptionCause() { + MailboxException mbe = new MailboxException(EXCEPTION_MESSAGE, EXCEPTION_CAUSE); + Assert.assertEquals(EXCEPTION_MESSAGE, mbe.getMessage()); + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExpressionTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExpressionTest.java new file mode 100644 index 0000000..c87bcc8 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MailboxExpressionTest.java @@ -0,0 +1,317 @@ +/**************************************************************** + * 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.junit.Assert.*; + +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MailboxQuery; +import org.junit.Test; + +public class MailboxExpressionTest { + + private static final String PART = "mailbox"; + + private static final String SECOND_PART = "sub"; + + private static final String BASE = "BASE"; + private static final MailboxPath BASE_PATH = new MailboxPath(null, null, BASE); + + + private MailboxQuery create(String expression) { + return new MailboxQuery(BASE_PATH, expression, '.'); + } + + @Test + public void testIsWild() throws Exception { + assertTrue(create("*").isWild()); + assertTrue(create("%").isWild()); + assertTrue(create("One*").isWild()); + assertTrue(create("*One").isWild()); + assertTrue(create("A*A").isWild()); + assertTrue(create("One%").isWild()); + assertTrue(create("%One").isWild()); + assertTrue(create("A%A").isWild()); + assertFalse(create("").isWild()); + assertFalse(create(null).isWild()); + assertFalse(create("ONE").isWild()); + } + + @Test + public void testCombinedNameEmptyPart() throws Exception { + MailboxQuery expression = new MailboxQuery(BASE_PATH, "", '.'); + assertEquals(BASE, expression.getCombinedName()); + } + + @Test + public void testNullCombinedName() throws Exception { + MailboxQuery expression = new MailboxQuery(new MailboxPath(null, null, null), null, '.'); + assertNotNull(expression.getCombinedName()); + } + + @Test + public void testSimpleCombinedName() throws Exception { + MailboxQuery expression = create(PART); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testCombinedNamePartStartsWithDelimiter() throws Exception { + MailboxQuery expression = create("." + PART); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testCombinedNameBaseEndsWithDelimiter() throws Exception { + MailboxQuery expression = new MailboxQuery(new MailboxPath(null, null, BASE + '.'), PART, '.'); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testCombinedNameBaseEndsWithDelimiterPartStarts() + throws Exception { + MailboxQuery expression = new MailboxQuery(new MailboxPath(null, null, BASE + '.'), '.' + PART, '.'); + assertEquals(BASE + "." + PART, expression.getCombinedName()); + } + + @Test + public void testSimpleExpression() throws Exception { + MailboxQuery expression = create(PART); + assertTrue(expression.isExpressionMatch(PART)); + assertFalse(expression.isExpressionMatch('.' + PART)); + assertFalse(expression.isExpressionMatch(PART + '.')); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + } + + @Test + public void testEmptyExpression() throws Exception { + MailboxQuery expression = create(""); + assertTrue(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch("whatever")); + assertFalse(expression.isExpressionMatch(BASE + '.' + "whatever")); + assertFalse(expression.isExpressionMatch(BASE + "whatever")); + } + + @Test + public void testOnlyLocalWildcard() throws Exception { + MailboxQuery expression = create("%"); + assertTrue(expression.isExpressionMatch("")); + assertTrue(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testOnlyFreeWildcard() throws Exception { + MailboxQuery expression = create("*"); + assertTrue(expression.isExpressionMatch("")); + assertTrue(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testEndsWithLocalWildcard() throws Exception { + MailboxQuery expression = create(PART + '%'); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testStartsWithLocalWildcard() throws Exception { + MailboxQuery expression = create('%' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART + '.' + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + } + + @Test + public void testContainsLocalWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + '%' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testEndsWithFreeWildcard() throws Exception { + MailboxQuery expression = create(PART + '*'); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(PART + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testStartsWithFreeWildcard() throws Exception { + MailboxQuery expression = create('*' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + } + + @Test + public void testContainsFreeWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + '*' + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testDoubleFreeWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + "**" + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testFreeLocalWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + "*%" + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testLocalFreeWildcard() throws Exception { + MailboxQuery expression = create(SECOND_PART + "%*" + PART); + assertFalse(expression.isExpressionMatch("")); + assertFalse(expression.isExpressionMatch(SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART)); + assertFalse(expression.isExpressionMatch(PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "w.hat.eve.r" + PART)); + } + + @Test + public void testMultipleFreeWildcards() throws Exception { + MailboxQuery expression = create(SECOND_PART + '*' + PART + '*' + + SECOND_PART + "**"); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + '.' + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + "tosh.bosh" + + PART + "tosh.bosh" + SECOND_PART + "boshtosh")); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + + PART.substring(1) + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + + PART.substring(1) + '.' + SECOND_PART + PART + '.' + + SECOND_PART + "toshbosh")); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + + PART.substring(1) + '.' + SECOND_PART + PART + '.' + + SECOND_PART.substring(1))); + assertTrue(expression.isExpressionMatch(SECOND_PART + "tosh.bosh" + + PART + "tosh.bosh" + PART + SECOND_PART + "boshtosh" + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART.substring(1) + + "tosh.bosh" + PART + "tosh.bosh" + SECOND_PART + + PART.substring(1) + SECOND_PART + "boshtosh" + PART + + SECOND_PART.substring(1))); + } + + @Test + public void testSimpleMixedWildcards() throws Exception { + MailboxQuery expression = create(SECOND_PART + '%' + PART + '*' + + SECOND_PART); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + '.' + + SECOND_PART)); + + // Disable this tests as these are wrong. See MAILBOX-65 + //assertTrue(expression.isExpressionMatch(SECOND_PART + PART + // + SECOND_PART + "Whatever")); + //assertTrue(expression.isExpressionMatch(SECOND_PART + PART + // + SECOND_PART + ".Whatever.")); + } + + @Test + public void testFreeLocalMixedWildcards() throws Exception { + MailboxQuery expression = create(SECOND_PART + '*' + PART + '%' + + SECOND_PART); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + PART + '.' + + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + PART + "Whatever" + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + PART + + SECOND_PART + ".Whatever.")); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART)); + assertFalse(expression.isExpressionMatch(SECOND_PART + '.' + PART + + SECOND_PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(SECOND_PART + '.' + PART + '.' + + SECOND_PART + PART + SECOND_PART)); + } + + @Test + public void testTwoLocalWildcardsShouldMatchMailboxs() throws Exception { + MailboxQuery expression = create("%.%"); + assertFalse(expression.isExpressionMatch(PART)); + assertFalse(expression.isExpressionMatch(PART + '.' + SECOND_PART + '.' + SECOND_PART)); + assertTrue(expression.isExpressionMatch(PART + '.' + SECOND_PART)); + } + + @Test + public void testMailbox65() throws Exception { + MailboxQuery expression = create("*.test"); + assertTrue(expression.isExpressionMatch("blah.test")); + assertFalse(expression.isExpressionMatch("blah.test.go")); + + assertFalse(expression.isExpressionMatch("blah.test3")); + + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MessageRangeTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MessageRangeTest.java new file mode 100644 index 0000000..6207350 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/MessageRangeTest.java @@ -0,0 +1,95 @@ +/**************************************************************** + * 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.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +import org.apache.james.mailbox.model.MessageRange; +import org.junit.Test; + +public class MessageRangeTest { + + @Test + public void testToRanges() { + List ranges = MessageRange.toRanges(Arrays.asList(1L,2L,3L,5L,6L,9L)); + assertEquals(3, ranges.size()); + checkRange(1, 3, ranges.get(0)); + checkRange(5, 6, ranges.get(1)); + checkRange(9, 9, ranges.get(2)); + + } + + @Test + public void testOneUidToRange() { + List ranges = MessageRange.toRanges(Arrays.asList(1L)); + assertEquals(1, ranges.size()); + checkRange(1, 1, ranges.get(0)); + } + + // Test for MAILBOX-56 + @Test + public void testTwoSeqUidToRange() { + List ranges = MessageRange.toRanges(Arrays.asList(1L,2L)); + assertEquals(1, ranges.size()); + checkRange(1, 2, ranges.get(0)); + + } + + + private void checkRange(long from, long to, MessageRange range) { + assertEquals(from, range.getUidFrom()); + assertEquals(to, range.getUidTo()); + } + + @Test + public void testSplitOne() { + MessageRange one = MessageRange.one(1); + List ranges = one.split(2); + assertEquals(1, ranges.size()); + checkRange(1, 1, ranges.get(0)); + assertEquals(MessageRange.Type.ONE, ranges.get(0).getType()); + } + + @Test + public void testSplitFrom() { + MessageRange from = MessageRange.from(1); + List ranges = from.split(2); + assertEquals(1, ranges.size()); + checkRange(1, MessageRange.NOT_A_UID, ranges.get(0)); + assertEquals(MessageRange.Type.FROM, ranges.get(0).getType()); + } + + @Test + public void testSplitRange() { + MessageRange range = MessageRange.range(1,10); + List ranges = range.split(3); + assertEquals(4, ranges.size()); + checkRange(1, 3, ranges.get(0)); + assertEquals(MessageRange.Type.RANGE, ranges.get(0).getType()); + checkRange(4, 6, ranges.get(1)); + assertEquals(MessageRange.Type.RANGE, ranges.get(1).getType()); + checkRange(7, 9, ranges.get(2)); + assertEquals(MessageRange.Type.RANGE, ranges.get(2).getType()); + checkRange(10, 10, ranges.get(3)); + assertEquals(MessageRange.Type.ONE, ranges.get(3).getType()); + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/all-wcprops new file mode 100644 index 0000000..d3147a8 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/acl +END +UnionMailboxACLResolverTest.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/acl/UnionMailboxACLResolverTest.java +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/entries new file mode 100644 index 0000000..14798f7 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/acl +http://svn.apache.org/repos/asf + + + +2012-02-21T21:05:02.067525Z +1292019 +ppalaga + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +UnionMailboxACLResolverTest.java +file + + + + +2013-09-02T02:54:40.000000Z +79f0355c676bb0bc6f26322a6381e501 +2012-02-21T21:05:02.067525Z +1292019 +ppalaga +has-props + + + + + + + + + + + + + + + + + + + + +52017 + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolverTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolverTest.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/prop-base/UnionMailboxACLResolverTest.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolverTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolverTest.java.svn-base new file mode 100644 index 0000000..489a245 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/.svn/text-base/UnionMailboxACLResolverTest.java.svn-base @@ -0,0 +1,595 @@ +/* + * 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.acl; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.NameType; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class UnionMailboxACLResolverTest { + + private static final String GROUP_1 = "group1"; + private static final String GROUP_2 = "group2"; + + private static final String USER_1 = "user1"; + private static final String USER_2 = "user2"; + + private MailboxACL anybodyRead; + private MailboxACL anybodyReadNegative; + private UnionMailboxACLResolver anyoneReadListGlobal; + private MailboxACL authenticatedRead; + private UnionMailboxACLResolver authenticatedReadListWriteGlobal; + private MailboxACL authenticatedReadNegative; + private MailboxACL group1Read; + private MailboxACL group1ReadNegative; + private SimpleGroupMembershipResolver groupMembershipResolver; + private UnionMailboxACLResolver negativeGroup2FullGlobal; + private UnionMailboxACLResolver noGlobals; + private UnionMailboxACLResolver ownerFullGlobal; + private MailboxACL ownerRead; + private MailboxACL ownerReadNegative; + private MailboxACL user1Read; + private MailboxACL user1ReadNegative; + private SimpleMailboxACLEntryKey user1Key; + private SimpleMailboxACLEntryKey user2Key; + private SimpleMailboxACLEntryKey group1Key; + private SimpleMailboxACLEntryKey group2Key; + + @Before + public void setUp() throws Exception { + user1Key = SimpleMailboxACLEntryKey.createUser(USER_1); + user2Key = SimpleMailboxACLEntryKey.createUser(USER_2); + group1Key = SimpleMailboxACLEntryKey.createGroup(GROUP_1); + group2Key = SimpleMailboxACLEntryKey.createGroup(GROUP_2); + + MailboxACL acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.AUTHENTICATED_KEY, SimpleMailboxACL.FULL_RIGHTS) }); + authenticatedReadListWriteGlobal = new UnionMailboxACLResolver(acl, acl); + acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.ANYBODY_KEY, new Rfc4314Rights("rl")) }); + anyoneReadListGlobal = new UnionMailboxACLResolver(acl, acl); + acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS) }); + ownerFullGlobal = new UnionMailboxACLResolver(acl, acl); + noGlobals = new UnionMailboxACLResolver(SimpleMailboxACL.EMPTY, SimpleMailboxACL.EMPTY); + acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(new SimpleMailboxACLEntryKey(GROUP_2, NameType.group, true), SimpleMailboxACL.FULL_RIGHTS) }); + negativeGroup2FullGlobal = new UnionMailboxACLResolver(acl, new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(new SimpleMailboxACLEntryKey(GROUP_2, NameType.group, true), SimpleMailboxACL.FULL_RIGHTS) })); + + groupMembershipResolver = new SimpleGroupMembershipResolver(); + groupMembershipResolver.addMembership(GROUP_1, USER_1); + groupMembershipResolver.addMembership(GROUP_2, USER_2); + + user1Read = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(user1Key, new Rfc4314Rights("r")) }); + user1ReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACLEntryKey.createUser(USER_1, true), new Rfc4314Rights("r")) }); + + group1Read = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(group1Key, new Rfc4314Rights("r")) }); + group1ReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACLEntryKey.createGroup(GROUP_1, true), new Rfc4314Rights("r")) }); + + anybodyRead = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.ANYBODY_KEY, new Rfc4314Rights("r")) }); + anybodyReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.ANYBODY_NEGATIVE_KEY, new Rfc4314Rights("r")) }); + + authenticatedRead = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.AUTHENTICATED_KEY, new Rfc4314Rights("r")) }); + authenticatedReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.AUTHENTICATED_NEGATIVE_KEY, new Rfc4314Rights("r")) }); + + ownerRead = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, new Rfc4314Rights("r")) }); + ownerReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_NEGATIVE_KEY, new Rfc4314Rights("r")) }); + + } + + @Test + public void testAppliesNullUser() throws UnsupportedRightException { + + Assert.assertFalse(UnionMailboxACLResolver.applies(user1Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group1Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, null, groupMembershipResolver, USER_1, false)); + } + + @Test + public void testAppliesUser() throws UnsupportedRightException { + /* requester is the resource owner */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, USER_1, false)); + + /* requester is not the resource user */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, USER_2, false)); + + /* requester member of owner group */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, GROUP_1, true)); + + /* requester not member of owner group */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, GROUP_2, true)); + + /* owner query */ + Assert.assertFalse(UnionMailboxACLResolver.applies(user1Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group1Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + + } + + @Test + public void testHasRightNullUser() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + } + + @Test + public void testHasRightNullUserGlobals() throws UnsupportedRightException { + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + } + + + @Test + public void testHasRightUserSelfOwner() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + } + + + @Test + public void testHasRightUserNotOwner() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + } + @Test + public void testHasRightUserMemberOfOwnerGroup() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + } + + + @Test + public void testHasRightUserNotMemberOfOwnerGroup() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/UnionMailboxACLResolverTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/UnionMailboxACLResolverTest.java new file mode 100644 index 0000000..489a245 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/acl/UnionMailboxACLResolverTest.java @@ -0,0 +1,595 @@ +/* + * 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.acl; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.NameType; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class UnionMailboxACLResolverTest { + + private static final String GROUP_1 = "group1"; + private static final String GROUP_2 = "group2"; + + private static final String USER_1 = "user1"; + private static final String USER_2 = "user2"; + + private MailboxACL anybodyRead; + private MailboxACL anybodyReadNegative; + private UnionMailboxACLResolver anyoneReadListGlobal; + private MailboxACL authenticatedRead; + private UnionMailboxACLResolver authenticatedReadListWriteGlobal; + private MailboxACL authenticatedReadNegative; + private MailboxACL group1Read; + private MailboxACL group1ReadNegative; + private SimpleGroupMembershipResolver groupMembershipResolver; + private UnionMailboxACLResolver negativeGroup2FullGlobal; + private UnionMailboxACLResolver noGlobals; + private UnionMailboxACLResolver ownerFullGlobal; + private MailboxACL ownerRead; + private MailboxACL ownerReadNegative; + private MailboxACL user1Read; + private MailboxACL user1ReadNegative; + private SimpleMailboxACLEntryKey user1Key; + private SimpleMailboxACLEntryKey user2Key; + private SimpleMailboxACLEntryKey group1Key; + private SimpleMailboxACLEntryKey group2Key; + + @Before + public void setUp() throws Exception { + user1Key = SimpleMailboxACLEntryKey.createUser(USER_1); + user2Key = SimpleMailboxACLEntryKey.createUser(USER_2); + group1Key = SimpleMailboxACLEntryKey.createGroup(GROUP_1); + group2Key = SimpleMailboxACLEntryKey.createGroup(GROUP_2); + + MailboxACL acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.AUTHENTICATED_KEY, SimpleMailboxACL.FULL_RIGHTS) }); + authenticatedReadListWriteGlobal = new UnionMailboxACLResolver(acl, acl); + acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.ANYBODY_KEY, new Rfc4314Rights("rl")) }); + anyoneReadListGlobal = new UnionMailboxACLResolver(acl, acl); + acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS) }); + ownerFullGlobal = new UnionMailboxACLResolver(acl, acl); + noGlobals = new UnionMailboxACLResolver(SimpleMailboxACL.EMPTY, SimpleMailboxACL.EMPTY); + acl = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(new SimpleMailboxACLEntryKey(GROUP_2, NameType.group, true), SimpleMailboxACL.FULL_RIGHTS) }); + negativeGroup2FullGlobal = new UnionMailboxACLResolver(acl, new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(new SimpleMailboxACLEntryKey(GROUP_2, NameType.group, true), SimpleMailboxACL.FULL_RIGHTS) })); + + groupMembershipResolver = new SimpleGroupMembershipResolver(); + groupMembershipResolver.addMembership(GROUP_1, USER_1); + groupMembershipResolver.addMembership(GROUP_2, USER_2); + + user1Read = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(user1Key, new Rfc4314Rights("r")) }); + user1ReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACLEntryKey.createUser(USER_1, true), new Rfc4314Rights("r")) }); + + group1Read = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(group1Key, new Rfc4314Rights("r")) }); + group1ReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACLEntryKey.createGroup(GROUP_1, true), new Rfc4314Rights("r")) }); + + anybodyRead = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.ANYBODY_KEY, new Rfc4314Rights("r")) }); + anybodyReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.ANYBODY_NEGATIVE_KEY, new Rfc4314Rights("r")) }); + + authenticatedRead = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.AUTHENTICATED_KEY, new Rfc4314Rights("r")) }); + authenticatedReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.AUTHENTICATED_NEGATIVE_KEY, new Rfc4314Rights("r")) }); + + ownerRead = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_KEY, new Rfc4314Rights("r")) }); + ownerReadNegative = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(SimpleMailboxACL.OWNER_NEGATIVE_KEY, new Rfc4314Rights("r")) }); + + } + + @Test + public void testAppliesNullUser() throws UnsupportedRightException { + + Assert.assertFalse(UnionMailboxACLResolver.applies(user1Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group1Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, null, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, null, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, null, groupMembershipResolver, USER_1, false)); + } + + @Test + public void testAppliesUser() throws UnsupportedRightException { + /* requester is the resource owner */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, USER_1, false)); + + /* requester is not the resource user */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, USER_2, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, USER_2, false)); + + /* requester member of owner group */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, GROUP_1, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, GROUP_1, true)); + + /* requester not member of owner group */ + Assert.assertTrue(UnionMailboxACLResolver.applies(user1Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(group1Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, user1Key, groupMembershipResolver, GROUP_2, true)); + Assert.assertFalse(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, user1Key, groupMembershipResolver, GROUP_2, true)); + + /* owner query */ + Assert.assertFalse(UnionMailboxACLResolver.applies(user1Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(user2Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group1Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertFalse(UnionMailboxACLResolver.applies(group2Key, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.ANYBODY_KEY, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.AUTHENTICATED_KEY, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + Assert.assertTrue(UnionMailboxACLResolver.applies(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.OWNER_KEY, groupMembershipResolver, USER_1, false)); + + } + + @Test + public void testHasRightNullUser() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + } + + @Test + public void testHasRightNullUserGlobals() throws UnsupportedRightException { + Assert.assertTrue(anyoneReadListGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(null, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, SimpleMailboxACL.EMPTY, USER_2, false)); + } + + + @Test + public void testHasRightUserSelfOwner() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_1, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_1, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_1, false)); + + } + + + @Test + public void testHasRightUserNotOwner() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, USER_2, false)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, USER_2, false)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, USER_2, false)); + + } + @Test + public void testHasRightUserMemberOfOwnerGroup() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_1, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_1, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_1, true)); + + } + + + @Test + public void testHasRightUserNotMemberOfOwnerGroup() throws UnsupportedRightException { + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1Read, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, user1ReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1Read, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, group1ReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyRead, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, anybodyReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + Assert.assertTrue(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedRead, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, authenticatedReadNegative, GROUP_2, true)); + + + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertTrue(anyoneReadListGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertTrue(authenticatedReadListWriteGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertFalse(ownerFullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertFalse(noGlobals.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerRead, GROUP_2, true)); + Assert.assertFalse(negativeGroup2FullGlobal.hasRight(USER_1, groupMembershipResolver, Rfc4314Rights.r_Read_RIGHT, ownerReadNegative, GROUP_2, true)); + + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/all-wcprops new file mode 100644 index 0000000..aa0eea6 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/mock +END +MockMailboxManager.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxManager.java +END +MockMailboxSession.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1127594/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxSession.java +END +MockMail.java +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1051407/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/mock/MockMail.java +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/entries new file mode 100644 index 0000000..a587a57 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/mock +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MockMailboxManager.java +file + + + + +2013-09-02T02:54:40.000000Z +848098cd8317b1d19349b2f4fc863481 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +6538 + +MockMailboxSession.java +file + + + + +2013-09-02T02:54:40.000000Z +5683ed4fd8b8ff1f78ef811ab47c3c26 +2011-05-25T17:33:30.175401Z +1127594 +norman + + + + + + + + + + + + + + + + + + + + + +3051 + +MockMail.java +file + + + + +2013-09-02T02:54:40.000000Z +d1025aec76b4af4454b38215cd17650b +2010-12-21T08:02:32.087154Z +1051407 +eric + + + + + + + + + + + + + + + + + + + + + +1899 + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMail.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMail.java.svn-base new file mode 100644 index 0000000..9ea3416 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMail.java.svn-base @@ -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.mock; + +public class MockMail { + + public static final String MAIL_TEXT_PLAIN = "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\n" + + "From: Fred Foobar \n" + + "Subject: Test 03\n" + + "To: mooch@owatagu.siam.edu\n" + + "Message-Id: \n" + + "MIME-Version: 1.0\n" + + "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\n" + + "\n" + + "Mock Mail\n"; + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxManager.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxManager.java.svn-base new file mode 100644 index 0000000..4631009 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxManager.java.svn-base @@ -0,0 +1,165 @@ +/**************************************************************** + * 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.mock; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.util.Calendar; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.slf4j.LoggerFactory; + +/** + * A mock mailbox manager. + * + */ +public class MockMailboxManager { + + /** + * The mock mailbox manager constructed based on a provided mailboxmanager. + */ + private MailboxManager mockMailboxManager; + + /** + * Number of Domains to be created in the Mailbox Manager. + */ + public static final int DOMAIN_COUNT = 3; + + /** + * Number of Users (with INBOX) to be created in the Mailbox Manager. + */ + public static final int USER_COUNT = 3; + + /** + * Number of Sub Mailboxes (mailbox in INBOX) to be created in the Mailbox Manager. + */ + public static final int SUB_MAILBOXES_COUNT = 3; + + /** + * Number of Sub Sub Mailboxes (mailbox in a mailbox under INBOX) to be created in the Mailbox Manager. + */ + public static final int SUB_SUB_MAILBOXES_COUNT = 3; + + /** + * The expected Mailboxes count calculated based on the feeded mails. + */ + public static final int EXPECTED_MAILBOXES_COUNT = DOMAIN_COUNT * + (USER_COUNT + // INBOX + USER_COUNT * SUB_MAILBOXES_COUNT + // INBOX.SUB_FOLDER + USER_COUNT * SUB_MAILBOXES_COUNT * SUB_SUB_MAILBOXES_COUNT); // INBOX.SUB_FOLDER.SUBSUB_FOLDER + + /** + * Number of Messages per Mailbox to be created in the Mailbox Manager. + */ + public static final int MESSAGE_PER_MAILBOX_COUNT = 3; + + /** + * Construct a mock mailboxManager based on a valid mailboxManager. + * The mailboxManager will be feeded with mailboxes and mails. + * + * @param mailboxManager + * @throws UnsupportedEncodingException + * @throws MailboxException + */ + public MockMailboxManager(MailboxManager mailboxManager) throws MailboxException, UnsupportedEncodingException { + this.mockMailboxManager = mailboxManager; + feedMockMailboxManager(); + } + + /** + * @return + */ + public MailboxManager getMockMailboxManager() { + return mockMailboxManager; + } + + /** + * Utility method to feed the Mailbox Manager with a number of + * mailboxes and messages per mailbox. + * + * @throws MailboxException + * @throws UnsupportedEncodingException + */ + private void feedMockMailboxManager() throws MailboxException, UnsupportedEncodingException { + + MailboxPath mailboxPath = null; + + for (int i=0; i < DOMAIN_COUNT; i++) { + + for (int j=0; j < USER_COUNT; j++) { + + String user = "user" + j + "@localhost" + i; + + String folderName = "INBOX"; + + MailboxSession mailboxSession = getMockMailboxManager().createSystemSession(user, LoggerFactory.getLogger("mailboxmanager-test")); + mailboxPath = new MailboxPath("#private", user, folderName); + createMailbox(mailboxSession, mailboxPath); + + for (int k=0; k < SUB_MAILBOXES_COUNT; k++) { + + String subFolderName = folderName + ".SUB_FOLDER_" + k; + mailboxPath = new MailboxPath("#private", user, subFolderName); + createMailbox(mailboxSession, mailboxPath); + + for (int l=0; l < SUB_SUB_MAILBOXES_COUNT; l++) { + + String subSubfolderName = subFolderName + ".SUBSUB_FOLDER_" + l; + mailboxPath = new MailboxPath("#private", user, subSubfolderName); + createMailbox(mailboxSession, mailboxPath); + + } + + } + + getMockMailboxManager().logout(mailboxSession, true); + + } + + } + + } + + /** + * + * @param mailboxPath + * @throws MailboxException + * @throws UnsupportedEncodingException + */ + private void createMailbox(MailboxSession mailboxSession, MailboxPath mailboxPath) throws MailboxException, UnsupportedEncodingException { + getMockMailboxManager().startProcessingRequest(mailboxSession); + getMockMailboxManager().createMailbox(mailboxPath, mailboxSession); + MessageManager messageManager = getMockMailboxManager().getMailbox(mailboxPath, mailboxSession); + for (int j=0; j < MESSAGE_PER_MAILBOX_COUNT; j++) { + messageManager.appendMessage(new ByteArrayInputStream(MockMail.MAIL_TEXT_PLAIN.getBytes("UTF-8")), + Calendar.getInstance().getTime(), + mailboxSession, + true, + new Flags(Flags.Flag.RECENT)); + } + getMockMailboxManager().endProcessingRequest(mailboxSession); + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxSession.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxSession.java.svn-base new file mode 100644 index 0000000..ecb901f --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/.svn/text-base/MockMailboxSession.java.svn-base @@ -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.mailbox.mock; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; + +import org.apache.james.mailbox.MailboxSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockMailboxSession implements MailboxSession{ + + private User user; + private boolean close; + private Map attrs = new HashMap(); + private Logger log = LoggerFactory.getLogger("MockLog"); + private final static Random RANDOM = new Random(); + + private long sessionId = RANDOM.nextLong(); + + public MockMailboxSession(final String username) { + this.user = new User() { + + public String getUserName() { + return username; + } + + public String getPassword() { + return null; + } + + public List getLocalePreferences() { + return new ArrayList(); + } + }; + } + + public void close() { + this.close = true; + } + + public Map getAttributes() { + return attrs; + } + + public Logger getLog() { + return log; + } + + public String getOtherUsersSpace() { + return null; + } + + public String getPersonalSpace() { + return ""; + } + + public long getSessionId() { + return sessionId; + } + + public Collection getSharedSpaces() { + return new ArrayList(); + } + + public User getUser() { + return user; + } + + public boolean isOpen() { + return close == false; + } + + public char getPathDelimiter() { + return '.'; + } + + public SessionType getType() { + return SessionType.User; + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMail.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMail.java new file mode 100644 index 0000000..9ea3416 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMail.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.mock; + +public class MockMail { + + public static final String MAIL_TEXT_PLAIN = "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\n" + + "From: Fred Foobar \n" + + "Subject: Test 03\n" + + "To: mooch@owatagu.siam.edu\n" + + "Message-Id: \n" + + "MIME-Version: 1.0\n" + + "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\n" + + "\n" + + "Mock Mail\n"; + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxManager.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxManager.java new file mode 100644 index 0000000..4631009 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxManager.java @@ -0,0 +1,165 @@ +/**************************************************************** + * 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.mock; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.util.Calendar; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.slf4j.LoggerFactory; + +/** + * A mock mailbox manager. + * + */ +public class MockMailboxManager { + + /** + * The mock mailbox manager constructed based on a provided mailboxmanager. + */ + private MailboxManager mockMailboxManager; + + /** + * Number of Domains to be created in the Mailbox Manager. + */ + public static final int DOMAIN_COUNT = 3; + + /** + * Number of Users (with INBOX) to be created in the Mailbox Manager. + */ + public static final int USER_COUNT = 3; + + /** + * Number of Sub Mailboxes (mailbox in INBOX) to be created in the Mailbox Manager. + */ + public static final int SUB_MAILBOXES_COUNT = 3; + + /** + * Number of Sub Sub Mailboxes (mailbox in a mailbox under INBOX) to be created in the Mailbox Manager. + */ + public static final int SUB_SUB_MAILBOXES_COUNT = 3; + + /** + * The expected Mailboxes count calculated based on the feeded mails. + */ + public static final int EXPECTED_MAILBOXES_COUNT = DOMAIN_COUNT * + (USER_COUNT + // INBOX + USER_COUNT * SUB_MAILBOXES_COUNT + // INBOX.SUB_FOLDER + USER_COUNT * SUB_MAILBOXES_COUNT * SUB_SUB_MAILBOXES_COUNT); // INBOX.SUB_FOLDER.SUBSUB_FOLDER + + /** + * Number of Messages per Mailbox to be created in the Mailbox Manager. + */ + public static final int MESSAGE_PER_MAILBOX_COUNT = 3; + + /** + * Construct a mock mailboxManager based on a valid mailboxManager. + * The mailboxManager will be feeded with mailboxes and mails. + * + * @param mailboxManager + * @throws UnsupportedEncodingException + * @throws MailboxException + */ + public MockMailboxManager(MailboxManager mailboxManager) throws MailboxException, UnsupportedEncodingException { + this.mockMailboxManager = mailboxManager; + feedMockMailboxManager(); + } + + /** + * @return + */ + public MailboxManager getMockMailboxManager() { + return mockMailboxManager; + } + + /** + * Utility method to feed the Mailbox Manager with a number of + * mailboxes and messages per mailbox. + * + * @throws MailboxException + * @throws UnsupportedEncodingException + */ + private void feedMockMailboxManager() throws MailboxException, UnsupportedEncodingException { + + MailboxPath mailboxPath = null; + + for (int i=0; i < DOMAIN_COUNT; i++) { + + for (int j=0; j < USER_COUNT; j++) { + + String user = "user" + j + "@localhost" + i; + + String folderName = "INBOX"; + + MailboxSession mailboxSession = getMockMailboxManager().createSystemSession(user, LoggerFactory.getLogger("mailboxmanager-test")); + mailboxPath = new MailboxPath("#private", user, folderName); + createMailbox(mailboxSession, mailboxPath); + + for (int k=0; k < SUB_MAILBOXES_COUNT; k++) { + + String subFolderName = folderName + ".SUB_FOLDER_" + k; + mailboxPath = new MailboxPath("#private", user, subFolderName); + createMailbox(mailboxSession, mailboxPath); + + for (int l=0; l < SUB_SUB_MAILBOXES_COUNT; l++) { + + String subSubfolderName = subFolderName + ".SUBSUB_FOLDER_" + l; + mailboxPath = new MailboxPath("#private", user, subSubfolderName); + createMailbox(mailboxSession, mailboxPath); + + } + + } + + getMockMailboxManager().logout(mailboxSession, true); + + } + + } + + } + + /** + * + * @param mailboxPath + * @throws MailboxException + * @throws UnsupportedEncodingException + */ + private void createMailbox(MailboxSession mailboxSession, MailboxPath mailboxPath) throws MailboxException, UnsupportedEncodingException { + getMockMailboxManager().startProcessingRequest(mailboxSession); + getMockMailboxManager().createMailbox(mailboxPath, mailboxSession); + MessageManager messageManager = getMockMailboxManager().getMailbox(mailboxPath, mailboxSession); + for (int j=0; j < MESSAGE_PER_MAILBOX_COUNT; j++) { + messageManager.appendMessage(new ByteArrayInputStream(MockMail.MAIL_TEXT_PLAIN.getBytes("UTF-8")), + Calendar.getInstance().getTime(), + mailboxSession, + true, + new Flags(Flags.Flag.RECENT)); + } + getMockMailboxManager().endProcessingRequest(mailboxSession); + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxSession.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxSession.java new file mode 100644 index 0000000..ecb901f --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/mock/MockMailboxSession.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.mailbox.mock; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; + +import org.apache.james.mailbox.MailboxSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MockMailboxSession implements MailboxSession{ + + private User user; + private boolean close; + private Map attrs = new HashMap(); + private Logger log = LoggerFactory.getLogger("MockLog"); + private final static Random RANDOM = new Random(); + + private long sessionId = RANDOM.nextLong(); + + public MockMailboxSession(final String username) { + this.user = new User() { + + public String getUserName() { + return username; + } + + public String getPassword() { + return null; + } + + public List getLocalePreferences() { + return new ArrayList(); + } + }; + } + + public void close() { + this.close = true; + } + + public Map getAttributes() { + return attrs; + } + + public Logger getLog() { + return log; + } + + public String getOtherUsersSpace() { + return null; + } + + public String getPersonalSpace() { + return ""; + } + + public long getSessionId() { + return sessionId; + } + + public Collection getSharedSpaces() { + return new ArrayList(); + } + + public User getUser() { + return user; + } + + public boolean isOpen() { + return close == false; + } + + public char getPathDelimiter() { + return '.'; + } + + public SessionType getType() { + return SessionType.User; + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/all-wcprops new file mode 100644 index 0000000..90caf75 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 96 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/model +END +SimpleMailboxACLEntryKeyTest.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLEntryKeyTest.java +END +SimpleMailboxACLTest.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLTest.java +END +Rfc4314RightsTest.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1292019/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/model/Rfc4314RightsTest.java +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/entries new file mode 100644 index 0000000..3928db0 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/model +http://svn.apache.org/repos/asf + + + +2012-02-21T21:05:02.067525Z +1292019 +ppalaga + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SimpleMailboxACLEntryKeyTest.java +file + + + + +2013-09-02T02:54:40.000000Z +ef8c50001d6d5d116a6b4454348401ae +2012-02-21T21:05:02.067525Z +1292019 +ppalaga + + + + + + + + + + + + + + + + + + + + + +5245 + +SimpleMailboxACLTest.java +file + + + + +2013-09-02T02:54:40.000000Z +a93c9def882be3989ac69c49075ba08b +2012-02-21T21:05:02.067525Z +1292019 +ppalaga + + + + + + + + + + + + + + + + + + + + + +8766 + +Rfc4314RightsTest.java +file + + + + +2013-09-02T02:54:40.000000Z +3513cd8dc22f2ea03c5e3ac825670320 +2012-02-21T21:05:02.067525Z +1292019 +ppalaga +has-props + + + + + + + + + + + + + + + + + + + + +4392 + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/prop-base/Rfc4314RightsTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/prop-base/Rfc4314RightsTest.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/prop-base/Rfc4314RightsTest.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/Rfc4314RightsTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/Rfc4314RightsTest.java.svn-base new file mode 100644 index 0000000..e8ca632 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/Rfc4314RightsTest.java.svn-base @@ -0,0 +1,124 @@ +/* + * 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.model; + +import static org.junit.Assert.assertEquals; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class Rfc4314RightsTest { + + private Rfc4314Rights aeik; + private MailboxACLRights full; + private Rfc4314Rights lprs; + private MailboxACLRights none; + private Rfc4314Rights twx; + + @Before + public void setUp() throws Exception { + aeik = new SimpleMailboxACL.Rfc4314Rights("aeik"); + lprs = new SimpleMailboxACL.Rfc4314Rights("lprs"); + twx = new SimpleMailboxACL.Rfc4314Rights("twx"); + full = SimpleMailboxACL.FULL_RIGHTS; + none = SimpleMailboxACL.NO_RIGHTS; + } + @Test + public void testExceptFull() throws UnsupportedRightException { + assertEquals(none, full.except(aeik).except(lprs).except(twx)); + } + + @Test + public void testExceptNonExistent() throws UnsupportedRightException { + assertEquals(aeik, aeik.except(lprs)); + } + + @Test + public void testExceptUnsupportedFlag() { + try { + String unsupportedFlag = "z"; + new SimpleMailboxACL.Rfc4314Rights(unsupportedFlag ); + Assert.fail(UnsupportedRightException.class.getName() +" expected for unsupported right flag '"+ unsupportedFlag +"'."); + } catch (UnsupportedRightException e) { + /* OK */ + } + } + + @Test + public void testExceptZero() throws UnsupportedRightException { + assertEquals(aeik, aeik.except(none)); + } + + @Test + public void testIterable() { + testIterable(full); + testIterable(none); + testIterable(aeik); + testIterable(lprs); + testIterable(twx); + } + + private static void testIterable(MailboxACLRights rights) { + String stringRights = rights.serialize(); + int i = 0; + for (MailboxACLRight r : rights) { + assertEquals(stringRights.charAt(i++), r.getValue()); + } + assertEquals(stringRights.length(), i); + + } + + @Test + public void testParse() throws UnsupportedRightException { + assertEquals(aeik.getValue(), Rfc4314Rights.a_Administer_MASK | Rfc4314Rights.e_PerformExpunge_MASK | Rfc4314Rights.i_Insert_MASK | Rfc4314Rights.k_CreateMailbox_MASK); + assertEquals(lprs.getValue(), Rfc4314Rights.l_Lookup_MASK | Rfc4314Rights.p_Post_MASK | Rfc4314Rights.s_WriteSeenFlag_MASK | Rfc4314Rights.r_Read_MASK); + assertEquals(twx.getValue(), Rfc4314Rights.t_DeleteMessages_MASK | Rfc4314Rights.w_Write_MASK | Rfc4314Rights.x_DeleteMailbox_MASK); + } + + @Test + public void testSerialize() throws UnsupportedRightException { + assertEquals("aeik", aeik.serialize()); + assertEquals("lprs", lprs.serialize()); + assertEquals("twx", twx.serialize()); + assertEquals("aeiklprstwx", full.serialize()); + assertEquals("", none.serialize()); + } + + @Test + public void testUnionFull() throws UnsupportedRightException { + assertEquals(full, aeik.union(lprs).union(twx)); + } + @Test + public void testUnionZero() throws UnsupportedRightException { + assertEquals(lprs, lprs.union(none)); + } + + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLEntryKeyTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLEntryKeyTest.java.svn-base new file mode 100644 index 0000000..6bfed79 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLEntryKeyTest.java.svn-base @@ -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.model; + +import static org.junit.Assert.assertEquals; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.NameType; +import org.apache.james.mailbox.model.MailboxACL.SpecialName; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class SimpleMailboxACLEntryKeyTest { + + private static final String GROUP_1 = "group1"; + + private static final String USER_1 = "user1"; + + @Test + public void testUser() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(USER_1); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.user); + assertEquals(k.getName(), USER_1); + + } + + @Test + public void testNegativeUser() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + USER_1); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.user); + assertEquals(k.getName(), USER_1); + + } + + + @Test + public void testGroup() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_GROUP_MARKER + GROUP_1); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.group); + assertEquals(k.getName(), GROUP_1); + + } + + @Test + public void testNegativeGroup() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey("" + MailboxACL.DEFAULT_NEGATIVE_MARKER + MailboxACL.DEFAULT_GROUP_MARKER + GROUP_1); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.group); + assertEquals(k.getName(), GROUP_1); + + } + + + @Test + public void testOwner() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(SpecialName.owner.toString()); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.owner.toString()); + + } + + @Test + public void testNegativeOwner() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + SpecialName.owner.toString()); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.owner.toString()); + + } + + @Test + public void testAnybody() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(SpecialName.anybody.toString()); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.anybody.toString()); + + } + + @Test + public void testNegativeAnybody() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + SpecialName.anybody.toString()); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.anybody.toString()); + + } + + + @Test + public void testAuthenticated() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(SpecialName.authenticated.toString()); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.authenticated.toString()); + + } + + @Test + public void testNegativeAuthenticated() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + SpecialName.authenticated.toString()); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.authenticated.toString()); + + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLTest.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLTest.java.svn-base new file mode 100644 index 0000000..2d20fb5 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/.svn/text-base/SimpleMailboxACLTest.java.svn-base @@ -0,0 +1,214 @@ +/* + * 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.model; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class SimpleMailboxACLTest { + + private static final String USER_1 = "user1"; + private static final String USER_2 = "user2"; + + private static final String ae = "ae"; + private static final String ik = "ik"; + private static final String aeik = "aeik"; + private static final String lprs = "lprs"; + private static final String twx = "twx"; + + private Properties u1u2g1g2Properties; + + private MailboxACL u1u2g1g2ACL; + + @Before + public void setUp() throws Exception { + + u1u2g1g2Properties = new Properties(); + + u1u2g1g2Properties.setProperty(USER_1, aeik); + u1u2g1g2Properties.setProperty(MailboxACL.DEFAULT_NEGATIVE_MARKER + USER_1, lprs); + u1u2g1g2Properties.setProperty(USER_2, lprs); + u1u2g1g2Properties.setProperty(MailboxACL.DEFAULT_NEGATIVE_MARKER + USER_2, twx); + + u1u2g1g2ACL = new SimpleMailboxACL(u1u2g1g2Properties); + + } + + @Test + public void testUnionACLNew() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + MailboxACL toAdd = SimpleMailboxACL.OWNER_FULL_ACL; + MailboxACL result = u1u2g1g2ACL.union(toAdd); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionEntryNew() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + MailboxACL result = u1u2g1g2ACL.union(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionACLExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(aeik + lprs)); + + MailboxACL toAdd = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(USER_1, lprs) }); + MailboxACL result = u1u2g1g2ACL.union(toAdd); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionEntryExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(aeik + lprs)); + + MailboxACL result = u1u2g1g2ACL.union(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(lprs)); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionACLZero() throws UnsupportedRightException { + + } + + @Test + public void testUnionEntryZero() throws UnsupportedRightException { + + } + + @Test + public void testExceptACLNew() throws UnsupportedRightException { + + /* actually no change expected */ + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + + MailboxACL toRemove = SimpleMailboxACL.OWNER_FULL_ACL; + MailboxACL result = u1u2g1g2ACL.except(toRemove); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptEntryNew() throws UnsupportedRightException { + + /* actually no change expected */ + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + + MailboxACL result = u1u2g1g2ACL.except(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptACLExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(ik)); + + MailboxACL toRemove = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(USER_1, ae) }); + MailboxACL result = u1u2g1g2ACL.except(toRemove); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptEntryExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(ik)); + + MailboxACL result = u1u2g1g2ACL.except(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(ae)); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptACLFull() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.remove(new SimpleMailboxACLEntryKey(USER_1)); + + MailboxACL toRemove = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(USER_1, SimpleMailboxACL.FULL_RIGHTS.serialize()) }); + MailboxACL result = u1u2g1g2ACL.except(toRemove); + + Map foundEntries = result.getEntries(); + + assertEquals(expectedEntries, foundEntries); + } + + @Test + public void testExceptEntryFull() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.remove(new SimpleMailboxACLEntryKey(USER_1)); + + MailboxACL result = u1u2g1g2ACL.except(new SimpleMailboxACLEntryKey(USER_1), SimpleMailboxACL.FULL_RIGHTS); + + Map foundEntries = result.getEntries(); + + assertEquals(expectedEntries, foundEntries); + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/Rfc4314RightsTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/Rfc4314RightsTest.java new file mode 100644 index 0000000..e8ca632 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/Rfc4314RightsTest.java @@ -0,0 +1,124 @@ +/* + * 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.model; + +import static org.junit.Assert.assertEquals; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class Rfc4314RightsTest { + + private Rfc4314Rights aeik; + private MailboxACLRights full; + private Rfc4314Rights lprs; + private MailboxACLRights none; + private Rfc4314Rights twx; + + @Before + public void setUp() throws Exception { + aeik = new SimpleMailboxACL.Rfc4314Rights("aeik"); + lprs = new SimpleMailboxACL.Rfc4314Rights("lprs"); + twx = new SimpleMailboxACL.Rfc4314Rights("twx"); + full = SimpleMailboxACL.FULL_RIGHTS; + none = SimpleMailboxACL.NO_RIGHTS; + } + @Test + public void testExceptFull() throws UnsupportedRightException { + assertEquals(none, full.except(aeik).except(lprs).except(twx)); + } + + @Test + public void testExceptNonExistent() throws UnsupportedRightException { + assertEquals(aeik, aeik.except(lprs)); + } + + @Test + public void testExceptUnsupportedFlag() { + try { + String unsupportedFlag = "z"; + new SimpleMailboxACL.Rfc4314Rights(unsupportedFlag ); + Assert.fail(UnsupportedRightException.class.getName() +" expected for unsupported right flag '"+ unsupportedFlag +"'."); + } catch (UnsupportedRightException e) { + /* OK */ + } + } + + @Test + public void testExceptZero() throws UnsupportedRightException { + assertEquals(aeik, aeik.except(none)); + } + + @Test + public void testIterable() { + testIterable(full); + testIterable(none); + testIterable(aeik); + testIterable(lprs); + testIterable(twx); + } + + private static void testIterable(MailboxACLRights rights) { + String stringRights = rights.serialize(); + int i = 0; + for (MailboxACLRight r : rights) { + assertEquals(stringRights.charAt(i++), r.getValue()); + } + assertEquals(stringRights.length(), i); + + } + + @Test + public void testParse() throws UnsupportedRightException { + assertEquals(aeik.getValue(), Rfc4314Rights.a_Administer_MASK | Rfc4314Rights.e_PerformExpunge_MASK | Rfc4314Rights.i_Insert_MASK | Rfc4314Rights.k_CreateMailbox_MASK); + assertEquals(lprs.getValue(), Rfc4314Rights.l_Lookup_MASK | Rfc4314Rights.p_Post_MASK | Rfc4314Rights.s_WriteSeenFlag_MASK | Rfc4314Rights.r_Read_MASK); + assertEquals(twx.getValue(), Rfc4314Rights.t_DeleteMessages_MASK | Rfc4314Rights.w_Write_MASK | Rfc4314Rights.x_DeleteMailbox_MASK); + } + + @Test + public void testSerialize() throws UnsupportedRightException { + assertEquals("aeik", aeik.serialize()); + assertEquals("lprs", lprs.serialize()); + assertEquals("twx", twx.serialize()); + assertEquals("aeiklprstwx", full.serialize()); + assertEquals("", none.serialize()); + } + + @Test + public void testUnionFull() throws UnsupportedRightException { + assertEquals(full, aeik.union(lprs).union(twx)); + } + @Test + public void testUnionZero() throws UnsupportedRightException { + assertEquals(lprs, lprs.union(none)); + } + + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLEntryKeyTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLEntryKeyTest.java new file mode 100644 index 0000000..6bfed79 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLEntryKeyTest.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.model; + +import static org.junit.Assert.assertEquals; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.NameType; +import org.apache.james.mailbox.model.MailboxACL.SpecialName; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class SimpleMailboxACLEntryKeyTest { + + private static final String GROUP_1 = "group1"; + + private static final String USER_1 = "user1"; + + @Test + public void testUser() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(USER_1); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.user); + assertEquals(k.getName(), USER_1); + + } + + @Test + public void testNegativeUser() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + USER_1); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.user); + assertEquals(k.getName(), USER_1); + + } + + + @Test + public void testGroup() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_GROUP_MARKER + GROUP_1); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.group); + assertEquals(k.getName(), GROUP_1); + + } + + @Test + public void testNegativeGroup() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey("" + MailboxACL.DEFAULT_NEGATIVE_MARKER + MailboxACL.DEFAULT_GROUP_MARKER + GROUP_1); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.group); + assertEquals(k.getName(), GROUP_1); + + } + + + @Test + public void testOwner() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(SpecialName.owner.toString()); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.owner.toString()); + + } + + @Test + public void testNegativeOwner() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + SpecialName.owner.toString()); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.owner.toString()); + + } + + @Test + public void testAnybody() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(SpecialName.anybody.toString()); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.anybody.toString()); + + } + + @Test + public void testNegativeAnybody() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + SpecialName.anybody.toString()); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.anybody.toString()); + + } + + + @Test + public void testAuthenticated() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(SpecialName.authenticated.toString()); + assertEquals(k.isNegative(), false); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.authenticated.toString()); + + } + + @Test + public void testNegativeAuthenticated() throws UnsupportedRightException { + + MailboxACLEntryKey k = new SimpleMailboxACLEntryKey(MailboxACL.DEFAULT_NEGATIVE_MARKER + SpecialName.authenticated.toString()); + assertEquals(k.isNegative(), true); + assertEquals(k.getNameType(), NameType.special); + assertEquals(k.getName(), SpecialName.authenticated.toString()); + + } +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLTest.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLTest.java new file mode 100644 index 0000000..2d20fb5 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/model/SimpleMailboxACLTest.java @@ -0,0 +1,214 @@ +/* + * 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.model; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Peter Palaga + */ +public class SimpleMailboxACLTest { + + private static final String USER_1 = "user1"; + private static final String USER_2 = "user2"; + + private static final String ae = "ae"; + private static final String ik = "ik"; + private static final String aeik = "aeik"; + private static final String lprs = "lprs"; + private static final String twx = "twx"; + + private Properties u1u2g1g2Properties; + + private MailboxACL u1u2g1g2ACL; + + @Before + public void setUp() throws Exception { + + u1u2g1g2Properties = new Properties(); + + u1u2g1g2Properties.setProperty(USER_1, aeik); + u1u2g1g2Properties.setProperty(MailboxACL.DEFAULT_NEGATIVE_MARKER + USER_1, lprs); + u1u2g1g2Properties.setProperty(USER_2, lprs); + u1u2g1g2Properties.setProperty(MailboxACL.DEFAULT_NEGATIVE_MARKER + USER_2, twx); + + u1u2g1g2ACL = new SimpleMailboxACL(u1u2g1g2Properties); + + } + + @Test + public void testUnionACLNew() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + MailboxACL toAdd = SimpleMailboxACL.OWNER_FULL_ACL; + MailboxACL result = u1u2g1g2ACL.union(toAdd); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionEntryNew() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + MailboxACL result = u1u2g1g2ACL.union(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionACLExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(aeik + lprs)); + + MailboxACL toAdd = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(USER_1, lprs) }); + MailboxACL result = u1u2g1g2ACL.union(toAdd); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionEntryExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(aeik + lprs)); + + MailboxACL result = u1u2g1g2ACL.union(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(lprs)); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testUnionACLZero() throws UnsupportedRightException { + + } + + @Test + public void testUnionEntryZero() throws UnsupportedRightException { + + } + + @Test + public void testExceptACLNew() throws UnsupportedRightException { + + /* actually no change expected */ + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + + MailboxACL toRemove = SimpleMailboxACL.OWNER_FULL_ACL; + MailboxACL result = u1u2g1g2ACL.except(toRemove); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptEntryNew() throws UnsupportedRightException { + + /* actually no change expected */ + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + + MailboxACL result = u1u2g1g2ACL.except(SimpleMailboxACL.OWNER_KEY, SimpleMailboxACL.FULL_RIGHTS); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptACLExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(ik)); + + MailboxACL toRemove = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(USER_1, ae) }); + MailboxACL result = u1u2g1g2ACL.except(toRemove); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptEntryExisting() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.put(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(ik)); + + MailboxACL result = u1u2g1g2ACL.except(new SimpleMailboxACLEntryKey(USER_1), new Rfc4314Rights(ae)); + + Map foundEntries = result.getEntries(); + + assertEquals(foundEntries, expectedEntries); + } + + @Test + public void testExceptACLFull() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.remove(new SimpleMailboxACLEntryKey(USER_1)); + + MailboxACL toRemove = new SimpleMailboxACL(new SimpleMailboxACL.SimpleMailboxACLEntry[] { new SimpleMailboxACL.SimpleMailboxACLEntry(USER_1, SimpleMailboxACL.FULL_RIGHTS.serialize()) }); + MailboxACL result = u1u2g1g2ACL.except(toRemove); + + Map foundEntries = result.getEntries(); + + assertEquals(expectedEntries, foundEntries); + } + + @Test + public void testExceptEntryFull() throws UnsupportedRightException { + + Map expectedEntries = new HashMap(u1u2g1g2ACL.getEntries()); + expectedEntries.remove(new SimpleMailboxACLEntryKey(USER_1)); + + MailboxACL result = u1u2g1g2ACL.except(new SimpleMailboxACLEntryKey(USER_1), SimpleMailboxACL.FULL_RIGHTS); + + Map foundEntries = result.getEntries(); + + assertEquals(expectedEntries, foundEntries); + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/all-wcprops b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/all-wcprops new file mode 100644 index 0000000..f3d5654 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/util +END +EventCollector.java +K 25 +svn:wc:ra_dav:version-url +V 115 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java +END diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/entries b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/entries new file mode 100644 index 0000000..4a4e8e5 --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/api/src/test/java/org/apache/james/mailbox/util +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +EventCollector.java +file + + + + +2013-09-02T02:54:40.000000Z +e4221a8ef645b7299de9b99fa3bd0e3e +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1702 + diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/text-base/EventCollector.java.svn-base b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/text-base/EventCollector.java.svn-base new file mode 100644 index 0000000..8624cca --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/.svn/text-base/EventCollector.java.svn-base @@ -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.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.james.mailbox.MailboxListener; + +public class EventCollector implements MailboxListener { + + public final List events = new ArrayList(); + + public void event(Event event) { + events.add(event); + } + + public void mailboxDeleted() { + } + + public void mailboxRenamed(String origName, String newName) { + } + + public boolean isClosed() { + return false; + } + +} diff --git a/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.java new file mode 100644 index 0000000..8624cca --- /dev/null +++ b/james/apache-james-mailbox/api/src/test/java/org/apache/james/mailbox/util/EventCollector.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.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.james.mailbox.MailboxListener; + +public class EventCollector implements MailboxListener { + + public final List events = new ArrayList(); + + public void event(Event event) { + events.add(event); + } + + public void mailboxDeleted() { + } + + public void mailboxRenamed(String origName, String newName) { + } + + public boolean isClosed() { + return false; + } + +} diff --git a/james/apache-james-mailbox/caching/.svn/all-wcprops b/james/apache-james-mailbox/caching/.svn/all-wcprops new file mode 100644 index 0000000..8a3bfa1 --- /dev/null +++ b/james/apache-james-mailbox/caching/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 55 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/caching/pom.xml +END diff --git a/james/apache-james-mailbox/caching/.svn/dir-prop-base b/james/apache-james-mailbox/caching/.svn/dir-prop-base new file mode 100644 index 0000000..8b48092 --- /dev/null +++ b/james/apache-james-mailbox/caching/.svn/dir-prop-base @@ -0,0 +1,7 @@ +K 10 +svn:ignore +V 10 +target +.* + +END diff --git a/james/apache-james-mailbox/caching/.svn/entries b/james/apache-james-mailbox/caching/.svn/entries new file mode 100644 index 0000000..f2d7d40 --- /dev/null +++ b/james/apache-james-mailbox/caching/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:39.000000Z +b270c818736c2e7a4e5ec091d09ac79d +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + + + + + + + + +2570 + diff --git a/james/apache-james-mailbox/caching/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/caching/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..3def15e --- /dev/null +++ b/james/apache-james-mailbox/caching/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,68 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-caching + Apache James :: Mailbox :: Caching + bundle + + + + org.apache.james + apache-james-mailbox-store + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + org.jmock + jmock + test + + + org.jmock + jmock-junit4 + test + + + diff --git a/james/apache-james-mailbox/caching/pom.xml b/james/apache-james-mailbox/caching/pom.xml new file mode 100644 index 0000000..3def15e --- /dev/null +++ b/james/apache-james-mailbox/caching/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-caching + Apache James :: Mailbox :: Caching + bundle + + + + org.apache.james + apache-james-mailbox-store + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + org.jmock + jmock + test + + + org.jmock + jmock-junit4 + test + + + diff --git a/james/apache-james-mailbox/caching/src/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/.svn/all-wcprops new file mode 100644 index 0000000..eedb334 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 59 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src +END diff --git a/james/apache-james-mailbox/caching/src/.svn/entries b/james/apache-james-mailbox/caching/src/.svn/entries new file mode 100644 index 0000000..6792a55 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +main +dir + diff --git a/james/apache-james-mailbox/caching/src/main/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/main/.svn/all-wcprops new file mode 100644 index 0000000..3fb0628 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 64 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main +END diff --git a/james/apache-james-mailbox/caching/src/main/.svn/entries b/james/apache-james-mailbox/caching/src/main/.svn/entries new file mode 100644 index 0000000..1686a12 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src/main +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/caching/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..1f6c6b4 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java +END diff --git a/james/apache-james-mailbox/caching/src/main/java/.svn/entries b/james/apache-james-mailbox/caching/src/main/java/.svn/entries new file mode 100644 index 0000000..ebafc38 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src/main/java +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/caching/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..c328819 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org +END diff --git a/james/apache-james-mailbox/caching/src/main/java/org/.svn/entries b/james/apache-james-mailbox/caching/src/main/java/org/.svn/entries new file mode 100644 index 0000000..166b244 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..b45589f --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 80 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..a57a9ab --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..c8f1275 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 88 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox +END diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/entries b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/entries new file mode 100644 index 0000000..7220a59 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +caching +dir + diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/all-wcprops new file mode 100644 index 0000000..34eff86 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/all-wcprops @@ -0,0 +1,47 @@ +K 25 +svn:wc:ra_dav:version-url +V 96 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching +END +CacheInvalidatingMailboxListener.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java +END +CacheLoaderFromUnderlying.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1476501/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java +END +MailboxMetadataCache.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java +END +CachingMessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java +END +MailboxByPathCache.java +K 25 +svn:wc:ra_dav:version-url +V 120 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java +END +CachingMailboxSessionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 136 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java +END +CachingMailboxMapper.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java +END diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/entries b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/entries new file mode 100644 index 0000000..bbd2c2e --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/entries @@ -0,0 +1,269 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +CacheInvalidatingMailboxListener.java +file + + + + +2013-09-02T02:54:39.000000Z +59c98b0440f71f74b1378e1c92d33e12 +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +1717 + +CacheLoaderFromUnderlying.java +file + + + + +2013-09-02T02:54:39.000000Z +9b2163155497c58236b4a5fd93aae31a +2013-04-27T06:33:50.792211Z +1476501 +eric + + + + + + + + + + + + + + + + + + + + + +193 + +MailboxMetadataCache.java +file + + + + +2013-09-02T02:54:39.000000Z +88d806e371b895a5fe6f142ae6a324a5 +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +1153 + +guava +dir + +CachingMessageMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +6c48c51ad4631666dae26dab362738eb +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +3918 + +MailboxByPathCache.java +file + + + + +2013-09-02T02:54:39.000000Z +0b0acef63b7607fc85d55674d1d5ba63 +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +874 + +CachingMailboxSessionMapperFactory.java +file + + + + +2013-09-02T02:54:39.000000Z +db454e3bb2e01dcf817177df0a512071 +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +1890 + +CachingMailboxMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +e11d016e3a7902dd9f728c0977abddc6 +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +2278 + diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheInvalidatingMailboxListener.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheInvalidatingMailboxListener.java.svn-base new file mode 100644 index 0000000..c2d69a8 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheInvalidatingMailboxListener.java.svn-base @@ -0,0 +1,58 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxListenerSupport; +import org.apache.james.mailbox.exception.MailboxException; +/** + * 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()); + } + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheLoaderFromUnderlying.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheLoaderFromUnderlying.java.svn-base new file mode 100644 index 0000000..8aaf4b3 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CacheLoaderFromUnderlying.java.svn-base @@ -0,0 +1,5 @@ +package org.apache.mailbox.caching; + +public interface CacheLoaderFromUnderlying { + Value load(Key key, Underlying underlying) throws Except; +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxMapper.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxMapper.java.svn-base new file mode 100644 index 0000000..8d76684 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxMapper.java.svn-base @@ -0,0 +1,85 @@ +package org.apache.mailbox.caching; +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * A MailboxMapper implementation that uses a MailboxByPathCache to cache the information + * from the underlying MailboxMapper + * + * @param + */ + +public class CachingMailboxMapper implements MailboxMapper { + + private MailboxMapper underlying; + private MailboxByPathCache cache; + + public CachingMailboxMapper(MailboxMapper underlying, MailboxByPathCache cache) { + this.underlying = underlying; + this.cache = cache; + } + + @Override + public void endRequest() { + underlying.endRequest(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + return underlying.execute(transaction); + } + + @Override + public void save(Mailbox mailbox) throws MailboxException { + invalidate(mailbox); + underlying.save(mailbox); + } + + @Override + public void delete(Mailbox mailbox) throws MailboxException { + invalidate(mailbox); + underlying.delete(mailbox); + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxName) + throws MailboxException, MailboxNotFoundException { + try { + return cache.findMailboxByPath(mailboxName, underlying); + } catch (MailboxNotFoundException e) { + cache.invalidate(mailboxName); + throw e; + } + } + + @Override + public List> findMailboxWithPathLike(MailboxPath mailboxPath) + throws MailboxException { + // TODO possible to meaningfully cache it? + return underlying.findMailboxWithPathLike(mailboxPath); + } + + @Override + public boolean hasChildren(Mailbox mailbox, char delimiter) + throws MailboxException, MailboxNotFoundException { + // TODO possible to meaningfully cache it? + return underlying.hasChildren(mailbox, delimiter); + } + + @Override + public List> list() throws MailboxException { + // TODO possible to meaningfully cache it? is it used at all? + return underlying.list(); + } + + private void invalidate(Mailbox mailbox) { + cache.invalidate(mailbox); + } + + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxSessionMapperFactory.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxSessionMapperFactory.java.svn-base new file mode 100644 index 0000000..b2e831e --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMailboxSessionMapperFactory.java.svn-base @@ -0,0 +1,49 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * A MailboxSessionMapperFactory that uses the underlying MailboxSessionMapperFactory to provide + * caching variants of MessageMapper and MailboxMapper built around the MessageMapper and MailboxMapper + * provided by it + * + * @param + */ +public class CachingMailboxSessionMapperFactory extends + MailboxSessionMapperFactory { + + private MailboxSessionMapperFactory underlying; + private MailboxByPathCache mailboxByPathCache; + private MailboxMetadataCache mailboxMetadataCache; + + public CachingMailboxSessionMapperFactory(MailboxSessionMapperFactory underlying, MailboxByPathCache mailboxByPathCache, MailboxMetadataCache mailboxMetadataCache) { + this.underlying = underlying; + this.mailboxByPathCache = mailboxByPathCache; + this.mailboxMetadataCache = mailboxMetadataCache; + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) + throws MailboxException { + return new CachingMessageMapper(underlying.createMessageMapper(session), mailboxMetadataCache); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) + throws MailboxException { + return new CachingMailboxMapper(underlying.createMailboxMapper(session), mailboxByPathCache); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) + throws SubscriptionException { + return underlying.createSubscriptionMapper(session); + } + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMessageMapper.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMessageMapper.java.svn-base new file mode 100644 index 0000000..035b9ed --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/CachingMessageMapper.java.svn-base @@ -0,0 +1,136 @@ +package org.apache.mailbox.caching; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * A MessageMapper implementation that uses a MailboxMetadataCache to cache the information + * from the underlying MessageMapper + * + * @param + */ +public class CachingMessageMapper implements MessageMapper { + + + private MessageMapper underlying; + private MailboxMetadataCache cache; + + public CachingMessageMapper(MessageMapper underlying, MailboxMetadataCache cache) { + this.underlying = underlying; + this.cache = cache; + } + + @Override + public void endRequest() { + underlying.endRequest(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + return underlying.execute(transaction); + } + + @Override + public Iterator> findInMailbox(Mailbox mailbox, + MessageRange set, + org.apache.james.mailbox.store.mail.MessageMapper.FetchType type, + int limit) throws MailboxException { + return underlying.findInMailbox(mailbox, set, type, limit); + } + + @Override + public Map expungeMarkedForDeletionInMailbox( + Mailbox mailbox, MessageRange set) throws MailboxException { + invalidateMetadata(mailbox); + return underlying.expungeMarkedForDeletionInMailbox(mailbox, set); + } + + @Override + public long countMessagesInMailbox(Mailbox mailbox) + throws MailboxException { + return cache.countMessagesInMailbox(mailbox, underlying); + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) + throws MailboxException { + return cache.countUnseenMessagesInMailbox(mailbox, underlying); + } + + @Override + public void delete(Mailbox mailbox, Message message) + throws MailboxException { + invalidateMetadata(mailbox); + underlying.delete(mailbox, message); + + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) + throws MailboxException { + return cache.findFirstUnseenMessageUid(mailbox, underlying); + } + + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) + throws MailboxException { + // TODO can be meaningfully cached? + return underlying.findRecentMessageUidsInMailbox(mailbox); + } + + @Override + public MessageMetaData add(Mailbox mailbox, Message message) + throws MailboxException { + invalidateMetadata(mailbox); + return underlying.add(mailbox, message); + } + + @Override + public Iterator updateFlags(Mailbox mailbox, Flags flags, + boolean value, boolean replace, MessageRange set) + throws MailboxException { + //check if there are in fact any updates + if (set.iterator().hasNext()) + invalidateMetadata(mailbox); + return underlying.updateFlags(mailbox, flags, value, replace, set); + } + + + @Override + public MessageMetaData copy(Mailbox mailbox, Message original) + throws MailboxException { + invalidateMetadata(mailbox); + return underlying.copy(mailbox, original); + } + + @Override + public long getLastUid(Mailbox mailbox) throws MailboxException { + return cache.getLastUid(mailbox, underlying); + } + + @Override + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + return cache.getHighestModSeq(mailbox, underlying); + } + + private void invalidateMetadata(Mailbox mailbox) { + cache.invalidate(mailbox); + + } + + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Move is not yet supported"); + } + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxByPathCache.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxByPathCache.java.svn-base new file mode 100644 index 0000000..f72cf1f --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxByPathCache.java.svn-base @@ -0,0 +1,27 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Caches the MailboxPath -> Mailbox mapping + * + * @param + */ +public interface MailboxByPathCache { + + public abstract Mailbox findMailboxByPath(MailboxPath mailboxName, + MailboxMapper underlying) throws MailboxNotFoundException, + MailboxException; + + public abstract void invalidate(Mailbox mailbox); + + public abstract void invalidate(MailboxPath mailboxPath); + + // for the purpose of cascading the invalidations; does it make sense? + //public void connectTo(MailboxMetadataCache mailboxMetadataCache); + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxMetadataCache.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxMetadataCache.java.svn-base new file mode 100644 index 0000000..ab77550 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/.svn/text-base/MailboxMetadataCache.java.svn-base @@ -0,0 +1,34 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Caches the simple yet possibly expensive to compute metadata info + * about a Mailbox like all/unseen messages count and similar + * + * @param + */ +public interface MailboxMetadataCache { + + public abstract long countMessagesInMailbox(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long countUnseenMessagesInMailbox(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract Long findFirstUnseenMessageUid(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long getLastUid(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long getHighestModSeq(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract void invalidate(Mailbox mailbox); + +// public abstract void invalidate(MailboxPath mailboxPath); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java new file mode 100644 index 0000000..c2d69a8 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheInvalidatingMailboxListener.java @@ -0,0 +1,58 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxListenerSupport; +import org.apache.james.mailbox.exception.MailboxException; +/** + * 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()); + } + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java new file mode 100644 index 0000000..8aaf4b3 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CacheLoaderFromUnderlying.java @@ -0,0 +1,5 @@ +package org.apache.mailbox.caching; + +public interface CacheLoaderFromUnderlying { + Value load(Key key, Underlying underlying) throws Except; +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java new file mode 100644 index 0000000..8d76684 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxMapper.java @@ -0,0 +1,85 @@ +package org.apache.mailbox.caching; +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * A MailboxMapper implementation that uses a MailboxByPathCache to cache the information + * from the underlying MailboxMapper + * + * @param + */ + +public class CachingMailboxMapper implements MailboxMapper { + + private MailboxMapper underlying; + private MailboxByPathCache cache; + + public CachingMailboxMapper(MailboxMapper underlying, MailboxByPathCache cache) { + this.underlying = underlying; + this.cache = cache; + } + + @Override + public void endRequest() { + underlying.endRequest(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + return underlying.execute(transaction); + } + + @Override + public void save(Mailbox mailbox) throws MailboxException { + invalidate(mailbox); + underlying.save(mailbox); + } + + @Override + public void delete(Mailbox mailbox) throws MailboxException { + invalidate(mailbox); + underlying.delete(mailbox); + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxName) + throws MailboxException, MailboxNotFoundException { + try { + return cache.findMailboxByPath(mailboxName, underlying); + } catch (MailboxNotFoundException e) { + cache.invalidate(mailboxName); + throw e; + } + } + + @Override + public List> findMailboxWithPathLike(MailboxPath mailboxPath) + throws MailboxException { + // TODO possible to meaningfully cache it? + return underlying.findMailboxWithPathLike(mailboxPath); + } + + @Override + public boolean hasChildren(Mailbox mailbox, char delimiter) + throws MailboxException, MailboxNotFoundException { + // TODO possible to meaningfully cache it? + return underlying.hasChildren(mailbox, delimiter); + } + + @Override + public List> list() throws MailboxException { + // TODO possible to meaningfully cache it? is it used at all? + return underlying.list(); + } + + private void invalidate(Mailbox mailbox) { + cache.invalidate(mailbox); + } + + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java new file mode 100644 index 0000000..b2e831e --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMailboxSessionMapperFactory.java @@ -0,0 +1,49 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * A MailboxSessionMapperFactory that uses the underlying MailboxSessionMapperFactory to provide + * caching variants of MessageMapper and MailboxMapper built around the MessageMapper and MailboxMapper + * provided by it + * + * @param + */ +public class CachingMailboxSessionMapperFactory extends + MailboxSessionMapperFactory { + + private MailboxSessionMapperFactory underlying; + private MailboxByPathCache mailboxByPathCache; + private MailboxMetadataCache mailboxMetadataCache; + + public CachingMailboxSessionMapperFactory(MailboxSessionMapperFactory underlying, MailboxByPathCache mailboxByPathCache, MailboxMetadataCache mailboxMetadataCache) { + this.underlying = underlying; + this.mailboxByPathCache = mailboxByPathCache; + this.mailboxMetadataCache = mailboxMetadataCache; + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) + throws MailboxException { + return new CachingMessageMapper(underlying.createMessageMapper(session), mailboxMetadataCache); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) + throws MailboxException { + return new CachingMailboxMapper(underlying.createMailboxMapper(session), mailboxByPathCache); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) + throws SubscriptionException { + return underlying.createSubscriptionMapper(session); + } + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java new file mode 100644 index 0000000..035b9ed --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/CachingMessageMapper.java @@ -0,0 +1,136 @@ +package org.apache.mailbox.caching; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * A MessageMapper implementation that uses a MailboxMetadataCache to cache the information + * from the underlying MessageMapper + * + * @param + */ +public class CachingMessageMapper implements MessageMapper { + + + private MessageMapper underlying; + private MailboxMetadataCache cache; + + public CachingMessageMapper(MessageMapper underlying, MailboxMetadataCache cache) { + this.underlying = underlying; + this.cache = cache; + } + + @Override + public void endRequest() { + underlying.endRequest(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + return underlying.execute(transaction); + } + + @Override + public Iterator> findInMailbox(Mailbox mailbox, + MessageRange set, + org.apache.james.mailbox.store.mail.MessageMapper.FetchType type, + int limit) throws MailboxException { + return underlying.findInMailbox(mailbox, set, type, limit); + } + + @Override + public Map expungeMarkedForDeletionInMailbox( + Mailbox mailbox, MessageRange set) throws MailboxException { + invalidateMetadata(mailbox); + return underlying.expungeMarkedForDeletionInMailbox(mailbox, set); + } + + @Override + public long countMessagesInMailbox(Mailbox mailbox) + throws MailboxException { + return cache.countMessagesInMailbox(mailbox, underlying); + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) + throws MailboxException { + return cache.countUnseenMessagesInMailbox(mailbox, underlying); + } + + @Override + public void delete(Mailbox mailbox, Message message) + throws MailboxException { + invalidateMetadata(mailbox); + underlying.delete(mailbox, message); + + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) + throws MailboxException { + return cache.findFirstUnseenMessageUid(mailbox, underlying); + } + + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) + throws MailboxException { + // TODO can be meaningfully cached? + return underlying.findRecentMessageUidsInMailbox(mailbox); + } + + @Override + public MessageMetaData add(Mailbox mailbox, Message message) + throws MailboxException { + invalidateMetadata(mailbox); + return underlying.add(mailbox, message); + } + + @Override + public Iterator updateFlags(Mailbox mailbox, Flags flags, + boolean value, boolean replace, MessageRange set) + throws MailboxException { + //check if there are in fact any updates + if (set.iterator().hasNext()) + invalidateMetadata(mailbox); + return underlying.updateFlags(mailbox, flags, value, replace, set); + } + + + @Override + public MessageMetaData copy(Mailbox mailbox, Message original) + throws MailboxException { + invalidateMetadata(mailbox); + return underlying.copy(mailbox, original); + } + + @Override + public long getLastUid(Mailbox mailbox) throws MailboxException { + return cache.getLastUid(mailbox, underlying); + } + + @Override + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + return cache.getHighestModSeq(mailbox, underlying); + } + + private void invalidateMetadata(Mailbox mailbox) { + cache.invalidate(mailbox); + + } + + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Move is not yet supported"); + } + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java new file mode 100644 index 0000000..f72cf1f --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxByPathCache.java @@ -0,0 +1,27 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Caches the MailboxPath -> Mailbox mapping + * + * @param + */ +public interface MailboxByPathCache { + + public abstract Mailbox findMailboxByPath(MailboxPath mailboxName, + MailboxMapper underlying) throws MailboxNotFoundException, + MailboxException; + + public abstract void invalidate(Mailbox mailbox); + + public abstract void invalidate(MailboxPath mailboxPath); + + // for the purpose of cascading the invalidations; does it make sense? + //public void connectTo(MailboxMetadataCache mailboxMetadataCache); + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java new file mode 100644 index 0000000..ab77550 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/MailboxMetadataCache.java @@ -0,0 +1,34 @@ +package org.apache.mailbox.caching; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Caches the simple yet possibly expensive to compute metadata info + * about a Mailbox like all/unseen messages count and similar + * + * @param + */ +public interface MailboxMetadataCache { + + public abstract long countMessagesInMailbox(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long countUnseenMessagesInMailbox(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract Long findFirstUnseenMessageUid(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long getLastUid(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract long getHighestModSeq(Mailbox mailbox, + MessageMapper underlying) throws MailboxException; + + public abstract void invalidate(Mailbox mailbox); + +// public abstract void invalidate(MailboxPath mailboxPath); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/all-wcprops b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/all-wcprops new file mode 100644 index 0000000..24b2fa1 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/guava +END +GuavaMailboxMetadataCache.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java +END +GuavaCacheWrapper.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1476501/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java +END +AbstractGuavaCache.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1476501/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java +END +GuavaMailboxByPathCache.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1478947/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java +END diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/entries b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/entries new file mode 100644 index 0000000..1ae5654 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/entries @@ -0,0 +1,164 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/caching/src/main/java/org/apache/mailbox/caching/guava +http://svn.apache.org/repos/asf + + + +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +GuavaMailboxMetadataCache.java +file + + + + +2013-09-02T02:54:39.000000Z +1c38c11ce9c17ab20fa986458b0754b2 +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +4838 + +GuavaCacheWrapper.java +file + + + + +2013-09-02T02:54:39.000000Z +79f4e308fca3c0d9d76f7861729dadd0 +2013-04-27T06:33:50.792211Z +1476501 +eric + + + + + + + + + + + + + + + + + + + + + +1124 + +AbstractGuavaCache.java +file + + + + +2013-09-02T02:54:39.000000Z +2671e62d241789ae69192709c42cabc1 +2013-04-27T06:33:50.792211Z +1476501 +eric + + + + + + + + + + + + + + + + + + + + + +390 + +GuavaMailboxByPathCache.java +file + + + + +2013-09-02T02:54:39.000000Z +f7d479d0434a8b33c1a4100a99538655 +2013-05-03T19:34:57.780772Z +1478947 +andrzej + + + + + + + + + + + + + + + + + + + + + +3038 + diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/AbstractGuavaCache.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/AbstractGuavaCache.java.svn-base new file mode 100644 index 0000000..c2a64db --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/AbstractGuavaCache.java.svn-base @@ -0,0 +1,15 @@ +package org.apache.mailbox.caching.guava; + +import java.util.concurrent.TimeUnit; + +import com.google.common.cache.CacheBuilder; + +public class AbstractGuavaCache { + + // TODO this can probably be instantiated more elegant way + protected static final CacheBuilder BUILDER = + CacheBuilder.newBuilder() + .maximumSize(100000) + .expireAfterWrite(15, TimeUnit.MINUTES); + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaCacheWrapper.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaCacheWrapper.java.svn-base new file mode 100644 index 0000000..0e7e528 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaCacheWrapper.java.svn-base @@ -0,0 +1,37 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.mailbox.caching.CacheLoaderFromUnderlying; + +import com.google.common.cache.Cache; + +public abstract class GuavaCacheWrapper + implements CacheLoaderFromUnderlying { + + private final Cache cache; +// private final CacheLoaderFromUnderlying loader; + + public GuavaCacheWrapper(Cache cache/*, CacheLoaderFromUnderlying loader*/) { + this.cache = cache; +// this.loader = loader; + } + + public Value get(Key key, Underlying underlying) throws Except { + Value value = cache.getIfPresent(getKeyRepresentation(key)); + if (value != null) + return value; + else { + value = load(key, underlying); + cache.put(getKeyRepresentation(key), value); + return value; + } + + } + + public void invalidate(Key key) { + if (key != null) //needed? + cache.invalidate(getKeyRepresentation(key)); + } + + public abstract KeyRepresentation getKeyRepresentation(Key key); + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxByPathCache.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxByPathCache.java.svn-base new file mode 100644 index 0000000..e1ab2d0 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxByPathCache.java.svn-base @@ -0,0 +1,88 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.mailbox.caching.MailboxByPathCache; + +import com.google.common.cache.Cache; + +/** + * Guava-based implementation of MailboxByPathCache. + * Note: for efficiency/simplicity reasons the cache key is MailboxPath.toString() + * That may help also make it compatible with other cache backends in the future. + * + * @param + */ +public class GuavaMailboxByPathCache extends AbstractGuavaCache implements MailboxByPathCache { + + private final Cache> findMailboxByPathCache = BUILDER.build(); + + private final MailboxByPathCacheWrapper wrapper; + + + public GuavaMailboxByPathCache() { + this.wrapper = new MailboxByPathCacheWrapper(findMailboxByPathCache); + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxNotFoundException, MailboxException { + + return wrapper.get(mailboxName, underlying); + } + +// alternative plain implementation - review and choose the better +// public Mailbox findMailboxByPath(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxNotFoundException, MailboxException { +// Mailbox mailbox = findMailboxByPathCache.getIfPresent(mailboxName.toString()); +// if (mailbox != null) +// return mailbox; +// else { +// mailbox = new MailboxByPathCacheLoaderFromUnderlying().load(mailboxName, underlying); +// findMailboxByPathCache.put(mailboxName.toString(), mailbox); +// return mailbox; +// } +// } + + + + @Override + public void invalidate(Mailbox mailbox) { + invalidate(new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); + } + + @Override + public void invalidate(MailboxPath mailboxPath) { + wrapper.invalidate(mailboxPath); + } + + + //Does it make sense to define such loaders as separate classes for reuse? +// class MailboxByPathCacheLoaderFromUnderlying implements CacheLoaderFromUnderlying, MailboxMapper, MailboxException> { +// @Override +// public Mailbox load(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxException { +// return underlying.findMailboxByPath(mailboxName); +// } +// } + + class MailboxByPathCacheWrapper extends GuavaCacheWrapper, MailboxMapper, String, MailboxException> { + + public MailboxByPathCacheWrapper( + Cache> cache/*, + MailboxByPathCacheLoaderFromUnderlying loader*/) { + super(cache); + } + + @Override + public Mailbox load(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxException { + return underlying.findMailboxByPath(mailboxName); + } + + @Override + public String getKeyRepresentation(MailboxPath key) { + return key.toString(); + } + + } +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxMetadataCache.java.svn-base b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxMetadataCache.java.svn-base new file mode 100644 index 0000000..e1fb235 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/.svn/text-base/GuavaMailboxMetadataCache.java.svn-base @@ -0,0 +1,141 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.mailbox.caching.MailboxMetadataCache; + +import com.google.common.cache.Cache; +/** + * Guava-based implementation of MailboxMetadataCache. + * Note: for efficiency/simplicity reasons the cache key is Mailbox.getMailboxId() + * + * @param + */ + +public class GuavaMailboxMetadataCache extends AbstractGuavaCache implements MailboxMetadataCache { + + // TODO these can probably be instantiated more elegant way + private final Cache cacheCountMessagesInMailbox = BUILDER.build(); + private final Cache cacheCountUnseenMessagesInMailbox = BUILDER.build(); + private final Cache cacheFindFirstUnseenMessageUid = BUILDER.build(); + private final Cache cacheGetLastUid = BUILDER.build(); + private final Cache cacheGetHighestModSeq = BUILDER.build(); + + private final MetadataCacheWrapper countMessagesInMailboxWrapper = new CountMessagesInMailboxWrapper(cacheCountMessagesInMailbox); + private final MetadataCacheWrapper countUnseenMessagesInMailboxWrapper = new CountUnseenMessagesInMailboxWrapper(cacheCountUnseenMessagesInMailbox); + private final MetadataCacheWrapper findFirstUnseenMessageUid = new FindFirstUnseenMessageUidWrapper(cacheFindFirstUnseenMessageUid); + private final MetadataCacheWrapper highestModSeqWrapper = new HighestModseqCacheWrapper(cacheGetHighestModSeq); + private final MetadataCacheWrapper lastUidWrapper = new LastUidCacheWrapper(cacheGetLastUid); + + @Override + public long countMessagesInMailbox(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return countMessagesInMailboxWrapper.get(mailbox, underlying); + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return countUnseenMessagesInMailboxWrapper.get(mailbox, underlying); + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return findFirstUnseenMessageUid.get(mailbox, underlying); + } + + @Override + public long getLastUid(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return lastUidWrapper.get(mailbox, underlying); + + } + + @Override + public long getHighestModSeq(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return highestModSeqWrapper.get(mailbox, underlying); + } + + @Override + public void invalidate(Mailbox mailbox) { + cacheCountMessagesInMailbox.invalidate(mailbox); + cacheCountUnseenMessagesInMailbox.invalidate(mailbox); + cacheFindFirstUnseenMessageUid.invalidate(mailbox); + lastUidWrapper.invalidate(mailbox); + highestModSeqWrapper.invalidate(mailbox); + } + + + abstract class MetadataCacheWrapper extends GuavaCacheWrapper, Long, MessageMapper, Id, MailboxException> { + + public MetadataCacheWrapper(Cache cache) { + super(cache); + } + + @Override + public Id getKeyRepresentation(Mailbox key) { + return key.getMailboxId(); + } + + } + + class CountMessagesInMailboxWrapper extends MetadataCacheWrapper { + + public CountMessagesInMailboxWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.countMessagesInMailbox(mailbox); + } + + } + + class CountUnseenMessagesInMailboxWrapper extends MetadataCacheWrapper { + + public CountUnseenMessagesInMailboxWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.countUnseenMessagesInMailbox(mailbox); + } + + } + + class FindFirstUnseenMessageUidWrapper extends MetadataCacheWrapper { + + public FindFirstUnseenMessageUidWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.findFirstUnseenMessageUid(mailbox); + } + + } + + class LastUidCacheWrapper extends MetadataCacheWrapper { + public LastUidCacheWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return underlying.getLastUid(mailbox); + } + } + + class HighestModseqCacheWrapper extends MetadataCacheWrapper { + public HighestModseqCacheWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return underlying.getHighestModSeq(mailbox); + } + } + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java new file mode 100644 index 0000000..c2a64db --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/AbstractGuavaCache.java @@ -0,0 +1,15 @@ +package org.apache.mailbox.caching.guava; + +import java.util.concurrent.TimeUnit; + +import com.google.common.cache.CacheBuilder; + +public class AbstractGuavaCache { + + // TODO this can probably be instantiated more elegant way + protected static final CacheBuilder BUILDER = + CacheBuilder.newBuilder() + .maximumSize(100000) + .expireAfterWrite(15, TimeUnit.MINUTES); + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java new file mode 100644 index 0000000..0e7e528 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaCacheWrapper.java @@ -0,0 +1,37 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.mailbox.caching.CacheLoaderFromUnderlying; + +import com.google.common.cache.Cache; + +public abstract class GuavaCacheWrapper + implements CacheLoaderFromUnderlying { + + private final Cache cache; +// private final CacheLoaderFromUnderlying loader; + + public GuavaCacheWrapper(Cache cache/*, CacheLoaderFromUnderlying loader*/) { + this.cache = cache; +// this.loader = loader; + } + + public Value get(Key key, Underlying underlying) throws Except { + Value value = cache.getIfPresent(getKeyRepresentation(key)); + if (value != null) + return value; + else { + value = load(key, underlying); + cache.put(getKeyRepresentation(key), value); + return value; + } + + } + + public void invalidate(Key key) { + if (key != null) //needed? + cache.invalidate(getKeyRepresentation(key)); + } + + public abstract KeyRepresentation getKeyRepresentation(Key key); + +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java new file mode 100644 index 0000000..e1ab2d0 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxByPathCache.java @@ -0,0 +1,88 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.mailbox.caching.MailboxByPathCache; + +import com.google.common.cache.Cache; + +/** + * Guava-based implementation of MailboxByPathCache. + * Note: for efficiency/simplicity reasons the cache key is MailboxPath.toString() + * That may help also make it compatible with other cache backends in the future. + * + * @param + */ +public class GuavaMailboxByPathCache extends AbstractGuavaCache implements MailboxByPathCache { + + private final Cache> findMailboxByPathCache = BUILDER.build(); + + private final MailboxByPathCacheWrapper wrapper; + + + public GuavaMailboxByPathCache() { + this.wrapper = new MailboxByPathCacheWrapper(findMailboxByPathCache); + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxNotFoundException, MailboxException { + + return wrapper.get(mailboxName, underlying); + } + +// alternative plain implementation - review and choose the better +// public Mailbox findMailboxByPath(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxNotFoundException, MailboxException { +// Mailbox mailbox = findMailboxByPathCache.getIfPresent(mailboxName.toString()); +// if (mailbox != null) +// return mailbox; +// else { +// mailbox = new MailboxByPathCacheLoaderFromUnderlying().load(mailboxName, underlying); +// findMailboxByPathCache.put(mailboxName.toString(), mailbox); +// return mailbox; +// } +// } + + + + @Override + public void invalidate(Mailbox mailbox) { + invalidate(new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); + } + + @Override + public void invalidate(MailboxPath mailboxPath) { + wrapper.invalidate(mailboxPath); + } + + + //Does it make sense to define such loaders as separate classes for reuse? +// class MailboxByPathCacheLoaderFromUnderlying implements CacheLoaderFromUnderlying, MailboxMapper, MailboxException> { +// @Override +// public Mailbox load(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxException { +// return underlying.findMailboxByPath(mailboxName); +// } +// } + + class MailboxByPathCacheWrapper extends GuavaCacheWrapper, MailboxMapper, String, MailboxException> { + + public MailboxByPathCacheWrapper( + Cache> cache/*, + MailboxByPathCacheLoaderFromUnderlying loader*/) { + super(cache); + } + + @Override + public Mailbox load(MailboxPath mailboxName, MailboxMapper underlying) throws MailboxException { + return underlying.findMailboxByPath(mailboxName); + } + + @Override + public String getKeyRepresentation(MailboxPath key) { + return key.toString(); + } + + } +} diff --git a/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java new file mode 100644 index 0000000..e1fb235 --- /dev/null +++ b/james/apache-james-mailbox/caching/src/main/java/org/apache/mailbox/caching/guava/GuavaMailboxMetadataCache.java @@ -0,0 +1,141 @@ +package org.apache.mailbox.caching.guava; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.mailbox.caching.MailboxMetadataCache; + +import com.google.common.cache.Cache; +/** + * Guava-based implementation of MailboxMetadataCache. + * Note: for efficiency/simplicity reasons the cache key is Mailbox.getMailboxId() + * + * @param + */ + +public class GuavaMailboxMetadataCache extends AbstractGuavaCache implements MailboxMetadataCache { + + // TODO these can probably be instantiated more elegant way + private final Cache cacheCountMessagesInMailbox = BUILDER.build(); + private final Cache cacheCountUnseenMessagesInMailbox = BUILDER.build(); + private final Cache cacheFindFirstUnseenMessageUid = BUILDER.build(); + private final Cache cacheGetLastUid = BUILDER.build(); + private final Cache cacheGetHighestModSeq = BUILDER.build(); + + private final MetadataCacheWrapper countMessagesInMailboxWrapper = new CountMessagesInMailboxWrapper(cacheCountMessagesInMailbox); + private final MetadataCacheWrapper countUnseenMessagesInMailboxWrapper = new CountUnseenMessagesInMailboxWrapper(cacheCountUnseenMessagesInMailbox); + private final MetadataCacheWrapper findFirstUnseenMessageUid = new FindFirstUnseenMessageUidWrapper(cacheFindFirstUnseenMessageUid); + private final MetadataCacheWrapper highestModSeqWrapper = new HighestModseqCacheWrapper(cacheGetHighestModSeq); + private final MetadataCacheWrapper lastUidWrapper = new LastUidCacheWrapper(cacheGetLastUid); + + @Override + public long countMessagesInMailbox(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return countMessagesInMailboxWrapper.get(mailbox, underlying); + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return countUnseenMessagesInMailboxWrapper.get(mailbox, underlying); + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return findFirstUnseenMessageUid.get(mailbox, underlying); + } + + @Override + public long getLastUid(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return lastUidWrapper.get(mailbox, underlying); + + } + + @Override + public long getHighestModSeq(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return highestModSeqWrapper.get(mailbox, underlying); + } + + @Override + public void invalidate(Mailbox mailbox) { + cacheCountMessagesInMailbox.invalidate(mailbox); + cacheCountUnseenMessagesInMailbox.invalidate(mailbox); + cacheFindFirstUnseenMessageUid.invalidate(mailbox); + lastUidWrapper.invalidate(mailbox); + highestModSeqWrapper.invalidate(mailbox); + } + + + abstract class MetadataCacheWrapper extends GuavaCacheWrapper, Long, MessageMapper, Id, MailboxException> { + + public MetadataCacheWrapper(Cache cache) { + super(cache); + } + + @Override + public Id getKeyRepresentation(Mailbox key) { + return key.getMailboxId(); + } + + } + + class CountMessagesInMailboxWrapper extends MetadataCacheWrapper { + + public CountMessagesInMailboxWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.countMessagesInMailbox(mailbox); + } + + } + + class CountUnseenMessagesInMailboxWrapper extends MetadataCacheWrapper { + + public CountUnseenMessagesInMailboxWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.countUnseenMessagesInMailbox(mailbox); + } + + } + + class FindFirstUnseenMessageUidWrapper extends MetadataCacheWrapper { + + public FindFirstUnseenMessageUidWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) + throws MailboxException { + return underlying.findFirstUnseenMessageUid(mailbox); + } + + } + + class LastUidCacheWrapper extends MetadataCacheWrapper { + public LastUidCacheWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return underlying.getLastUid(mailbox); + } + } + + class HighestModseqCacheWrapper extends MetadataCacheWrapper { + public HighestModseqCacheWrapper(Cache cache) { + super(cache); + } + @Override + public Long load(Mailbox mailbox, MessageMapper underlying) throws MailboxException { + return underlying.getHighestModSeq(mailbox); + } + } + +} diff --git a/james/apache-james-mailbox/hbase/.svn/all-wcprops b/james/apache-james-mailbox/hbase/.svn/all-wcprops new file mode 100644 index 0000000..f1221ef --- /dev/null +++ b/james/apache-james-mailbox/hbase/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 53 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 61 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/hbase/pom.xml +END diff --git a/james/apache-james-mailbox/hbase/.svn/dir-prop-base b/james/apache-james-mailbox/hbase/.svn/dir-prop-base new file mode 100644 index 0000000..85195f0 --- /dev/null +++ b/james/apache-james-mailbox/hbase/.svn/dir-prop-base @@ -0,0 +1,7 @@ +K 10 +svn:ignore +V 10 +.* +target + +END diff --git a/james/apache-james-mailbox/hbase/.svn/entries b/james/apache-james-mailbox/hbase/.svn/entries new file mode 100644 index 0000000..b697fc0 --- /dev/null +++ b/james/apache-james-mailbox/hbase/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:38.000000Z +8952e2bd77ad05caa277a44c058ed005 +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +5150 + diff --git a/james/apache-james-mailbox/hbase/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/hbase/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..998a6e7 --- /dev/null +++ b/james/apache-james-mailbox/hbase/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,140 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-hbase + Apache James Mailbox implementation over HBase + Apache James :: Mailbox :: HBase + + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + junit + junit + test + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + ${project.groupId} + apache-james-mailbox-api + + + ${project.groupId} + apache-james-mailbox-store + + + ${project.groupId} + apache-james-mailbox-api + test + test-jar + + + org.apache.hbase + hbase + + + org.jruby + jruby-complete + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + commons-logging + commons-logging + + + + + org.apache.hbase + hbase + test-jar + test + + + org.jruby + jruby-complete + + + + + org.apache.hadoop + hadoop-test + test + + + org.apache.hadoop + hadoop-core + + + commons-logging + commons-logging + + + + + + + + noTest + + + windows + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/pom.xml b/james/apache-james-mailbox/hbase/pom.xml new file mode 100644 index 0000000..998a6e7 --- /dev/null +++ b/james/apache-james-mailbox/hbase/pom.xml @@ -0,0 +1,140 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-hbase + Apache James Mailbox implementation over HBase + Apache James :: Mailbox :: HBase + + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + junit + junit + test + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + ${project.groupId} + apache-james-mailbox-api + + + ${project.groupId} + apache-james-mailbox-store + + + ${project.groupId} + apache-james-mailbox-api + test + test-jar + + + org.apache.hbase + hbase + + + org.jruby + jruby-complete + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + commons-logging + commons-logging + + + + + org.apache.hbase + hbase + test-jar + test + + + org.jruby + jruby-complete + + + + + org.apache.hadoop + hadoop-test + test + + + org.apache.hadoop + hadoop-core + + + commons-logging + commons-logging + + + + + + + + noTest + + + windows + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/src/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/.svn/all-wcprops new file mode 100644 index 0000000..60333ac --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 57 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src +END diff --git a/james/apache-james-mailbox/hbase/src/.svn/entries b/james/apache-james-mailbox/hbase/src/.svn/entries new file mode 100644 index 0000000..e747771 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/.svn/entries @@ -0,0 +1,40 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +site +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/.svn/all-wcprops new file mode 100644 index 0000000..2f33d78 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main +END diff --git a/james/apache-james-mailbox/hbase/src/main/.svn/entries b/james/apache-james-mailbox/hbase/src/main/.svn/entries new file mode 100644 index 0000000..5d07cc9 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +config +dir + +resources +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/config/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/config/.svn/all-wcprops new file mode 100644 index 0000000..15ce911 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/config/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1178182/james/mailbox/trunk/hbase/src/main/config +END +hbase-site.xml +K 25 +svn:wc:ra_dav:version-url +V 84 +/repos/asf/!svn/ver/1178182/james/mailbox/trunk/hbase/src/main/config/hbase-site.xml +END diff --git a/james/apache-james-mailbox/hbase/src/main/config/.svn/entries b/james/apache-james-mailbox/hbase/src/main/config/.svn/entries new file mode 100644 index 0000000..8bc6045 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/config/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/config +http://svn.apache.org/repos/asf + + + +2011-10-02T11:21:49.204507Z +1178182 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +hbase-site.xml +file + + + + +2013-09-02T02:54:38.000000Z +38e6b68bf1e776ee03a7a408ca5dc250 +2011-10-02T11:21:49.204507Z +1178182 +ieugen + + + + + + + + + + + + + + + + + + + + + +1572 + diff --git a/james/apache-james-mailbox/hbase/src/main/config/.svn/text-base/hbase-site.xml.svn-base b/james/apache-james-mailbox/hbase/src/main/config/.svn/text-base/hbase-site.xml.svn-base new file mode 100644 index 0000000..1857bac --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/config/.svn/text-base/hbase-site.xml.svn-base @@ -0,0 +1,49 @@ + + + + + + + hbase.rootdir + hdfs://localhost:9000/hbase + + + + hbase.master.port + 60000 + + + + hbase.regionserver.info.port + 6030 + + + + hbase.regionserver.info.bindAddress + 0.0.0.0 + + + diff --git a/james/apache-james-mailbox/hbase/src/main/config/hbase-site.xml b/james/apache-james-mailbox/hbase/src/main/config/hbase-site.xml new file mode 100644 index 0000000..1857bac --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/config/hbase-site.xml @@ -0,0 +1,49 @@ + + + + + + + hbase.rootdir + hdfs://localhost:9000/hbase + + + + hbase.master.port + 60000 + + + + hbase.regionserver.info.port + 6030 + + + + hbase.regionserver.info.bindAddress + 0.0.0.0 + + + diff --git a/james/apache-james-mailbox/hbase/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..b9245e5 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 67 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main/java +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/.svn/entries new file mode 100644 index 0000000..bfac8c2 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..e21333c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 71 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main/java/org +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/.svn/entries new file mode 100644 index 0000000..10236b3 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..44ebb4e --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 78 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..3ea8594 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..dd8520a --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 84 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..9602450 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..ab03298 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 92 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..91c232b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +hbase +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/all-wcprops new file mode 100644 index 0000000..9bb4c0e --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/all-wcprops @@ -0,0 +1,53 @@ +K 25 +svn:wc:ra_dav:version-url +V 98 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase +END +HBaseMailboxSessionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 136 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactory.java +END +HBaseMessageManager.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java +END +HBaseUtils.java +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseUtils.java +END +HBaseNonTransactionalMapper.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1164981/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNonTransactionalMapper.java +END +HBaseNames.java +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNames.java +END +PropertyConvertor.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1179554/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/PropertyConvertor.java +END +HBaseMailboxManager.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxManager.java +END +FlagConvertor.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1164981/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/FlagConvertor.java +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/entries new file mode 100644 index 0000000..ac92873 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/entries @@ -0,0 +1,309 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +HBaseMailboxSessionMapperFactory.java +file + + + + +2013-09-02T02:54:38.000000Z +a26545ad0b4ccec14131294e62046925 +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + + + + + + + + +6542 + +mail +dir + +HBaseMessageManager.java +file + + + + +2013-09-02T02:54:38.000000Z +461718c4a8826e4a16ba4edaf0c03c5f +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2668 + +HBaseUtils.java +file + + + + +2013-09-02T02:54:38.000000Z +b3f1928395863928d66773e145346da1 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +18602 + +HBaseNonTransactionalMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +d053201ba2d86c7240501f4fa8675661 +2011-09-04T09:49:14.713666Z +1164981 +ieugen + + + + + + + + + + + + + + + + + + + + + +1684 + +HBaseNames.java +file + + + + +2013-09-02T02:54:38.000000Z +58764c0cc3a9315383aad8a4bdbbc2a5 +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +4701 + +PropertyConvertor.java +file + + + + +2013-09-02T02:54:38.000000Z +25c1d93ce9862471e27c62d6d1236bbe +2011-10-06T10:08:47.240473Z +1179554 +felixk + + + + + + + + + + + + + + + + + + + + + +3398 + +io +dir + +HBaseMailboxManager.java +file + + + + +2013-09-02T02:54:38.000000Z +68b0ec257e710f8c460e99292399a60b +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4199 + +FlagConvertor.java +file + + + + +2013-09-02T02:54:38.000000Z +94d74cc55b92a2385e44eb2a59b1798b +2011-09-04T09:49:14.713666Z +1164981 +ieugen + + + + + + + + + + + + + + + + + + + + + +5414 + +user +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/FlagConvertor.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/FlagConvertor.java.svn-base new file mode 100644 index 0000000..0e6611b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/FlagConvertor.java.svn-base @@ -0,0 +1,133 @@ +/**************************************************************** + * 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 javax.mail.Flags; +import javax.mail.Flags.Flag; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Class used for converting message flags to and from byte arrays for use as + * HBase column qualifiers. + */ +public class FlagConvertor { + + public static final String PREFIX_SFLAGS = "sf:"; + public static final byte[] PREFIX_SFLAGS_B = Bytes.toBytes(PREFIX_SFLAGS); + public static final String PREFIX_UFLAGS = "uf:"; + public static final byte[] PREFIX_UFLAGS_B = Bytes.toBytes(PREFIX_UFLAGS); + /*TODO: find a way to store all flags as a single byte (HBase bitewise operations). + * this will be efficient also because we will not store the column names. + */ + public static final byte[] FLAGS_ANSWERED = Bytes.toBytes("sf:A"); + public static final byte[] FLAGS_DELETED = Bytes.toBytes("sf:DE"); + public static final byte[] FLAGS_DRAFT = Bytes.toBytes("sf:DR"); + public static final byte[] FLAGS_FLAGGED = Bytes.toBytes("sf:F"); + public static final byte[] FLAGS_RECENT = Bytes.toBytes("sf:R"); + public static final byte[] FLAGS_SEEN = Bytes.toBytes("sf:S"); + public static final byte[] FLAGS_USER = Bytes.toBytes("sf:U"); + + /** + * Converts a {@link javax.mail.Flags.Flag} to a byte array representation + * used for storing in HBase (as a column qualifier). + * @param flag + * @return a byte representation of the flag. + * + * Throws RuntimeException if Flag is not recognized. + */ + public static byte[] systemFlagToBytes(Flags.Flag flag) { + if (flag.equals(Flag.ANSWERED)) { + return FLAGS_ANSWERED; + } + if (flag.equals(Flag.DELETED)) { + return FLAGS_DELETED; + } + if (flag.equals(Flag.DRAFT)) { + return FLAGS_DRAFT; + } + if (flag.equals(Flag.FLAGGED)) { + return FLAGS_FLAGGED; + } + if (flag.equals(Flag.RECENT)) { + return FLAGS_RECENT; + } + if (flag.equals(Flag.SEEN)) { + return FLAGS_SEEN; + } + if (flag.equals(Flag.USER)) { + return FLAGS_USER; + } + throw new RuntimeException("Invalid Flag supplied"); + } + + /** + * Returns a {@link javax.mail.Flags.Flag} coresponding to the supplyed + * byte array. + * @param bytes byte array representation + * @return one of {@link javax.mail.Flags.Flag} + * @throws RuntimeException if the byte array does not match a + * suitable represetnation. + */ + public static Flag systemFlagFromBytes(byte[] bytes) { + if (Bytes.equals(bytes, FLAGS_ANSWERED)) { + return Flag.ANSWERED; + } + if (Bytes.equals(bytes, FLAGS_DELETED)) { + return Flag.DELETED; + } + if (Bytes.equals(bytes, FLAGS_DRAFT)) { + return Flag.DRAFT; + } + if (Bytes.equals(bytes, FLAGS_FLAGGED)) { + return Flag.FLAGGED; + } + if (Bytes.equals(bytes, FLAGS_RECENT)) { + return Flag.RECENT; + } + if (Bytes.equals(bytes, FLAGS_SEEN)) { + return Flag.SEEN; + } + if (Bytes.equals(bytes, FLAGS_USER)) { + return Flag.USER; + } + throw new RuntimeException("This is not a recognized system flag: " + Bytes.toString(bytes)); + } + + /** + * Converts a user flag to a byte array for use as a HBase column qualifier. + * @param flag user flag to convert + * @return a byte array representation of the user flag + */ + public static byte[] userFlagToBytes(String flag) { + return Bytes.toBytes(PREFIX_UFLAGS + flag); + } + + /** + * Converts a byte array to a user flag. + * @param bytes the user flag byte representation + * @return a {@link String} representaion of the user flag + * @throws RuntimeException if the user flag prefix is not found. + */ + public static String userFlagFromBytes(byte[] bytes) { + if (Bytes.startsWith(bytes, PREFIX_UFLAGS_B)) { + return Bytes.toString(bytes, PREFIX_UFLAGS_B.length, bytes.length - PREFIX_UFLAGS_B.length); + } + throw new RuntimeException("This is not a user flag representation: " + Bytes.toString(bytes)); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManager.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManager.java.svn-base new file mode 100644 index 0000000..3850d48 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManager.java.svn-base @@ -0,0 +1,88 @@ +/**************************************************************** + * 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 java.util.UUID; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.hbase.mail.HBaseMailboxMapper; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * HBase implementation of {@link StoreMailboxManager} + * + */ +public class HBaseMailboxManager extends StoreMailboxManager { + + public HBaseMailboxManager(HBaseMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxPathLocker locker, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + } + + public HBaseMailboxManager(HBaseMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + } + + @Override + protected Mailbox doCreateMailbox(MailboxPath mailboxPath, MailboxSession session) throws MailboxException { + return new HBaseMailbox(mailboxPath, randomUidValidity()); + } + + /** + * Delete all mailboxes + * + * @param mailboxSession + * @throws MailboxException + */ + public void deleteEverything(MailboxSession mailboxSession) throws MailboxException { + + final HBaseMailboxMapper mapper = (HBaseMailboxMapper) getMapperFactory().getMailboxMapper(mailboxSession); + + mapper.execute(new TransactionalMapper.VoidTransaction() { + + @Override + public void runVoid() throws MailboxException { + mapper.deleteAllMemberships(); + } + }); + mapper.execute(new TransactionalMapper.VoidTransaction() { + + @Override + public void runVoid() throws MailboxException { + mapper.deleteAllMailboxes(); + } + }); + } + + @Override + protected StoreMessageManager createMessageManager(Mailbox mailboxRow, MailboxSession session) throws MailboxException { + StoreMessageManager result = new HBaseMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), mailboxRow, getAclResolver(), getGroupMembershipResolver()); + return result; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactory.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactory.java.svn-base new file mode 100644 index 0000000..8640abc --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactory.java.svn-base @@ -0,0 +1,154 @@ +/**************************************************************** + * 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 java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MasterNotRunningException; +import org.apache.hadoop.hbase.ZooKeeperConnectionException; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.james.mailbox.MailboxSession; +import static org.apache.james.mailbox.hbase.HBaseNames.*; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.hbase.mail.HBaseMailboxMapper; +import org.apache.james.mailbox.hbase.mail.HBaseMessageMapper; +import org.apache.james.mailbox.hbase.user.HBaseSubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * HBase implementation of {@link MailboxSessionMapperFactory} + * + */ +public class HBaseMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private final Configuration conf; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + /** + * Creates the necessary tables in HBase if they do not exist. + * + * @param conf Configuration for the cluster + * @param uidProvider UID provider for mailbox uid. + * @param modSeqProvider + * @throws MasterNotRunningException + * @throws ZooKeeperConnectionException + * @throws IOException + */ + public HBaseMailboxSessionMapperFactory(Configuration conf, UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this.conf = conf; + this.uidProvider = uidProvider; + this.modSeqProvider = modSeqProvider; + + //TODO: add better exception handling for this + try { + HBaseAdmin hbaseAdmin = new HBaseAdmin(conf); + HTableDescriptor desc = null; + HColumnDescriptor hColumnDescriptor = null; + + /* create the tables if it does not exist */ + + if (!hbaseAdmin.tableExists(MAILBOXES_TABLE)) { + desc = new HTableDescriptor(MAILBOXES_TABLE); + hColumnDescriptor = new HColumnDescriptor(MAILBOX_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hbaseAdmin.createTable(desc); + } + + if (!hbaseAdmin.tableExists(MESSAGES_TABLE)) { + /**TODO: try to reduce the number of column families as suggested by: + * http://hbase.apache.org/book.html#number.of.cfs + * Down to three column families, striking for just two. + */ + desc = new HTableDescriptor(MESSAGES_TABLE); + hColumnDescriptor = new HColumnDescriptor(MESSAGES_META_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hColumnDescriptor = new HColumnDescriptor(MESSAGE_DATA_HEADERS_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hColumnDescriptor = new HColumnDescriptor(MESSAGE_DATA_BODY_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hbaseAdmin.createTable(desc); + } + + + if (!hbaseAdmin.tableExists(SUBSCRIPTIONS_TABLE)) { + desc = new HTableDescriptor(SUBSCRIPTIONS_TABLE); + hColumnDescriptor = new HColumnDescriptor(SUBSCRIPTION_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hbaseAdmin.createTable(desc); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) throws MailboxException { + return new HBaseMessageMapper(session, uidProvider, modSeqProvider, this.conf); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException { + return new HBaseMailboxMapper(this.conf); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException { + return new HBaseSubscriptionMapper(this.conf); + } + + /** + * Returns the configuration object for accessing the cluster. + * @return The configuration for accessing the cluster + */ + public Configuration getClusterConfiguration() { + return conf; + } + + /** + * Returns the ModSeqProvider used. + * @return The used modSeqProvider + */ + public ModSeqProvider getModSeqProvider() { + return modSeqProvider; + } + + /** + * Returns the UidProvider that generates UID's for mailboxes. + * @return The provider that generates UID's for mailboxes + */ + public UidProvider getUidProvider() { + return uidProvider; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMessageManager.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMessageManager.java.svn-base new file mode 100644 index 0000000..fb57a49 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMessageManager.java.svn-base @@ -0,0 +1,57 @@ +/**************************************************************** + * 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 java.util.UUID; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.search.MessageSearchIndex; + +/** + * HBase implementation of MessageManager. + * + */ +public class HBaseMessageManager extends StoreMessageManager { + + public HBaseMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + MailboxEventDispatcher dispatcher, MailboxPathLocker locker, Mailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + + } + + /** + * Support user flags + */ + @Override + protected Flags getPermanentFlags(MailboxSession session) { + Flags flags = super.getPermanentFlags(session); + flags.add(Flags.Flag.USER); + return flags; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNames.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNames.java.svn-base new file mode 100644 index 0000000..6b2eb4a --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNames.java.svn-base @@ -0,0 +1,84 @@ +/**************************************************************** + * 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.hadoop.hbase.util.Bytes; + +/** + * Contains table names, column family names, qualifier names and other constants + * for use in HBase. + * + * Each qualifier in the META column will begin with a short prefix that will + * determine it's purpose.
+ * Qualifier prefix meaning:
+ *
    + *
  • m: meta information;
  • + *
  • sf: system flag (DELETE, RECENT, etc.)
  • + *
  • uf: user flag
  • + *
  • p: user property
  • + *
+ */ +public interface HBaseNames { + + /** + * The HBase table name for storing mailbox names + */ + public static final String MAILBOXES = "JAMES_MAILBOXES"; + public static final byte[] MAILBOXES_TABLE = Bytes.toBytes(MAILBOXES); + /** Default mailbox column family */ + public static final byte[] MAILBOX_CF = Bytes.toBytes("D"); + /** HBase column qualifiers: field names stored as byte arrays*/ + public static final byte[] MAILBOX_NAME = Bytes.toBytes("name"); + public static final byte[] MAILBOX_USER = Bytes.toBytes("user"); + public static final byte[] MAILBOX_NAMESPACE = Bytes.toBytes("namespace"); + public static final byte[] MAILBOX_LASTUID = Bytes.toBytes("lastUID"); + public static final byte[] MAILBOX_UIDVALIDITY = Bytes.toBytes("uidValidity"); + public static final byte[] MAILBOX_HIGHEST_MODSEQ = Bytes.toBytes("hModSeq"); + public static final byte[] MAILBOX_MESSAGE_COUNT = Bytes.toBytes("count"); + /** The HBase table name for storing subscriptions */ + public static final String SUBSCRIPTIONS = "JAMES_SUBSCRIPTIONS"; + /** The HBase table name for storing subscriptions */ + public static final byte[] SUBSCRIPTIONS_TABLE = Bytes.toBytes(SUBSCRIPTIONS); + /** Default subscription column family */ + public static final byte[] SUBSCRIPTION_CF = Bytes.toBytes("D"); + /** The HBase table name for storing messages */ + public static final String MESSAGES = "JAMES_MESSAGES"; + /** The HBase table name for storing messages */ + public static final byte[] MESSAGES_TABLE = Bytes.toBytes(MESSAGES); + /** Column family for storing message meta information*/ + public static final byte[] MESSAGES_META_CF = Bytes.toBytes("M"); + /** Column family for storing message headers*/ + public static final byte[] MESSAGE_DATA_HEADERS_CF = Bytes.toBytes("H"); + /** Column family for storing message body*/ + public static final byte[] MESSAGE_DATA_BODY_CF = Bytes.toBytes("B"); + public static final String PREFIX_META = "m:"; + public static final byte[] PREFIX_META_B = Bytes.toBytes(PREFIX_META); + /** kept sorted */ + public static final byte[] MESSAGE_BODY_OCTETS = Bytes.toBytes(PREFIX_META + "body"); + public static final byte[] MESSAGE_CONTENT_OCTETS = Bytes.toBytes(PREFIX_META + "content"); + public static final byte[] MESSAGE_INTERNALDATE = Bytes.toBytes(PREFIX_META + "date"); + public static final byte[] MESSAGE_TEXT_LINE_COUNT = Bytes.toBytes(PREFIX_META + "lcount"); + public static final byte[] MESSAGE_MODSEQ = Bytes.toBytes(PREFIX_META + "mseq"); + public static final byte[] MESSAGE_MEDIA_TYPE = Bytes.toBytes(PREFIX_META + "mtype"); + public static final byte[] MESSAGE_SUB_TYPE = Bytes.toBytes(PREFIX_META + "stype"); + public static final byte[] MARKER_PRESENT = Bytes.toBytes("X"); + public static final byte[] MARKER_MISSING = Bytes.toBytes(" "); + // the maximum recomended HBase column size is 10 MB + public static final int MAX_COLUMN_SIZE = 1024; //2 * 1024 * 1024; +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNonTransactionalMapper.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNonTransactionalMapper.java.svn-base new file mode 100644 index 0000000..93c4e77 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseNonTransactionalMapper.java.svn-base @@ -0,0 +1,39 @@ +/**************************************************************** + * 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.transaction.NonTransactionalMapper; + +/** + * HBase implementation of TransactionMapper. + * I don't know if this class is thread-safe! + * Assume it is not! + * + */ +public class HBaseNonTransactionalMapper extends NonTransactionalMapper { + + /** + * End request + */ + @Override + public void endRequest() { + //TODO: maybe do some thing more wise here? + //System.out.println("Bye!"); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtils.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtils.java.svn-base new file mode 100644 index 0000000..3e1908c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtils.java.svn-base @@ -0,0 +1,434 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.NavigableMap; +import java.util.UUID; +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.hbase.io.ChunkInputStream; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.hbase.mail.HBaseMessage; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.user.model.Subscription; + +import static org.apache.james.mailbox.hbase.FlagConvertor.*; +import static org.apache.james.mailbox.hbase.PropertyConvertor.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; + +/** + * HBase utility classes for mailbox and message manipulation. + * @author ieugen + */ +public class HBaseUtils { + // TODO: swith to a bit wise implementation of flags. + + /** + * Creates a Mailbox object from a HBase Result object. + * @param result a result of a HBase Get operation + * @return a Mailbox object + */ + public static Mailbox mailboxFromResult(Result result) { + NavigableMap rawMailbox = result.getFamilyMap(MAILBOX_CF); + //TODO: should we test for null values? + MailboxPath path = new MailboxPath(Bytes.toString(rawMailbox.get(MAILBOX_NAMESPACE)), + Bytes.toString(rawMailbox.get(MAILBOX_USER)), + Bytes.toString(rawMailbox.get(MAILBOX_NAME))); + + HBaseMailbox mailbox = new HBaseMailbox(path, Bytes.toLong(rawMailbox.get(MAILBOX_UIDVALIDITY))); + mailbox.setMailboxId(UUIDFromRowKey(result.getRow())); + mailbox.setHighestModSeq(Bytes.toLong(rawMailbox.get(MAILBOX_HIGHEST_MODSEQ))); + mailbox.setLastUid(Bytes.toLong(rawMailbox.get(MAILBOX_LASTUID))); + mailbox.setMessageCount(Bytes.toLong(rawMailbox.get(MAILBOX_MESSAGE_COUNT))); + return mailbox; + } + + /** + * This returns the row key needed for HBase. Having the method here ensure + * we have a consistent way to generate the rowkey. + * + * Convenience method for generating a rowKey when you don't have a mailbox object. + * @param uuid + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] mailboxRowKey(UUID uuid) { + byte[] rowKey = new byte[16]; + int offset = Bytes.putLong(rowKey, 0, uuid.getMostSignificantBits()); + Bytes.putLong(rowKey, offset, uuid.getLeastSignificantBits()); + return rowKey; + } + + /** + * Returns a UUID from the a byte array. + * @param rowkey + * @return UUID calculated from the byte array + */ + public static UUID UUIDFromRowKey(byte[] rowkey) { + return new UUID(Bytes.toLong(rowkey, 0), Bytes.toLong(rowkey, 8)); + } + + /** + * Transforms the mailbox into a Put operation. + * @return a Put object + */ + public static Put toPut(HBaseMailbox mailbox) { + Put put = new Put(mailboxRowKey(mailbox.getMailboxId())); + // we don't store null values and we don't restore them. it's a column based store. + if (mailbox.getName() != null) { + put.add(MAILBOX_CF, MAILBOX_NAME, Bytes.toBytes(mailbox.getName())); + } + + if (mailbox.getUser() != null) { + put.add(MAILBOX_CF, MAILBOX_USER, Bytes.toBytes(mailbox.getUser())); + } + if (mailbox.getNamespace() != null) { + put.add(MAILBOX_CF, MAILBOX_NAMESPACE, Bytes.toBytes(mailbox.getNamespace())); + } + put.add(MAILBOX_CF, MAILBOX_LASTUID, Bytes.toBytes(mailbox.getLastUid())); + put.add(MAILBOX_CF, MAILBOX_UIDVALIDITY, Bytes.toBytes(mailbox.getUidValidity())); + put.add(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, Bytes.toBytes(mailbox.getHighestModSeq())); + put.add(MAILBOX_CF, MAILBOX_MESSAGE_COUNT, Bytes.toBytes(mailbox.getMessageCount())); + return put; + } + + /** + * Transforms only the metadata into a Put object. The rest of the message will + * be transfered using multiple Puts if size requires it. + * @param message + * @return a put that contains all metadata information. + */ + public static Put metadataToPut(Message message) { + Put put = new Put(messageRowKey(message)); + // we store the message uid and mailbox uid in the row key + // store the meta data + put.add(MESSAGES_META_CF, MESSAGE_MODSEQ, Bytes.toBytes(message.getModSeq())); + put.add(MESSAGES_META_CF, MESSAGE_INTERNALDATE, Bytes.toBytes(message.getInternalDate().getTime())); + put.add(MESSAGES_META_CF, MESSAGE_MEDIA_TYPE, Bytes.toBytes(message.getMediaType())); + put.add(MESSAGES_META_CF, MESSAGE_SUB_TYPE, Bytes.toBytes(message.getSubType())); + put.add(MESSAGES_META_CF, MESSAGE_CONTENT_OCTETS, Bytes.toBytes(message.getFullContentOctets())); + put.add(MESSAGES_META_CF, MESSAGE_BODY_OCTETS, Bytes.toBytes(message.getBodyOctets())); + if (message.getTextualLineCount() != null) { + put.add(MESSAGES_META_CF, MESSAGE_TEXT_LINE_COUNT, Bytes.toBytes(message.getTextualLineCount())); + } + // store system flags in meta and user flags in uflags to avoid name clashes + Flags flags = message.createFlags(); + // system flags + if (flags.contains(Flag.ANSWERED)) { + put.add(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_PRESENT); + } + if (flags.contains(Flag.DELETED)) { + put.add(MESSAGES_META_CF, FLAGS_DELETED, MARKER_PRESENT); + } + if (flags.contains(Flag.DRAFT)) { + put.add(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_PRESENT); + } + if (flags.contains(Flag.FLAGGED)) { + put.add(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_PRESENT); + } + if (flags.contains(Flag.RECENT)) { + put.add(MESSAGES_META_CF, FLAGS_RECENT, MARKER_PRESENT); + } + if (flags.contains(Flag.SEEN)) { + put.add(MESSAGES_META_CF, FLAGS_SEEN, MARKER_PRESENT); + } + if (flags.contains(Flag.USER)) { + put.add(MESSAGES_META_CF, FLAGS_USER, MARKER_PRESENT); + } + + // user flags + for (String flag : flags.getUserFlags()) { + put.add(MESSAGES_META_CF, userFlagToBytes(flag), MARKER_PRESENT); + } + int propNumber = 0; + // add the properties + for (Property prop : message.getProperties()) { + put.add(MESSAGES_META_CF, getQualifier(propNumber++), getValue(prop)); + } + + return put; + } + + /** + * Create a row key for a message in a mailbox. The current row key is mailboxID followed by messageID. + * Both values are fixed length so no separator is needed. + * Downside: we will be storing the same message multiple times, one time for each recipient. + * @param message message to get row key from + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] messageRowKey(Message message) { + return messageRowKey(message.getMailboxId(), message.getUid()); + } + + /** + * Utility method to build row keys from mailbox UUID and message uid. + * The message uid's are stored in reverse order by substracting the uid value + * from Long.MAX_VALUE. + * @param mailboxUid mailbox UUID + * @param uid message uid + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] messageRowKey(UUID mailboxUid, long uid) { + /** message uid's are stored in reverse order so we will always have the most recent messages first*/ + byte[] ba = Bytes.add(Bytes.toBytes(mailboxUid.getMostSignificantBits()), + Bytes.toBytes(mailboxUid.getLeastSignificantBits()), + Bytes.toBytes(Long.MAX_VALUE - uid)); + //System.out.println(Bytes.toStringBinary(ba)); + return ba; + } + + /** + * Utility to build row keys from mailboxUID and a value. The value is added to + * the key without any other operations. + * @param mailboxUid mailbox UUID + * @param value + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] customMessageRowKey(UUID mailboxUid, long value) { + return Bytes.add(Bytes.toBytes(mailboxUid.getMostSignificantBits()), + Bytes.toBytes(mailboxUid.getLeastSignificantBits()), + Bytes.toBytes(value)); + } + + /** + * Creates a HBaseMessage from a Result object. This method retrieves all information + * except for body and header related bytes. The message content will be loaded on demand + * through a specialised InputStream called {@link ChunkInputStream}. + * IMPORTANT: the method expects a single version of each cell. Use setMaxVersions(1). + * @param conf configuration object for HBase cluster + * @param result the result object containing message data + * @return a HBaseMessage instance with message metadata. + */ + public static Message messageMetaFromResult(Configuration conf, Result result) { + HBaseMessage message = null; + Flags flags = new Flags(); + List propList = new ArrayList(); + KeyValue[] keys = result.raw(); + String mediaType = null, subType = null; + Long modSeq = null, uid, bodyOctets = null, contentOctets = null, textualLineCount = null; + Date internalDate = null; + + int i = 0; + /** it is VERY IMPORTANT that the byte arrays are kept ascending */ + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_BODY_OCTETS)) { + bodyOctets = Bytes.toLong(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_CONTENT_OCTETS)) { + contentOctets = Bytes.toLong(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_INTERNALDATE)) { + internalDate = new Date(Bytes.toLong(keys[i].getValue())); + i++; + } + // may be null so it will probably skip + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_TEXT_LINE_COUNT)) { + textualLineCount = Bytes.toLong(keys[i].getValue()); + i++; + } + + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_MODSEQ)) { + modSeq = Bytes.toLong(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_MEDIA_TYPE)) { + mediaType = Bytes.toString(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_SUB_TYPE)) { + subType = Bytes.toString(keys[i].getValue()); + i++; + } + // only TEXT_LINE_COUNT can be missing if message is binary + if (i < 5) { + throw new RuntimeException("HBase message column names not sorted."); + } + while (i < keys.length) { + //get message properties + if (Bytes.startsWith(keys[i].getQualifier(), PREFIX_PROP_B)) { + propList.add(getProperty(keys[i].getValue())); + } else if (Bytes.startsWith(keys[i].getQualifier(), PREFIX_SFLAGS_B)) { + // get system flags, stored as qualifiers + if (Bytes.equals(MARKER_PRESENT, keys[i].getValue())) { + flags.add(systemFlagFromBytes(keys[i].getQualifier())); + } + } else if (Bytes.startsWith(keys[i].getQualifier(), PREFIX_UFLAGS_B)) { + // get user flags, stored as value qualifier + flags.add(userFlagFromBytes(keys[i].getQualifier())); + } + i++; + } + UUID uuid = UUIDFromRowKey(result.getRow()); + uid = Long.MAX_VALUE - Bytes.toLong(result.getRow(), 16); + PropertyBuilder props = new PropertyBuilder(propList); + props.setMediaType(mediaType); + props.setSubType(subType); + message = new HBaseMessage(conf, uuid, internalDate, flags, contentOctets, (int) (contentOctets - bodyOctets), props); + message.setUid(uid); + message.setModSeq(modSeq); + message.setTextualLineCount(textualLineCount); + return message; + } + + /** + * Creates a Put object from this subscription object + * @return Put object suitable for HBase persistence + */ + public static Put toPut(Subscription subscription) { + Put put = new Put(Bytes.toBytes(subscription.getUser())); + put.add(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()), MARKER_PRESENT); + return put; + } + + /** + * Utility method to transform message flags into a put opperation. + * @param message + * @param flags + * @return a put object with + */ + public static Put flagsToPut(Message message, Flags flags) { + Put put = new Put(messageRowKey(message)); + //system flags + if (flags.contains(Flag.ANSWERED)) { + put.add(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_MISSING); + } + if (flags.contains(Flag.DELETED)) { + put.add(MESSAGES_META_CF, FLAGS_DELETED, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_DELETED, MARKER_MISSING); + } + if (flags.contains(Flag.DRAFT)) { + put.add(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_MISSING); + } + if (flags.contains(Flag.FLAGGED)) { + put.add(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_MISSING); + } + if (flags.contains(Flag.RECENT)) { + put.add(MESSAGES_META_CF, FLAGS_RECENT, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_RECENT, MARKER_MISSING); + } + if (flags.contains(Flag.SEEN)) { + put.add(MESSAGES_META_CF, FLAGS_SEEN, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_SEEN, MARKER_MISSING); + } + if (flags.contains(Flag.USER)) { + put.add(MESSAGES_META_CF, FLAGS_USER, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_USER, MARKER_MISSING); + } + /**TODO: user flags are not deleted this way: store them all in a single column + * and replace that column full. + */ + // user flags + for (String flag : flags.getUserFlags()) { + put.add(MESSAGES_META_CF, userFlagToBytes(flag), MARKER_PRESENT); + } + return put; + } + + public static Delete flagsToDelete(Message message, Flags flags) { + Delete delete = new Delete(messageRowKey(message)); + //we mark for delete flags that are not present (they will be Put'ed) + if (flags.contains(Flag.ANSWERED)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_ANSWERED); + } + if (flags.contains(Flag.DELETED)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_DELETED); + } + if (flags.contains(Flag.DRAFT)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_DRAFT); + } + if (flags.contains(Flag.FLAGGED)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_FLAGGED); + } + if (flags.contains(Flag.RECENT)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_RECENT); + } + if (flags.contains(Flag.SEEN)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_SEEN); + } + if (flags.contains(Flag.USER)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_USER); + } + + // we delete all user flags that where not in the new configuration + for (String flag : flags.getUserFlags()) { + delete.deleteColumn(MESSAGES_META_CF, userFlagToBytes(flag)); + } + return delete; + } + + /** + * Returns a String composed of all flags in the parameter. + * @param flags + * @return a string representation of all flags + */ + public static String flagsToString(Flags flags) { + StringBuilder b = new StringBuilder(); + + if (flags.contains(Flag.ANSWERED)) { + b.append("ANSWERED "); + } + if (flags.contains(Flag.DELETED)) { + b.append("DELETED "); + } + if (flags.contains(Flag.DRAFT)) { + b.append("DRAFT "); + } + if (flags.contains(Flag.FLAGGED)) { + b.append("FLAGGED "); + } + if (flags.contains(Flag.RECENT)) { + b.append("RECENT "); + } + if (flags.contains(Flag.SEEN)) { + b.append("SEEN "); + } + if (flags.contains(Flag.USER)) { + b.append("USER "); + } + for (String flag : flags.getUserFlags()) { + b.append(flag); + b.append(" "); + } + return b.toString(); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/PropertyConvertor.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/PropertyConvertor.java.svn-base new file mode 100644 index 0000000..8a7d039 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/.svn/text-base/PropertyConvertor.java.svn-base @@ -0,0 +1,74 @@ +/**************************************************************** + * 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.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.SimpleProperty; + +/** + * Class used for converting message properties to and from byte arrays for use as + * HBase column qualifiers and values. + */ +public class PropertyConvertor { + + public static final String PREFIX_PROP = "p:"; + public static final byte[] PREFIX_PROP_B = Bytes.toBytes(PREFIX_PROP); + //TODO: find a better separator. + /** The separator must not be part of the property namespace or localName */ + private static final String SEPARATOR = "%%"; + + /** + * Returns a byte array that represents a HBase column qualifier for the + * provided property. + * @param propNumber the property for storage n HBase + * @return a byte array that represents a column qualifier for the property + */ + public static byte[] getQualifier(int propNumber) { + // allow for about 1000 properties to be stored, we pad them because HBase will store them sorted + return Bytes.toBytes(PREFIX_PROP + String.format("%03d", propNumber)); + } + + /** + * Returns a byte array representation of the Property value. + * (uses Bytes.toBytes) + * @param prop + * @return a byte array of the value. + */ + public static byte[] getValue(Property prop) { + return Bytes.toBytes(prop.getNamespace() + SEPARATOR + prop.getLocalName() + SEPARATOR + prop.getValue()); + } + + /** + * Returns a Property from a qualifier byte array. + * @param value + * @return a {@link Property} + * @throws RuntimeException if property prefix or separator is not present + */ + public static Property getProperty(byte[] value) { + String ns = Bytes.toString(value); + //TODO: we assume the SEPARATOR=%% can not appear in a normal property. This may not be true. + String[] parts = ns.split(SEPARATOR); + if (parts.length != 3) { + throw new RuntimeException("Separator not found in qualifier " + + Bytes.toString(value)); + } + return new SimpleProperty(parts[0], parts[1], parts[2]); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/FlagConvertor.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/FlagConvertor.java new file mode 100644 index 0000000..0e6611b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/FlagConvertor.java @@ -0,0 +1,133 @@ +/**************************************************************** + * 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 javax.mail.Flags; +import javax.mail.Flags.Flag; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Class used for converting message flags to and from byte arrays for use as + * HBase column qualifiers. + */ +public class FlagConvertor { + + public static final String PREFIX_SFLAGS = "sf:"; + public static final byte[] PREFIX_SFLAGS_B = Bytes.toBytes(PREFIX_SFLAGS); + public static final String PREFIX_UFLAGS = "uf:"; + public static final byte[] PREFIX_UFLAGS_B = Bytes.toBytes(PREFIX_UFLAGS); + /*TODO: find a way to store all flags as a single byte (HBase bitewise operations). + * this will be efficient also because we will not store the column names. + */ + public static final byte[] FLAGS_ANSWERED = Bytes.toBytes("sf:A"); + public static final byte[] FLAGS_DELETED = Bytes.toBytes("sf:DE"); + public static final byte[] FLAGS_DRAFT = Bytes.toBytes("sf:DR"); + public static final byte[] FLAGS_FLAGGED = Bytes.toBytes("sf:F"); + public static final byte[] FLAGS_RECENT = Bytes.toBytes("sf:R"); + public static final byte[] FLAGS_SEEN = Bytes.toBytes("sf:S"); + public static final byte[] FLAGS_USER = Bytes.toBytes("sf:U"); + + /** + * Converts a {@link javax.mail.Flags.Flag} to a byte array representation + * used for storing in HBase (as a column qualifier). + * @param flag + * @return a byte representation of the flag. + * + * Throws RuntimeException if Flag is not recognized. + */ + public static byte[] systemFlagToBytes(Flags.Flag flag) { + if (flag.equals(Flag.ANSWERED)) { + return FLAGS_ANSWERED; + } + if (flag.equals(Flag.DELETED)) { + return FLAGS_DELETED; + } + if (flag.equals(Flag.DRAFT)) { + return FLAGS_DRAFT; + } + if (flag.equals(Flag.FLAGGED)) { + return FLAGS_FLAGGED; + } + if (flag.equals(Flag.RECENT)) { + return FLAGS_RECENT; + } + if (flag.equals(Flag.SEEN)) { + return FLAGS_SEEN; + } + if (flag.equals(Flag.USER)) { + return FLAGS_USER; + } + throw new RuntimeException("Invalid Flag supplied"); + } + + /** + * Returns a {@link javax.mail.Flags.Flag} coresponding to the supplyed + * byte array. + * @param bytes byte array representation + * @return one of {@link javax.mail.Flags.Flag} + * @throws RuntimeException if the byte array does not match a + * suitable represetnation. + */ + public static Flag systemFlagFromBytes(byte[] bytes) { + if (Bytes.equals(bytes, FLAGS_ANSWERED)) { + return Flag.ANSWERED; + } + if (Bytes.equals(bytes, FLAGS_DELETED)) { + return Flag.DELETED; + } + if (Bytes.equals(bytes, FLAGS_DRAFT)) { + return Flag.DRAFT; + } + if (Bytes.equals(bytes, FLAGS_FLAGGED)) { + return Flag.FLAGGED; + } + if (Bytes.equals(bytes, FLAGS_RECENT)) { + return Flag.RECENT; + } + if (Bytes.equals(bytes, FLAGS_SEEN)) { + return Flag.SEEN; + } + if (Bytes.equals(bytes, FLAGS_USER)) { + return Flag.USER; + } + throw new RuntimeException("This is not a recognized system flag: " + Bytes.toString(bytes)); + } + + /** + * Converts a user flag to a byte array for use as a HBase column qualifier. + * @param flag user flag to convert + * @return a byte array representation of the user flag + */ + public static byte[] userFlagToBytes(String flag) { + return Bytes.toBytes(PREFIX_UFLAGS + flag); + } + + /** + * Converts a byte array to a user flag. + * @param bytes the user flag byte representation + * @return a {@link String} representaion of the user flag + * @throws RuntimeException if the user flag prefix is not found. + */ + public static String userFlagFromBytes(byte[] bytes) { + if (Bytes.startsWith(bytes, PREFIX_UFLAGS_B)) { + return Bytes.toString(bytes, PREFIX_UFLAGS_B.length, bytes.length - PREFIX_UFLAGS_B.length); + } + throw new RuntimeException("This is not a user flag representation: " + Bytes.toString(bytes)); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxManager.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxManager.java new file mode 100644 index 0000000..3850d48 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxManager.java @@ -0,0 +1,88 @@ +/**************************************************************** + * 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 java.util.UUID; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.hbase.mail.HBaseMailboxMapper; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * HBase implementation of {@link StoreMailboxManager} + * + */ +public class HBaseMailboxManager extends StoreMailboxManager { + + public HBaseMailboxManager(HBaseMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxPathLocker locker, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + } + + public HBaseMailboxManager(HBaseMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + } + + @Override + protected Mailbox doCreateMailbox(MailboxPath mailboxPath, MailboxSession session) throws MailboxException { + return new HBaseMailbox(mailboxPath, randomUidValidity()); + } + + /** + * Delete all mailboxes + * + * @param mailboxSession + * @throws MailboxException + */ + public void deleteEverything(MailboxSession mailboxSession) throws MailboxException { + + final HBaseMailboxMapper mapper = (HBaseMailboxMapper) getMapperFactory().getMailboxMapper(mailboxSession); + + mapper.execute(new TransactionalMapper.VoidTransaction() { + + @Override + public void runVoid() throws MailboxException { + mapper.deleteAllMemberships(); + } + }); + mapper.execute(new TransactionalMapper.VoidTransaction() { + + @Override + public void runVoid() throws MailboxException { + mapper.deleteAllMailboxes(); + } + }); + } + + @Override + protected StoreMessageManager createMessageManager(Mailbox mailboxRow, MailboxSession session) throws MailboxException { + StoreMessageManager result = new HBaseMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), mailboxRow, getAclResolver(), getGroupMembershipResolver()); + return result; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactory.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactory.java new file mode 100644 index 0000000..8640abc --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactory.java @@ -0,0 +1,154 @@ +/**************************************************************** + * 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 java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MasterNotRunningException; +import org.apache.hadoop.hbase.ZooKeeperConnectionException; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.james.mailbox.MailboxSession; +import static org.apache.james.mailbox.hbase.HBaseNames.*; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.hbase.mail.HBaseMailboxMapper; +import org.apache.james.mailbox.hbase.mail.HBaseMessageMapper; +import org.apache.james.mailbox.hbase.user.HBaseSubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * HBase implementation of {@link MailboxSessionMapperFactory} + * + */ +public class HBaseMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private final Configuration conf; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + /** + * Creates the necessary tables in HBase if they do not exist. + * + * @param conf Configuration for the cluster + * @param uidProvider UID provider for mailbox uid. + * @param modSeqProvider + * @throws MasterNotRunningException + * @throws ZooKeeperConnectionException + * @throws IOException + */ + public HBaseMailboxSessionMapperFactory(Configuration conf, UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this.conf = conf; + this.uidProvider = uidProvider; + this.modSeqProvider = modSeqProvider; + + //TODO: add better exception handling for this + try { + HBaseAdmin hbaseAdmin = new HBaseAdmin(conf); + HTableDescriptor desc = null; + HColumnDescriptor hColumnDescriptor = null; + + /* create the tables if it does not exist */ + + if (!hbaseAdmin.tableExists(MAILBOXES_TABLE)) { + desc = new HTableDescriptor(MAILBOXES_TABLE); + hColumnDescriptor = new HColumnDescriptor(MAILBOX_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hbaseAdmin.createTable(desc); + } + + if (!hbaseAdmin.tableExists(MESSAGES_TABLE)) { + /**TODO: try to reduce the number of column families as suggested by: + * http://hbase.apache.org/book.html#number.of.cfs + * Down to three column families, striking for just two. + */ + desc = new HTableDescriptor(MESSAGES_TABLE); + hColumnDescriptor = new HColumnDescriptor(MESSAGES_META_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hColumnDescriptor = new HColumnDescriptor(MESSAGE_DATA_HEADERS_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hColumnDescriptor = new HColumnDescriptor(MESSAGE_DATA_BODY_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hbaseAdmin.createTable(desc); + } + + + if (!hbaseAdmin.tableExists(SUBSCRIPTIONS_TABLE)) { + desc = new HTableDescriptor(SUBSCRIPTIONS_TABLE); + hColumnDescriptor = new HColumnDescriptor(SUBSCRIPTION_CF); + hColumnDescriptor.setMaxVersions(1); + desc.addFamily(hColumnDescriptor); + hbaseAdmin.createTable(desc); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) throws MailboxException { + return new HBaseMessageMapper(session, uidProvider, modSeqProvider, this.conf); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException { + return new HBaseMailboxMapper(this.conf); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException { + return new HBaseSubscriptionMapper(this.conf); + } + + /** + * Returns the configuration object for accessing the cluster. + * @return The configuration for accessing the cluster + */ + public Configuration getClusterConfiguration() { + return conf; + } + + /** + * Returns the ModSeqProvider used. + * @return The used modSeqProvider + */ + public ModSeqProvider getModSeqProvider() { + return modSeqProvider; + } + + /** + * Returns the UidProvider that generates UID's for mailboxes. + * @return The provider that generates UID's for mailboxes + */ + public UidProvider getUidProvider() { + return uidProvider; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java new file mode 100644 index 0000000..fb57a49 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseMessageManager.java @@ -0,0 +1,57 @@ +/**************************************************************** + * 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 java.util.UUID; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.search.MessageSearchIndex; + +/** + * HBase implementation of MessageManager. + * + */ +public class HBaseMessageManager extends StoreMessageManager { + + public HBaseMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + MailboxEventDispatcher dispatcher, MailboxPathLocker locker, Mailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + + } + + /** + * Support user flags + */ + @Override + protected Flags getPermanentFlags(MailboxSession session) { + Flags flags = super.getPermanentFlags(session); + flags.add(Flags.Flag.USER); + return flags; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNames.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNames.java new file mode 100644 index 0000000..6b2eb4a --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNames.java @@ -0,0 +1,84 @@ +/**************************************************************** + * 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.hadoop.hbase.util.Bytes; + +/** + * Contains table names, column family names, qualifier names and other constants + * for use in HBase. + * + * Each qualifier in the META column will begin with a short prefix that will + * determine it's purpose.
+ * Qualifier prefix meaning:
+ *
    + *
  • m: meta information;
  • + *
  • sf: system flag (DELETE, RECENT, etc.)
  • + *
  • uf: user flag
  • + *
  • p: user property
  • + *
+ */ +public interface HBaseNames { + + /** + * The HBase table name for storing mailbox names + */ + public static final String MAILBOXES = "JAMES_MAILBOXES"; + public static final byte[] MAILBOXES_TABLE = Bytes.toBytes(MAILBOXES); + /** Default mailbox column family */ + public static final byte[] MAILBOX_CF = Bytes.toBytes("D"); + /** HBase column qualifiers: field names stored as byte arrays*/ + public static final byte[] MAILBOX_NAME = Bytes.toBytes("name"); + public static final byte[] MAILBOX_USER = Bytes.toBytes("user"); + public static final byte[] MAILBOX_NAMESPACE = Bytes.toBytes("namespace"); + public static final byte[] MAILBOX_LASTUID = Bytes.toBytes("lastUID"); + public static final byte[] MAILBOX_UIDVALIDITY = Bytes.toBytes("uidValidity"); + public static final byte[] MAILBOX_HIGHEST_MODSEQ = Bytes.toBytes("hModSeq"); + public static final byte[] MAILBOX_MESSAGE_COUNT = Bytes.toBytes("count"); + /** The HBase table name for storing subscriptions */ + public static final String SUBSCRIPTIONS = "JAMES_SUBSCRIPTIONS"; + /** The HBase table name for storing subscriptions */ + public static final byte[] SUBSCRIPTIONS_TABLE = Bytes.toBytes(SUBSCRIPTIONS); + /** Default subscription column family */ + public static final byte[] SUBSCRIPTION_CF = Bytes.toBytes("D"); + /** The HBase table name for storing messages */ + public static final String MESSAGES = "JAMES_MESSAGES"; + /** The HBase table name for storing messages */ + public static final byte[] MESSAGES_TABLE = Bytes.toBytes(MESSAGES); + /** Column family for storing message meta information*/ + public static final byte[] MESSAGES_META_CF = Bytes.toBytes("M"); + /** Column family for storing message headers*/ + public static final byte[] MESSAGE_DATA_HEADERS_CF = Bytes.toBytes("H"); + /** Column family for storing message body*/ + public static final byte[] MESSAGE_DATA_BODY_CF = Bytes.toBytes("B"); + public static final String PREFIX_META = "m:"; + public static final byte[] PREFIX_META_B = Bytes.toBytes(PREFIX_META); + /** kept sorted */ + public static final byte[] MESSAGE_BODY_OCTETS = Bytes.toBytes(PREFIX_META + "body"); + public static final byte[] MESSAGE_CONTENT_OCTETS = Bytes.toBytes(PREFIX_META + "content"); + public static final byte[] MESSAGE_INTERNALDATE = Bytes.toBytes(PREFIX_META + "date"); + public static final byte[] MESSAGE_TEXT_LINE_COUNT = Bytes.toBytes(PREFIX_META + "lcount"); + public static final byte[] MESSAGE_MODSEQ = Bytes.toBytes(PREFIX_META + "mseq"); + public static final byte[] MESSAGE_MEDIA_TYPE = Bytes.toBytes(PREFIX_META + "mtype"); + public static final byte[] MESSAGE_SUB_TYPE = Bytes.toBytes(PREFIX_META + "stype"); + public static final byte[] MARKER_PRESENT = Bytes.toBytes("X"); + public static final byte[] MARKER_MISSING = Bytes.toBytes(" "); + // the maximum recomended HBase column size is 10 MB + public static final int MAX_COLUMN_SIZE = 1024; //2 * 1024 * 1024; +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNonTransactionalMapper.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNonTransactionalMapper.java new file mode 100644 index 0000000..93c4e77 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseNonTransactionalMapper.java @@ -0,0 +1,39 @@ +/**************************************************************** + * 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.transaction.NonTransactionalMapper; + +/** + * HBase implementation of TransactionMapper. + * I don't know if this class is thread-safe! + * Assume it is not! + * + */ +public class HBaseNonTransactionalMapper extends NonTransactionalMapper { + + /** + * End request + */ + @Override + public void endRequest() { + //TODO: maybe do some thing more wise here? + //System.out.println("Bye!"); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseUtils.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseUtils.java new file mode 100644 index 0000000..3e1908c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/HBaseUtils.java @@ -0,0 +1,434 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.NavigableMap; +import java.util.UUID; +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.hbase.io.ChunkInputStream; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.hbase.mail.HBaseMessage; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.user.model.Subscription; + +import static org.apache.james.mailbox.hbase.FlagConvertor.*; +import static org.apache.james.mailbox.hbase.PropertyConvertor.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; + +/** + * HBase utility classes for mailbox and message manipulation. + * @author ieugen + */ +public class HBaseUtils { + // TODO: swith to a bit wise implementation of flags. + + /** + * Creates a Mailbox object from a HBase Result object. + * @param result a result of a HBase Get operation + * @return a Mailbox object + */ + public static Mailbox mailboxFromResult(Result result) { + NavigableMap rawMailbox = result.getFamilyMap(MAILBOX_CF); + //TODO: should we test for null values? + MailboxPath path = new MailboxPath(Bytes.toString(rawMailbox.get(MAILBOX_NAMESPACE)), + Bytes.toString(rawMailbox.get(MAILBOX_USER)), + Bytes.toString(rawMailbox.get(MAILBOX_NAME))); + + HBaseMailbox mailbox = new HBaseMailbox(path, Bytes.toLong(rawMailbox.get(MAILBOX_UIDVALIDITY))); + mailbox.setMailboxId(UUIDFromRowKey(result.getRow())); + mailbox.setHighestModSeq(Bytes.toLong(rawMailbox.get(MAILBOX_HIGHEST_MODSEQ))); + mailbox.setLastUid(Bytes.toLong(rawMailbox.get(MAILBOX_LASTUID))); + mailbox.setMessageCount(Bytes.toLong(rawMailbox.get(MAILBOX_MESSAGE_COUNT))); + return mailbox; + } + + /** + * This returns the row key needed for HBase. Having the method here ensure + * we have a consistent way to generate the rowkey. + * + * Convenience method for generating a rowKey when you don't have a mailbox object. + * @param uuid + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] mailboxRowKey(UUID uuid) { + byte[] rowKey = new byte[16]; + int offset = Bytes.putLong(rowKey, 0, uuid.getMostSignificantBits()); + Bytes.putLong(rowKey, offset, uuid.getLeastSignificantBits()); + return rowKey; + } + + /** + * Returns a UUID from the a byte array. + * @param rowkey + * @return UUID calculated from the byte array + */ + public static UUID UUIDFromRowKey(byte[] rowkey) { + return new UUID(Bytes.toLong(rowkey, 0), Bytes.toLong(rowkey, 8)); + } + + /** + * Transforms the mailbox into a Put operation. + * @return a Put object + */ + public static Put toPut(HBaseMailbox mailbox) { + Put put = new Put(mailboxRowKey(mailbox.getMailboxId())); + // we don't store null values and we don't restore them. it's a column based store. + if (mailbox.getName() != null) { + put.add(MAILBOX_CF, MAILBOX_NAME, Bytes.toBytes(mailbox.getName())); + } + + if (mailbox.getUser() != null) { + put.add(MAILBOX_CF, MAILBOX_USER, Bytes.toBytes(mailbox.getUser())); + } + if (mailbox.getNamespace() != null) { + put.add(MAILBOX_CF, MAILBOX_NAMESPACE, Bytes.toBytes(mailbox.getNamespace())); + } + put.add(MAILBOX_CF, MAILBOX_LASTUID, Bytes.toBytes(mailbox.getLastUid())); + put.add(MAILBOX_CF, MAILBOX_UIDVALIDITY, Bytes.toBytes(mailbox.getUidValidity())); + put.add(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, Bytes.toBytes(mailbox.getHighestModSeq())); + put.add(MAILBOX_CF, MAILBOX_MESSAGE_COUNT, Bytes.toBytes(mailbox.getMessageCount())); + return put; + } + + /** + * Transforms only the metadata into a Put object. The rest of the message will + * be transfered using multiple Puts if size requires it. + * @param message + * @return a put that contains all metadata information. + */ + public static Put metadataToPut(Message message) { + Put put = new Put(messageRowKey(message)); + // we store the message uid and mailbox uid in the row key + // store the meta data + put.add(MESSAGES_META_CF, MESSAGE_MODSEQ, Bytes.toBytes(message.getModSeq())); + put.add(MESSAGES_META_CF, MESSAGE_INTERNALDATE, Bytes.toBytes(message.getInternalDate().getTime())); + put.add(MESSAGES_META_CF, MESSAGE_MEDIA_TYPE, Bytes.toBytes(message.getMediaType())); + put.add(MESSAGES_META_CF, MESSAGE_SUB_TYPE, Bytes.toBytes(message.getSubType())); + put.add(MESSAGES_META_CF, MESSAGE_CONTENT_OCTETS, Bytes.toBytes(message.getFullContentOctets())); + put.add(MESSAGES_META_CF, MESSAGE_BODY_OCTETS, Bytes.toBytes(message.getBodyOctets())); + if (message.getTextualLineCount() != null) { + put.add(MESSAGES_META_CF, MESSAGE_TEXT_LINE_COUNT, Bytes.toBytes(message.getTextualLineCount())); + } + // store system flags in meta and user flags in uflags to avoid name clashes + Flags flags = message.createFlags(); + // system flags + if (flags.contains(Flag.ANSWERED)) { + put.add(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_PRESENT); + } + if (flags.contains(Flag.DELETED)) { + put.add(MESSAGES_META_CF, FLAGS_DELETED, MARKER_PRESENT); + } + if (flags.contains(Flag.DRAFT)) { + put.add(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_PRESENT); + } + if (flags.contains(Flag.FLAGGED)) { + put.add(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_PRESENT); + } + if (flags.contains(Flag.RECENT)) { + put.add(MESSAGES_META_CF, FLAGS_RECENT, MARKER_PRESENT); + } + if (flags.contains(Flag.SEEN)) { + put.add(MESSAGES_META_CF, FLAGS_SEEN, MARKER_PRESENT); + } + if (flags.contains(Flag.USER)) { + put.add(MESSAGES_META_CF, FLAGS_USER, MARKER_PRESENT); + } + + // user flags + for (String flag : flags.getUserFlags()) { + put.add(MESSAGES_META_CF, userFlagToBytes(flag), MARKER_PRESENT); + } + int propNumber = 0; + // add the properties + for (Property prop : message.getProperties()) { + put.add(MESSAGES_META_CF, getQualifier(propNumber++), getValue(prop)); + } + + return put; + } + + /** + * Create a row key for a message in a mailbox. The current row key is mailboxID followed by messageID. + * Both values are fixed length so no separator is needed. + * Downside: we will be storing the same message multiple times, one time for each recipient. + * @param message message to get row key from + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] messageRowKey(Message message) { + return messageRowKey(message.getMailboxId(), message.getUid()); + } + + /** + * Utility method to build row keys from mailbox UUID and message uid. + * The message uid's are stored in reverse order by substracting the uid value + * from Long.MAX_VALUE. + * @param mailboxUid mailbox UUID + * @param uid message uid + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] messageRowKey(UUID mailboxUid, long uid) { + /** message uid's are stored in reverse order so we will always have the most recent messages first*/ + byte[] ba = Bytes.add(Bytes.toBytes(mailboxUid.getMostSignificantBits()), + Bytes.toBytes(mailboxUid.getLeastSignificantBits()), + Bytes.toBytes(Long.MAX_VALUE - uid)); + //System.out.println(Bytes.toStringBinary(ba)); + return ba; + } + + /** + * Utility to build row keys from mailboxUID and a value. The value is added to + * the key without any other operations. + * @param mailboxUid mailbox UUID + * @param value + * @return rowkey byte array that can be used with HBase API + */ + public static byte[] customMessageRowKey(UUID mailboxUid, long value) { + return Bytes.add(Bytes.toBytes(mailboxUid.getMostSignificantBits()), + Bytes.toBytes(mailboxUid.getLeastSignificantBits()), + Bytes.toBytes(value)); + } + + /** + * Creates a HBaseMessage from a Result object. This method retrieves all information + * except for body and header related bytes. The message content will be loaded on demand + * through a specialised InputStream called {@link ChunkInputStream}. + * IMPORTANT: the method expects a single version of each cell. Use setMaxVersions(1). + * @param conf configuration object for HBase cluster + * @param result the result object containing message data + * @return a HBaseMessage instance with message metadata. + */ + public static Message messageMetaFromResult(Configuration conf, Result result) { + HBaseMessage message = null; + Flags flags = new Flags(); + List propList = new ArrayList(); + KeyValue[] keys = result.raw(); + String mediaType = null, subType = null; + Long modSeq = null, uid, bodyOctets = null, contentOctets = null, textualLineCount = null; + Date internalDate = null; + + int i = 0; + /** it is VERY IMPORTANT that the byte arrays are kept ascending */ + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_BODY_OCTETS)) { + bodyOctets = Bytes.toLong(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_CONTENT_OCTETS)) { + contentOctets = Bytes.toLong(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_INTERNALDATE)) { + internalDate = new Date(Bytes.toLong(keys[i].getValue())); + i++; + } + // may be null so it will probably skip + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_TEXT_LINE_COUNT)) { + textualLineCount = Bytes.toLong(keys[i].getValue()); + i++; + } + + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_MODSEQ)) { + modSeq = Bytes.toLong(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_MEDIA_TYPE)) { + mediaType = Bytes.toString(keys[i].getValue()); + i++; + } + if (Bytes.equals(keys[i].getQualifier(), MESSAGE_SUB_TYPE)) { + subType = Bytes.toString(keys[i].getValue()); + i++; + } + // only TEXT_LINE_COUNT can be missing if message is binary + if (i < 5) { + throw new RuntimeException("HBase message column names not sorted."); + } + while (i < keys.length) { + //get message properties + if (Bytes.startsWith(keys[i].getQualifier(), PREFIX_PROP_B)) { + propList.add(getProperty(keys[i].getValue())); + } else if (Bytes.startsWith(keys[i].getQualifier(), PREFIX_SFLAGS_B)) { + // get system flags, stored as qualifiers + if (Bytes.equals(MARKER_PRESENT, keys[i].getValue())) { + flags.add(systemFlagFromBytes(keys[i].getQualifier())); + } + } else if (Bytes.startsWith(keys[i].getQualifier(), PREFIX_UFLAGS_B)) { + // get user flags, stored as value qualifier + flags.add(userFlagFromBytes(keys[i].getQualifier())); + } + i++; + } + UUID uuid = UUIDFromRowKey(result.getRow()); + uid = Long.MAX_VALUE - Bytes.toLong(result.getRow(), 16); + PropertyBuilder props = new PropertyBuilder(propList); + props.setMediaType(mediaType); + props.setSubType(subType); + message = new HBaseMessage(conf, uuid, internalDate, flags, contentOctets, (int) (contentOctets - bodyOctets), props); + message.setUid(uid); + message.setModSeq(modSeq); + message.setTextualLineCount(textualLineCount); + return message; + } + + /** + * Creates a Put object from this subscription object + * @return Put object suitable for HBase persistence + */ + public static Put toPut(Subscription subscription) { + Put put = new Put(Bytes.toBytes(subscription.getUser())); + put.add(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()), MARKER_PRESENT); + return put; + } + + /** + * Utility method to transform message flags into a put opperation. + * @param message + * @param flags + * @return a put object with + */ + public static Put flagsToPut(Message message, Flags flags) { + Put put = new Put(messageRowKey(message)); + //system flags + if (flags.contains(Flag.ANSWERED)) { + put.add(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_MISSING); + } + if (flags.contains(Flag.DELETED)) { + put.add(MESSAGES_META_CF, FLAGS_DELETED, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_DELETED, MARKER_MISSING); + } + if (flags.contains(Flag.DRAFT)) { + put.add(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_MISSING); + } + if (flags.contains(Flag.FLAGGED)) { + put.add(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_MISSING); + } + if (flags.contains(Flag.RECENT)) { + put.add(MESSAGES_META_CF, FLAGS_RECENT, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_RECENT, MARKER_MISSING); + } + if (flags.contains(Flag.SEEN)) { + put.add(MESSAGES_META_CF, FLAGS_SEEN, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_SEEN, MARKER_MISSING); + } + if (flags.contains(Flag.USER)) { + put.add(MESSAGES_META_CF, FLAGS_USER, MARKER_PRESENT); + } else { + put.add(MESSAGES_META_CF, FLAGS_USER, MARKER_MISSING); + } + /**TODO: user flags are not deleted this way: store them all in a single column + * and replace that column full. + */ + // user flags + for (String flag : flags.getUserFlags()) { + put.add(MESSAGES_META_CF, userFlagToBytes(flag), MARKER_PRESENT); + } + return put; + } + + public static Delete flagsToDelete(Message message, Flags flags) { + Delete delete = new Delete(messageRowKey(message)); + //we mark for delete flags that are not present (they will be Put'ed) + if (flags.contains(Flag.ANSWERED)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_ANSWERED); + } + if (flags.contains(Flag.DELETED)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_DELETED); + } + if (flags.contains(Flag.DRAFT)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_DRAFT); + } + if (flags.contains(Flag.FLAGGED)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_FLAGGED); + } + if (flags.contains(Flag.RECENT)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_RECENT); + } + if (flags.contains(Flag.SEEN)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_SEEN); + } + if (flags.contains(Flag.USER)) { + delete.deleteColumn(MESSAGES_META_CF, FLAGS_USER); + } + + // we delete all user flags that where not in the new configuration + for (String flag : flags.getUserFlags()) { + delete.deleteColumn(MESSAGES_META_CF, userFlagToBytes(flag)); + } + return delete; + } + + /** + * Returns a String composed of all flags in the parameter. + * @param flags + * @return a string representation of all flags + */ + public static String flagsToString(Flags flags) { + StringBuilder b = new StringBuilder(); + + if (flags.contains(Flag.ANSWERED)) { + b.append("ANSWERED "); + } + if (flags.contains(Flag.DELETED)) { + b.append("DELETED "); + } + if (flags.contains(Flag.DRAFT)) { + b.append("DRAFT "); + } + if (flags.contains(Flag.FLAGGED)) { + b.append("FLAGGED "); + } + if (flags.contains(Flag.RECENT)) { + b.append("RECENT "); + } + if (flags.contains(Flag.SEEN)) { + b.append("SEEN "); + } + if (flags.contains(Flag.USER)) { + b.append("USER "); + } + for (String flag : flags.getUserFlags()) { + b.append(flag); + b.append(" "); + } + return b.toString(); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/PropertyConvertor.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/PropertyConvertor.java new file mode 100644 index 0000000..8a7d039 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/PropertyConvertor.java @@ -0,0 +1,74 @@ +/**************************************************************** + * 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.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.SimpleProperty; + +/** + * Class used for converting message properties to and from byte arrays for use as + * HBase column qualifiers and values. + */ +public class PropertyConvertor { + + public static final String PREFIX_PROP = "p:"; + public static final byte[] PREFIX_PROP_B = Bytes.toBytes(PREFIX_PROP); + //TODO: find a better separator. + /** The separator must not be part of the property namespace or localName */ + private static final String SEPARATOR = "%%"; + + /** + * Returns a byte array that represents a HBase column qualifier for the + * provided property. + * @param propNumber the property for storage n HBase + * @return a byte array that represents a column qualifier for the property + */ + public static byte[] getQualifier(int propNumber) { + // allow for about 1000 properties to be stored, we pad them because HBase will store them sorted + return Bytes.toBytes(PREFIX_PROP + String.format("%03d", propNumber)); + } + + /** + * Returns a byte array representation of the Property value. + * (uses Bytes.toBytes) + * @param prop + * @return a byte array of the value. + */ + public static byte[] getValue(Property prop) { + return Bytes.toBytes(prop.getNamespace() + SEPARATOR + prop.getLocalName() + SEPARATOR + prop.getValue()); + } + + /** + * Returns a Property from a qualifier byte array. + * @param value + * @return a {@link Property} + * @throws RuntimeException if property prefix or separator is not present + */ + public static Property getProperty(byte[] value) { + String ns = Bytes.toString(value); + //TODO: we assume the SEPARATOR=%% can not appear in a normal property. This may not be true. + String[] parts = ns.split(SEPARATOR); + if (parts.length != 3) { + throw new RuntimeException("Separator not found in qualifier " + + Bytes.toString(value)); + } + return new SimpleProperty(parts[0], parts[1], parts[2]); + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/all-wcprops new file mode 100644 index 0000000..1b474b5 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 101 +/repos/asf/!svn/ver/1164981/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/io +END +ChunkInputStream.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1164981/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkInputStream.java +END +ChunkOutputStream.java +K 25 +svn:wc:ra_dav:version-url +V 124 +/repos/asf/!svn/ver/1164981/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkOutputStream.java +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/entries new file mode 100644 index 0000000..5778658 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/io +http://svn.apache.org/repos/asf + + + +2011-09-04T09:49:14.713666Z +1164981 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +ChunkInputStream.java +file + + + + +2013-09-02T02:54:38.000000Z +e65baeef135f5d749bc4b06726a030a8 +2011-09-04T09:49:14.713666Z +1164981 +ieugen + + + + + + + + + + + + + + + + + + + + + +3869 + +ChunkOutputStream.java +file + + + + +2013-09-02T02:54:38.000000Z +efa95537410052c755706427763c1774 +2011-09-04T09:49:14.713666Z +1164981 +ieugen + + + + + + + + + + + + + + + + + + + + + +4349 + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkInputStream.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkInputStream.java.svn-base new file mode 100644 index 0000000..53ffcf6 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkInputStream.java.svn-base @@ -0,0 +1,114 @@ +/**************************************************************** + * 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.io; + +import java.io.IOException; +import java.io.InputStream; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Result; + +/** + * Return an InputStream which retrieve columns from a row which stores chunk of + * data. See also {@link ChunkOutputStream} + * + * This implementation is not thread-safe! + * + * Bsed on Hector implementation for Cassandra. + * https://github.com/rantav/hector/blob/master/core/src/main/java/me/prettyprint/cassandra/io/ChunkInputStream.java + */ +public class ChunkInputStream extends InputStream { + + private final Configuration conf; + private final byte[] tableName; + private final byte[] cf; + private final byte[] key; + private int pos; + private long chunkPos = 1; + private byte[] chunk; + + public ChunkInputStream(Configuration conf, byte[] tableName, byte[] cf, byte[] key) { + this.key = key; + this.conf = conf; + this.tableName = tableName; + this.cf = cf; + } + + public ChunkInputStream(Configuration conf, String tableName, String cf, byte[] key) { + this(conf, Bytes.toBytes(tableName), Bytes.toBytes(cf), key); + } + + /* + * (non-Javadoc) + * + * @see java.io.InputStream#read() + */ + @Override + public int read() throws IOException { + if (chunk == null || pos + 1 == chunk.length) { + if (!fetchChunk()) { + return -1; + } + } + return chunk[pos++]; + } + + /** + * Fetch the next chunk. + * + * @return exists if there was a chunk to fetch. + * @throws IOException + */ + private boolean fetchChunk() throws IOException { + HTable messages = null; + try { + byte[] cp = Bytes.toBytes(chunkPos); + messages = new HTable(conf, tableName); + Get get = new Get(key); + get.addColumn(cf, cp); + get.setMaxVersions(1); + Result result = messages.get(get); + if (!result.isEmpty()) { + chunk = result.getValue(cf, cp); + chunkPos++; + pos = 0; + return true; + } else { + return false; + } + } catch (IOException e) { + throw new IOException("Unable to read data", e); + } finally { + if (messages != null) { + messages.close(); + + } + } + } + + /** + * Not supported + */ + @Override + public boolean markSupported() { + return false; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkOutputStream.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkOutputStream.java.svn-base new file mode 100644 index 0000000..7bd2948 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/.svn/text-base/ChunkOutputStream.java.svn-base @@ -0,0 +1,118 @@ +/**************************************************************** + * 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.io; + +import java.io.IOException; +import java.io.OutputStream; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Provide an {@link OutputStream} which will write to a row. The written data + * will be split up by chunks of the given chunkSize. Each chunk we get written + * to own column which will have the chunk number (starting at 0) as column key + * (Long). + * + * This implementation is not thread-safe! + * Based on Hector implementation for Cassandra. + * https://github.com/rantav/hector/blob/master/core/src/main/java/me/prettyprint/cassandra/io/ChunkOutputStream.java + */ +public class ChunkOutputStream extends OutputStream { + + private final Configuration conf; + private final byte[] tableName; + private final byte[] cf; + private final byte[] key; + private byte[] chunk; + private long chunkPos = 1; + private long pos = 0; + + /** + * Creates a special type of {@link OutputStream} that writes data directly to HBase. + * @param conf HBase cluster configuration + * @param tableName name of the table that writes will be made + * @param cf name of the column family where data is going to be written + * @param key the row key + * @param chunkSize the size of each column, in bytes. For HBase, max is 10MB + */ + public ChunkOutputStream(Configuration conf, byte[] tableName, byte[] cf, byte[] key, int chunkSize) { + this.conf = conf; + this.tableName = tableName; + this.cf = cf; + this.key = key; + this.chunk = new byte[chunkSize]; + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#write(int) + */ + @Override + public void write(int b) throws IOException { + if (chunk.length - 1 == pos) { + flush(); + } + chunk[(int) pos++] = (byte) b; + } + + @Override + public void close() throws IOException { + writeData(true); + } + + /** + * Trigger a flush. This will only write the content to the column if the + * chunk size is reached + */ + @Override + public void flush() throws IOException { + writeData(false); + } + + /** + * Write the data to column if the configured chunk size is reached or if the + * stream should be closed + * + * @param close + * @throws IOException + */ + private void writeData(boolean close) throws IOException { + if (pos != 0 && (close || pos == chunk.length - 1)) { + HTable messages = null; + try { + messages = new HTable(conf, tableName); + Put put = new Put(key); + put.add(cf, Bytes.toBytes(chunkPos), Bytes.head(chunk, (int) pos + 1)); + messages.put(put); + chunkPos++; + pos = 0; + + } catch (IOException e) { + throw new IOException("Unable to write data", e); + } finally { + if (messages != null) { + messages.close(); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkInputStream.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkInputStream.java new file mode 100644 index 0000000..53ffcf6 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkInputStream.java @@ -0,0 +1,114 @@ +/**************************************************************** + * 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.io; + +import java.io.IOException; +import java.io.InputStream; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Result; + +/** + * Return an InputStream which retrieve columns from a row which stores chunk of + * data. See also {@link ChunkOutputStream} + * + * This implementation is not thread-safe! + * + * Bsed on Hector implementation for Cassandra. + * https://github.com/rantav/hector/blob/master/core/src/main/java/me/prettyprint/cassandra/io/ChunkInputStream.java + */ +public class ChunkInputStream extends InputStream { + + private final Configuration conf; + private final byte[] tableName; + private final byte[] cf; + private final byte[] key; + private int pos; + private long chunkPos = 1; + private byte[] chunk; + + public ChunkInputStream(Configuration conf, byte[] tableName, byte[] cf, byte[] key) { + this.key = key; + this.conf = conf; + this.tableName = tableName; + this.cf = cf; + } + + public ChunkInputStream(Configuration conf, String tableName, String cf, byte[] key) { + this(conf, Bytes.toBytes(tableName), Bytes.toBytes(cf), key); + } + + /* + * (non-Javadoc) + * + * @see java.io.InputStream#read() + */ + @Override + public int read() throws IOException { + if (chunk == null || pos + 1 == chunk.length) { + if (!fetchChunk()) { + return -1; + } + } + return chunk[pos++]; + } + + /** + * Fetch the next chunk. + * + * @return exists if there was a chunk to fetch. + * @throws IOException + */ + private boolean fetchChunk() throws IOException { + HTable messages = null; + try { + byte[] cp = Bytes.toBytes(chunkPos); + messages = new HTable(conf, tableName); + Get get = new Get(key); + get.addColumn(cf, cp); + get.setMaxVersions(1); + Result result = messages.get(get); + if (!result.isEmpty()) { + chunk = result.getValue(cf, cp); + chunkPos++; + pos = 0; + return true; + } else { + return false; + } + } catch (IOException e) { + throw new IOException("Unable to read data", e); + } finally { + if (messages != null) { + messages.close(); + + } + } + } + + /** + * Not supported + */ + @Override + public boolean markSupported() { + return false; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkOutputStream.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkOutputStream.java new file mode 100644 index 0000000..7bd2948 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/io/ChunkOutputStream.java @@ -0,0 +1,118 @@ +/**************************************************************** + * 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.io; + +import java.io.IOException; +import java.io.OutputStream; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Provide an {@link OutputStream} which will write to a row. The written data + * will be split up by chunks of the given chunkSize. Each chunk we get written + * to own column which will have the chunk number (starting at 0) as column key + * (Long). + * + * This implementation is not thread-safe! + * Based on Hector implementation for Cassandra. + * https://github.com/rantav/hector/blob/master/core/src/main/java/me/prettyprint/cassandra/io/ChunkOutputStream.java + */ +public class ChunkOutputStream extends OutputStream { + + private final Configuration conf; + private final byte[] tableName; + private final byte[] cf; + private final byte[] key; + private byte[] chunk; + private long chunkPos = 1; + private long pos = 0; + + /** + * Creates a special type of {@link OutputStream} that writes data directly to HBase. + * @param conf HBase cluster configuration + * @param tableName name of the table that writes will be made + * @param cf name of the column family where data is going to be written + * @param key the row key + * @param chunkSize the size of each column, in bytes. For HBase, max is 10MB + */ + public ChunkOutputStream(Configuration conf, byte[] tableName, byte[] cf, byte[] key, int chunkSize) { + this.conf = conf; + this.tableName = tableName; + this.cf = cf; + this.key = key; + this.chunk = new byte[chunkSize]; + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#write(int) + */ + @Override + public void write(int b) throws IOException { + if (chunk.length - 1 == pos) { + flush(); + } + chunk[(int) pos++] = (byte) b; + } + + @Override + public void close() throws IOException { + writeData(true); + } + + /** + * Trigger a flush. This will only write the content to the column if the + * chunk size is reached + */ + @Override + public void flush() throws IOException { + writeData(false); + } + + /** + * Write the data to column if the configured chunk size is reached or if the + * stream should be closed + * + * @param close + * @throws IOException + */ + private void writeData(boolean close) throws IOException { + if (pos != 0 && (close || pos == chunk.length - 1)) { + HTable messages = null; + try { + messages = new HTable(conf, tableName); + Put put = new Put(key); + put.add(cf, Bytes.toBytes(chunkPos), Bytes.head(chunk, (int) pos + 1)); + messages.put(put); + chunkPos++; + pos = 0; + + } catch (IOException e) { + throw new IOException("Unable to write data", e); + } finally { + if (messages != null) { + messages.close(); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops new file mode 100644 index 0000000..55dfdb7 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail +END +HBaseUidProvider.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseUidProvider.java +END +HBaseMailboxMapper.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapper.java +END +HBaseModSeqProvider.java +K 25 +svn:wc:ra_dav:version-url +V 128 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseModSeqProvider.java +END +HBaseMessage.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessage.java +END +HBaseMessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/entries new file mode 100644 index 0000000..07688d0 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/entries @@ -0,0 +1,201 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +HBaseUidProvider.java +file + + + + +2013-09-02T02:54:38.000000Z +e0f3dd0cc9d06711cb9f2f5ed1dea68d +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4548 + +model +dir + +HBaseMailboxMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +c20929f26e1637613859c0886568258e +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +17001 + +HBaseModSeqProvider.java +file + + + + +2013-09-02T02:54:38.000000Z +7219444a422c4cc46e88c98596397a31 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4025 + +HBaseMessage.java +file + + + + +2013-09-02T02:54:38.000000Z +8cba3e5a9782a239bde7879b79c0c0ec +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +12375 + +HBaseMessageMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +18f238eaa118ff632a69b213f2f11b15 +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + + + + + + + + +34598 + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapper.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapper.java.svn-base new file mode 100644 index 0000000..8308f49 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapper.java.svn-base @@ -0,0 +1,395 @@ +/**************************************************************** + * 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.mail; + +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOXES_TABLE; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_CF; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_MESSAGE_COUNT; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_NAME; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_NAMESPACE; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_USER; +import static org.apache.james.mailbox.hbase.HBaseNames.MESSAGES_META_CF; +import static org.apache.james.mailbox.hbase.HBaseNames.MESSAGES_TABLE; +import static org.apache.james.mailbox.hbase.HBaseNames.MESSAGE_INTERNALDATE; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxFromResult; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxRowKey; +import static org.apache.james.mailbox.hbase.HBaseUtils.toPut; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.filter.BinaryPrefixComparator; +import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; +import org.apache.hadoop.hbase.filter.FilterList; +import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; +import org.apache.hadoop.hbase.filter.SubstringComparator; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.hbase.HBaseNonTransactionalMapper; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Data access management for mailbox. + * + */ +public class HBaseMailboxMapper extends HBaseNonTransactionalMapper implements MailboxMapper { + + /** + * Link to the HBase Configuration object and specific mailbox names + */ + private final Configuration conf; + + public HBaseMailboxMapper(Configuration conf) { + this.conf = conf; + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxPath) throws MailboxException, MailboxNotFoundException { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + + /* + * Filters is ORDERED. Passing the parameters in the right order + * might improve performance: passing the user first means that the + * other filters will not be tested if the mailbox does not belong + * to the passed user. + */ + FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL); + + if (mailboxPath.getUser() != null) { + SingleColumnValueFilter userFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_USER, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getUser())); + filters.addFilter(userFilter); + } + SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAME, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getName())); + filters.addFilter(nameFilter); + SingleColumnValueFilter namespaceFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAMESPACE, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getNamespace())); + filters.addFilter(namespaceFilter); + + scan.setFilter(filters); + scanner = mailboxes.getScanner(scan); + Result result = scanner.next(); + + if (result == null) { + throw new MailboxNotFoundException(mailboxPath); + } + return mailboxFromResult(result); + } catch (IOException e) { + throw new MailboxException("Search of mailbox " + mailboxPath + " failed", e); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public List> findMailboxWithPathLike(MailboxPath mailboxPath) throws MailboxException { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + + FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL); + + if (mailboxPath.getUser() != null) { + SingleColumnValueFilter userFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_USER, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getUser())); + filters.addFilter(userFilter); + } + SubstringComparator pathComparator; + String mboxName = mailboxPath.getName(); + /* + * TODO: use a RegExFiler + */ + if (mboxName.length() >= 1) { + if (mboxName.charAt(mboxName.length() - 1) == '%') { + mboxName = mboxName.substring(0, mboxName.length() - 1); + } + } + if (mboxName.length() >= 1) { + if (mboxName.charAt(0) == '%') { + mboxName = mboxName.substring(1); + } + } + pathComparator = new SubstringComparator(mboxName); + SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAME, CompareOp.EQUAL, pathComparator); + filters.addFilter(nameFilter); + SingleColumnValueFilter namespaceFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAMESPACE, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getNamespace())); + filters.addFilter(namespaceFilter); + + scan.setFilter(filters); + scanner = mailboxes.getScanner(scan); + + List> mailboxList = new ArrayList>(); + + for (Result result : scanner) { + mailboxList.add(mailboxFromResult(result)); + } + return mailboxList; + } catch (IOException e) { + throw new MailboxException("Search of mailbox " + mailboxPath + " failed", e); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public List> list() throws MailboxException { + HTable mailboxes = null; + ResultScanner scanner = null; + //TODO: possible performance isssues, we are creating an object from all the rows in HBase mailbox table + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + scanner = mailboxes.getScanner(scan); + List> mailboxList = new ArrayList>(); + + Result result; + while ((result = scanner.next()) != null) { + Mailbox mlbx = mailboxFromResult(result); + mailboxList.add(mlbx); + } + return mailboxList; + } catch (IOException ex) { + throw new MailboxException("HBase IOException in list()", ex); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public void endRequest() { + } + + @Override + public void save(Mailbox mlbx) throws MailboxException { + //TODO: maybe switch to checkAndPut for transactions + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + /* + * cast to HBaseMailbox to access lastuid and ModSeq + */ + Put put = toPut((HBaseMailbox) mlbx); + mailboxes.put(put); + } catch (IOException ex) { + throw new MailboxException("IOExeption", ex); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public void delete(Mailbox mlbx) throws MailboxException { + //TODO: maybe switch to checkAndDelete + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + //TODO: delete all maessages from this mailbox + Delete delete = new Delete(mailboxRowKey(mlbx.getMailboxId())); + mailboxes.delete(delete); + } catch (IOException ex) { + throw new MailboxException("IOException in HBase cluster during delete()", ex); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public boolean hasChildren(final Mailbox mailbox, final char c) throws MailboxException, MailboxNotFoundException { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + + FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL); + + if (mailbox.getUser() != null) { + SingleColumnValueFilter userFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_USER, CompareOp.EQUAL, Bytes.toBytes(mailbox.getUser())); + filters.addFilter(userFilter); + } + SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(MAILBOX_CF, + MAILBOX_NAME, + CompareOp.EQUAL, + new BinaryPrefixComparator(Bytes.toBytes(mailbox.getName() + c))); + filters.addFilter(nameFilter); + SingleColumnValueFilter namespaceFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAMESPACE, CompareOp.EQUAL, Bytes.toBytes(mailbox.getNamespace())); + filters.addFilter(namespaceFilter); + + scan.setFilter(filters); + scanner = mailboxes.getScanner(scan); + try { + if (scanner.next() != null) { + return true; + } + } catch (IOException e) { + throw new MailboxNotFoundException("hasChildren() " + mailbox.getName()); + } + return false; + } catch (IOException e) { + throw new MailboxException("Search of mailbox " + mailbox + " failed", e); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + public void deleteAllMemberships() { + HTable messages = null; + HTable mailboxes = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Scan scan = new Scan(); + scan.setMaxVersions(1); + scan.addColumn(MESSAGES_META_CF, MESSAGE_INTERNALDATE); + scanner = messages.getScanner(scan); + Result result; + List deletes = new ArrayList(); + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + messages.delete(deletes); + if (deletes.size() > 0) { + //TODO: what shoul we do if not all messages are deleted? + System.out.println("Just " + deletes.size() + " out of " + totalDeletes + " messages have been deleted"); + //throw new RuntimeException("Just " + deletes.size() + " out of " + totalDeletes + " messages have been deleted"); + } + List puts = new ArrayList(); + scan = new Scan(); + scan.setMaxVersions(1); + scan.addColumn(MAILBOX_CF, MAILBOX_MESSAGE_COUNT); + scanner = mailboxes.getScanner(scan); + Put put = null; + while ((result = scanner.next()) != null) { + put = new Put(result.getRow()); + put.add(MAILBOX_CF, MAILBOX_MESSAGE_COUNT, Bytes.toBytes(0L)); + puts.add(new Put()); + } + } catch (IOException e) { + throw new RuntimeException("Error deleting MESSAGES table ", e); + } finally { + IOUtils.closeStream(scanner); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new RuntimeException("Error closing table " + messages, ex); + } + } + } + } + + public void deleteAllMailboxes() { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Scan scan = new Scan(); + scan.setMaxVersions(1); + scan.addColumn(MAILBOX_CF, MAILBOX_NAME); + scanner = mailboxes.getScanner(scan); + Result result; + List deletes = new ArrayList(); + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + mailboxes.delete(deletes); + } catch (IOException ex) { + throw new RuntimeException("IOException deleting mailboxes", ex); + } finally { + IOUtils.closeStream(scanner); + // TODO Temporary commented, was not compiling. +// IOUtils.closeStream(mailboxes); + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessage.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessage.java.svn-base new file mode 100644 index 0000000..3df807c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessage.java.svn-base @@ -0,0 +1,397 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import javax.mail.Flags; +import org.apache.hadoop.conf.Configuration; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.hbase.io.ChunkInputStream; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; + +/** + * Concrete HBaseMessage implementation. This implementation does not store any + * message content. The message content is retrieved using a ChunkedInputStream + * directly from HBase. + */ +public class HBaseMessage extends AbstractMessage { + + private static final String TOSTRING_SEPARATOR = " "; + /** Configuration for the HBase cluster */ + private final Configuration conf; + /** The value for the mailboxId field */ + private UUID mailboxId; + /** The value for the uid field */ + private long uid; + /** The value for the modSeq field */ + private long modSeq; + /** The value for the internalDate field */ + private Date internalDate; + /** The value for the answered field */ + private boolean answered = false; + /** The value for the deleted field */ + private boolean deleted = false; + /** The value for the draft field */ + private boolean draft = false; + /** The value for the flagged field */ + private boolean flagged = false; + /** The value for the recent field */ + private boolean recent = false; + /** The value for the seen field */ + private boolean seen = false; + /** The first body octet */ + private int bodyStartOctet; + /** Number of octets in the full document content */ + private long contentOctets; + /** MIME media type */ + private String mediaType; + /** MIME sub type */ + private String subType; + /** THE CRFL count when this document is textual, null otherwise */ + private Long textualLineCount; + /** Meta data for this message */ + private List properties; + private List userFlags; + + /** + * Create a copy of the given message. + * All properties are cloned except mailbox and UID. + * @param mailboxId + * @param uid + * @param modSeq + * @param original + * @throws MailboxException + */ + public HBaseMessage(Configuration conf, UUID mailboxId, long uid, long modSeq, Message original) throws MailboxException { + super(); + this.conf = conf; + this.mailboxId = mailboxId; + this.uid = uid; + this.modSeq = modSeq; + this.userFlags = new ArrayList(); + setFlags(original.createFlags()); + + // A copy of a message is recent + // See MAILBOX-85 + this.recent = true; + + this.contentOctets = original.getFullContentOctets(); + this.bodyStartOctet = (int) (original.getFullContentOctets() - original.getBodyOctets()); + this.internalDate = original.getInternalDate(); + + this.textualLineCount = original.getTextualLineCount(); + this.mediaType = original.getMediaType(); + this.subType = original.getSubType(); + this.properties = original.getProperties(); + } + + /** + * Create a copy of the given message. + * @param mailboxId + * @param internalDate + * @param flags + * @param contentOctets + * @param bodyStartOctet + * @param propertyBuilder + */ + public HBaseMessage(Configuration conf, UUID mailboxId, Date internalDate, Flags flags, long contentOctets, int bodyStartOctet, PropertyBuilder propertyBuilder) { + super(); + this.conf = conf; + this.mailboxId = mailboxId; + this.internalDate = internalDate; + userFlags = new ArrayList(); + + setFlags(flags); + this.contentOctets = contentOctets; + this.bodyStartOctet = bodyStartOctet; + this.textualLineCount = propertyBuilder.getTextualLineCount(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + this.properties = propertyBuilder.toProperties(); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + @Override + public InputStream getBodyContent() throws IOException { + return new ChunkInputStream(conf, MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, messageRowKey(this)); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + @Override + public InputStream getHeaderContent() throws IOException { + return new ChunkInputStream(conf, MESSAGES_TABLE, MESSAGE_DATA_HEADERS_CF, messageRowKey(this)); + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (getMailboxId().getMostSignificantBits() ^ (getMailboxId().getMostSignificantBits() >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HBaseMessage other = (HBaseMessage) obj; + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) { + return false; + } + } else { + if (other.getMailboxId() != null) { + return false; + } + } + if (uid != other.uid) { + return false; + } + return true; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + @Override + public long getModSeq() { + return modSeq; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + @Override + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + @Override + public String getMediaType() { + return mediaType; + } + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + @Override + public String getSubType() { + return subType; + } + + /** + * Gets a read-only list of meta-data properties. + * For properties with multiple values, this list will contain + * several enteries with the same namespace and local name. + * @return unmodifiable list of meta-data, not null + */ + @Override + public List getProperties() { + return new ArrayList(properties); + } + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + @Override + public Long getTextualLineCount() { + return textualLineCount; + } + + public void setTextualLineCount(Long textualLineCount) { + this.textualLineCount = textualLineCount; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getFullContentOctets() + */ + @Override + public long getFullContentOctets() { + return contentOctets; + } + + @Override + protected int getBodyStartOctet() { + return bodyStartOctet; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getInternalDate() + */ + @Override + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getMailboxId() + */ + @Override + public UUID getMailboxId() { + return mailboxId; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getUid() + */ + @Override + public long getUid() { + return uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isAnswered() + */ + @Override + public boolean isAnswered() { + return answered; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDeleted() + */ + @Override + public boolean isDeleted() { + return deleted; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDraft() + */ + @Override + public boolean isDraft() { + return draft; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isFlagged() + */ + @Override + public boolean isFlagged() { + return flagged; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isRecent() + */ + @Override + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isSeen() + */ + @Override + public boolean isSeen() { + return seen; + } + + @Override + public void setUid(long uid) { + this.uid = uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setFlags(javax.mail.Flags) + */ + @Override + public final void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + String[] userflags = flags.getUserFlags(); + userFlags.clear(); + userFlags.addAll(Arrays.asList(userflags)); + } + + /** + * This implementation supports user flags + * + * + */ + @Override + public String[] createUserFlags() { + String[] flags = new String[userFlags.size()]; + for (int i = 0; i < userFlags.size(); i++) { + flags[i] = userFlags.get(i); + } + return flags; + } + + @Override + public String toString() { + final String retValue = + "message(" + + "mailboxId = " + this.getMailboxId() + TOSTRING_SEPARATOR + + "uid = " + this.uid + TOSTRING_SEPARATOR + + "internalDate = " + this.internalDate + TOSTRING_SEPARATOR + + "answered = " + this.answered + TOSTRING_SEPARATOR + + "deleted = " + this.deleted + TOSTRING_SEPARATOR + + "draft = " + this.draft + TOSTRING_SEPARATOR + + "flagged = " + this.flagged + TOSTRING_SEPARATOR + + "recent = " + this.recent + TOSTRING_SEPARATOR + + "seen = " + this.seen + TOSTRING_SEPARATOR + + " )"; + return retValue; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapper.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapper.java.svn-base new file mode 100644 index 0000000..4bad749 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapper.java.svn-base @@ -0,0 +1,761 @@ +/**************************************************************** + * 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.mail; + +import org.apache.hadoop.conf.Configuration; +import java.io.BufferedInputStream; +import org.apache.hadoop.hbase.client.Put; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.mail.Flags; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; +import org.apache.hadoop.hbase.filter.PrefixFilter; +import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter; +import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.hbase.io.ChunkOutputStream; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import static org.apache.james.mailbox.hbase.FlagConvertor.*; + +/** + * HBase implementation of a {@link MessageMapper}. + * I don't know if this class is thread-safe! Asume it is not! + * + */ +public class HBaseMessageMapper extends NonTransactionalMapper implements MessageMapper { + + private final Configuration conf; + private final MailboxSession mailboxSession; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + public HBaseMessageMapper(final MailboxSession session, + final UidProvider uidProvider, + ModSeqProvider modSeqProvider, Configuration conf) { + this.mailboxSession = session; + this.modSeqProvider = modSeqProvider; + this.uidProvider = uidProvider; + this.conf = conf; + } + + @Override + public void endRequest() { + } + + @Override + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) throws MailboxException { + try { + List> results; + long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, max, false); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, max, false); + break; + case ONE: + results = findMessagesInMailboxWithUID(mailbox, from, false); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max, false); + break; + } + return results.iterator(); + + } catch (IOException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + private List> findMessagesInMailbox(Mailbox mailbox, int batchSize, boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + HTable messages = new HTable(conf, MESSAGES_TABLE); + Scan scan = new Scan(customMessageRowKey(mailbox.getMailboxId(), 0L), + new PrefixFilter(Bytes.add(Bytes.toBytes(mailbox.getMailboxId().getMostSignificantBits()), + Bytes.toBytes(mailbox.getMailboxId().getLeastSignificantBits())))); + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + scan.setFilter(filter); + } + scan.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + scan.addFamily(MESSAGES_META_CF); + ResultScanner scanner = messages.getScanner(scan); + Result result; + long count = batchSize > 0 ? batchSize : Long.MAX_VALUE; + while (((result = scanner.next()) != null) && (count > 0)) { + messageList.add(messageMetaFromResult(conf, result)); + count--; + } + scanner.close(); + messages.close(); + // we store uids in reverse order, we send them ascending + Collections.reverse(messageList); + return messageList; + } + + private List> findMessagesInMailboxWithUID(Mailbox mailbox, final long messageUid, final boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + HTable messages = new HTable(conf, MESSAGES_TABLE); + Get get = new Get(messageRowKey(mailbox.getMailboxId(), messageUid)); + get.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + get.setFilter(filter); + } + get.addFamily(MESSAGES_META_CF); + Result result = messages.get(get); + Message message = null; + if (!result.isEmpty()) { + message = messageMetaFromResult(conf, result); + messageList.add(message); + } + messages.close(); + return messageList; + } + + private List> findMessagesInMailboxAfterUID(Mailbox mailbox, final long from, final int batchSize, final boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + HTable messages = new HTable(conf, MESSAGES_TABLE); + // uids are stored in reverse so we need to search + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), + messageRowKey(mailbox.getMailboxId(), from - 1)); + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + scan.setFilter(filter); + } + scan.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + scan.addFamily(MESSAGES_META_CF); + ResultScanner scanner = messages.getScanner(scan); + Result result; + long count = batchSize > 0 ? batchSize : Long.MAX_VALUE; + while (((result = scanner.next()) != null) && (count > 0)) { + messageList.add(messageMetaFromResult(conf, result)); + count--; + } + scanner.close(); + messages.close(); + // uids are stored in reverese so we change the list + Collections.reverse(messageList); + return messageList; + } + + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, final long from, final long to, final int batchSize, final boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + if (from > to) { + return messageList; + } + HTable messages = new HTable(conf, MESSAGES_TABLE); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), to), messageRowKey(mailbox.getMailboxId(), from - 1)); + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + scan.setFilter(filter); + } + scan.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + scan.addFamily(MESSAGES_META_CF); + ResultScanner scanner = messages.getScanner(scan); + Result result; + + long count = batchSize > 0 ? batchSize : Long.MAX_VALUE; + while (((result = scanner.next()) != null)) { + if (count == 0) { + break; + } + Message message = messageMetaFromResult(conf, result); + messageList.add(message); + count--; + } + scanner.close(); + messages.close(); + // uids are stored in reverse order + Collections.reverse(messageList); + return messageList; + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) throws MailboxException { + try { + final Map data; + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + + switch (set.getType()) { + case ONE: + results = findMessagesInMailboxWithUID(mailbox, from, true); + data = createMetaData(results); + deleteDeletedMessagesInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, -1, true); + data = createMetaData(results); + deleteDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, -1, true); + data = createMetaData(results); + deleteDeletedMessagesInMailboxAfterUID(mailbox, from); + break; + default: + case ALL: + results = findMessagesInMailbox(mailbox, -1, true); + data = createMetaData(results); + deleteDeletedMessagesInMailbox(mailbox); + break; + } + + return data; + } catch (IOException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + @Override + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Get get = new Get(mailboxRowKey(mailbox.getMailboxId())); + get.addColumn(MAILBOX_CF, MAILBOX_MESSAGE_COUNT); + get.setMaxVersions(1); + Result result = mailboxes.get(get); + long count = Bytes.toLong(result.getValue(MAILBOX_CF, MAILBOX_MESSAGE_COUNT)); + return count; + } catch (IOException e) { + throw new MailboxException("Count of messages failed in mailbox " + mailbox, e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + /* TODO: see if it is possible to store the number of unseen messages in the mailbox table + * and just return that value with a Get and kepp it up to date. + */ + HTable messages = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + /* Limit the number of entries scanned to just the mails in this mailbox */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), + messageRowKey(mailbox.getMailboxId(), 0)); + scan.addFamily(MESSAGES_META_CF); + scan.setFilter(new SingleColumnValueExcludeFilter(MESSAGES_META_CF, FLAGS_SEEN, CompareOp.EQUAL, MARKER_MISSING)); + scan.setCaching(messages.getScannerCaching() * 2); + scan.setMaxVersions(1); + scanner = messages.getScanner(scan); + long count = 0; + Result result; + while ((result = scanner.next()) != null) { + count++; + } + return count; + } catch (IOException e) { + throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e); + } finally { + scanner.close(); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + } + } + + @Override + public void delete(Mailbox mailbox, Message message) throws MailboxException { + //TODO: maybe switch to checkAndDelete + HTable messages = null; + HTable mailboxes = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + mailboxes = new HTable(conf, MAILBOXES_TABLE); + /** TODO: also implement/update the message count for this mailbox + * and implement countMessages with get. + */ + Delete delete = new Delete(messageRowKey(message)); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -1); + messages.delete(delete); + + } catch (IOException ex) { + throw new MailboxException("Delete of message " + message + " failed in mailbox " + mailbox, ex); + } finally { + + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + + } + + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + HTable messages = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + /* Limit the number of entries scanned to just the mails in this mailbox */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), messageRowKey(mailbox.getMailboxId(), 0)); + scan.addFamily(MESSAGES_META_CF); + // filter out all rows with FLAGS_SEEN qualifier + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_SEEN, CompareOp.EQUAL, MARKER_MISSING); + scan.setFilter(filter); + scan.setCaching(messages.getScannerCaching() * 2); + scan.setMaxVersions(1); + scanner = messages.getScanner(scan); + Result result; + Long lastUnseen = null; + byte[] row = null; + while ((result = scanner.next()) != null) { + row = result.getRow(); + } + if (row != null) { + lastUnseen = Long.MAX_VALUE - Bytes.toLong(row, 16, 8); + } + return lastUnseen; + } catch (IOException e) { + throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e); + } finally { + scanner.close(); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + } + } + + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + /** TODO: improve performance by implementing a last seen and last recent value per mailbox. + * maybe one more call to HBase is less expensive than iterating throgh all rows. + */ + HTable messages = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + /* Limit the number of entries scanned to just the mails in this mailbox */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), + messageRowKey(mailbox.getMailboxId(), 0)); + // we add the column, if it exists, the message is recent, else it is not + scan.addColumn(MESSAGES_META_CF, FLAGS_RECENT); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_RECENT, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setCaching(messages.getScannerCaching() * 2); + scan.setMaxVersions(1); + + scanner = messages.getScanner(scan); + Result result; + List uids = new ArrayList(); + while ((result = scanner.next()) != null) { + uids.add(Long.MAX_VALUE - Bytes.toLong(result.getRow(), 16, 8)); + } + Collections.reverse(uids); + return uids; + } catch (IOException e) { + throw new MailboxException("Search of recent messages failed in mailbox " + mailbox, e); + } finally { + scanner.close(); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#add(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData add(Mailbox mailbox, Message message) throws MailboxException { + message.setUid(uidProvider.nextUid(mailboxSession, mailbox)); + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + message.setModSeq(modSeqProvider.nextModSeq(mailboxSession, mailbox)); + } + MessageMetaData data = save(mailbox, message); + + return data; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, javax.mail.Flags, boolean, boolean, org.apache.james.mailbox.MessageRange) + */ + @Override + public Iterator updateFlags(final Mailbox mailbox, final Flags flags, final boolean value, final boolean replace, MessageRange set) throws MailboxException { + + final List updatedFlags = new ArrayList(); + Iterator> messagesFound = findInMailbox(mailbox, set, FetchType.Metadata, -1); + + HTable messages = null; + long modSeq = -1; + if (messagesFound.hasNext() == false) { + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + } + + try { + messages = new HTable(conf, MESSAGES_TABLE); + while (messagesFound.hasNext()) { + Put put = null; + final Message member = messagesFound.next(); + Flags originalFlags = member.createFlags(); + + if (replace) { + member.setFlags(flags); + } else { + Flags current = member.createFlags(); + if (value) { + current.add(flags); + } else { + current.remove(flags); + } + member.setFlags(current); + } + Flags newFlags = member.createFlags(); + put = flagsToPut(member, newFlags); + if (UpdatedFlags.flagsChanged(originalFlags, newFlags)) { + // increase the mod-seq as we changed the flags + put.add(MESSAGES_META_CF, MESSAGE_MODSEQ, Bytes.toBytes(modSeq)); + // update put not to include the allready existing flags + messages.put(put); + messages.flushCommits(); + } + + UpdatedFlags uFlags = new UpdatedFlags(member.getUid(), member.getModSeq(), originalFlags, newFlags); + updatedFlags.add(uFlags); + } + } catch (IOException e) { + throw new MailboxException("Error setting flags for messages in " + mailbox, e); + } finally { + if (messages != null) { + try { + messages.close(); + } catch (IOException e) { + throw new MailboxException("Error setting flags for messages in " + mailbox, e); + } + } + } + + return updatedFlags.iterator(); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData copy(Mailbox mailbox, Message original) throws MailboxException { + long uid = uidProvider.nextUid(mailboxSession, mailbox); + long modSeq = -1; + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + //TODO: check if creating a HBase message is the right thing to do + HBaseMessage message = new HBaseMessage(conf, + mailbox.getMailboxId(), uid, modSeq, original); + return save(mailbox, message); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + //TODO implement if possible + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#getLastUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long getLastUid(Mailbox mailbox) throws MailboxException { + return uidProvider.lastUid(mailboxSession, mailbox); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#getHighestModSeq(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + return modSeqProvider.highestModSeq(mailboxSession, mailbox); + } + + /** + * Save the {@link Message} for the given {@link Mailbox} and return the {@link MessageMetaData} + * + * @param mailbox + * @param message + * @return metaData + * @throws MailboxException + */ + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + HTable messages = null; + HTable mailboxes = null; + BufferedInputStream in = null; + ChunkOutputStream out = null; + try { + //TODO: update the mailbox information about messages + messages = new HTable(conf, MESSAGES_TABLE); + mailboxes = new HTable(conf, MAILBOXES_TABLE); + //save the message metadata + Put put = metadataToPut(message); + messages.put(put); + //save the message content + //TODO: current implementation is crude. + + int b; + out = new ChunkOutputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, messageRowKey(message), MAX_COLUMN_SIZE); + in = new BufferedInputStream(message.getBodyContent()); + while ((b = in.read()) != -1) { + out.write(b); + } + in.close(); + out.close(); + out = new ChunkOutputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_HEADERS_CF, messageRowKey(message), MAX_COLUMN_SIZE); + in = new BufferedInputStream(message.getHeaderContent()); + while ((b = in.read()) != -1) { + out.write(b); + } + in.close(); + out.close(); + // increase the message count for the current mailbox + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, 1); + return new SimpleMessageMetaData(message); + } catch (IOException ex) { + throw new MailboxException("Error setting flags for messages in " + mailbox, ex); + } finally { + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + if (in != null) { + try { + in.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing Inputtream", ex); + } + } + if (out != null) { + try { + out.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing OutputStream", ex); + } + } + } + } + + private void deleteDeletedMessagesInMailboxWithUID(Mailbox mailbox, long uid) throws IOException { + //TODO: do I have to check if the message is flagged for delete here? + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + Delete delete = new Delete(messageRowKey(mailbox.getMailboxId(), uid)); + messages.delete(delete); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -1); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private void deleteDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long fromUid, long toUid) throws IOException { + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + List deletes = new ArrayList(); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), fromUid), messageRowKey(mailbox.getMailboxId(), toUid)); + scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setMaxVersions(1); + ResultScanner scanner = messages.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + scanner.close(); + messages.delete(deletes); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size())); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private void deleteDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long fromUid) throws IOException { + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + List deletes = new ArrayList(); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), fromUid)); + scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setMaxVersions(1); + ResultScanner scanner = messages.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + scanner.close(); + messages.delete(deletes); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size())); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private void deleteDeletedMessagesInMailbox(Mailbox mailbox) throws IOException { + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + List deletes = new ArrayList(); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(customMessageRowKey(mailbox.getMailboxId(), 0L), + new PrefixFilter(Bytes.add(Bytes.toBytes(mailbox.getMailboxId().getMostSignificantBits()), + Bytes.toBytes(mailbox.getMailboxId().getLeastSignificantBits())))); + scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setMaxVersions(1); + ResultScanner scanner = messages.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + scanner.close(); + messages.delete(deletes); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size())); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private Map createMetaData(List> uids) { + final Map data = new HashMap(); + for (int i = 0; i < uids.size(); i++) { + Message m = uids.get(i); + data.put(m.getUid(), new SimpleMessageMetaData(m)); + } + return data; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseModSeqProvider.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseModSeqProvider.java.svn-base new file mode 100644 index 0000000..2b612ee --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseModSeqProvider.java.svn-base @@ -0,0 +1,95 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +/** + * ModSeqProvider implementation for HBase. + * + */ +public class HBaseModSeqProvider implements ModSeqProvider { + + /** Link to the HBase Configuration object and specific mailbox names */ + private final Configuration conf; + + public HBaseModSeqProvider(Configuration conf) { + this.conf = conf; + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Get get = new Get(mailboxRowKey(mailbox.getMailboxId())); + get.addColumn(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ); + get.setMaxVersions(1); + Result result = mailboxes.get(get); + + if (result == null) { + throw new MailboxException("Row or column not found!"); + } + long modSeq = Bytes.toLong(result.getValue(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ)); + return modSeq; + } catch (IOException e) { + throw new MailboxException("highestModSeq", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + long newValue = mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + return newValue; + } catch (IOException e) { + throw new MailboxException("lastUid", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidProvider.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidProvider.java.svn-base new file mode 100644 index 0000000..1f1a5b4 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidProvider.java.svn-base @@ -0,0 +1,112 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.UUID; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +/** + * Message UidProvider for HBase. + * + */ +public class HBaseUidProvider implements UidProvider { + + /** Link to the HBase Configuration object and specific mailbox names */ + private final Configuration conf; + + public HBaseUidProvider(Configuration conf) { + this.conf = conf; + } + + /** + * Returns the last message uid used in a mailbox. + * @param session the session + * @param mailbox the mailbox for which to get the last uid + * @return the last uid used + * @throws MailboxException + */ + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Get get = new Get(mailboxRowKey(mailbox.getMailboxId())); + get.addColumn(MAILBOX_CF, MAILBOX_LASTUID); + get.setMaxVersions(1); + Result result = mailboxes.get(get); + + if (result == null) { + throw new MailboxException("Row or column not found!"); + } + long uid = Bytes.toLong(result.getValue(MAILBOX_CF, MAILBOX_LASTUID)); + return uid; + } catch (IOException e) { + throw new MailboxException("lastUid", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + /** + * Returns the next uid. Implemented using HTable.incrementColumnValue(row, family, qualifier, amount). + * + * @param session the mailbox session + * @param mailbox the mailbox for which we are getting the next uid. + * @return the next uid to be used. + * @throws MailboxException + */ + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + long newValue = mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_LASTUID, 1); + mailboxes.close(); + return newValue; + } catch (IOException e) { + throw new MailboxException("lastUid", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapper.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapper.java new file mode 100644 index 0000000..8308f49 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapper.java @@ -0,0 +1,395 @@ +/**************************************************************** + * 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.mail; + +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOXES_TABLE; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_CF; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_MESSAGE_COUNT; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_NAME; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_NAMESPACE; +import static org.apache.james.mailbox.hbase.HBaseNames.MAILBOX_USER; +import static org.apache.james.mailbox.hbase.HBaseNames.MESSAGES_META_CF; +import static org.apache.james.mailbox.hbase.HBaseNames.MESSAGES_TABLE; +import static org.apache.james.mailbox.hbase.HBaseNames.MESSAGE_INTERNALDATE; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxFromResult; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxRowKey; +import static org.apache.james.mailbox.hbase.HBaseUtils.toPut; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.filter.BinaryPrefixComparator; +import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; +import org.apache.hadoop.hbase.filter.FilterList; +import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; +import org.apache.hadoop.hbase.filter.SubstringComparator; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.hbase.HBaseNonTransactionalMapper; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Data access management for mailbox. + * + */ +public class HBaseMailboxMapper extends HBaseNonTransactionalMapper implements MailboxMapper { + + /** + * Link to the HBase Configuration object and specific mailbox names + */ + private final Configuration conf; + + public HBaseMailboxMapper(Configuration conf) { + this.conf = conf; + } + + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxPath) throws MailboxException, MailboxNotFoundException { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + + /* + * Filters is ORDERED. Passing the parameters in the right order + * might improve performance: passing the user first means that the + * other filters will not be tested if the mailbox does not belong + * to the passed user. + */ + FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL); + + if (mailboxPath.getUser() != null) { + SingleColumnValueFilter userFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_USER, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getUser())); + filters.addFilter(userFilter); + } + SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAME, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getName())); + filters.addFilter(nameFilter); + SingleColumnValueFilter namespaceFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAMESPACE, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getNamespace())); + filters.addFilter(namespaceFilter); + + scan.setFilter(filters); + scanner = mailboxes.getScanner(scan); + Result result = scanner.next(); + + if (result == null) { + throw new MailboxNotFoundException(mailboxPath); + } + return mailboxFromResult(result); + } catch (IOException e) { + throw new MailboxException("Search of mailbox " + mailboxPath + " failed", e); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public List> findMailboxWithPathLike(MailboxPath mailboxPath) throws MailboxException { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + + FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL); + + if (mailboxPath.getUser() != null) { + SingleColumnValueFilter userFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_USER, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getUser())); + filters.addFilter(userFilter); + } + SubstringComparator pathComparator; + String mboxName = mailboxPath.getName(); + /* + * TODO: use a RegExFiler + */ + if (mboxName.length() >= 1) { + if (mboxName.charAt(mboxName.length() - 1) == '%') { + mboxName = mboxName.substring(0, mboxName.length() - 1); + } + } + if (mboxName.length() >= 1) { + if (mboxName.charAt(0) == '%') { + mboxName = mboxName.substring(1); + } + } + pathComparator = new SubstringComparator(mboxName); + SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAME, CompareOp.EQUAL, pathComparator); + filters.addFilter(nameFilter); + SingleColumnValueFilter namespaceFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAMESPACE, CompareOp.EQUAL, Bytes.toBytes(mailboxPath.getNamespace())); + filters.addFilter(namespaceFilter); + + scan.setFilter(filters); + scanner = mailboxes.getScanner(scan); + + List> mailboxList = new ArrayList>(); + + for (Result result : scanner) { + mailboxList.add(mailboxFromResult(result)); + } + return mailboxList; + } catch (IOException e) { + throw new MailboxException("Search of mailbox " + mailboxPath + " failed", e); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public List> list() throws MailboxException { + HTable mailboxes = null; + ResultScanner scanner = null; + //TODO: possible performance isssues, we are creating an object from all the rows in HBase mailbox table + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + scanner = mailboxes.getScanner(scan); + List> mailboxList = new ArrayList>(); + + Result result; + while ((result = scanner.next()) != null) { + Mailbox mlbx = mailboxFromResult(result); + mailboxList.add(mlbx); + } + return mailboxList; + } catch (IOException ex) { + throw new MailboxException("HBase IOException in list()", ex); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public void endRequest() { + } + + @Override + public void save(Mailbox mlbx) throws MailboxException { + //TODO: maybe switch to checkAndPut for transactions + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + /* + * cast to HBaseMailbox to access lastuid and ModSeq + */ + Put put = toPut((HBaseMailbox) mlbx); + mailboxes.put(put); + } catch (IOException ex) { + throw new MailboxException("IOExeption", ex); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public void delete(Mailbox mlbx) throws MailboxException { + //TODO: maybe switch to checkAndDelete + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + //TODO: delete all maessages from this mailbox + Delete delete = new Delete(mailboxRowKey(mlbx.getMailboxId())); + mailboxes.delete(delete); + } catch (IOException ex) { + throw new MailboxException("IOException in HBase cluster during delete()", ex); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public boolean hasChildren(final Mailbox mailbox, final char c) throws MailboxException, MailboxNotFoundException { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + + Scan scan = new Scan(); + scan.addFamily(MAILBOX_CF); + scan.setCaching(mailboxes.getScannerCaching() * 2); + scan.setMaxVersions(1); + + FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL); + + if (mailbox.getUser() != null) { + SingleColumnValueFilter userFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_USER, CompareOp.EQUAL, Bytes.toBytes(mailbox.getUser())); + filters.addFilter(userFilter); + } + SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(MAILBOX_CF, + MAILBOX_NAME, + CompareOp.EQUAL, + new BinaryPrefixComparator(Bytes.toBytes(mailbox.getName() + c))); + filters.addFilter(nameFilter); + SingleColumnValueFilter namespaceFilter = new SingleColumnValueFilter(MAILBOX_CF, MAILBOX_NAMESPACE, CompareOp.EQUAL, Bytes.toBytes(mailbox.getNamespace())); + filters.addFilter(namespaceFilter); + + scan.setFilter(filters); + scanner = mailboxes.getScanner(scan); + try { + if (scanner.next() != null) { + return true; + } + } catch (IOException e) { + throw new MailboxNotFoundException("hasChildren() " + mailbox.getName()); + } + return false; + } catch (IOException e) { + throw new MailboxException("Search of mailbox " + mailbox + " failed", e); + } finally { + scanner.close(); + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + public void deleteAllMemberships() { + HTable messages = null; + HTable mailboxes = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Scan scan = new Scan(); + scan.setMaxVersions(1); + scan.addColumn(MESSAGES_META_CF, MESSAGE_INTERNALDATE); + scanner = messages.getScanner(scan); + Result result; + List deletes = new ArrayList(); + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + messages.delete(deletes); + if (deletes.size() > 0) { + //TODO: what shoul we do if not all messages are deleted? + System.out.println("Just " + deletes.size() + " out of " + totalDeletes + " messages have been deleted"); + //throw new RuntimeException("Just " + deletes.size() + " out of " + totalDeletes + " messages have been deleted"); + } + List puts = new ArrayList(); + scan = new Scan(); + scan.setMaxVersions(1); + scan.addColumn(MAILBOX_CF, MAILBOX_MESSAGE_COUNT); + scanner = mailboxes.getScanner(scan); + Put put = null; + while ((result = scanner.next()) != null) { + put = new Put(result.getRow()); + put.add(MAILBOX_CF, MAILBOX_MESSAGE_COUNT, Bytes.toBytes(0L)); + puts.add(new Put()); + } + } catch (IOException e) { + throw new RuntimeException("Error deleting MESSAGES table ", e); + } finally { + IOUtils.closeStream(scanner); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new RuntimeException("Error closing table " + messages, ex); + } + } + } + } + + public void deleteAllMailboxes() { + HTable mailboxes = null; + ResultScanner scanner = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Scan scan = new Scan(); + scan.setMaxVersions(1); + scan.addColumn(MAILBOX_CF, MAILBOX_NAME); + scanner = mailboxes.getScanner(scan); + Result result; + List deletes = new ArrayList(); + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + mailboxes.delete(deletes); + } catch (IOException ex) { + throw new RuntimeException("IOException deleting mailboxes", ex); + } finally { + IOUtils.closeStream(scanner); + // TODO Temporary commented, was not compiling. +// IOUtils.closeStream(mailboxes); + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessage.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessage.java new file mode 100644 index 0000000..3df807c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessage.java @@ -0,0 +1,397 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import javax.mail.Flags; +import org.apache.hadoop.conf.Configuration; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.hbase.io.ChunkInputStream; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; + +/** + * Concrete HBaseMessage implementation. This implementation does not store any + * message content. The message content is retrieved using a ChunkedInputStream + * directly from HBase. + */ +public class HBaseMessage extends AbstractMessage { + + private static final String TOSTRING_SEPARATOR = " "; + /** Configuration for the HBase cluster */ + private final Configuration conf; + /** The value for the mailboxId field */ + private UUID mailboxId; + /** The value for the uid field */ + private long uid; + /** The value for the modSeq field */ + private long modSeq; + /** The value for the internalDate field */ + private Date internalDate; + /** The value for the answered field */ + private boolean answered = false; + /** The value for the deleted field */ + private boolean deleted = false; + /** The value for the draft field */ + private boolean draft = false; + /** The value for the flagged field */ + private boolean flagged = false; + /** The value for the recent field */ + private boolean recent = false; + /** The value for the seen field */ + private boolean seen = false; + /** The first body octet */ + private int bodyStartOctet; + /** Number of octets in the full document content */ + private long contentOctets; + /** MIME media type */ + private String mediaType; + /** MIME sub type */ + private String subType; + /** THE CRFL count when this document is textual, null otherwise */ + private Long textualLineCount; + /** Meta data for this message */ + private List properties; + private List userFlags; + + /** + * Create a copy of the given message. + * All properties are cloned except mailbox and UID. + * @param mailboxId + * @param uid + * @param modSeq + * @param original + * @throws MailboxException + */ + public HBaseMessage(Configuration conf, UUID mailboxId, long uid, long modSeq, Message original) throws MailboxException { + super(); + this.conf = conf; + this.mailboxId = mailboxId; + this.uid = uid; + this.modSeq = modSeq; + this.userFlags = new ArrayList(); + setFlags(original.createFlags()); + + // A copy of a message is recent + // See MAILBOX-85 + this.recent = true; + + this.contentOctets = original.getFullContentOctets(); + this.bodyStartOctet = (int) (original.getFullContentOctets() - original.getBodyOctets()); + this.internalDate = original.getInternalDate(); + + this.textualLineCount = original.getTextualLineCount(); + this.mediaType = original.getMediaType(); + this.subType = original.getSubType(); + this.properties = original.getProperties(); + } + + /** + * Create a copy of the given message. + * @param mailboxId + * @param internalDate + * @param flags + * @param contentOctets + * @param bodyStartOctet + * @param propertyBuilder + */ + public HBaseMessage(Configuration conf, UUID mailboxId, Date internalDate, Flags flags, long contentOctets, int bodyStartOctet, PropertyBuilder propertyBuilder) { + super(); + this.conf = conf; + this.mailboxId = mailboxId; + this.internalDate = internalDate; + userFlags = new ArrayList(); + + setFlags(flags); + this.contentOctets = contentOctets; + this.bodyStartOctet = bodyStartOctet; + this.textualLineCount = propertyBuilder.getTextualLineCount(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + this.properties = propertyBuilder.toProperties(); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + @Override + public InputStream getBodyContent() throws IOException { + return new ChunkInputStream(conf, MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, messageRowKey(this)); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + @Override + public InputStream getHeaderContent() throws IOException { + return new ChunkInputStream(conf, MESSAGES_TABLE, MESSAGE_DATA_HEADERS_CF, messageRowKey(this)); + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (getMailboxId().getMostSignificantBits() ^ (getMailboxId().getMostSignificantBits() >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HBaseMessage other = (HBaseMessage) obj; + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) { + return false; + } + } else { + if (other.getMailboxId() != null) { + return false; + } + } + if (uid != other.uid) { + return false; + } + return true; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + @Override + public long getModSeq() { + return modSeq; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + @Override + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + @Override + public String getMediaType() { + return mediaType; + } + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + @Override + public String getSubType() { + return subType; + } + + /** + * Gets a read-only list of meta-data properties. + * For properties with multiple values, this list will contain + * several enteries with the same namespace and local name. + * @return unmodifiable list of meta-data, not null + */ + @Override + public List getProperties() { + return new ArrayList(properties); + } + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + @Override + public Long getTextualLineCount() { + return textualLineCount; + } + + public void setTextualLineCount(Long textualLineCount) { + this.textualLineCount = textualLineCount; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getFullContentOctets() + */ + @Override + public long getFullContentOctets() { + return contentOctets; + } + + @Override + protected int getBodyStartOctet() { + return bodyStartOctet; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getInternalDate() + */ + @Override + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getMailboxId() + */ + @Override + public UUID getMailboxId() { + return mailboxId; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getUid() + */ + @Override + public long getUid() { + return uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isAnswered() + */ + @Override + public boolean isAnswered() { + return answered; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDeleted() + */ + @Override + public boolean isDeleted() { + return deleted; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDraft() + */ + @Override + public boolean isDraft() { + return draft; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isFlagged() + */ + @Override + public boolean isFlagged() { + return flagged; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isRecent() + */ + @Override + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isSeen() + */ + @Override + public boolean isSeen() { + return seen; + } + + @Override + public void setUid(long uid) { + this.uid = uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setFlags(javax.mail.Flags) + */ + @Override + public final void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + String[] userflags = flags.getUserFlags(); + userFlags.clear(); + userFlags.addAll(Arrays.asList(userflags)); + } + + /** + * This implementation supports user flags + * + * + */ + @Override + public String[] createUserFlags() { + String[] flags = new String[userFlags.size()]; + for (int i = 0; i < userFlags.size(); i++) { + flags[i] = userFlags.get(i); + } + return flags; + } + + @Override + public String toString() { + final String retValue = + "message(" + + "mailboxId = " + this.getMailboxId() + TOSTRING_SEPARATOR + + "uid = " + this.uid + TOSTRING_SEPARATOR + + "internalDate = " + this.internalDate + TOSTRING_SEPARATOR + + "answered = " + this.answered + TOSTRING_SEPARATOR + + "deleted = " + this.deleted + TOSTRING_SEPARATOR + + "draft = " + this.draft + TOSTRING_SEPARATOR + + "flagged = " + this.flagged + TOSTRING_SEPARATOR + + "recent = " + this.recent + TOSTRING_SEPARATOR + + "seen = " + this.seen + TOSTRING_SEPARATOR + + " )"; + return retValue; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java new file mode 100644 index 0000000..4bad749 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java @@ -0,0 +1,761 @@ +/**************************************************************** + * 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.mail; + +import org.apache.hadoop.conf.Configuration; +import java.io.BufferedInputStream; +import org.apache.hadoop.hbase.client.Put; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.mail.Flags; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; +import org.apache.hadoop.hbase.filter.PrefixFilter; +import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter; +import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.hbase.io.ChunkOutputStream; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import static org.apache.james.mailbox.hbase.FlagConvertor.*; + +/** + * HBase implementation of a {@link MessageMapper}. + * I don't know if this class is thread-safe! Asume it is not! + * + */ +public class HBaseMessageMapper extends NonTransactionalMapper implements MessageMapper { + + private final Configuration conf; + private final MailboxSession mailboxSession; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + public HBaseMessageMapper(final MailboxSession session, + final UidProvider uidProvider, + ModSeqProvider modSeqProvider, Configuration conf) { + this.mailboxSession = session; + this.modSeqProvider = modSeqProvider; + this.uidProvider = uidProvider; + this.conf = conf; + } + + @Override + public void endRequest() { + } + + @Override + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) throws MailboxException { + try { + List> results; + long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, max, false); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, max, false); + break; + case ONE: + results = findMessagesInMailboxWithUID(mailbox, from, false); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max, false); + break; + } + return results.iterator(); + + } catch (IOException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + private List> findMessagesInMailbox(Mailbox mailbox, int batchSize, boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + HTable messages = new HTable(conf, MESSAGES_TABLE); + Scan scan = new Scan(customMessageRowKey(mailbox.getMailboxId(), 0L), + new PrefixFilter(Bytes.add(Bytes.toBytes(mailbox.getMailboxId().getMostSignificantBits()), + Bytes.toBytes(mailbox.getMailboxId().getLeastSignificantBits())))); + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + scan.setFilter(filter); + } + scan.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + scan.addFamily(MESSAGES_META_CF); + ResultScanner scanner = messages.getScanner(scan); + Result result; + long count = batchSize > 0 ? batchSize : Long.MAX_VALUE; + while (((result = scanner.next()) != null) && (count > 0)) { + messageList.add(messageMetaFromResult(conf, result)); + count--; + } + scanner.close(); + messages.close(); + // we store uids in reverse order, we send them ascending + Collections.reverse(messageList); + return messageList; + } + + private List> findMessagesInMailboxWithUID(Mailbox mailbox, final long messageUid, final boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + HTable messages = new HTable(conf, MESSAGES_TABLE); + Get get = new Get(messageRowKey(mailbox.getMailboxId(), messageUid)); + get.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + get.setFilter(filter); + } + get.addFamily(MESSAGES_META_CF); + Result result = messages.get(get); + Message message = null; + if (!result.isEmpty()) { + message = messageMetaFromResult(conf, result); + messageList.add(message); + } + messages.close(); + return messageList; + } + + private List> findMessagesInMailboxAfterUID(Mailbox mailbox, final long from, final int batchSize, final boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + HTable messages = new HTable(conf, MESSAGES_TABLE); + // uids are stored in reverse so we need to search + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), + messageRowKey(mailbox.getMailboxId(), from - 1)); + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + scan.setFilter(filter); + } + scan.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + scan.addFamily(MESSAGES_META_CF); + ResultScanner scanner = messages.getScanner(scan); + Result result; + long count = batchSize > 0 ? batchSize : Long.MAX_VALUE; + while (((result = scanner.next()) != null) && (count > 0)) { + messageList.add(messageMetaFromResult(conf, result)); + count--; + } + scanner.close(); + messages.close(); + // uids are stored in reverese so we change the list + Collections.reverse(messageList); + return messageList; + } + + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, final long from, final long to, final int batchSize, final boolean flaggedForDelete) throws IOException { + List> messageList = new ArrayList>(); + if (from > to) { + return messageList; + } + HTable messages = new HTable(conf, MESSAGES_TABLE); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), to), messageRowKey(mailbox.getMailboxId(), from - 1)); + if (flaggedForDelete) { + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + filter.setFilterIfMissing(true); + scan.setFilter(filter); + } + scan.setMaxVersions(1); + /* we exclude the message content column family because it could be too large. + * the content will be pulled from HBase on demand by using a a ChunkedInputStream implementation. + */ + scan.addFamily(MESSAGES_META_CF); + ResultScanner scanner = messages.getScanner(scan); + Result result; + + long count = batchSize > 0 ? batchSize : Long.MAX_VALUE; + while (((result = scanner.next()) != null)) { + if (count == 0) { + break; + } + Message message = messageMetaFromResult(conf, result); + messageList.add(message); + count--; + } + scanner.close(); + messages.close(); + // uids are stored in reverse order + Collections.reverse(messageList); + return messageList; + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) throws MailboxException { + try { + final Map data; + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + + switch (set.getType()) { + case ONE: + results = findMessagesInMailboxWithUID(mailbox, from, true); + data = createMetaData(results); + deleteDeletedMessagesInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, -1, true); + data = createMetaData(results); + deleteDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, -1, true); + data = createMetaData(results); + deleteDeletedMessagesInMailboxAfterUID(mailbox, from); + break; + default: + case ALL: + results = findMessagesInMailbox(mailbox, -1, true); + data = createMetaData(results); + deleteDeletedMessagesInMailbox(mailbox); + break; + } + + return data; + } catch (IOException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + @Override + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Get get = new Get(mailboxRowKey(mailbox.getMailboxId())); + get.addColumn(MAILBOX_CF, MAILBOX_MESSAGE_COUNT); + get.setMaxVersions(1); + Result result = mailboxes.get(get); + long count = Bytes.toLong(result.getValue(MAILBOX_CF, MAILBOX_MESSAGE_COUNT)); + return count; + } catch (IOException e) { + throw new MailboxException("Count of messages failed in mailbox " + mailbox, e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + /* TODO: see if it is possible to store the number of unseen messages in the mailbox table + * and just return that value with a Get and kepp it up to date. + */ + HTable messages = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + /* Limit the number of entries scanned to just the mails in this mailbox */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), + messageRowKey(mailbox.getMailboxId(), 0)); + scan.addFamily(MESSAGES_META_CF); + scan.setFilter(new SingleColumnValueExcludeFilter(MESSAGES_META_CF, FLAGS_SEEN, CompareOp.EQUAL, MARKER_MISSING)); + scan.setCaching(messages.getScannerCaching() * 2); + scan.setMaxVersions(1); + scanner = messages.getScanner(scan); + long count = 0; + Result result; + while ((result = scanner.next()) != null) { + count++; + } + return count; + } catch (IOException e) { + throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e); + } finally { + scanner.close(); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + } + } + + @Override + public void delete(Mailbox mailbox, Message message) throws MailboxException { + //TODO: maybe switch to checkAndDelete + HTable messages = null; + HTable mailboxes = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + mailboxes = new HTable(conf, MAILBOXES_TABLE); + /** TODO: also implement/update the message count for this mailbox + * and implement countMessages with get. + */ + Delete delete = new Delete(messageRowKey(message)); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -1); + messages.delete(delete); + + } catch (IOException ex) { + throw new MailboxException("Delete of message " + message + " failed in mailbox " + mailbox, ex); + } finally { + + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + + } + + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + HTable messages = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + /* Limit the number of entries scanned to just the mails in this mailbox */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), messageRowKey(mailbox.getMailboxId(), 0)); + scan.addFamily(MESSAGES_META_CF); + // filter out all rows with FLAGS_SEEN qualifier + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_SEEN, CompareOp.EQUAL, MARKER_MISSING); + scan.setFilter(filter); + scan.setCaching(messages.getScannerCaching() * 2); + scan.setMaxVersions(1); + scanner = messages.getScanner(scan); + Result result; + Long lastUnseen = null; + byte[] row = null; + while ((result = scanner.next()) != null) { + row = result.getRow(); + } + if (row != null) { + lastUnseen = Long.MAX_VALUE - Bytes.toLong(row, 16, 8); + } + return lastUnseen; + } catch (IOException e) { + throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e); + } finally { + scanner.close(); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + } + } + + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + /** TODO: improve performance by implementing a last seen and last recent value per mailbox. + * maybe one more call to HBase is less expensive than iterating throgh all rows. + */ + HTable messages = null; + ResultScanner scanner = null; + try { + messages = new HTable(conf, MESSAGES_TABLE); + /* Limit the number of entries scanned to just the mails in this mailbox */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), Long.MAX_VALUE), + messageRowKey(mailbox.getMailboxId(), 0)); + // we add the column, if it exists, the message is recent, else it is not + scan.addColumn(MESSAGES_META_CF, FLAGS_RECENT); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_RECENT, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setCaching(messages.getScannerCaching() * 2); + scan.setMaxVersions(1); + + scanner = messages.getScanner(scan); + Result result; + List uids = new ArrayList(); + while ((result = scanner.next()) != null) { + uids.add(Long.MAX_VALUE - Bytes.toLong(result.getRow(), 16, 8)); + } + Collections.reverse(uids); + return uids; + } catch (IOException e) { + throw new MailboxException("Search of recent messages failed in mailbox " + mailbox, e); + } finally { + scanner.close(); + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#add(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData add(Mailbox mailbox, Message message) throws MailboxException { + message.setUid(uidProvider.nextUid(mailboxSession, mailbox)); + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + message.setModSeq(modSeqProvider.nextModSeq(mailboxSession, mailbox)); + } + MessageMetaData data = save(mailbox, message); + + return data; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, javax.mail.Flags, boolean, boolean, org.apache.james.mailbox.MessageRange) + */ + @Override + public Iterator updateFlags(final Mailbox mailbox, final Flags flags, final boolean value, final boolean replace, MessageRange set) throws MailboxException { + + final List updatedFlags = new ArrayList(); + Iterator> messagesFound = findInMailbox(mailbox, set, FetchType.Metadata, -1); + + HTable messages = null; + long modSeq = -1; + if (messagesFound.hasNext() == false) { + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + } + + try { + messages = new HTable(conf, MESSAGES_TABLE); + while (messagesFound.hasNext()) { + Put put = null; + final Message member = messagesFound.next(); + Flags originalFlags = member.createFlags(); + + if (replace) { + member.setFlags(flags); + } else { + Flags current = member.createFlags(); + if (value) { + current.add(flags); + } else { + current.remove(flags); + } + member.setFlags(current); + } + Flags newFlags = member.createFlags(); + put = flagsToPut(member, newFlags); + if (UpdatedFlags.flagsChanged(originalFlags, newFlags)) { + // increase the mod-seq as we changed the flags + put.add(MESSAGES_META_CF, MESSAGE_MODSEQ, Bytes.toBytes(modSeq)); + // update put not to include the allready existing flags + messages.put(put); + messages.flushCommits(); + } + + UpdatedFlags uFlags = new UpdatedFlags(member.getUid(), member.getModSeq(), originalFlags, newFlags); + updatedFlags.add(uFlags); + } + } catch (IOException e) { + throw new MailboxException("Error setting flags for messages in " + mailbox, e); + } finally { + if (messages != null) { + try { + messages.close(); + } catch (IOException e) { + throw new MailboxException("Error setting flags for messages in " + mailbox, e); + } + } + } + + return updatedFlags.iterator(); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData copy(Mailbox mailbox, Message original) throws MailboxException { + long uid = uidProvider.nextUid(mailboxSession, mailbox); + long modSeq = -1; + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + //TODO: check if creating a HBase message is the right thing to do + HBaseMessage message = new HBaseMessage(conf, + mailbox.getMailboxId(), uid, modSeq, original); + return save(mailbox, message); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + //TODO implement if possible + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#getLastUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long getLastUid(Mailbox mailbox) throws MailboxException { + return uidProvider.lastUid(mailboxSession, mailbox); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#getHighestModSeq(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + return modSeqProvider.highestModSeq(mailboxSession, mailbox); + } + + /** + * Save the {@link Message} for the given {@link Mailbox} and return the {@link MessageMetaData} + * + * @param mailbox + * @param message + * @return metaData + * @throws MailboxException + */ + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + HTable messages = null; + HTable mailboxes = null; + BufferedInputStream in = null; + ChunkOutputStream out = null; + try { + //TODO: update the mailbox information about messages + messages = new HTable(conf, MESSAGES_TABLE); + mailboxes = new HTable(conf, MAILBOXES_TABLE); + //save the message metadata + Put put = metadataToPut(message); + messages.put(put); + //save the message content + //TODO: current implementation is crude. + + int b; + out = new ChunkOutputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, messageRowKey(message), MAX_COLUMN_SIZE); + in = new BufferedInputStream(message.getBodyContent()); + while ((b = in.read()) != -1) { + out.write(b); + } + in.close(); + out.close(); + out = new ChunkOutputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_HEADERS_CF, messageRowKey(message), MAX_COLUMN_SIZE); + in = new BufferedInputStream(message.getHeaderContent()); + while ((b = in.read()) != -1) { + out.write(b); + } + in.close(); + out.close(); + // increase the message count for the current mailbox + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, 1); + return new SimpleMessageMetaData(message); + } catch (IOException ex) { + throw new MailboxException("Error setting flags for messages in " + mailbox, ex); + } finally { + if (messages != null) { + try { + messages.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + messages, ex); + } + } + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + if (in != null) { + try { + in.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing Inputtream", ex); + } + } + if (out != null) { + try { + out.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing OutputStream", ex); + } + } + } + } + + private void deleteDeletedMessagesInMailboxWithUID(Mailbox mailbox, long uid) throws IOException { + //TODO: do I have to check if the message is flagged for delete here? + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + Delete delete = new Delete(messageRowKey(mailbox.getMailboxId(), uid)); + messages.delete(delete); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -1); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private void deleteDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long fromUid, long toUid) throws IOException { + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + List deletes = new ArrayList(); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), fromUid), messageRowKey(mailbox.getMailboxId(), toUid)); + scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setMaxVersions(1); + ResultScanner scanner = messages.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + scanner.close(); + messages.delete(deletes); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size())); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private void deleteDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long fromUid) throws IOException { + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + List deletes = new ArrayList(); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(messageRowKey(mailbox.getMailboxId(), fromUid)); + scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setMaxVersions(1); + ResultScanner scanner = messages.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + scanner.close(); + messages.delete(deletes); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size())); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private void deleteDeletedMessagesInMailbox(Mailbox mailbox) throws IOException { + HTable messages = new HTable(conf, MESSAGES_TABLE); + HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + List deletes = new ArrayList(); + /*TODO: check if Between should be inclusive or exclusive regarding limits. + * HBase scan operaion are exclusive to the upper bound when providing stop row key. + */ + Scan scan = new Scan(customMessageRowKey(mailbox.getMailboxId(), 0L), + new PrefixFilter(Bytes.add(Bytes.toBytes(mailbox.getMailboxId().getMostSignificantBits()), + Bytes.toBytes(mailbox.getMailboxId().getLeastSignificantBits())))); + scan.addColumn(MESSAGES_META_CF, FLAGS_DELETED); + SingleColumnValueFilter filter = new SingleColumnValueFilter(MESSAGES_META_CF, FLAGS_DELETED, CompareOp.EQUAL, MARKER_PRESENT); + scan.setFilter(filter); + scan.setMaxVersions(1); + ResultScanner scanner = messages.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + deletes.add(new Delete(result.getRow())); + } + long totalDeletes = deletes.size(); + scanner.close(); + messages.delete(deletes); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_MESSAGE_COUNT, -(totalDeletes - deletes.size())); + mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + mailboxes.close(); + messages.close(); + } + + private Map createMetaData(List> uids) { + final Map data = new HashMap(); + for (int i = 0; i < uids.size(); i++) { + Message m = uids.get(i); + data.put(m.getUid(), new SimpleMessageMetaData(m)); + } + return data; + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseModSeqProvider.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseModSeqProvider.java new file mode 100644 index 0000000..2b612ee --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseModSeqProvider.java @@ -0,0 +1,95 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +/** + * ModSeqProvider implementation for HBase. + * + */ +public class HBaseModSeqProvider implements ModSeqProvider { + + /** Link to the HBase Configuration object and specific mailbox names */ + private final Configuration conf; + + public HBaseModSeqProvider(Configuration conf) { + this.conf = conf; + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Get get = new Get(mailboxRowKey(mailbox.getMailboxId())); + get.addColumn(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ); + get.setMaxVersions(1); + Result result = mailboxes.get(get); + + if (result == null) { + throw new MailboxException("Row or column not found!"); + } + long modSeq = Bytes.toLong(result.getValue(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ)); + return modSeq; + } catch (IOException e) { + throw new MailboxException("highestModSeq", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + @Override + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + long newValue = mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, 1); + return newValue; + } catch (IOException e) { + throw new MailboxException("lastUid", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseUidProvider.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseUidProvider.java new file mode 100644 index 0000000..1f1a5b4 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseUidProvider.java @@ -0,0 +1,112 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.UUID; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +/** + * Message UidProvider for HBase. + * + */ +public class HBaseUidProvider implements UidProvider { + + /** Link to the HBase Configuration object and specific mailbox names */ + private final Configuration conf; + + public HBaseUidProvider(Configuration conf) { + this.conf = conf; + } + + /** + * Returns the last message uid used in a mailbox. + * @param session the session + * @param mailbox the mailbox for which to get the last uid + * @return the last uid used + * @throws MailboxException + */ + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + Get get = new Get(mailboxRowKey(mailbox.getMailboxId())); + get.addColumn(MAILBOX_CF, MAILBOX_LASTUID); + get.setMaxVersions(1); + Result result = mailboxes.get(get); + + if (result == null) { + throw new MailboxException("Row or column not found!"); + } + long uid = Bytes.toLong(result.getValue(MAILBOX_CF, MAILBOX_LASTUID)); + return uid; + } catch (IOException e) { + throw new MailboxException("lastUid", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } + + /** + * Returns the next uid. Implemented using HTable.incrementColumnValue(row, family, qualifier, amount). + * + * @param session the mailbox session + * @param mailbox the mailbox for which we are getting the next uid. + * @return the next uid to be used. + * @throws MailboxException + */ + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + HTable mailboxes = null; + try { + mailboxes = new HTable(conf, MAILBOXES_TABLE); + long newValue = mailboxes.incrementColumnValue(mailboxRowKey(mailbox.getMailboxId()), MAILBOX_CF, MAILBOX_LASTUID, 1); + mailboxes.close(); + return newValue; + } catch (IOException e) { + throw new MailboxException("lastUid", e); + } finally { + if (mailboxes != null) { + try { + mailboxes.close(); + } catch (IOException ex) { + throw new MailboxException("Error closing table " + mailboxes, ex); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops new file mode 100644 index 0000000..df3a218 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model +END +HBaseMailbox.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailbox.java +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries new file mode 100644 index 0000000..8a56a93 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +HBaseMailbox.java +file + + + + +2013-09-02T02:54:38.000000Z +38a542422f9762a1fd03e1fd1f03247d +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +6263 + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailbox.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailbox.java.svn-base new file mode 100644 index 0000000..641d610 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailbox.java.svn-base @@ -0,0 +1,217 @@ +/**************************************************************** + * 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.mail.model; + +import java.util.UUID; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * This class implements a mailbox. Most of the code is done after mailbox-jpa + * implementations. + * + */ +public class HBaseMailbox implements Mailbox { + + private static final String TAB = " "; + /** The value for the mailboxId field */ + private UUID mailboxId; + /** The value for the name field */ + private String name; + /** The value for the uidValidity field */ + private long uidValidity; + private String user; + private String namespace; + private long lastUid; + private long highestModSeq; + private long messageCount; + + public HBaseMailbox(MailboxPath mailboxPath, long uidValidity) { + super(); + this.name = mailboxPath.getName(); + this.user = mailboxPath.getUser(); + this.namespace = mailboxPath.getNamespace(); + this.uidValidity = uidValidity; + //TODO: this has to change to something that can guarantee that mailboxId is unique + this.mailboxId = UUID.randomUUID(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + @Override + public UUID getMailboxId() { + return mailboxId; + } + + public void setMailboxId(UUID mailboxId) { + this.mailboxId = mailboxId; + } + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + + @Override + public String getNamespace() { + return namespace; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + @Override + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + @Override + public String getUser() { + return user; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + @Override + public void setUser(String user) { + this.user = user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + @Override + public String getName() { + return name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String) + */ + @Override + public void setName(String name) { + this.name = name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + @Override + public long getUidValidity() { + return uidValidity; + } + + @Override + public String toString() { + final String retValue = "Mailbox ( " + + "mailboxId = " + this.mailboxId + TAB +// + "namespace = " + this.namespace + TAB + + "name = " + this.name + TAB +// + "user = " + this.user + TAB + + "uidValidity = " + this.uidValidity + TAB + + " )"; + return retValue; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailboxId.getMostSignificantBits() ^ (mailboxId.getMostSignificantBits() >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HBaseMailbox other = (HBaseMailbox) obj; + if (!mailboxId.equals(other.getMailboxId())) { + return false; + } + return true; + } + + public long getLastUid() { + return lastUid; + } + + public void setLastUid(long lastUid) { + this.lastUid = lastUid; + } + + public long getHighestModSeq() { + return highestModSeq; + } + + public void setHighestModSeq(long highestModSeq) { + this.highestModSeq = highestModSeq; + } + + public long consumeUid() { + return ++lastUid; + } + + public long consumeModSeq() { + return ++highestModSeq; + } + + public long getMessageCount() { + return messageCount; + } + + public void setMessageCount(long messageCount) { + this.messageCount = messageCount; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + // TODO ACL support + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + // TODO ACL support + } + +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailbox.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailbox.java new file mode 100644 index 0000000..641d610 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailbox.java @@ -0,0 +1,217 @@ +/**************************************************************** + * 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.mail.model; + +import java.util.UUID; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * This class implements a mailbox. Most of the code is done after mailbox-jpa + * implementations. + * + */ +public class HBaseMailbox implements Mailbox { + + private static final String TAB = " "; + /** The value for the mailboxId field */ + private UUID mailboxId; + /** The value for the name field */ + private String name; + /** The value for the uidValidity field */ + private long uidValidity; + private String user; + private String namespace; + private long lastUid; + private long highestModSeq; + private long messageCount; + + public HBaseMailbox(MailboxPath mailboxPath, long uidValidity) { + super(); + this.name = mailboxPath.getName(); + this.user = mailboxPath.getUser(); + this.namespace = mailboxPath.getNamespace(); + this.uidValidity = uidValidity; + //TODO: this has to change to something that can guarantee that mailboxId is unique + this.mailboxId = UUID.randomUUID(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + @Override + public UUID getMailboxId() { + return mailboxId; + } + + public void setMailboxId(UUID mailboxId) { + this.mailboxId = mailboxId; + } + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + + @Override + public String getNamespace() { + return namespace; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + @Override + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + @Override + public String getUser() { + return user; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + @Override + public void setUser(String user) { + this.user = user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + @Override + public String getName() { + return name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String) + */ + @Override + public void setName(String name) { + this.name = name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + @Override + public long getUidValidity() { + return uidValidity; + } + + @Override + public String toString() { + final String retValue = "Mailbox ( " + + "mailboxId = " + this.mailboxId + TAB +// + "namespace = " + this.namespace + TAB + + "name = " + this.name + TAB +// + "user = " + this.user + TAB + + "uidValidity = " + this.uidValidity + TAB + + " )"; + return retValue; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailboxId.getMostSignificantBits() ^ (mailboxId.getMostSignificantBits() >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HBaseMailbox other = (HBaseMailbox) obj; + if (!mailboxId.equals(other.getMailboxId())) { + return false; + } + return true; + } + + public long getLastUid() { + return lastUid; + } + + public void setLastUid(long lastUid) { + this.lastUid = lastUid; + } + + public long getHighestModSeq() { + return highestModSeq; + } + + public void setHighestModSeq(long highestModSeq) { + this.highestModSeq = highestModSeq; + } + + public long consumeUid() { + return ++lastUid; + } + + public long consumeModSeq() { + return ++highestModSeq; + } + + public long getMessageCount() { + return messageCount; + } + + public void setMessageCount(long messageCount) { + this.messageCount = messageCount; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + // TODO ACL support + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + // TODO ACL support + } + +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops new file mode 100644 index 0000000..799e70b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/user +END +HBaseSubscriptionMapper.java +K 25 +svn:wc:ra_dav:version-url +V 132 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapper.java +END diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/entries b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/entries new file mode 100644 index 0000000..4649226 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/java/org/apache/james/mailbox/hbase/user +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +HBaseSubscriptionMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +a80f86136eb0b86b7b7e0a8b00b53544 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +6948 + diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapper.java.svn-base b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapper.java.svn-base new file mode 100644 index 0000000..3459ae3 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapper.java.svn-base @@ -0,0 +1,172 @@ +/**************************************************************** + * 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.user; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.hbase.HBaseNonTransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +/** + * HBase implementation of a {@link SubscriptionMapper}. + * I don't know if this class is thread-safe! + * + */ +public class HBaseSubscriptionMapper extends HBaseNonTransactionalMapper implements SubscriptionMapper { + + /** Link to the HBase Configuration object and specific mailbox names */ + private final Configuration conf; + + public HBaseSubscriptionMapper(Configuration conf) { + this.conf = conf; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + @Override + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) throws SubscriptionException { + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + Subscription subscription = null; + Get get = new Get(Bytes.toBytes(user)); + get.addFamily(SUBSCRIPTION_CF); + Result result = subscriptions.get(get); + + if (!result.isEmpty()) { + if (result.containsColumn(SUBSCRIPTION_CF, Bytes.toBytes(mailbox))) { + subscription = new SimpleSubscription(user, mailbox); + return subscription; + } + } + return null; + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(Subscription) + */ + @Override + public void save(Subscription subscription) throws SubscriptionException { + //TODO: maybe switch to checkAndPut + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + Put put = toPut(subscription); + subscriptions.put(put); + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @Override + public List findSubscriptionsForUser(String user) throws SubscriptionException { + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + List subscriptionList = new ArrayList(); + Get get = new Get(Bytes.toBytes(user)); + get.addFamily(SUBSCRIPTION_CF); + Result result = subscriptions.get(get); + if (!result.isEmpty()) { + List columns = result.list(); + for (KeyValue key : columns) { + subscriptionList.add(new SimpleSubscription(user, Bytes.toString(key.getQualifier()))); + } + } + return subscriptionList; + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(Subscription) + */ + @Override + public void delete(Subscription subscription) throws SubscriptionException { + //TODO: maybe switch to checkAndDelete + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + Delete delete = new Delete(Bytes.toBytes(subscription.getUser())); + delete.deleteColumns(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox())); + subscriptions.delete(delete); + subscriptions.close(); + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapper.java b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapper.java new file mode 100644 index 0000000..3459ae3 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapper.java @@ -0,0 +1,172 @@ +/**************************************************************** + * 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.user; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.hbase.HBaseNonTransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; + +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +/** + * HBase implementation of a {@link SubscriptionMapper}. + * I don't know if this class is thread-safe! + * + */ +public class HBaseSubscriptionMapper extends HBaseNonTransactionalMapper implements SubscriptionMapper { + + /** Link to the HBase Configuration object and specific mailbox names */ + private final Configuration conf; + + public HBaseSubscriptionMapper(Configuration conf) { + this.conf = conf; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + @Override + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) throws SubscriptionException { + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + Subscription subscription = null; + Get get = new Get(Bytes.toBytes(user)); + get.addFamily(SUBSCRIPTION_CF); + Result result = subscriptions.get(get); + + if (!result.isEmpty()) { + if (result.containsColumn(SUBSCRIPTION_CF, Bytes.toBytes(mailbox))) { + subscription = new SimpleSubscription(user, mailbox); + return subscription; + } + } + return null; + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(Subscription) + */ + @Override + public void save(Subscription subscription) throws SubscriptionException { + //TODO: maybe switch to checkAndPut + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + Put put = toPut(subscription); + subscriptions.put(put); + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @Override + public List findSubscriptionsForUser(String user) throws SubscriptionException { + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + List subscriptionList = new ArrayList(); + Get get = new Get(Bytes.toBytes(user)); + get.addFamily(SUBSCRIPTION_CF); + Result result = subscriptions.get(get); + if (!result.isEmpty()) { + List columns = result.list(); + for (KeyValue key : columns) { + subscriptionList.add(new SimpleSubscription(user, Bytes.toString(key.getQualifier()))); + } + } + return subscriptionList; + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(Subscription) + */ + @Override + public void delete(Subscription subscription) throws SubscriptionException { + //TODO: maybe switch to checkAndDelete + HTable subscriptions = null; + try { + subscriptions = new HTable(conf, SUBSCRIPTIONS_TABLE); + Delete delete = new Delete(Bytes.toBytes(subscription.getUser())); + delete.deleteColumns(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox())); + subscriptions.delete(delete); + subscriptions.close(); + } catch (IOException e) { + throw new SubscriptionException(e); + } finally { + if (subscriptions != null) { + try { + subscriptions.close(); + } catch (IOException ex) { + throw new SubscriptionException(ex); + } + } + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..6d93d21 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/hbase/src/main/resources +END diff --git a/james/apache-james-mailbox/hbase/src/main/resources/.svn/entries b/james/apache-james-mailbox/hbase/src/main/resources/.svn/entries new file mode 100644 index 0000000..5c7d63c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/resources +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..0ca5d2e --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 81 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/hbase/src/main/resources/META-INF +END diff --git a/james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/entries b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/entries new file mode 100644 index 0000000..52eab87 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + diff --git a/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/all-wcprops new file mode 100644 index 0000000..4f985e4 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 88 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/hbase/src/main/resources/META-INF/spring +END +mailbox-hbase.xml +K 25 +svn:wc:ra_dav:version-url +V 106 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml +END diff --git a/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/entries b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/entries new file mode 100644 index 0000000..0f4a0ee --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/main/resources/META-INF/spring +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-hbase.xml +file + + + + +2013-09-02T02:54:38.000000Z +c985306eb6fa97be177532af3b99f463 +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +2828 + diff --git a/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/text-base/mailbox-hbase.xml.svn-base b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/text-base/mailbox-hbase.xml.svn-base new file mode 100644 index 0000000..534fcf6 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/.svn/text-base/mailbox-hbase.xml.svn-base @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml new file mode 100644 index 0000000..534fcf6 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/main/resources/META-INF/spring/mailbox-hbase.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..c82bdf1 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1164981/james/mailbox/trunk/hbase/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 81 +/repos/asf/!svn/ver/1164981/james/mailbox/trunk/hbase/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/hbase/src/reporting-site/.svn/entries b/james/apache-james-mailbox/hbase/src/reporting-site/.svn/entries new file mode 100644 index 0000000..f9973bd --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-09-04T09:49:14.713666Z +1164981 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:38.000000Z +5bde8261948ff1881960902a322aa196 +2011-09-04T09:49:14.713666Z +1164981 +ieugen + + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/hbase/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/hbase/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/src/reporting-site/site.xml b/james/apache-james-mailbox/hbase/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/src/site/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/site/.svn/all-wcprops new file mode 100644 index 0000000..403d07c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1180347/james/mailbox/trunk/hbase/src/site +END diff --git a/james/apache-james-mailbox/hbase/src/site/.svn/entries b/james/apache-james-mailbox/hbase/src/site/.svn/entries new file mode 100644 index 0000000..01e7219 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/site +http://svn.apache.org/repos/asf + + + +2011-10-08T10:05:21.497039Z +1180347 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +resources +dir + diff --git a/james/apache-james-mailbox/hbase/src/site/resources/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/site/resources/.svn/all-wcprops new file mode 100644 index 0000000..5ff0d61 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1180347/james/mailbox/trunk/hbase/src/site/resources +END diff --git a/james/apache-james-mailbox/hbase/src/site/resources/.svn/entries b/james/apache-james-mailbox/hbase/src/site/resources/.svn/entries new file mode 100644 index 0000000..db12a43 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/site/resources +http://svn.apache.org/repos/asf + + + +2011-10-08T10:05:21.497039Z +1180347 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +images +dir + diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/all-wcprops new file mode 100644 index 0000000..e0eac89 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1180347/james/mailbox/trunk/hbase/src/site/resources/images +END +james-hbase-mailbox-schema.svg +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1180347/james/mailbox/trunk/hbase/src/site/resources/images/james-hbase-mailbox-schema.svg +END +james-hbase-mailbox-schema.png +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1180347/james/mailbox/trunk/hbase/src/site/resources/images/james-hbase-mailbox-schema.png +END +james-hbase-mailbox-schema.mm +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1180347/james/mailbox/trunk/hbase/src/site/resources/images/james-hbase-mailbox-schema.mm +END diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/entries b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/entries new file mode 100644 index 0000000..31cc12b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/site/resources/images +http://svn.apache.org/repos/asf + + + +2011-10-08T10:05:21.497039Z +1180347 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james-hbase-mailbox-schema.svg +file + + + + +2013-09-02T02:54:38.000000Z +b46701a4d173040b27b76f8497120edd +2011-10-08T10:05:21.497039Z +1180347 +ieugen + + + + + + + + + + + + + + + + + + + + + +102104 + +james-hbase-mailbox-schema.png +file + + + + +2013-09-02T02:54:38.000000Z +f77e19571931aa49f1c3c43f67eae780 +2011-10-08T10:05:21.497039Z +1180347 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +118995 + +james-hbase-mailbox-schema.mm +file + + + + +2013-09-02T02:54:38.000000Z +7ca19afedbca78323ba04a607b2a70d3 +2011-10-08T10:05:21.497039Z +1180347 +ieugen + + + + + + + + + + + + + + + + + + + + + +7873 + diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/prop-base/james-hbase-mailbox-schema.png.svn-base b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/prop-base/james-hbase-mailbox-schema.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/prop-base/james-hbase-mailbox-schema.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.mm.svn-base b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.mm.svn-base new file mode 100644 index 0000000..eb89513 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.mm.svn-base @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.png.svn-base b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..0f63cd10f67c6127265d9a9aceeb88e21db1e404 GIT binary patch literal 118995 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 E14K%*TL1t6 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.svg.svn-base b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.svg.svn-base new file mode 100644 index 0000000..83e0697 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/images/.svn/text-base/james-hbase-mailbox-schema.svg.svn-base @@ -0,0 +1,842 @@ + + +James HBase Mailbox SchemaSUBSCRIPTIONS TableRow Keythe user name (String)Data Column FamilyColumnsEach existing column name represents  a user subscription to that particular mailbox.MAILBOXES TableRow KeyCurrent row key is formed from the mailbox  ID which is implemented with UUIDData Column FamilyColumnsMailbox Message CountMailbox Highest ModSeqMailbox UIDValidityMailbox Last UidMailbox NamespaceMailbox UserMailbox NameMESSAGES TableMessage Body Column FamilyColumnsData is stored in chunks and written/read by ChunkOutputStream and CunkInputStream classes. Column qualifiers are long values that start from 1.Contains the message body. This column family will be merged with the headers column family.Message Header Column FamilyColumnsData is stored in chunks and written/read by ChunkOutputStream and CunkInputStream classes. Column qualifiers are long values that start from 1.Contains the message headers. This column family will merge with the message body column family.Message MetaInformation Column FamilyColumnsMessage properties columns: record message properties. They begin with 'p:' prefix. The qualifier stores the properti namespace and local name separated by '%%' and the value as the column value.User flags columns: record message user flags if present. They begin with 'uf:' prefix and are followed by the user flag name. Value is not important.System flags columns: record the message system flags if present. Can be one of: sf:A, sf:DE, sf:DR, sf:F, sf:R, sf:S, sf:U. Value is the MARKER (currently X).Meta columns are prefix with 'p:' and record: body size, content size, date, lineCount (if text message), message modSequence, media type, and media sub type.Contains information, about the message : flags, properties, message size, etc. This information is stored in prefixed columns based on the information type. (e.g. system flags columns begin with 'sf:', properties begin with 'p:'), etc.Row KeyRow key is formed from mailbox ID, which is UUID folowed by message UID in reverse (Long.MAX_VALUE - uid) diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.mm b/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.mm new file mode 100644 index 0000000..eb89513 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.mm @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.png b/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.png new file mode 100644 index 0000000000000000000000000000000000000000..0f63cd10f67c6127265d9a9aceeb88e21db1e404 GIT binary patch literal 118995 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 E14K%*TL1t6 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.svg b/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.svg new file mode 100644 index 0000000..83e0697 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/site/resources/images/james-hbase-mailbox-schema.svg @@ -0,0 +1,842 @@ + + +James HBase Mailbox SchemaSUBSCRIPTIONS TableRow Keythe user name (String)Data Column FamilyColumnsEach existing column name represents  a user subscription to that particular mailbox.MAILBOXES TableRow KeyCurrent row key is formed from the mailbox  ID which is implemented with UUIDData Column FamilyColumnsMailbox Message CountMailbox Highest ModSeqMailbox UIDValidityMailbox Last UidMailbox NamespaceMailbox UserMailbox NameMESSAGES TableMessage Body Column FamilyColumnsData is stored in chunks and written/read by ChunkOutputStream and CunkInputStream classes. Column qualifiers are long values that start from 1.Contains the message body. This column family will be merged with the headers column family.Message Header Column FamilyColumnsData is stored in chunks and written/read by ChunkOutputStream and CunkInputStream classes. Column qualifiers are long values that start from 1.Contains the message headers. This column family will merge with the message body column family.Message MetaInformation Column FamilyColumnsMessage properties columns: record message properties. They begin with 'p:' prefix. The qualifier stores the properti namespace and local name separated by '%%' and the value as the column value.User flags columns: record message user flags if present. They begin with 'uf:' prefix and are followed by the user flag name. Value is not important.System flags columns: record the message system flags if present. Can be one of: sf:A, sf:DE, sf:DR, sf:F, sf:R, sf:S, sf:U. Value is the MARKER (currently X).Meta columns are prefix with 'p:' and record: body size, content size, date, lineCount (if text message), message modSequence, media type, and media sub type.Contains information, about the message : flags, properties, message size, etc. This information is stored in prefixed columns based on the information type. (e.g. system flags columns begin with 'sf:', properties begin with 'p:'), etc.Row KeyRow key is formed from mailbox ID, which is UUID folowed by message UID in reverse (Long.MAX_VALUE - uid) diff --git a/james/apache-james-mailbox/hbase/src/test/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/.svn/all-wcprops new file mode 100644 index 0000000..a7679aa --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test +END diff --git a/james/apache-james-mailbox/hbase/src/test/.svn/entries b/james/apache-james-mailbox/hbase/src/test/.svn/entries new file mode 100644 index 0000000..b48e41e --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test +http://svn.apache.org/repos/asf + + + +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/hbase/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..a26fd00 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 67 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test/java +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/.svn/entries new file mode 100644 index 0000000..3eafc3a --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java +http://svn.apache.org/repos/asf + + + +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..053f263 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 71 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test/java/org +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/.svn/entries new file mode 100644 index 0000000..442a919 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org +http://svn.apache.org/repos/asf + + + +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..55cddc9 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 78 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..c32dfc0 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..5adc617 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 84 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..aa6889b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..c07c059 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 92 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..413406b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +hbase +dir + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/all-wcprops new file mode 100644 index 0000000..bdcc593 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 98 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase +END +HBaseMailboxSessionMapperFactoryTest.java +K 25 +svn:wc:ra_dav:version-url +V 140 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactoryTest.java +END +HBaseUtilsTest.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseUtilsTest.java +END +HBaseClusterSingleton.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1372348/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseClusterSingleton.java +END +HBaseMailboxManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxManagerTest.java +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/entries new file mode 100644 index 0000000..a5a325d --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/entries @@ -0,0 +1,170 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase +http://svn.apache.org/repos/asf + + + +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +HBaseMailboxSessionMapperFactoryTest.java +file + + + + +2013-09-02T02:54:38.000000Z +0d369c9199dbbb7d3cdb4f7ea456ae2c +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +5675 + +mail +dir + +HBaseUtilsTest.java +file + + + + +2013-09-02T02:54:38.000000Z +c5c9b7fce678b2362f1a514f45ec75f5 +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +6408 + +HBaseClusterSingleton.java +file + + + + +2013-09-02T02:54:38.000000Z +2b38c7594e4196cac6ffa2647c4b93fe +2012-08-13T10:01:27.486313Z +1372348 +eric + + + + + + + + + + + + + + + + + + + + + +7246 + +HBaseMailboxManagerTest.java +file + + + + +2013-09-02T02:54:38.000000Z +35e50b322cd488f425477e4aaa07f710 +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +4739 + +user +dir + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseClusterSingleton.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseClusterSingleton.java.svn-base new file mode 100644 index 0000000..42f1835 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseClusterSingleton.java.svn-base @@ -0,0 +1,181 @@ +/**************************************************************** + * 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 java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.MiniHBaseCluster; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class that will creates a single instance of HBase MiniCluster. + */ +public final class HBaseClusterSingleton { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseClusterSingleton.class); + private static final HBaseTestingUtility htu = new HBaseTestingUtility(); + private static HBaseClusterSingleton cluster = null; + private MiniHBaseCluster hbaseCluster; + private Configuration conf; + + /** + * Builds a MiniCluster instance. + * @return the {@link HBaseClusterSingleton} instance + * @throws RuntimeException + */ + public static synchronized HBaseClusterSingleton build() + throws RuntimeException { + LOG.info("Retrieving cluster instance."); + if (cluster == null) { + cluster = new HBaseClusterSingleton(); + } + return cluster; + } + + private HBaseClusterSingleton() throws RuntimeException { + + // Workaround for HBASE-5711, we need to set config value dfs.datanode.data.dir.perm + // equal to the permissions of the temp dirs on the filesystem. These temp dirs were + // probably created using this process' umask. So we guess the temp dir permissions as + // 0777 & ~umask, and use that to set the config value. + try { + Process process = Runtime.getRuntime().exec("/bin/sh -c umask"); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + int rc = process.waitFor(); + if(rc == 0) { + String umask = br.readLine(); + + int umaskBits = Integer.parseInt(umask, 8); + int permBits = 0777 & ~umaskBits; + String perms = Integer.toString(permBits, 8); + + LOG.info("Setting dfs.datanode.data.dir.perm to " + perms); + htu.getConfiguration().set("dfs.datanode.data.dir.perm", perms); + } else { + LOG.warn("Failed running umask command in a shell, nonzero return value"); + } + } catch (Exception e) { + // ignore errors, we might not be running on POSIX, or "sh" might not be on the path + LOG.warn("Couldn't get umask", e); + } + + htu.getConfiguration().setBoolean("dfs.support.append", true); + htu.getConfiguration().setInt("zookeeper.session.timeout", 20000); +// htu.getConfiguration().setInt("hbase.client.retries.number", 2); + try { + hbaseCluster = htu.startMiniCluster(); + LOG.info("After cluster start-up."); + hbaseCluster.waitForActiveAndReadyMaster(); + LOG.info("After active and ready."); +// ensureTables(); + conf = hbaseCluster.getConfiguration(); + } catch (Exception ex) { + throw new RuntimeException("Minicluster not starting."); + } finally { + if (hbaseCluster != null) { + // add a shutdown hook for shuting down the minicluster. + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + + @Override + public void run() { + try { + hbaseCluster.shutdown(); + } catch (IOException e) { + throw new RuntimeException("Exception shuting down cluster."); + } + } + })); + } + } + } + + /** + * Return a configuration for the runnning MiniCluster. + * @return + */ + public Configuration getConf() { + return conf; + } + + /** + * Creates a table with the specified column families. + * @param tableName the table name + * @param columnFamilies the colum families + * @throws IOException + */ + public void ensureTable(String tableName, String... columnFamilies) throws IOException { + byte[][] cfs = new byte[columnFamilies.length][]; + for (int i = 0; i < columnFamilies.length; i++) { + cfs[i] = Bytes.toBytes(columnFamilies[i]); + } + ensureTable(Bytes.toBytes(tableName), cfs); + } + + /** + * Creates a table with the specified column families. + * @param tableName the table name + * @param cfs the column families + * @throws IOException + */ + public void ensureTable(byte[] tableName, byte[][] cfs) throws IOException { + HBaseAdmin admin = htu.getHBaseAdmin(); + if (!admin.tableExists(tableName)) { + htu.createTable(tableName, cfs); + } + } + + /** + * Delete all rows from specified table. + * + * @param tableName + */ + public void clearTable(String tableName) { + HTable table = null; + ResultScanner scanner = null; + try { + table = new HTable(conf, tableName); + Scan scan = new Scan(); + scan.setCaching(1000); + scanner = table.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + Delete delete = new Delete(result.getRow()); + table.delete(delete); + } + } catch (IOException ex) { + LOG.info("Exception clearing table {}", tableName); + } finally { + IOUtils.closeStream(scanner); + IOUtils.closeStream(table); + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManagerTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManagerTest.java.svn-base new file mode 100644 index 0000000..2021a13 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxManagerTest.java.svn-base @@ -0,0 +1,114 @@ +/**************************************************************** + * 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 java.io.IOException; +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.HBaseModSeqProvider; +import org.apache.james.mailbox.hbase.mail.HBaseUidProvider; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * HBaseMailboxManagerTest that extends the StoreMailboxManagerTest. + * + */ +public class HBaseMailboxManagerTest extends AbstractMailboxManagerTest { + + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + + /** + * Setup the mailboxManager. + + * @throws Exception + */ + @Before + public void setup() throws Exception { + ensureTables(); + clearTables(); + createMailboxManager(); + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + /** + * Close the system session and entityManagerFactory + * + * @throws MailboxException + * @throws BadCredentialsException + */ + @After + public void tearDown() throws Exception { + deleteAllMailboxes(); + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + } + + /* (non-Javadoc)i deve + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() throws MailboxException { + final HBaseUidProvider uidProvider = new HBaseUidProvider(CLUSTER.getConf()); + final HBaseModSeqProvider modSeqProvider = new HBaseModSeqProvider(CLUSTER.getConf()); + final HBaseMailboxSessionMapperFactory mapperFactory = new HBaseMailboxSessionMapperFactory(CLUSTER.getConf(), + uidProvider, modSeqProvider); + + final MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + final GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + final HBaseMailboxManager manager = new HBaseMailboxManager(mapperFactory, null, aclResolver, + groupMembershipResolver); + manager.init(); + + setMailboxManager(manager); + + deleteAllMailboxes(); + } + + private void deleteAllMailboxes() throws BadCredentialsException, MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + try { + ((HBaseMailboxManager) mailboxManager).deleteEverything(session); + } catch (MailboxException e) { + e.printStackTrace(); + } + session.close(); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactoryTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactoryTest.java.svn-base new file mode 100644 index 0000000..25be08e --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseMailboxSessionMapperFactoryTest.java.svn-base @@ -0,0 +1,135 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.james.mailbox.MailboxSession; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.HBaseModSeqProvider; +import org.apache.james.mailbox.hbase.mail.HBaseUidProvider; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The MailboxSessionMapperFactory test. + * + */ +public class HBaseMailboxSessionMapperFactoryTest { + + private final static Logger LOG = LoggerFactory.getLogger(HBaseMailboxSessionMapperFactoryTest.class); + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + + @Before + public void beforeMethod() throws IOException { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + /** + * Test of createMessageMapper method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testCreateMessageMapper() throws Exception { + LOG.info("createMessageMapper"); + MailboxSession session = null; + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, null); + MessageMapper messageMapper = instance.createMessageMapper(session); + assertNotNull(messageMapper); + assertTrue(messageMapper instanceof MessageMapper); + } + + /** + * Test of createMailboxMapper method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testCreateMailboxMapper() throws Exception { + LOG.info("createMailboxMapper"); + MailboxSession session = null; + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, null); + MailboxMapper mailboxMapper = instance.createMailboxMapper(session); + assertNotNull(mailboxMapper); + assertTrue(mailboxMapper instanceof MailboxMapper); + } + + /** + * Test of createSubscriptionMapper method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testCreateSubscriptionMapper() throws Exception { + LOG.info("createSubscriptionMapper"); + MailboxSession session = null; + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, null); + SubscriptionMapper subscriptionMapper = instance.createSubscriptionMapper(session); + assertNotNull(subscriptionMapper); + assertTrue(subscriptionMapper instanceof SubscriptionMapper); + } + + /** + * Test of getModSeqProvider method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testGetModSeqProvider() { + LOG.info("getModSeqProvider"); + ModSeqProvider expResult = new HBaseModSeqProvider(conf); + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, expResult); + ModSeqProvider result = instance.getModSeqProvider(); + assertEquals(expResult, result); + } + + /** + * Test of getUidProvider method, of class HBaseMailboxSessionMapperFactory. + */ + @Test + public void testGetUidProvider() { + LOG.info("getUidProvider"); + UidProvider expResult = new HBaseUidProvider(conf); + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, expResult, null); + UidProvider result = instance.getUidProvider(); + assertEquals(expResult, result); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtilsTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtilsTest.java.svn-base new file mode 100644 index 0000000..fd8177b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/.svn/text-base/HBaseUtilsTest.java.svn-base @@ -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.hbase; + +import java.util.Date; +import java.util.UUID; +import javax.mail.Flags; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.util.Bytes; +import static org.apache.james.mailbox.hbase.FlagConvertor.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.PropertyConvertor.getProperty; +import static org.apache.james.mailbox.hbase.PropertyConvertor.getValue; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import org.apache.james.mailbox.store.mail.model.impl.SimpleProperty; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * Tests for HBase Mailbox store utility methods . + */ +public class HBaseUtilsTest { + + /** + * Test of mailboxRowKey method, of class HBaseMailbox. + */ + @Test + public void testRowKey_All() { + System.out.println("getRowKey and UUIDFromRowKey"); + final HBaseMailbox mailbox = new HBaseMailbox(new MailboxPath("gsoc", "ieugen", "INBOX"), 1234); + UUID uuid = mailbox.getMailboxId(); + byte[] expResult = mailboxRowKey(uuid); + byte[] result = mailboxRowKey(mailbox.getMailboxId()); + assertArrayEquals(expResult, result); + + UUID newUUID = UUIDFromRowKey(result); + assertEquals(uuid, newUUID); + + newUUID = UUIDFromRowKey(expResult); + assertEquals(uuid, newUUID); + } + + /** + * Test of metadataToPut method, of class HBaseMailbox. + */ + @Test + public void testMailboxToPut() { + System.out.println("mailboxToPut"); + final HBaseMailbox instance = new HBaseMailbox(new MailboxPath("gsoc", "ieugen", "INBOX"), 1234); + + Put result = toPut(instance); + assertArrayEquals(mailboxRowKey(instance.getMailboxId()), result.getRow()); + assertTrue(result.has(MAILBOX_CF, MAILBOX_USER, Bytes.toBytes(instance.getUser()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_NAME, Bytes.toBytes(instance.getName()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_NAMESPACE, Bytes.toBytes(instance.getNamespace()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_UIDVALIDITY, Bytes.toBytes(instance.getUidValidity()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_LASTUID, Bytes.toBytes(instance.getLastUid()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, Bytes.toBytes(instance.getHighestModSeq()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_MESSAGE_COUNT, Bytes.toBytes(0L))); + } + + /** + * Test of metadataToPut method for message. + */ +// @Test + public void testMessageToPut() { + System.out.println("messageToPut"); + // left to implement + } + + @Test + public void testPropertyToBytes() { + final Property prop1 = new SimpleProperty("nspace", "localName", "test"); + byte[] value = getValue(prop1); + final Property prop2 = getProperty(value); + assertEquals(prop1.getNamespace(), prop2.getNamespace()); + assertEquals(prop1.getLocalName(), prop2.getLocalName()); + assertEquals(prop1.getValue(), prop2.getValue()); + } + + @Test + public void testSubscriptionToPut() { + System.out.println("subscription toPut"); + Subscription subscription = new SimpleSubscription("ieugen", "INBOX"); + Put put = toPut(subscription); + assertArrayEquals(Bytes.toBytes(subscription.getUser()), put.getRow()); + assertTrue(put.has(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()), MARKER_PRESENT)); + } + + @Test + public void testFlagsToPut() { + System.out.println("flagsToPut"); + + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DRAFT); + flags.add(Flags.Flag.RECENT); + flags.add(Flags.Flag.FLAGGED); + flags.add("userFlag1"); + flags.add("userFlag2"); + UUID uuid = UUID.randomUUID(); + final SimpleMessage message = new SimpleMessage(new Date(), 100, 10, null, flags, new PropertyBuilder(), uuid); + Put put = flagsToPut(message, flags); + //test for the system flags + assertTrue(put.has(MESSAGES_META_CF, FLAGS_SEEN, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_RECENT, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_MISSING)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_DELETED, MARKER_MISSING)); + assertTrue(put.has(MESSAGES_META_CF, userFlagToBytes("userFlag1"), MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, userFlagToBytes("userFlag2"), MARKER_PRESENT)); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseClusterSingleton.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseClusterSingleton.java new file mode 100644 index 0000000..42f1835 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseClusterSingleton.java @@ -0,0 +1,181 @@ +/**************************************************************** + * 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 java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.MiniHBaseCluster; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class that will creates a single instance of HBase MiniCluster. + */ +public final class HBaseClusterSingleton { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseClusterSingleton.class); + private static final HBaseTestingUtility htu = new HBaseTestingUtility(); + private static HBaseClusterSingleton cluster = null; + private MiniHBaseCluster hbaseCluster; + private Configuration conf; + + /** + * Builds a MiniCluster instance. + * @return the {@link HBaseClusterSingleton} instance + * @throws RuntimeException + */ + public static synchronized HBaseClusterSingleton build() + throws RuntimeException { + LOG.info("Retrieving cluster instance."); + if (cluster == null) { + cluster = new HBaseClusterSingleton(); + } + return cluster; + } + + private HBaseClusterSingleton() throws RuntimeException { + + // Workaround for HBASE-5711, we need to set config value dfs.datanode.data.dir.perm + // equal to the permissions of the temp dirs on the filesystem. These temp dirs were + // probably created using this process' umask. So we guess the temp dir permissions as + // 0777 & ~umask, and use that to set the config value. + try { + Process process = Runtime.getRuntime().exec("/bin/sh -c umask"); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + int rc = process.waitFor(); + if(rc == 0) { + String umask = br.readLine(); + + int umaskBits = Integer.parseInt(umask, 8); + int permBits = 0777 & ~umaskBits; + String perms = Integer.toString(permBits, 8); + + LOG.info("Setting dfs.datanode.data.dir.perm to " + perms); + htu.getConfiguration().set("dfs.datanode.data.dir.perm", perms); + } else { + LOG.warn("Failed running umask command in a shell, nonzero return value"); + } + } catch (Exception e) { + // ignore errors, we might not be running on POSIX, or "sh" might not be on the path + LOG.warn("Couldn't get umask", e); + } + + htu.getConfiguration().setBoolean("dfs.support.append", true); + htu.getConfiguration().setInt("zookeeper.session.timeout", 20000); +// htu.getConfiguration().setInt("hbase.client.retries.number", 2); + try { + hbaseCluster = htu.startMiniCluster(); + LOG.info("After cluster start-up."); + hbaseCluster.waitForActiveAndReadyMaster(); + LOG.info("After active and ready."); +// ensureTables(); + conf = hbaseCluster.getConfiguration(); + } catch (Exception ex) { + throw new RuntimeException("Minicluster not starting."); + } finally { + if (hbaseCluster != null) { + // add a shutdown hook for shuting down the minicluster. + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + + @Override + public void run() { + try { + hbaseCluster.shutdown(); + } catch (IOException e) { + throw new RuntimeException("Exception shuting down cluster."); + } + } + })); + } + } + } + + /** + * Return a configuration for the runnning MiniCluster. + * @return + */ + public Configuration getConf() { + return conf; + } + + /** + * Creates a table with the specified column families. + * @param tableName the table name + * @param columnFamilies the colum families + * @throws IOException + */ + public void ensureTable(String tableName, String... columnFamilies) throws IOException { + byte[][] cfs = new byte[columnFamilies.length][]; + for (int i = 0; i < columnFamilies.length; i++) { + cfs[i] = Bytes.toBytes(columnFamilies[i]); + } + ensureTable(Bytes.toBytes(tableName), cfs); + } + + /** + * Creates a table with the specified column families. + * @param tableName the table name + * @param cfs the column families + * @throws IOException + */ + public void ensureTable(byte[] tableName, byte[][] cfs) throws IOException { + HBaseAdmin admin = htu.getHBaseAdmin(); + if (!admin.tableExists(tableName)) { + htu.createTable(tableName, cfs); + } + } + + /** + * Delete all rows from specified table. + * + * @param tableName + */ + public void clearTable(String tableName) { + HTable table = null; + ResultScanner scanner = null; + try { + table = new HTable(conf, tableName); + Scan scan = new Scan(); + scan.setCaching(1000); + scanner = table.getScanner(scan); + Result result; + while ((result = scanner.next()) != null) { + Delete delete = new Delete(result.getRow()); + table.delete(delete); + } + } catch (IOException ex) { + LOG.info("Exception clearing table {}", tableName); + } finally { + IOUtils.closeStream(scanner); + IOUtils.closeStream(table); + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxManagerTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxManagerTest.java new file mode 100644 index 0000000..2021a13 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxManagerTest.java @@ -0,0 +1,114 @@ +/**************************************************************** + * 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 java.io.IOException; +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.HBaseModSeqProvider; +import org.apache.james.mailbox.hbase.mail.HBaseUidProvider; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * HBaseMailboxManagerTest that extends the StoreMailboxManagerTest. + * + */ +public class HBaseMailboxManagerTest extends AbstractMailboxManagerTest { + + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + + /** + * Setup the mailboxManager. + + * @throws Exception + */ + @Before + public void setup() throws Exception { + ensureTables(); + clearTables(); + createMailboxManager(); + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + /** + * Close the system session and entityManagerFactory + * + * @throws MailboxException + * @throws BadCredentialsException + */ + @After + public void tearDown() throws Exception { + deleteAllMailboxes(); + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + } + + /* (non-Javadoc)i deve + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() throws MailboxException { + final HBaseUidProvider uidProvider = new HBaseUidProvider(CLUSTER.getConf()); + final HBaseModSeqProvider modSeqProvider = new HBaseModSeqProvider(CLUSTER.getConf()); + final HBaseMailboxSessionMapperFactory mapperFactory = new HBaseMailboxSessionMapperFactory(CLUSTER.getConf(), + uidProvider, modSeqProvider); + + final MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + final GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + final HBaseMailboxManager manager = new HBaseMailboxManager(mapperFactory, null, aclResolver, + groupMembershipResolver); + manager.init(); + + setMailboxManager(manager); + + deleteAllMailboxes(); + } + + private void deleteAllMailboxes() throws BadCredentialsException, MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + try { + ((HBaseMailboxManager) mailboxManager).deleteEverything(session); + } catch (MailboxException e) { + e.printStackTrace(); + } + session.close(); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactoryTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactoryTest.java new file mode 100644 index 0000000..25be08e --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseMailboxSessionMapperFactoryTest.java @@ -0,0 +1,135 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.james.mailbox.MailboxSession; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.HBaseModSeqProvider; +import org.apache.james.mailbox.hbase.mail.HBaseUidProvider; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The MailboxSessionMapperFactory test. + * + */ +public class HBaseMailboxSessionMapperFactoryTest { + + private final static Logger LOG = LoggerFactory.getLogger(HBaseMailboxSessionMapperFactoryTest.class); + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + + @Before + public void beforeMethod() throws IOException { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + /** + * Test of createMessageMapper method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testCreateMessageMapper() throws Exception { + LOG.info("createMessageMapper"); + MailboxSession session = null; + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, null); + MessageMapper messageMapper = instance.createMessageMapper(session); + assertNotNull(messageMapper); + assertTrue(messageMapper instanceof MessageMapper); + } + + /** + * Test of createMailboxMapper method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testCreateMailboxMapper() throws Exception { + LOG.info("createMailboxMapper"); + MailboxSession session = null; + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, null); + MailboxMapper mailboxMapper = instance.createMailboxMapper(session); + assertNotNull(mailboxMapper); + assertTrue(mailboxMapper instanceof MailboxMapper); + } + + /** + * Test of createSubscriptionMapper method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testCreateSubscriptionMapper() throws Exception { + LOG.info("createSubscriptionMapper"); + MailboxSession session = null; + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, null); + SubscriptionMapper subscriptionMapper = instance.createSubscriptionMapper(session); + assertNotNull(subscriptionMapper); + assertTrue(subscriptionMapper instanceof SubscriptionMapper); + } + + /** + * Test of getModSeqProvider method, of class + * HBaseMailboxSessionMapperFactory. + */ + @Test + public void testGetModSeqProvider() { + LOG.info("getModSeqProvider"); + ModSeqProvider expResult = new HBaseModSeqProvider(conf); + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, null, expResult); + ModSeqProvider result = instance.getModSeqProvider(); + assertEquals(expResult, result); + } + + /** + * Test of getUidProvider method, of class HBaseMailboxSessionMapperFactory. + */ + @Test + public void testGetUidProvider() { + LOG.info("getUidProvider"); + UidProvider expResult = new HBaseUidProvider(conf); + HBaseMailboxSessionMapperFactory instance = new HBaseMailboxSessionMapperFactory(conf, expResult, null); + UidProvider result = instance.getUidProvider(); + assertEquals(expResult, result); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseUtilsTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseUtilsTest.java new file mode 100644 index 0000000..fd8177b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/HBaseUtilsTest.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.hbase; + +import java.util.Date; +import java.util.UUID; +import javax.mail.Flags; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.util.Bytes; +import static org.apache.james.mailbox.hbase.FlagConvertor.*; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import static org.apache.james.mailbox.hbase.HBaseUtils.*; +import static org.apache.james.mailbox.hbase.PropertyConvertor.getProperty; +import static org.apache.james.mailbox.hbase.PropertyConvertor.getValue; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import org.apache.james.mailbox.store.mail.model.impl.SimpleProperty; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * Tests for HBase Mailbox store utility methods . + */ +public class HBaseUtilsTest { + + /** + * Test of mailboxRowKey method, of class HBaseMailbox. + */ + @Test + public void testRowKey_All() { + System.out.println("getRowKey and UUIDFromRowKey"); + final HBaseMailbox mailbox = new HBaseMailbox(new MailboxPath("gsoc", "ieugen", "INBOX"), 1234); + UUID uuid = mailbox.getMailboxId(); + byte[] expResult = mailboxRowKey(uuid); + byte[] result = mailboxRowKey(mailbox.getMailboxId()); + assertArrayEquals(expResult, result); + + UUID newUUID = UUIDFromRowKey(result); + assertEquals(uuid, newUUID); + + newUUID = UUIDFromRowKey(expResult); + assertEquals(uuid, newUUID); + } + + /** + * Test of metadataToPut method, of class HBaseMailbox. + */ + @Test + public void testMailboxToPut() { + System.out.println("mailboxToPut"); + final HBaseMailbox instance = new HBaseMailbox(new MailboxPath("gsoc", "ieugen", "INBOX"), 1234); + + Put result = toPut(instance); + assertArrayEquals(mailboxRowKey(instance.getMailboxId()), result.getRow()); + assertTrue(result.has(MAILBOX_CF, MAILBOX_USER, Bytes.toBytes(instance.getUser()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_NAME, Bytes.toBytes(instance.getName()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_NAMESPACE, Bytes.toBytes(instance.getNamespace()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_UIDVALIDITY, Bytes.toBytes(instance.getUidValidity()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_LASTUID, Bytes.toBytes(instance.getLastUid()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_HIGHEST_MODSEQ, Bytes.toBytes(instance.getHighestModSeq()))); + assertTrue(result.has(MAILBOX_CF, MAILBOX_MESSAGE_COUNT, Bytes.toBytes(0L))); + } + + /** + * Test of metadataToPut method for message. + */ +// @Test + public void testMessageToPut() { + System.out.println("messageToPut"); + // left to implement + } + + @Test + public void testPropertyToBytes() { + final Property prop1 = new SimpleProperty("nspace", "localName", "test"); + byte[] value = getValue(prop1); + final Property prop2 = getProperty(value); + assertEquals(prop1.getNamespace(), prop2.getNamespace()); + assertEquals(prop1.getLocalName(), prop2.getLocalName()); + assertEquals(prop1.getValue(), prop2.getValue()); + } + + @Test + public void testSubscriptionToPut() { + System.out.println("subscription toPut"); + Subscription subscription = new SimpleSubscription("ieugen", "INBOX"); + Put put = toPut(subscription); + assertArrayEquals(Bytes.toBytes(subscription.getUser()), put.getRow()); + assertTrue(put.has(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()), MARKER_PRESENT)); + } + + @Test + public void testFlagsToPut() { + System.out.println("flagsToPut"); + + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DRAFT); + flags.add(Flags.Flag.RECENT); + flags.add(Flags.Flag.FLAGGED); + flags.add("userFlag1"); + flags.add("userFlag2"); + UUID uuid = UUID.randomUUID(); + final SimpleMessage message = new SimpleMessage(new Date(), 100, 10, null, flags, new PropertyBuilder(), uuid); + Put put = flagsToPut(message, flags); + //test for the system flags + assertTrue(put.has(MESSAGES_META_CF, FLAGS_SEEN, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_DRAFT, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_RECENT, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_FLAGGED, MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_ANSWERED, MARKER_MISSING)); + assertTrue(put.has(MESSAGES_META_CF, FLAGS_DELETED, MARKER_MISSING)); + assertTrue(put.has(MESSAGES_META_CF, userFlagToBytes("userFlag1"), MARKER_PRESENT)); + assertTrue(put.has(MESSAGES_META_CF, userFlagToBytes("userFlag2"), MARKER_PRESENT)); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops new file mode 100644 index 0000000..d0d47bb --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail +END +HBaseMailboxMapperTest.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapperTest.java +END +HBaseUidAndModSeqProviderTest.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseUidAndModSeqProviderTest.java +END +HBaseMessageMapperTest.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapperTest.java +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/entries new file mode 100644 index 0000000..8b36359 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/entries @@ -0,0 +1,133 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail +http://svn.apache.org/repos/asf + + + +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + +HBaseMailboxMapperTest.java +file + + + + +2013-09-02T02:54:38.000000Z +9abb96a2368033d88d0fd372c8dccd20 +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +12577 + +HBaseUidAndModSeqProviderTest.java +file + + + + +2013-09-02T02:54:38.000000Z +58376789e9a6c0ed3ca1b990f8a429dc +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +6783 + +HBaseMessageMapperTest.java +file + + + + +2013-09-02T02:54:38.000000Z +b58816c5978f76face3917454e72109a +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +8999 + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapperTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapperTest.java.svn-base new file mode 100644 index 0000000..f76d8f9 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMailboxMapperTest.java.svn-base @@ -0,0 +1,317 @@ +/** + * ************************************************************** + * 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.mail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxFromResult; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxRowKey; +import org.apache.james.mailbox.hbase.io.ChunkInputStream; +import org.apache.james.mailbox.hbase.io.ChunkOutputStream; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * HBaseMailboxMapper unit tests. + * + */ +public class HBaseMailboxMapperTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseMailboxMapperTest.class); + public static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + private static HBaseMailboxMapper mapper; + private static List mailboxList; + private static List pathsList; + private static final int NAMESPACES = 5; + private static final int USERS = 5; + private static final int MAILBOX_NO = 5; + private static final char SEPARATOR = '%'; + + @Before + public void setUp() throws Exception { + ensureTables(); + // start the test cluster + clearTables(); + conf = CLUSTER.getConf(); + fillMailboxList(); + mapper = new HBaseMailboxMapper(conf); + for (HBaseMailbox mailbox : mailboxList) { + mapper.save(mailbox); + } + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + /** + * Test an ordered scenario with list, delete... methods. + * + * @throws Exception + */ + @Test + public void testMailboxMapperScenario() throws Exception { + testFindMailboxByPath(); + testFindMailboxWithPathLike(); + testList(); + testSave(); + testDelete(); + testHasChildren(); +// testDeleteAllMemberships(); // Ignore this test + testDeleteAllMailboxes(); + testChunkStream(); + } + + /** + * Test of findMailboxByPath method, of class HBaseMailboxMapper. + */ + private void testFindMailboxByPath() throws Exception { + LOG.info("findMailboxByPath"); + HBaseMailbox mailbox; + for (MailboxPath path : pathsList) { + LOG.info("Searching for " + path); + mailbox = (HBaseMailbox) mapper.findMailboxByPath(path); + assertEquals(path, new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); + } + } + + /** + * Test of findMailboxWithPathLike method, of class HBaseMailboxMapper. + */ + private void testFindMailboxWithPathLike() throws Exception { + LOG.info("findMailboxWithPathLike"); + MailboxPath path = pathsList.get(pathsList.size() / 2); + + List> result = mapper.findMailboxWithPathLike(path); + assertEquals(1, result.size()); + + int start = 3; + int end = 7; + MailboxPath newPath; + + for (int i = start; i < end; i++) { + newPath = new MailboxPath(path); + newPath.setName(i + newPath.getName() + " " + i); + // test for paths with null user + if (i % 2 == 0) { + newPath.setUser(null); + } + addMailbox(new HBaseMailbox(newPath, 1234)); + } + result = mapper.findMailboxWithPathLike(path); + assertEquals(end - start + 1, result.size()); + } + + /** + * Test of list method, of class HBaseMailboxMapper. + */ + private void testList() throws Exception { + LOG.info("list"); + List> result = mapper.list(); + assertEquals(mailboxList.size(), result.size()); + + } + + /** + * Test of save method, of class HBaseMailboxMapper. + */ + private void testSave() throws Exception { + LOG.info("save and mailboxFromResult"); + final HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + + final HBaseMailbox mlbx = mailboxList.get(mailboxList.size() / 2); + + final Get get = new Get(mailboxRowKey(mlbx.getMailboxId())); + // get all columns for the DATA column family + get.addFamily(MAILBOX_CF); + + final Result result = mailboxes.get(get); + final HBaseMailbox newValue = (HBaseMailbox) mailboxFromResult(result); + assertEquals(mlbx, newValue); + assertEquals(mlbx.getUser(), newValue.getUser()); + assertEquals(mlbx.getName(), newValue.getName()); + assertEquals(mlbx.getNamespace(), newValue.getNamespace()); + assertEquals(mlbx.getMailboxId(), newValue.getMailboxId()); + assertEquals(mlbx.getLastUid(), newValue.getLastUid()); + assertEquals(mlbx.getUidValidity(), newValue.getUidValidity()); + assertEquals(mlbx.getHighestModSeq(), newValue.getHighestModSeq()); + assertArrayEquals(mailboxRowKey(mlbx.getMailboxId()), mailboxRowKey(newValue.getMailboxId())); + } + + /** + * Test of delete method, of class HBaseMailboxMapper. + */ + private void testDelete() throws Exception { + LOG.info("delete"); + // delete last 5 mailboxes from mailboxList + int offset = 5; + int notFoundCount = 0; + + Iterator iterator = mailboxList.subList(mailboxList.size() - offset, mailboxList.size()).iterator(); + + while (iterator.hasNext()) { + HBaseMailbox mailbox = iterator.next(); + mapper.delete(mailbox); + iterator.remove(); + MailboxPath path = new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()); + pathsList.remove(path); + LOG.info("Removing mailbox: {}", path); + try { + mapper.findMailboxByPath(path); + } catch (MailboxNotFoundException e) { + LOG.info("Succesfully removed {}", mailbox); + notFoundCount++; + } + } + assertEquals(offset, notFoundCount); + assertEquals(mailboxList.size(), mapper.list().size()); + } + + /** + * Test of hasChildren method, of class HBaseMailboxMapper. + */ + private void testHasChildren() throws Exception { + LOG.info("hasChildren"); + String oldName; + for (MailboxPath path : pathsList) { + final HBaseMailbox mailbox = new HBaseMailbox(path, 12455); + oldName = mailbox.getName(); + if (path.getUser().equals("user3")) { + mailbox.setName("test"); + } + boolean result = mapper.hasChildren(mailbox, SEPARATOR); + mailbox.setName(oldName); + if (path.getUser().equals("user3")) { + assertTrue(result); + } else { + assertFalse(result); + } + + } + } + + /** + * Test of deleteAllMemberships method, of class HBaseMailboxMapper. + */ + private void testDeleteAllMemberships() { + LOG.info("deleteAllMemberships"); + fail("Not yet implemented"); + } + + /** + * Test of deleteAllMailboxes method, of class HBaseMailboxMapper. + */ + private void testDeleteAllMailboxes() throws MailboxException { + LOG.info("deleteAllMailboxes"); + mapper.deleteAllMailboxes(); + assertEquals(0, mapper.list().size()); + fillMailboxList(); + } + + private void testChunkStream() throws IOException { + LOG.info("Checking ChunkOutpuStream and ChunkInputStream"); + final String original = "This is a proper test for the HBase ChunkInputStream and" + + "ChunkOutputStream. This text must be larger than the chunk size so we write" + + "and read more then one chunk size. I think that a few more lore ipsum lines" + + "will be enough." + + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor " + + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " + + "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa" + + " qui officia deserunt mollit anim id est laborum"; + byte[] data = Bytes.toBytes(original); + // we make the column size = 10 bytes + ChunkOutputStream out = new ChunkOutputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, Bytes.toBytes("10"), 10); + ChunkInputStream in = new ChunkInputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, Bytes.toBytes("10")); + //create the stream + ByteArrayInputStream bin = new ByteArrayInputStream(data); + ByteArrayOutputStream bout = new ByteArrayOutputStream(data.length); + int b; + while ((b = bin.read()) != -1) { + out.write(b); + } + out.close(); + while ((b = in.read()) != -1) { + bout.write(b); + } + String s = bout.toString(); + assertTrue(original.equals(s)); + } + + private static void fillMailboxList() { + mailboxList = new ArrayList(); + pathsList = new ArrayList(); + MailboxPath path; + String name; + for (int i = 0; i < NAMESPACES; i++) { + for (int j = 0; j < USERS; j++) { + for (int k = 0; k < MAILBOX_NO; k++) { + if (j == 3) { + name = "test" + SEPARATOR + "subbox" + k; + } else { + name = "mailbox" + k; + } + path = new MailboxPath("namespace" + i, "user" + j, name); + pathsList.add(path); + mailboxList.add(new HBaseMailbox(path, 13)); + } + } + } + LOG.info("Created test case with {} mailboxes and {} paths", + mailboxList.size(), pathsList.size()); + } + + private void addMailbox(HBaseMailbox mailbox) throws MailboxException { + mailboxList.add(mailbox); + pathsList.add(new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); + mapper = new HBaseMailboxMapper(conf); + mapper.save(mailbox); + LOG.info("Added new mailbox: {} paths: {}", mailboxList.size(), pathsList.size()); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapperTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapperTest.java.svn-base new file mode 100644 index 0000000..0ba3bda --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseMessageMapperTest.java.svn-base @@ -0,0 +1,221 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.*; +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for HBaseMessageMapper. + * + */ +public class HBaseMessageMapperTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseMailboxMapperTest.class); + public static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static HBaseUidProvider uidProvider; + private static HBaseModSeqProvider modSeqProvider; + private static HBaseMessageMapper messageMapper; + private static final List MBOX_PATHS = new ArrayList(); + private static final List> MBOXES = new ArrayList>(); + private static final List> MESSAGE_NO = new ArrayList>(); + private static final int COUNT = 5; + private static Configuration conf; + /* + * we mock a simple message content + */ + private static final byte[] messageTemplate = Bytes.toBytes( + "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\n" + + "From: Fred Foobar \n" + + "Subject: Test 02\n" + + "To: mooch@owatagu.siam.edu\n" + + "Message-Id: \n" + + "MIME-Version: 1.0\n" + + "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\n" + + "\n" + + "Test\n" + + "\n."); + private static SharedInputStream content = new SharedByteArrayInputStream(messageTemplate); + + @Before + public void setUp() throws Exception { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + uidProvider = new HBaseUidProvider(conf); + modSeqProvider = new HBaseModSeqProvider(conf); + generateTestData(); + final MailboxSession session = new MockMailboxSession("ieugen"); + messageMapper = new HBaseMessageMapper(session, uidProvider, modSeqProvider, conf); + for (int i = 0; i < MESSAGE_NO.size(); i++) { + messageMapper.add(MBOXES.get(1), MESSAGE_NO.get(i)); + } + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + public static void generateTestData() { + final Random random = new Random(); + MailboxPath mboxPath; + final PropertyBuilder propBuilder = new PropertyBuilder(); + + for (int i = 0; i < COUNT; i++) { + if (i % 2 == 0) { + mboxPath = new MailboxPath("gsoc", "ieugen" + i, "INBOX"); + } else { + mboxPath = new MailboxPath("gsoc", "ieugen" + i, "INBOX.box" + i); + } + MBOX_PATHS.add(mboxPath); + MBOXES.add(new HBaseMailbox(MBOX_PATHS.get(i), random.nextLong())); + propBuilder.setProperty("gsoc", "prop" + i, "value"); + } + propBuilder.setMediaType("text"); + propBuilder.setSubType("html"); + propBuilder.setTextualLineCount(2L); + + SimpleMessage myMsg; + final Flags flags = new Flags(Flags.Flag.RECENT); + final Date today = new Date(); + + for (int i = 0; i < COUNT * 2; i++) { + myMsg = new SimpleMessage(today, messageTemplate.length, + messageTemplate.length - 20, content, flags, propBuilder, + MBOXES.get(1).getMailboxId()); + if (i == COUNT * 2 - 1) { + flags.add(Flags.Flag.SEEN); + flags.remove(Flags.Flag.RECENT); + myMsg.setFlags(flags); + } + MESSAGE_NO.add(myMsg); + } + } + + /** + * Test an ordered scenario with count, find, add... methods. + * + * @throws Exception + */ + @Test + public void testMessageMapperScenario() throws Exception { + testCountMessagesInMailbox(); + testCountUnseenMessagesInMailbox(); + testFindFirstUnseenMessageUid(); + testFindRecentMessageUidsInMailbox(); + testAdd(); + testGetLastUid(); + testGetHighestModSeq(); + } + + /** + * Test of countMessagesInMailbox method, of class HBaseMessageMapper. + */ + private void testCountMessagesInMailbox() throws Exception { + LOG.info("countMessagesInMailbox"); + long messageCount = messageMapper.countMessagesInMailbox(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size(), messageCount); + } + + /** + * Test of countUnseenMessagesInMailbox method, of class HBaseMessageMapper. + */ + private void testCountUnseenMessagesInMailbox() throws Exception { + LOG.info("countUnseenMessagesInMailbox"); + long unseen = messageMapper.countUnseenMessagesInMailbox(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size() - 1, unseen); + } + + /** + * Test of findFirstUnseenMessageUid method, of class HBaseMessageMapper. + */ + private void testFindFirstUnseenMessageUid() throws Exception { + LOG.info("findFirstUnseenMessageUid"); + final long uid = messageMapper.findFirstUnseenMessageUid(MBOXES.get(1)); + assertEquals(1, uid); + } + + /** + * Test of findRecentMessageUidsInMailbox method, of class + * HBaseMessageMapper. + */ + private void testFindRecentMessageUidsInMailbox() throws Exception { + LOG.info("findRecentMessageUidsInMailbox"); + List recentMessages = messageMapper.findRecentMessageUidsInMailbox(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size() - 1, recentMessages.size()); + } + + /** + * Test of add method, of class HBaseMessageMapper. + */ + private void testAdd() throws Exception { + LOG.info("add"); + // The tables should be deleted every time the tests run. + long msgCount = messageMapper.countMessagesInMailbox(MBOXES.get(1)); + LOG.info(msgCount + " " + MESSAGE_NO.size()); + assertEquals(MESSAGE_NO.size(), msgCount); + } + + /** + * Test of getLastUid method, of class HBaseMessageMapper. + */ + private void testGetLastUid() throws Exception { + LOG.info("getLastUid"); + long lastUid = messageMapper.getLastUid(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size(), lastUid); + } + + /** + * Test of getHighestModSeq method, of class HBaseMessageMapper. + */ + private void testGetHighestModSeq() throws Exception { + LOG.info("getHighestModSeq"); + long highestModSeq = messageMapper.getHighestModSeq(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size(), highestModSeq); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidAndModSeqProviderTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidAndModSeqProviderTest.java.svn-base new file mode 100644 index 0000000..7c0ca26 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/.svn/text-base/HBaseUidAndModSeqProviderTest.java.svn-base @@ -0,0 +1,175 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.hadoop.conf.Configuration; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for UidProvider and ModSeqProvider. + * + */ +public class HBaseUidAndModSeqProviderTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseUidAndModSeqProviderTest.class); + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + private static HBaseUidProvider uidProvider; + private static HBaseModSeqProvider modSeqProvider; + private static HBaseMailboxMapper mapper; + private static List mailboxList; + private static List pathsList; + private static final int NAMESPACES = 5; + private static final int USERS = 5; + private static final int MAILBOX_NO = 5; + private static final char SEPARATOR = '%'; + + @Before + public void setUpClass() throws Exception { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + uidProvider = new HBaseUidProvider(conf); + modSeqProvider = new HBaseModSeqProvider(conf); + mapper = new HBaseMailboxMapper(conf); + fillMailboxList(); + for (HBaseMailbox mailbox : mailboxList) { + mapper.save(mailbox); + } + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + private static void fillMailboxList() { + mailboxList = new ArrayList(); + pathsList = new ArrayList(); + MailboxPath path; + String name; + for (int i = 0; i < NAMESPACES; i++) { + for (int j = 0; j < USERS; j++) { + for (int k = 0; k < MAILBOX_NO; k++) { + if (j == 3) { + name = "test" + SEPARATOR + "subbox" + k; + } else { + name = "mailbox" + k; + } + path = new MailboxPath("namespace" + i, "user" + j, name); + pathsList.add(path); + mailboxList.add(new HBaseMailbox(path, 13)); + } + } + } + + LOG.info("Created test case with {} mailboxes and {} paths", mailboxList.size(), + pathsList.size()); + } + + /** + * Test of lastUid method, of class HBaseUidProvider. + */ + @Test + public void testLastUid() throws Exception { + LOG.info("lastUid"); + final MailboxPath path = new MailboxPath("gsoc", "ieugen", "Trash"); + final HBaseMailbox newBox = new HBaseMailbox(path, 1234); + mapper.save(newBox); + mailboxList.add(newBox); + pathsList.add(path); + + final long result = uidProvider.lastUid(null, newBox); + assertEquals(0, result); + for (int i = 1; i < 10; i++) { + final long uid = uidProvider.nextUid(null, newBox); + assertEquals(uid, uidProvider.lastUid(null, newBox)); + } + } + + /** + * Test of nextUid method, of class HBaseUidProvider. + */ + @Test + public void testNextUid() throws Exception { + LOG.info("nextUid"); + final HBaseMailbox mailbox = mailboxList.get(mailboxList.size() / 2); + final long lastUid = uidProvider.lastUid(null, mailbox); + long result; + for (int i = (int) lastUid + 1; i < (lastUid + 10); i++) { + result = uidProvider.nextUid(null, mailbox); + assertEquals(i, result); + } + } + + /** + * Test of highestModSeq method, of class HBaseModSeqProvider. + */ + @Test + public void testHighestModSeq() throws Exception { + LOG.info("highestModSeq"); + LOG.info("lastUid"); + MailboxPath path = new MailboxPath("gsoc", "ieugen", "Trash"); + HBaseMailbox newBox = new HBaseMailbox(path, 1234); + mapper.save(newBox); + mailboxList.add(newBox); + pathsList.add(path); + + long result = modSeqProvider.highestModSeq(null, newBox); + assertEquals(0, result); + for (int i = 1; i < 10; i++) { + long uid = modSeqProvider.nextModSeq(null, newBox); + assertEquals(uid, modSeqProvider.highestModSeq(null, newBox)); + } + } + + /** + * Test of nextModSeq method, of class HBaseModSeqProvider. + */ + @Test + public void testNextModSeq() throws Exception { + LOG.info("nextModSeq"); + final HBaseMailbox mailbox = mailboxList.get(mailboxList.size() / 2); + final long lastUid = modSeqProvider.highestModSeq(null, mailbox); + long result; + for (int i = (int) lastUid + 1; i < (lastUid + 10); i++) { + result = modSeqProvider.nextModSeq(null, mailbox); + assertEquals(i, result); + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapperTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapperTest.java new file mode 100644 index 0000000..f76d8f9 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMailboxMapperTest.java @@ -0,0 +1,317 @@ +/** + * ************************************************************** + * 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.mail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxFromResult; +import static org.apache.james.mailbox.hbase.HBaseUtils.mailboxRowKey; +import org.apache.james.mailbox.hbase.io.ChunkInputStream; +import org.apache.james.mailbox.hbase.io.ChunkOutputStream; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * HBaseMailboxMapper unit tests. + * + */ +public class HBaseMailboxMapperTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseMailboxMapperTest.class); + public static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + private static HBaseMailboxMapper mapper; + private static List mailboxList; + private static List pathsList; + private static final int NAMESPACES = 5; + private static final int USERS = 5; + private static final int MAILBOX_NO = 5; + private static final char SEPARATOR = '%'; + + @Before + public void setUp() throws Exception { + ensureTables(); + // start the test cluster + clearTables(); + conf = CLUSTER.getConf(); + fillMailboxList(); + mapper = new HBaseMailboxMapper(conf); + for (HBaseMailbox mailbox : mailboxList) { + mapper.save(mailbox); + } + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + /** + * Test an ordered scenario with list, delete... methods. + * + * @throws Exception + */ + @Test + public void testMailboxMapperScenario() throws Exception { + testFindMailboxByPath(); + testFindMailboxWithPathLike(); + testList(); + testSave(); + testDelete(); + testHasChildren(); +// testDeleteAllMemberships(); // Ignore this test + testDeleteAllMailboxes(); + testChunkStream(); + } + + /** + * Test of findMailboxByPath method, of class HBaseMailboxMapper. + */ + private void testFindMailboxByPath() throws Exception { + LOG.info("findMailboxByPath"); + HBaseMailbox mailbox; + for (MailboxPath path : pathsList) { + LOG.info("Searching for " + path); + mailbox = (HBaseMailbox) mapper.findMailboxByPath(path); + assertEquals(path, new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); + } + } + + /** + * Test of findMailboxWithPathLike method, of class HBaseMailboxMapper. + */ + private void testFindMailboxWithPathLike() throws Exception { + LOG.info("findMailboxWithPathLike"); + MailboxPath path = pathsList.get(pathsList.size() / 2); + + List> result = mapper.findMailboxWithPathLike(path); + assertEquals(1, result.size()); + + int start = 3; + int end = 7; + MailboxPath newPath; + + for (int i = start; i < end; i++) { + newPath = new MailboxPath(path); + newPath.setName(i + newPath.getName() + " " + i); + // test for paths with null user + if (i % 2 == 0) { + newPath.setUser(null); + } + addMailbox(new HBaseMailbox(newPath, 1234)); + } + result = mapper.findMailboxWithPathLike(path); + assertEquals(end - start + 1, result.size()); + } + + /** + * Test of list method, of class HBaseMailboxMapper. + */ + private void testList() throws Exception { + LOG.info("list"); + List> result = mapper.list(); + assertEquals(mailboxList.size(), result.size()); + + } + + /** + * Test of save method, of class HBaseMailboxMapper. + */ + private void testSave() throws Exception { + LOG.info("save and mailboxFromResult"); + final HTable mailboxes = new HTable(conf, MAILBOXES_TABLE); + + final HBaseMailbox mlbx = mailboxList.get(mailboxList.size() / 2); + + final Get get = new Get(mailboxRowKey(mlbx.getMailboxId())); + // get all columns for the DATA column family + get.addFamily(MAILBOX_CF); + + final Result result = mailboxes.get(get); + final HBaseMailbox newValue = (HBaseMailbox) mailboxFromResult(result); + assertEquals(mlbx, newValue); + assertEquals(mlbx.getUser(), newValue.getUser()); + assertEquals(mlbx.getName(), newValue.getName()); + assertEquals(mlbx.getNamespace(), newValue.getNamespace()); + assertEquals(mlbx.getMailboxId(), newValue.getMailboxId()); + assertEquals(mlbx.getLastUid(), newValue.getLastUid()); + assertEquals(mlbx.getUidValidity(), newValue.getUidValidity()); + assertEquals(mlbx.getHighestModSeq(), newValue.getHighestModSeq()); + assertArrayEquals(mailboxRowKey(mlbx.getMailboxId()), mailboxRowKey(newValue.getMailboxId())); + } + + /** + * Test of delete method, of class HBaseMailboxMapper. + */ + private void testDelete() throws Exception { + LOG.info("delete"); + // delete last 5 mailboxes from mailboxList + int offset = 5; + int notFoundCount = 0; + + Iterator iterator = mailboxList.subList(mailboxList.size() - offset, mailboxList.size()).iterator(); + + while (iterator.hasNext()) { + HBaseMailbox mailbox = iterator.next(); + mapper.delete(mailbox); + iterator.remove(); + MailboxPath path = new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()); + pathsList.remove(path); + LOG.info("Removing mailbox: {}", path); + try { + mapper.findMailboxByPath(path); + } catch (MailboxNotFoundException e) { + LOG.info("Succesfully removed {}", mailbox); + notFoundCount++; + } + } + assertEquals(offset, notFoundCount); + assertEquals(mailboxList.size(), mapper.list().size()); + } + + /** + * Test of hasChildren method, of class HBaseMailboxMapper. + */ + private void testHasChildren() throws Exception { + LOG.info("hasChildren"); + String oldName; + for (MailboxPath path : pathsList) { + final HBaseMailbox mailbox = new HBaseMailbox(path, 12455); + oldName = mailbox.getName(); + if (path.getUser().equals("user3")) { + mailbox.setName("test"); + } + boolean result = mapper.hasChildren(mailbox, SEPARATOR); + mailbox.setName(oldName); + if (path.getUser().equals("user3")) { + assertTrue(result); + } else { + assertFalse(result); + } + + } + } + + /** + * Test of deleteAllMemberships method, of class HBaseMailboxMapper. + */ + private void testDeleteAllMemberships() { + LOG.info("deleteAllMemberships"); + fail("Not yet implemented"); + } + + /** + * Test of deleteAllMailboxes method, of class HBaseMailboxMapper. + */ + private void testDeleteAllMailboxes() throws MailboxException { + LOG.info("deleteAllMailboxes"); + mapper.deleteAllMailboxes(); + assertEquals(0, mapper.list().size()); + fillMailboxList(); + } + + private void testChunkStream() throws IOException { + LOG.info("Checking ChunkOutpuStream and ChunkInputStream"); + final String original = "This is a proper test for the HBase ChunkInputStream and" + + "ChunkOutputStream. This text must be larger than the chunk size so we write" + + "and read more then one chunk size. I think that a few more lore ipsum lines" + + "will be enough." + + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor " + + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " + + "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa" + + " qui officia deserunt mollit anim id est laborum"; + byte[] data = Bytes.toBytes(original); + // we make the column size = 10 bytes + ChunkOutputStream out = new ChunkOutputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, Bytes.toBytes("10"), 10); + ChunkInputStream in = new ChunkInputStream(conf, + MESSAGES_TABLE, MESSAGE_DATA_BODY_CF, Bytes.toBytes("10")); + //create the stream + ByteArrayInputStream bin = new ByteArrayInputStream(data); + ByteArrayOutputStream bout = new ByteArrayOutputStream(data.length); + int b; + while ((b = bin.read()) != -1) { + out.write(b); + } + out.close(); + while ((b = in.read()) != -1) { + bout.write(b); + } + String s = bout.toString(); + assertTrue(original.equals(s)); + } + + private static void fillMailboxList() { + mailboxList = new ArrayList(); + pathsList = new ArrayList(); + MailboxPath path; + String name; + for (int i = 0; i < NAMESPACES; i++) { + for (int j = 0; j < USERS; j++) { + for (int k = 0; k < MAILBOX_NO; k++) { + if (j == 3) { + name = "test" + SEPARATOR + "subbox" + k; + } else { + name = "mailbox" + k; + } + path = new MailboxPath("namespace" + i, "user" + j, name); + pathsList.add(path); + mailboxList.add(new HBaseMailbox(path, 13)); + } + } + } + LOG.info("Created test case with {} mailboxes and {} paths", + mailboxList.size(), pathsList.size()); + } + + private void addMailbox(HBaseMailbox mailbox) throws MailboxException { + mailboxList.add(mailbox); + pathsList.add(new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); + mapper = new HBaseMailboxMapper(conf); + mapper.save(mailbox); + LOG.info("Added new mailbox: {} paths: {}", mailboxList.size(), pathsList.size()); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapperTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapperTest.java new file mode 100644 index 0000000..0ba3bda --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapperTest.java @@ -0,0 +1,221 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.*; +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.mock.MockMailboxSession; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for HBaseMessageMapper. + * + */ +public class HBaseMessageMapperTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseMailboxMapperTest.class); + public static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static HBaseUidProvider uidProvider; + private static HBaseModSeqProvider modSeqProvider; + private static HBaseMessageMapper messageMapper; + private static final List MBOX_PATHS = new ArrayList(); + private static final List> MBOXES = new ArrayList>(); + private static final List> MESSAGE_NO = new ArrayList>(); + private static final int COUNT = 5; + private static Configuration conf; + /* + * we mock a simple message content + */ + private static final byte[] messageTemplate = Bytes.toBytes( + "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\n" + + "From: Fred Foobar \n" + + "Subject: Test 02\n" + + "To: mooch@owatagu.siam.edu\n" + + "Message-Id: \n" + + "MIME-Version: 1.0\n" + + "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\n" + + "\n" + + "Test\n" + + "\n."); + private static SharedInputStream content = new SharedByteArrayInputStream(messageTemplate); + + @Before + public void setUp() throws Exception { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + uidProvider = new HBaseUidProvider(conf); + modSeqProvider = new HBaseModSeqProvider(conf); + generateTestData(); + final MailboxSession session = new MockMailboxSession("ieugen"); + messageMapper = new HBaseMessageMapper(session, uidProvider, modSeqProvider, conf); + for (int i = 0; i < MESSAGE_NO.size(); i++) { + messageMapper.add(MBOXES.get(1), MESSAGE_NO.get(i)); + } + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + public static void generateTestData() { + final Random random = new Random(); + MailboxPath mboxPath; + final PropertyBuilder propBuilder = new PropertyBuilder(); + + for (int i = 0; i < COUNT; i++) { + if (i % 2 == 0) { + mboxPath = new MailboxPath("gsoc", "ieugen" + i, "INBOX"); + } else { + mboxPath = new MailboxPath("gsoc", "ieugen" + i, "INBOX.box" + i); + } + MBOX_PATHS.add(mboxPath); + MBOXES.add(new HBaseMailbox(MBOX_PATHS.get(i), random.nextLong())); + propBuilder.setProperty("gsoc", "prop" + i, "value"); + } + propBuilder.setMediaType("text"); + propBuilder.setSubType("html"); + propBuilder.setTextualLineCount(2L); + + SimpleMessage myMsg; + final Flags flags = new Flags(Flags.Flag.RECENT); + final Date today = new Date(); + + for (int i = 0; i < COUNT * 2; i++) { + myMsg = new SimpleMessage(today, messageTemplate.length, + messageTemplate.length - 20, content, flags, propBuilder, + MBOXES.get(1).getMailboxId()); + if (i == COUNT * 2 - 1) { + flags.add(Flags.Flag.SEEN); + flags.remove(Flags.Flag.RECENT); + myMsg.setFlags(flags); + } + MESSAGE_NO.add(myMsg); + } + } + + /** + * Test an ordered scenario with count, find, add... methods. + * + * @throws Exception + */ + @Test + public void testMessageMapperScenario() throws Exception { + testCountMessagesInMailbox(); + testCountUnseenMessagesInMailbox(); + testFindFirstUnseenMessageUid(); + testFindRecentMessageUidsInMailbox(); + testAdd(); + testGetLastUid(); + testGetHighestModSeq(); + } + + /** + * Test of countMessagesInMailbox method, of class HBaseMessageMapper. + */ + private void testCountMessagesInMailbox() throws Exception { + LOG.info("countMessagesInMailbox"); + long messageCount = messageMapper.countMessagesInMailbox(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size(), messageCount); + } + + /** + * Test of countUnseenMessagesInMailbox method, of class HBaseMessageMapper. + */ + private void testCountUnseenMessagesInMailbox() throws Exception { + LOG.info("countUnseenMessagesInMailbox"); + long unseen = messageMapper.countUnseenMessagesInMailbox(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size() - 1, unseen); + } + + /** + * Test of findFirstUnseenMessageUid method, of class HBaseMessageMapper. + */ + private void testFindFirstUnseenMessageUid() throws Exception { + LOG.info("findFirstUnseenMessageUid"); + final long uid = messageMapper.findFirstUnseenMessageUid(MBOXES.get(1)); + assertEquals(1, uid); + } + + /** + * Test of findRecentMessageUidsInMailbox method, of class + * HBaseMessageMapper. + */ + private void testFindRecentMessageUidsInMailbox() throws Exception { + LOG.info("findRecentMessageUidsInMailbox"); + List recentMessages = messageMapper.findRecentMessageUidsInMailbox(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size() - 1, recentMessages.size()); + } + + /** + * Test of add method, of class HBaseMessageMapper. + */ + private void testAdd() throws Exception { + LOG.info("add"); + // The tables should be deleted every time the tests run. + long msgCount = messageMapper.countMessagesInMailbox(MBOXES.get(1)); + LOG.info(msgCount + " " + MESSAGE_NO.size()); + assertEquals(MESSAGE_NO.size(), msgCount); + } + + /** + * Test of getLastUid method, of class HBaseMessageMapper. + */ + private void testGetLastUid() throws Exception { + LOG.info("getLastUid"); + long lastUid = messageMapper.getLastUid(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size(), lastUid); + } + + /** + * Test of getHighestModSeq method, of class HBaseMessageMapper. + */ + private void testGetHighestModSeq() throws Exception { + LOG.info("getHighestModSeq"); + long highestModSeq = messageMapper.getHighestModSeq(MBOXES.get(1)); + assertEquals(MESSAGE_NO.size(), highestModSeq); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseUidAndModSeqProviderTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseUidAndModSeqProviderTest.java new file mode 100644 index 0000000..7c0ca26 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/HBaseUidAndModSeqProviderTest.java @@ -0,0 +1,175 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.hadoop.conf.Configuration; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.hbase.mail.model.HBaseMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for UidProvider and ModSeqProvider. + * + */ +public class HBaseUidAndModSeqProviderTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseUidAndModSeqProviderTest.class); + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + private static HBaseUidProvider uidProvider; + private static HBaseModSeqProvider modSeqProvider; + private static HBaseMailboxMapper mapper; + private static List mailboxList; + private static List pathsList; + private static final int NAMESPACES = 5; + private static final int USERS = 5; + private static final int MAILBOX_NO = 5; + private static final char SEPARATOR = '%'; + + @Before + public void setUpClass() throws Exception { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + uidProvider = new HBaseUidProvider(conf); + modSeqProvider = new HBaseModSeqProvider(conf); + mapper = new HBaseMailboxMapper(conf); + fillMailboxList(); + for (HBaseMailbox mailbox : mailboxList) { + mapper.save(mailbox); + } + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + private static void fillMailboxList() { + mailboxList = new ArrayList(); + pathsList = new ArrayList(); + MailboxPath path; + String name; + for (int i = 0; i < NAMESPACES; i++) { + for (int j = 0; j < USERS; j++) { + for (int k = 0; k < MAILBOX_NO; k++) { + if (j == 3) { + name = "test" + SEPARATOR + "subbox" + k; + } else { + name = "mailbox" + k; + } + path = new MailboxPath("namespace" + i, "user" + j, name); + pathsList.add(path); + mailboxList.add(new HBaseMailbox(path, 13)); + } + } + } + + LOG.info("Created test case with {} mailboxes and {} paths", mailboxList.size(), + pathsList.size()); + } + + /** + * Test of lastUid method, of class HBaseUidProvider. + */ + @Test + public void testLastUid() throws Exception { + LOG.info("lastUid"); + final MailboxPath path = new MailboxPath("gsoc", "ieugen", "Trash"); + final HBaseMailbox newBox = new HBaseMailbox(path, 1234); + mapper.save(newBox); + mailboxList.add(newBox); + pathsList.add(path); + + final long result = uidProvider.lastUid(null, newBox); + assertEquals(0, result); + for (int i = 1; i < 10; i++) { + final long uid = uidProvider.nextUid(null, newBox); + assertEquals(uid, uidProvider.lastUid(null, newBox)); + } + } + + /** + * Test of nextUid method, of class HBaseUidProvider. + */ + @Test + public void testNextUid() throws Exception { + LOG.info("nextUid"); + final HBaseMailbox mailbox = mailboxList.get(mailboxList.size() / 2); + final long lastUid = uidProvider.lastUid(null, mailbox); + long result; + for (int i = (int) lastUid + 1; i < (lastUid + 10); i++) { + result = uidProvider.nextUid(null, mailbox); + assertEquals(i, result); + } + } + + /** + * Test of highestModSeq method, of class HBaseModSeqProvider. + */ + @Test + public void testHighestModSeq() throws Exception { + LOG.info("highestModSeq"); + LOG.info("lastUid"); + MailboxPath path = new MailboxPath("gsoc", "ieugen", "Trash"); + HBaseMailbox newBox = new HBaseMailbox(path, 1234); + mapper.save(newBox); + mailboxList.add(newBox); + pathsList.add(path); + + long result = modSeqProvider.highestModSeq(null, newBox); + assertEquals(0, result); + for (int i = 1; i < 10; i++) { + long uid = modSeqProvider.nextModSeq(null, newBox); + assertEquals(uid, modSeqProvider.highestModSeq(null, newBox)); + } + } + + /** + * Test of nextModSeq method, of class HBaseModSeqProvider. + */ + @Test + public void testNextModSeq() throws Exception { + LOG.info("nextModSeq"); + final HBaseMailbox mailbox = mailboxList.get(mailboxList.size() / 2); + final long lastUid = modSeqProvider.highestModSeq(null, mailbox); + long result; + for (int i = (int) lastUid + 1; i < (lastUid + 10); i++) { + result = modSeqProvider.nextModSeq(null, mailbox); + assertEquals(i, result); + } + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops new file mode 100644 index 0000000..a041106 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model +END +HBaseMailboxTest.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailboxTest.java +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries new file mode 100644 index 0000000..452d83d --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model +http://svn.apache.org/repos/asf + + + +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +HBaseMailboxTest.java +file + + + + +2013-09-02T02:54:38.000000Z +d6af1f671a3b63d44f3fba753f0966b1 +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +6072 + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailboxTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailboxTest.java.svn-base new file mode 100644 index 0000000..d709813 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/.svn/text-base/HBaseMailboxTest.java.svn-base @@ -0,0 +1,161 @@ +/**************************************************************** + * 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.mail.model; + +import java.util.UUID; +import org.apache.james.mailbox.model.MailboxPath; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +/** + * Unit tests for HBaseMailbox class. + */ +public class HBaseMailboxTest { + + /** + * Test of getter and setter for MailboxId + */ + @Test + public void testGetSetMailboxId() { + System.out.println("getSetMailboxId"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 10); + + UUID expResult = UUID.randomUUID(); + instance.setMailboxId(expResult); + assertEquals(expResult, instance.getMailboxId()); + + } + + /** + * Test of getter and setter for Namespace, of class HBaseMailbox. + */ + @Test + public void testGetSetNamespace() { + System.out.println("getSetNamespace"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 124566); + String result = instance.getNamespace(); + assertEquals(mailboxPath.getNamespace(), result); + + instance.setNamespace("newName"); + assertEquals("newName", instance.getNamespace()); + + } + + /** + * Test of getter and setter for User, of class HBaseMailbox. + */ + @Test + public void testGetSetUser() { + System.out.println("getUser"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 12); + String result = instance.getUser(); + assertEquals(mailboxPath.getUser(), result); + + instance.setUser("eric"); + assertEquals("eric", instance.getUser()); + } + + /** + * Test of getter and setter for Name, of class HBaseMailbox. + */ + @Test + public void testGetSetName() { + System.out.println("getSetName"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 1677); + String result = instance.getName(); + assertEquals(mailboxPath.getName(), result); + + instance.setName("newINBOX"); + assertEquals("newINBOX", instance.getName()); + } + + /** + * Test of getUidValidity method, of class HBaseMailbox. + */ + @Test + public void testGetUidValidity() { + System.out.println("getUidValidity"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 123345); + long expResult = 123345L; + long result = instance.getUidValidity(); + assertEquals(expResult, result); + + } + + /** + * Test of hashCode method, of class HBaseMailbox. + */ + @Test + public void testHashCode() { + System.out.println("hashCode"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 1234); + // from the hashCode() + final int PRIME = 31; + int result = 1; + UUID mailboxId = instance.getMailboxId(); + int expResult = PRIME * result + (int) (mailboxId.getMostSignificantBits() ^ (mailboxId.getMostSignificantBits() >>> 32)); + + assertEquals(expResult, instance.hashCode()); + } + + /** + * Test of equals method, of class HBaseMailbox. + */ + @Test + public void testEquals() { + System.out.println("equals"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 12345); + final HBaseMailbox instance2 = new HBaseMailbox(mailboxPath, 12345); + instance2.setMailboxId(instance.getMailboxId()); + assertEquals(instance, instance2); + } + + /** + * Test of consumeUid method, of class HBaseMailbox. + */ + @Test + public void testConsumeUid() { + System.out.println("consumeUid"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 10); + long expResult = instance.getLastUid() + 1; + long result = instance.consumeUid(); + assertEquals(expResult, result); + } + + /** + * Test of consumeModSeq method, of class HBaseMailbox. + */ + @Test + public void testConsumeModSeq() { + System.out.println("consumeModSeq"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 10); + long expResult = instance.getHighestModSeq() + 1; + long result = instance.consumeModSeq(); + assertEquals(expResult, result); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailboxTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailboxTest.java new file mode 100644 index 0000000..d709813 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/mail/model/HBaseMailboxTest.java @@ -0,0 +1,161 @@ +/**************************************************************** + * 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.mail.model; + +import java.util.UUID; +import org.apache.james.mailbox.model.MailboxPath; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +/** + * Unit tests for HBaseMailbox class. + */ +public class HBaseMailboxTest { + + /** + * Test of getter and setter for MailboxId + */ + @Test + public void testGetSetMailboxId() { + System.out.println("getSetMailboxId"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 10); + + UUID expResult = UUID.randomUUID(); + instance.setMailboxId(expResult); + assertEquals(expResult, instance.getMailboxId()); + + } + + /** + * Test of getter and setter for Namespace, of class HBaseMailbox. + */ + @Test + public void testGetSetNamespace() { + System.out.println("getSetNamespace"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 124566); + String result = instance.getNamespace(); + assertEquals(mailboxPath.getNamespace(), result); + + instance.setNamespace("newName"); + assertEquals("newName", instance.getNamespace()); + + } + + /** + * Test of getter and setter for User, of class HBaseMailbox. + */ + @Test + public void testGetSetUser() { + System.out.println("getUser"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 12); + String result = instance.getUser(); + assertEquals(mailboxPath.getUser(), result); + + instance.setUser("eric"); + assertEquals("eric", instance.getUser()); + } + + /** + * Test of getter and setter for Name, of class HBaseMailbox. + */ + @Test + public void testGetSetName() { + System.out.println("getSetName"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 1677); + String result = instance.getName(); + assertEquals(mailboxPath.getName(), result); + + instance.setName("newINBOX"); + assertEquals("newINBOX", instance.getName()); + } + + /** + * Test of getUidValidity method, of class HBaseMailbox. + */ + @Test + public void testGetUidValidity() { + System.out.println("getUidValidity"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 123345); + long expResult = 123345L; + long result = instance.getUidValidity(); + assertEquals(expResult, result); + + } + + /** + * Test of hashCode method, of class HBaseMailbox. + */ + @Test + public void testHashCode() { + System.out.println("hashCode"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 1234); + // from the hashCode() + final int PRIME = 31; + int result = 1; + UUID mailboxId = instance.getMailboxId(); + int expResult = PRIME * result + (int) (mailboxId.getMostSignificantBits() ^ (mailboxId.getMostSignificantBits() >>> 32)); + + assertEquals(expResult, instance.hashCode()); + } + + /** + * Test of equals method, of class HBaseMailbox. + */ + @Test + public void testEquals() { + System.out.println("equals"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 12345); + final HBaseMailbox instance2 = new HBaseMailbox(mailboxPath, 12345); + instance2.setMailboxId(instance.getMailboxId()); + assertEquals(instance, instance2); + } + + /** + * Test of consumeUid method, of class HBaseMailbox. + */ + @Test + public void testConsumeUid() { + System.out.println("consumeUid"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 10); + long expResult = instance.getLastUid() + 1; + long result = instance.consumeUid(); + assertEquals(expResult, result); + } + + /** + * Test of consumeModSeq method, of class HBaseMailbox. + */ + @Test + public void testConsumeModSeq() { + System.out.println("consumeModSeq"); + final MailboxPath mailboxPath = new MailboxPath("gsoc", "ieugen", "INBOX"); + final HBaseMailbox instance = new HBaseMailbox(mailboxPath, 10); + long expResult = instance.getHighestModSeq() + 1; + long result = instance.consumeModSeq(); + assertEquals(expResult, result); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops new file mode 100644 index 0000000..3cc7628 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/user +END +HBaseSubscriptionMapperTest.java +K 25 +svn:wc:ra_dav:version-url +V 136 +/repos/asf/!svn/ver/1302061/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapperTest.java +END diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/entries b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/entries new file mode 100644 index 0000000..fcd05cb --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/java/org/apache/james/mailbox/hbase/user +http://svn.apache.org/repos/asf + + + +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +HBaseSubscriptionMapperTest.java +file + + + + +2013-09-02T02:54:38.000000Z +e9045a7784bce2a82987d4efdf36f5e6 +2012-03-18T04:06:01.004365Z +1302061 +ieugen + + + + + + + + + + + + + + + + + + + + + +8622 + diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapperTest.java.svn-base b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapperTest.java.svn-base new file mode 100644 index 0000000..f3852d5 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/.svn/text-base/HBaseSubscriptionMapperTest.java.svn-base @@ -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.hbase.user; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import org.apache.james.mailbox.hbase.HBaseMailboxSessionMapperFactory; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runs tests for SubscriptionMapper. + * + */ +public class HBaseSubscriptionMapperTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseSubscriptionMapperTest.class); + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + private static HBaseMailboxSessionMapperFactory mapperFactory; + private static HBaseSubscriptionMapper mapper; + private static Map> subscriptionList; + private static final int USERS = 5; + private static final int MAILBOX_NO = 5; + + @Before + public void setUp() throws Exception { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + mapperFactory = new HBaseMailboxSessionMapperFactory(conf, null, null); + mapper = new HBaseSubscriptionMapper(conf); + fillSubscriptionList(); + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + private static void fillSubscriptionList() throws SubscriptionException { + LOG.info("Creating subscription list"); + SimpleSubscription subscription; + String user, mailbox; + subscriptionList = new HashMap>(); + for (int i = 0; i < USERS; i++) { + user = "user" + i; + final List mailboxes = new ArrayList(); + subscriptionList.put(user, mailboxes); + + for (int j = 0; j < MAILBOX_NO; j++) { + if (j == 0) { + mailbox = "INBOX"; + } else { + mailbox = "BOX" + j; + } + if ((i % 2 == 0) && (j > 0)) { + continue; + } + subscription = new SimpleSubscription(user, mailbox); + mailboxes.add(subscription); + mapper.save(subscription); + LOG.info("Adding subscription " + subscription); + } + } + } + + /** + * Test of findMailboxSubscriptionForUser method, of class + * HBseSubscriptionMapper. + */ + @Test + public void testFindMailboxSubscriptionForUser() throws Exception { + LOG.info("findMailboxSubscriptionForUser"); + + final SimpleSubscription fake1 = new SimpleSubscription("user1", "FAKEBOX"); + final SimpleSubscription fake2 = new SimpleSubscription("fakeUser", "INBOX"); + + for (String user : subscriptionList.keySet()) { + LOG.info("Searching for all subscriptions for user:{}", user); + for (SimpleSubscription subscription : subscriptionList.get(user)) { + final Subscription result = mapper.findMailboxSubscriptionForUser(user, subscription.getMailbox()); + assertEquals(subscription.getMailbox(), result.getMailbox()); + assertEquals(subscription.getUser(), result.getUser()); + } + } + assertNull(mapper.findMailboxSubscriptionForUser(fake1.getUser(), fake1.getMailbox())); + assertNull(mapper.findMailboxSubscriptionForUser(fake2.getUser(), fake2.getMailbox())); + } + + /** + * Test of save method, of class HBaseSubscriptionMapper. + */ + @Test + public void testSave() throws Exception { + LOG.info("save"); + final HTable subscriptions = new HTable(mapperFactory.getClusterConfiguration(), SUBSCRIPTIONS_TABLE); + + for (String user : subscriptionList.keySet()) { + final Get get = new Get(Bytes.toBytes(user)); + get.addFamily(SUBSCRIPTION_CF); + final Result result = subscriptions.get(get); + for (Subscription subscription : subscriptionList.get(user)) { + assertTrue(result.containsColumn(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()))); + } + } + subscriptions.close(); + } + + /** + * Test of findSubscriptionsForUser method, of class + * HBaseSubscriptionMapper. + */ + @Test + public void testFindSubscriptionsForUser() throws Exception { + LOG.info("findSubscriptionsForUser"); + final SimpleSubscription fake1 = new SimpleSubscription("user1", "FAKEBOX"); + final SimpleSubscription fake2 = new SimpleSubscription("fakeUser", "INBOX"); + for (String user : subscriptionList.keySet()) { + LOG.info("Searching for all subscriptions for user: " + user); + final List found = mapper.findSubscriptionsForUser(user); + assertEquals(subscriptionList.get(user).size(), found.size()); + // TODO: patch Subscription to implement equals + //assertTrue(subscriptionList.get(user).containsAll(foundSubscriptions)); + //assertTrue(foundSubscriptions.containsAll(subscriptionList.get(user))); + //assertFalse(foundSubscriptions.contains(fake1)); + //assertFalse(foundSubscriptions.contains(fake2)); + } + //TODO: check what value we should return in case of no subscriptions: null or empty list + assertEquals(mapper.findSubscriptionsForUser(fake2.getMailbox()).size(), 0); + + } + + /** + * Test of delete method, of class HBaseSubscriptionMapper. + */ + @Test + public void testDelete() throws Exception { + LOG.info("delete"); + final HTable subscriptions = new HTable(mapperFactory.getClusterConfiguration(), SUBSCRIPTIONS_TABLE); + + for (String user : subscriptionList.keySet()) { + LOG.info("Deleting subscriptions for user: " + user); + for (SimpleSubscription subscription : subscriptionList.get(user)) { + LOG.info("Deleting subscription : " + subscription); + mapper.delete(subscription); + final Get get = new Get(Bytes.toBytes(subscription.getUser())); + final Result result = subscriptions.get(get); + assertFalse(result.containsColumn(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()))); + } + } + subscriptions.close(); + fillSubscriptionList(); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapperTest.java b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapperTest.java new file mode 100644 index 0000000..f3852d5 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/java/org/apache/james/mailbox/hbase/user/HBaseSubscriptionMapperTest.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.hbase.user; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.hbase.HBaseClusterSingleton; +import org.apache.james.mailbox.hbase.HBaseMailboxSessionMapperFactory; +import static org.apache.james.mailbox.hbase.HBaseNames.*; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runs tests for SubscriptionMapper. + * + */ +public class HBaseSubscriptionMapperTest { + + private static final Logger LOG = LoggerFactory.getLogger(HBaseSubscriptionMapperTest.class); + private static final HBaseClusterSingleton CLUSTER = HBaseClusterSingleton.build(); + private static Configuration conf; + private static HBaseMailboxSessionMapperFactory mapperFactory; + private static HBaseSubscriptionMapper mapper; + private static Map> subscriptionList; + private static final int USERS = 5; + private static final int MAILBOX_NO = 5; + + @Before + public void setUp() throws Exception { + ensureTables(); + clearTables(); + conf = CLUSTER.getConf(); + mapperFactory = new HBaseMailboxSessionMapperFactory(conf, null, null); + mapper = new HBaseSubscriptionMapper(conf); + fillSubscriptionList(); + } + + private void ensureTables() throws IOException { + CLUSTER.ensureTable(MAILBOXES_TABLE, new byte[][]{MAILBOX_CF}); + CLUSTER.ensureTable(MESSAGES_TABLE, + new byte[][]{MESSAGES_META_CF, MESSAGE_DATA_HEADERS_CF, MESSAGE_DATA_BODY_CF}); + CLUSTER.ensureTable(SUBSCRIPTIONS_TABLE, new byte[][]{SUBSCRIPTION_CF}); + } + + private void clearTables() { + CLUSTER.clearTable(MAILBOXES); + CLUSTER.clearTable(MESSAGES); + CLUSTER.clearTable(SUBSCRIPTIONS); + } + + private static void fillSubscriptionList() throws SubscriptionException { + LOG.info("Creating subscription list"); + SimpleSubscription subscription; + String user, mailbox; + subscriptionList = new HashMap>(); + for (int i = 0; i < USERS; i++) { + user = "user" + i; + final List mailboxes = new ArrayList(); + subscriptionList.put(user, mailboxes); + + for (int j = 0; j < MAILBOX_NO; j++) { + if (j == 0) { + mailbox = "INBOX"; + } else { + mailbox = "BOX" + j; + } + if ((i % 2 == 0) && (j > 0)) { + continue; + } + subscription = new SimpleSubscription(user, mailbox); + mailboxes.add(subscription); + mapper.save(subscription); + LOG.info("Adding subscription " + subscription); + } + } + } + + /** + * Test of findMailboxSubscriptionForUser method, of class + * HBseSubscriptionMapper. + */ + @Test + public void testFindMailboxSubscriptionForUser() throws Exception { + LOG.info("findMailboxSubscriptionForUser"); + + final SimpleSubscription fake1 = new SimpleSubscription("user1", "FAKEBOX"); + final SimpleSubscription fake2 = new SimpleSubscription("fakeUser", "INBOX"); + + for (String user : subscriptionList.keySet()) { + LOG.info("Searching for all subscriptions for user:{}", user); + for (SimpleSubscription subscription : subscriptionList.get(user)) { + final Subscription result = mapper.findMailboxSubscriptionForUser(user, subscription.getMailbox()); + assertEquals(subscription.getMailbox(), result.getMailbox()); + assertEquals(subscription.getUser(), result.getUser()); + } + } + assertNull(mapper.findMailboxSubscriptionForUser(fake1.getUser(), fake1.getMailbox())); + assertNull(mapper.findMailboxSubscriptionForUser(fake2.getUser(), fake2.getMailbox())); + } + + /** + * Test of save method, of class HBaseSubscriptionMapper. + */ + @Test + public void testSave() throws Exception { + LOG.info("save"); + final HTable subscriptions = new HTable(mapperFactory.getClusterConfiguration(), SUBSCRIPTIONS_TABLE); + + for (String user : subscriptionList.keySet()) { + final Get get = new Get(Bytes.toBytes(user)); + get.addFamily(SUBSCRIPTION_CF); + final Result result = subscriptions.get(get); + for (Subscription subscription : subscriptionList.get(user)) { + assertTrue(result.containsColumn(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()))); + } + } + subscriptions.close(); + } + + /** + * Test of findSubscriptionsForUser method, of class + * HBaseSubscriptionMapper. + */ + @Test + public void testFindSubscriptionsForUser() throws Exception { + LOG.info("findSubscriptionsForUser"); + final SimpleSubscription fake1 = new SimpleSubscription("user1", "FAKEBOX"); + final SimpleSubscription fake2 = new SimpleSubscription("fakeUser", "INBOX"); + for (String user : subscriptionList.keySet()) { + LOG.info("Searching for all subscriptions for user: " + user); + final List found = mapper.findSubscriptionsForUser(user); + assertEquals(subscriptionList.get(user).size(), found.size()); + // TODO: patch Subscription to implement equals + //assertTrue(subscriptionList.get(user).containsAll(foundSubscriptions)); + //assertTrue(foundSubscriptions.containsAll(subscriptionList.get(user))); + //assertFalse(foundSubscriptions.contains(fake1)); + //assertFalse(foundSubscriptions.contains(fake2)); + } + //TODO: check what value we should return in case of no subscriptions: null or empty list + assertEquals(mapper.findSubscriptionsForUser(fake2.getMailbox()).size(), 0); + + } + + /** + * Test of delete method, of class HBaseSubscriptionMapper. + */ + @Test + public void testDelete() throws Exception { + LOG.info("delete"); + final HTable subscriptions = new HTable(mapperFactory.getClusterConfiguration(), SUBSCRIPTIONS_TABLE); + + for (String user : subscriptionList.keySet()) { + LOG.info("Deleting subscriptions for user: " + user); + for (SimpleSubscription subscription : subscriptionList.get(user)) { + LOG.info("Deleting subscription : " + subscription); + mapper.delete(subscription); + final Get get = new Get(Bytes.toBytes(subscription.getUser())); + final Result result = subscriptions.get(get); + assertFalse(result.containsColumn(SUBSCRIPTION_CF, Bytes.toBytes(subscription.getMailbox()))); + } + } + subscriptions.close(); + fillSubscriptionList(); + } +} diff --git a/james/apache-james-mailbox/hbase/src/test/resources/.svn/all-wcprops b/james/apache-james-mailbox/hbase/src/test/resources/.svn/all-wcprops new file mode 100644 index 0000000..b0252b3 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1240009/james/mailbox/trunk/hbase/src/test/resources +END +hdfs-site.xml +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1240009/james/mailbox/trunk/hbase/src/test/resources/hdfs-site.xml +END +hadoop-metrics2.properties +K 25 +svn:wc:ra_dav:version-url +V 99 +/repos/asf/!svn/ver/1181805/james/mailbox/trunk/hbase/src/test/resources/hadoop-metrics2.properties +END diff --git a/james/apache-james-mailbox/hbase/src/test/resources/.svn/entries b/james/apache-james-mailbox/hbase/src/test/resources/.svn/entries new file mode 100644 index 0000000..671fd9b --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/hbase/src/test/resources +http://svn.apache.org/repos/asf + + + +2012-02-03T06:55:06.997663Z +1240009 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +hdfs-site.xml +file + + + + +2013-09-02T02:54:38.000000Z +117ab56f33382f4065c361e615ba5678 +2012-02-03T06:55:06.997663Z +1240009 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +1072 + +hadoop-metrics2.properties +file + + + + +2013-09-02T02:54:38.000000Z +ac04f9bf14ba3d0b282178e6a6ded696 +2011-10-11T14:27:45.423969Z +1181805 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +302 + diff --git a/james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base b/james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base b/james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base b/james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base new file mode 100644 index 0000000..e5a9785 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base @@ -0,0 +1,8 @@ +# Configuration of the "dfs" context for null +dfs.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "mapred" context for null +mapred.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "jvm" context for null +jvm.class=org.apache.hadoop.metrics.spi.NullContext diff --git a/james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base b/james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base new file mode 100644 index 0000000..9284b7c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base @@ -0,0 +1,33 @@ + + + + + + + dfs.permissions + true + + If "true", enable permission checking in HDFS. + If "false", permission checking is turned off, + but all other behavior is unchanged. + Switching from one parameter value to the other does not change the mode, + owner or group of files or directories. + + + + + dfs.datanode.data.dir.perm + 755 + Permissions for the directories on on the local filesystem where + the DFS data node store its blocks. The permissions can either be octal or symbolic. + + + + + dfs.support.append + true + This branch of HDFS supports reliable append/sync. + + + + diff --git a/james/apache-james-mailbox/hbase/src/test/resources/hadoop-metrics2.properties b/james/apache-james-mailbox/hbase/src/test/resources/hadoop-metrics2.properties new file mode 100644 index 0000000..e5a9785 --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/hadoop-metrics2.properties @@ -0,0 +1,8 @@ +# Configuration of the "dfs" context for null +dfs.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "mapred" context for null +mapred.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "jvm" context for null +jvm.class=org.apache.hadoop.metrics.spi.NullContext diff --git a/james/apache-james-mailbox/hbase/src/test/resources/hdfs-site.xml b/james/apache-james-mailbox/hbase/src/test/resources/hdfs-site.xml new file mode 100644 index 0000000..9284b7c --- /dev/null +++ b/james/apache-james-mailbox/hbase/src/test/resources/hdfs-site.xml @@ -0,0 +1,33 @@ + + + + + + + dfs.permissions + true + + If "true", enable permission checking in HDFS. + If "false", permission checking is turned off, + but all other behavior is unchanged. + Switching from one parameter value to the other does not change the mode, + owner or group of files or directories. + + + + + dfs.datanode.data.dir.perm + 755 + Permissions for the directories on on the local filesystem where + the DFS data node store its blocks. The permissions can either be octal or symbolic. + + + + + dfs.support.append + true + This branch of HDFS supports reliable append/sync. + + + + diff --git a/james/apache-james-mailbox/jcr/.svn/all-wcprops b/james/apache-james-mailbox/jcr/.svn/all-wcprops new file mode 100644 index 0000000..d714638 --- /dev/null +++ b/james/apache-james-mailbox/jcr/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 51 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/jcr +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 59 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/jcr/pom.xml +END diff --git a/james/apache-james-mailbox/jcr/.svn/dir-prop-base b/james/apache-james-mailbox/jcr/.svn/dir-prop-base new file mode 100644 index 0000000..0412bba --- /dev/null +++ b/james/apache-james-mailbox/jcr/.svn/dir-prop-base @@ -0,0 +1,8 @@ +K 10 +svn:ignore +V 22 +target +.* +coverage.ec + +END diff --git a/james/apache-james-mailbox/jcr/.svn/entries b/james/apache-james-mailbox/jcr/.svn/entries new file mode 100644 index 0000000..4f2e23f --- /dev/null +++ b/james/apache-james-mailbox/jcr/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr +http://svn.apache.org/repos/asf + + + +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:38.000000Z +5689ac059b15033d33cc92198e92af3f +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +3829 + diff --git a/james/apache-james-mailbox/jcr/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/jcr/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..634ac39 --- /dev/null +++ b/james/apache-james-mailbox/jcr/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,102 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-jcr + Apache James :: Mailbox :: JCR + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + commons-io + commons-io + + + javax.jcr + jcr + + + org.apache.jackrabbit + jackrabbit-core + test + + + org.apache.jackrabbit + jackrabbit-jcr-commons + + + junit + junit + test + + + + xerces + xercesImpl + test + + + xml-apis + xml-apis + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + diff --git a/james/apache-james-mailbox/jcr/pom.xml b/james/apache-james-mailbox/jcr/pom.xml new file mode 100644 index 0000000..634ac39 --- /dev/null +++ b/james/apache-james-mailbox/jcr/pom.xml @@ -0,0 +1,102 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-jcr + Apache James :: Mailbox :: JCR + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + commons-io + commons-io + + + javax.jcr + jcr + + + org.apache.jackrabbit + jackrabbit-core + test + + + org.apache.jackrabbit + jackrabbit-jcr-commons + + + junit + junit + test + + + + xerces + xercesImpl + test + + + xml-apis + xml-apis + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + diff --git a/james/apache-james-mailbox/jcr/src/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/.svn/all-wcprops new file mode 100644 index 0000000..403233b --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 55 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src +END diff --git a/james/apache-james-mailbox/jcr/src/.svn/entries b/james/apache-james-mailbox/jcr/src/.svn/entries new file mode 100644 index 0000000..58936be --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/.svn/all-wcprops new file mode 100644 index 0000000..1ef257c --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jcr/src/main +END diff --git a/james/apache-james-mailbox/jcr/src/main/.svn/entries b/james/apache-james-mailbox/jcr/src/main/.svn/entries new file mode 100644 index 0000000..13d57a6 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..8e3a4fe --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 65 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/.svn/entries new file mode 100644 index 0000000..e9a36ab --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..21fb4bd --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java/org +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/.svn/entries new file mode 100644 index 0000000..0aaf26b --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..e9d043d --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 76 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..5ff4d2b --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..380c68b --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..57e2bbd --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..d7b1411 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..a4c4af3 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +jcr +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/all-wcprops new file mode 100644 index 0000000..6332bc5 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/all-wcprops @@ -0,0 +1,71 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr +END +GlobalMailboxSessionJCRRepository.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1041808/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/GlobalMailboxSessionJCRRepository.java +END +JCRUtils.java +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1291197/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRUtils.java +END +Persistent.java +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1179563/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/Persistent.java +END +JCRImapConstants.java +K 25 +svn:wc:ra_dav:version-url +V 116 +/repos/asf/!svn/ver/1041808/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRImapConstants.java +END +JCRMailboxManager.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxManager.java +END +AbstractJCRScalingMapper.java +K 25 +svn:wc:ra_dav:version-url +V 124 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/AbstractJCRScalingMapper.java +END +JCRRepositoryAuthenticator.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1041808/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRRepositoryAuthenticator.java +END +JCRMailboxSessionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxSessionMapperFactory.java +END +JCRMessageManager.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java +END +JCRSubscriptionManager.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1075221/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRSubscriptionManager.java +END +MailboxSessionJCRRepository.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1041808/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/MailboxSessionJCRRepository.java +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/entries new file mode 100644 index 0000000..556f593 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/entries @@ -0,0 +1,408 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +GlobalMailboxSessionJCRRepository.java +file + + + + +2013-09-02T02:54:38.000000Z +f0bbbe723819f0024561438667ed7628 +2010-09-06T18:59:58.520525Z +993124 +norman + + + + + + + + + + + + + + + + + + + + + +2177 + +JCRUtils.java +file + + + + +2013-09-02T02:54:38.000000Z +504fa55ba566870dee2a50f578bc2fdf +2012-02-20T11:14:47.341630Z +1291197 +eric + + + + + + + + + + + + + + + + + + + + + +3037 + +Persistent.java +file + + + + +2013-09-02T02:54:38.000000Z +36dc0e86a0e0d160fa1f55b5eab1baa0 +2011-10-06T10:26:20.851280Z +1179563 +felixk + + + + + + + + + + + + + + + + + + + + + +1794 + +mail +dir + +JCRImapConstants.java +file + + + + +2013-09-02T02:54:38.000000Z +caa9bb9aba10220674ee3420462b3bda +2010-09-06T18:59:58.520525Z +993124 +norman + + + + + + + + + + + + + + + + + + + + + +1422 + +JCRMailboxManager.java +file + + + + +2013-09-02T02:54:38.000000Z +b4874281a63a17245ec39530fc2d0083 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3462 + +AbstractJCRScalingMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +58205996370fd1205db66fecf695d05d +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +6037 + +JCRRepositoryAuthenticator.java +file + + + + +2013-09-02T02:54:38.000000Z +543f8425d7f89c4b9ecb5e97ffbf0730 +2010-09-06T18:59:58.520525Z +993124 +norman + + + + + + + + + + + + + + + + + + + + + +2984 + +JCRMailboxSessionMapperFactory.java +file + + + + +2013-09-02T02:54:38.000000Z +d1d00d22bcbb34dbf01ca5cc89c7a634 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3975 + +user +dir + +JCRMessageManager.java +file + + + + +2013-09-02T02:54:38.000000Z +624b8c09056ddcd4acdef0d94dc72e9e +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3549 + +JCRSubscriptionManager.java +file + + + + +2013-09-02T02:54:38.000000Z +b3d06a79f3db5b1cc323302e7af0e349 +2011-02-28T06:46:25.698562Z +1075221 +norman + + + + + + + + + + + + + + + + + + + + + +2089 + +MailboxSessionJCRRepository.java +file + + + + +2013-09-02T02:54:38.000000Z +ebcc92441d34842090ecf88f8b4a5acb +2010-09-06T18:59:58.520525Z +993124 +norman + + + + + + + + + + + + + + + + + + + + + +3788 + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/AbstractJCRScalingMapper.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/AbstractJCRScalingMapper.java.svn-base new file mode 100644 index 0000000..62fe583 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/AbstractJCRScalingMapper.java.svn-base @@ -0,0 +1,180 @@ +/**************************************************************** + * 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 javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; +import org.slf4j.Logger; + +/** + * Abstract base class for Mapper's which support scaling. This supports Level 1 Implementations of JCR. So no real transaction management is used. + * + * The Session.save() will get called on commit() method, session.refresh(false) on rollback, and session.refresh(true) on begin() + * + */ +public abstract class AbstractJCRScalingMapper extends TransactionalMapper implements JCRImapConstants { + public final static String MAILBOXES_PATH = "mailboxes"; + + private final MailboxSessionJCRRepository repository; + private final int scaling; + + private MailboxSession mSession; + private final static char PAD ='_'; + + public AbstractJCRScalingMapper(MailboxSessionJCRRepository repository, MailboxSession mSession, int scaling) { + this.scaling = scaling; + + this.mSession = mSession; + this.repository = repository; + } + + /** + * Return the logger + * + * @return logger + */ + protected Logger getLogger() { + return mSession.getLog(); + } + + /** + * Return the JCR Session + * + * @return session + */ + protected Session getSession() throws RepositoryException{ + return repository.login(mSession); + } + + /** + * Begin is not supported by level 1 JCR implementations, however we refresh the session + */ + protected void begin() throws MailboxException { + try { + getSession().refresh(true); + } catch (RepositoryException e) { + // do nothin on refresh + } + // Do nothing + } + + /** + * Just call save on the underlying JCR Session, because level 1 JCR implementation does not offer Transactions + */ + protected void commit() throws MailboxException { + try { + if (getSession().hasPendingChanges()) { + getSession().save(); + } + } catch (RepositoryException e) { + throw new MailboxException("Unable to commit", e); + } + } + + /** + * Rollback is not supported by level 1 JCR implementations, so just do nothing + */ + protected void rollback() throws MailboxException { + try { + // just refresh session and discard all pending changes + getSession().refresh(false); + } catch (RepositoryException e) { + // just catch on rollback by now + } + } + + /** + * Logout from open JCR Session + */ + public void endRequest() { + repository.logout(mSession); + } + + /** + * Construct the user node path part, using the specified scaling factor. + * If the username is not long enough it will fill it with {@link #PAD} + * + * So if you use a scaling of 2 it will look like: + * + * foo: + * f/fo/foo + * + * fo: + * f/fo/fo + * + * f: + * f/f_/f + * + * @param username + * @return path + */ + protected String constructUserPathPart(String username) { + StringBuffer sb = new StringBuffer(); + for (int i = 0 ; i < scaling; i++) { + if (username.length() > i) { + sb.append(username.substring(0,i+1)); + } else { + sb.append(username); + int a = i - username.length(); + for (int b = 0; b < a; b++) { + sb.append(PAD); + } + } + sb.append(NODE_DELIMITER); + } + sb.append(username); + return sb.toString(); + + } + + /** + * Create the needed Node structure for the given username using the given Node as parent. + * + * The method will use {@link #constructUserPathPart(String)} for create the needed node path and split + * it when a NODE_DELIMITER was found + * + * @param parent + * @param username + * @return userNode + * @throws RepositoryException + */ + protected Node createUserPathStructure(Node parent, String username) + throws RepositoryException { + String userpath = constructUserPathPart(username); + + String[] userPathParts = userpath.split(NODE_DELIMITER); + for (int a = 0; a < userPathParts.length; a++) { + parent = JcrUtils.getOrAddNode(parent, userPathParts[a], + "nt:unstructured"); + + // thats the last user node so add the right mixin type + if (a + 1 == userPathParts.length) + parent.addMixin("jamesMailbox:user"); + } + + return parent; + + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/GlobalMailboxSessionJCRRepository.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/GlobalMailboxSessionJCRRepository.java.svn-base new file mode 100644 index 0000000..3ca4174 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/GlobalMailboxSessionJCRRepository.java.svn-base @@ -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.jcr; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.james.mailbox.MailboxSession; + + +/** + * + * Manage JCR {@link Session}. It will use one user and password for all + * + */ +public class GlobalMailboxSessionJCRRepository extends MailboxSessionJCRRepository{ + + private String username; + private char[] pass; + + public GlobalMailboxSessionJCRRepository(Repository repository, String workspace, String username, String password) { + super(repository, workspace); + this.username = username; + if (password == null) { + pass = null; + } else { + pass = password.toCharArray(); + } + } + + /** + * Login in the {@link Repository} with the global configured user and password + */ + @Override + public Session login(MailboxSession session) throws RepositoryException { + return login(session, username, pass); + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRImapConstants.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRImapConstants.java.svn-base new file mode 100644 index 0000000..1bf31b5 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRImapConstants.java.svn-base @@ -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.jcr; + +/** + * Constants for JCR + * + */ +public interface JCRImapConstants { + + + /** + * Delimiter for Nodes + */ + public static final String NODE_DELIMITER = "/"; + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManager.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManager.java.svn-base new file mode 100644 index 0000000..e52f372 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManager.java.svn-base @@ -0,0 +1,63 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JCR implementation of a MailboxManager + * + */ +public class JCRMailboxManager extends StoreMailboxManager implements JCRImapConstants { + + private final Logger logger = LoggerFactory.getLogger(JCRMailboxManager.class); + + public JCRMailboxManager(JCRMailboxSessionMapperFactory mapperFactory, final Authenticator authenticator, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + this(mapperFactory, authenticator, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + } + + public JCRMailboxManager(JCRMailboxSessionMapperFactory mapperFactory, final Authenticator authenticator, final MailboxPathLocker locker, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + } + + + @Override + protected StoreMessageManager createMessageManager(Mailbox mailboxEntity, MailboxSession session) throws MailboxException{ + return new JCRMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), (JCRMailbox) mailboxEntity, getAclResolver(), getGroupMembershipResolver(), logger, getDelimiter()); + } + + @Override + protected Mailbox doCreateMailbox(MailboxPath path, MailboxSession session) throws MailboxException { + return new org.apache.james.mailbox.jcr.mail.model.JCRMailbox(path, randomUidValidity(), logger); + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxSessionMapperFactory.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxSessionMapperFactory.java.svn-base new file mode 100644 index 0000000..de44f88 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxSessionMapperFactory.java.svn-base @@ -0,0 +1,82 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.jcr.mail.JCRMailboxMapper; +import org.apache.james.mailbox.jcr.mail.JCRMessageMapper; +import org.apache.james.mailbox.jcr.user.JCRSubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * JCR implementation of a {@link MailboxSessionMapperFactory} + * + * + */ +public class JCRMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private final MailboxSessionJCRRepository repository; + private final static int DEFAULT_SCALING = 2; + private final int scaling; + private int messageScaling; + private UidProvider uidProvider; + private ModSeqProvider modSeqProvider; + + public JCRMailboxSessionMapperFactory(final MailboxSessionJCRRepository repository, final UidProvider uidProvider, final ModSeqProvider modSeqProvider) { + this(repository, uidProvider, modSeqProvider, DEFAULT_SCALING, JCRMessageMapper.MESSAGE_SCALE_DAY); + } + + public JCRMailboxSessionMapperFactory(final MailboxSessionJCRRepository repository, final UidProvider uidProvider, final ModSeqProvider modSeqProvider, final int scaling, final int messageScaling) { + this.repository = repository; + this.scaling = scaling; + this.messageScaling = messageScaling; + this.uidProvider= uidProvider; + this.modSeqProvider = modSeqProvider; + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException { + JCRMailboxMapper mapper = new JCRMailboxMapper(repository, session, scaling); + return mapper; + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) throws MailboxException { + JCRMessageMapper messageMapper = new JCRMessageMapper(repository, session, uidProvider, modSeqProvider, messageScaling); + return messageMapper; + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException { + JCRSubscriptionMapper mapper = new JCRSubscriptionMapper(repository, session, DEFAULT_SCALING); + return mapper; + } + + public MailboxSessionJCRRepository getRepository() { + return repository; + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMessageManager.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMessageManager.java.svn-base new file mode 100644 index 0000000..12853dc --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMessageManager.java.svn-base @@ -0,0 +1,74 @@ +/**************************************************************** + * 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 java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.jcr.mail.model.JCRMessage; +import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.search.MessageSearchIndex; +import org.slf4j.Logger; + +/** + * JCR implementation of a {@link org.apache.james.mailbox.MessageManager} + * + */ +public class JCRMessageManager extends StoreMessageManager { + + private final Logger log; + + public JCRMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + final MailboxEventDispatcher dispatcher, final MailboxPathLocker locker, final JCRMailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver, final Logger log, final char delimiter) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + this.log = log; + } + + + @Override + protected Message createMessage(Date internalDate, int size, int bodyStartOctet, SharedInputStream content, Flags flags, PropertyBuilder propertyBuilder) throws MailboxException{ + final Message message = new JCRMessage(getMailboxEntity().getMailboxId(), internalDate, + size, flags, content, bodyStartOctet, propertyBuilder, log); + return message; + } + + /** + * This implementation allow to store ANY user flag in a permanent manner + */ + @Override + protected Flags getPermanentFlags(MailboxSession session) { + Flags perm = super.getPermanentFlags(session); + perm.add(Flags.Flag.USER); + return perm; + } + + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRRepositoryAuthenticator.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRRepositoryAuthenticator.java.svn-base new file mode 100644 index 0000000..f134581 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRRepositoryAuthenticator.java.svn-base @@ -0,0 +1,61 @@ +/**************************************************************** + * 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 javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.james.mailbox.store.Authenticator; + +/** + * {@link Authenticator} implementation which will try to log in the {@link Repository} and obtain a Session. If this fails it will handle the + * user/pass as invalid. If it success it will logout from the session again and handle the user/pass as valid. + * + * This implementation should be used if you want to handle the IMAP Users transparent to the JCR Users. The use of the implementation only makes + * real sense in conjunction with the direct {@link MailboxSessionJCRRepository} implementation (not a subclass). + * + */ +public class JCRRepositoryAuthenticator implements Authenticator{ + + private MailboxSessionJCRRepository repository; + + public JCRRepositoryAuthenticator(MailboxSessionJCRRepository repository) { + this.repository = repository; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.Authenticator#isAuthentic(java.lang.String, java.lang.CharSequence) + */ + public boolean isAuthentic(String userid, CharSequence passwd) { + Repository repos = repository.getRepository(); + try { + // Try to log in the Repository with the user and password. If this success we can asume that the user/pass is valid. In + // all cases we need to logout again to not bind two JCR Session per thread later + Session session = repos.login(new SimpleCredentials(userid,passwd.toString().toCharArray()), repository.getWorkspace()); + session.logout(); + return true; + } catch (RepositoryException e) { + return false; + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManager.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManager.java.svn-base new file mode 100644 index 0000000..7c5827b --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManager.java.svn-base @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.jcr.user.model.JCRSubscription; +import org.apache.james.mailbox.store.StoreSubscriptionManager; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JCR implementation of a SubscriptionManager + */ +public class JCRSubscriptionManager extends StoreSubscriptionManager implements JCRImapConstants { + + private final Logger logger = LoggerFactory.getLogger(JCRSubscriptionManager.class); + + public JCRSubscriptionManager(JCRMailboxSessionMapperFactory mapperFactory) { + super(mapperFactory); + } + + @Override + protected Subscription createSubscription(MailboxSession session, String mailbox) { + return new JCRSubscription(session.getUser().getUserName(), mailbox, logger); + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRUtils.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRUtils.java.svn-base new file mode 100644 index 0000000..94719db --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRUtils.java.svn-base @@ -0,0 +1,81 @@ +/**************************************************************** + * 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 java.io.InputStream; +import java.io.InputStreamReader; + +import javax.jcr.Repository; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.jackrabbit.commons.cnd.CndImporter; + +/** + * Utilities used for JCR + * + */ +public class JCRUtils implements JCRImapConstants{ + + /** + * Register the imap CND file in the workspace + * + * @param repository + * @param workspace + * @param username + * @param password + */ + public static void registerCnd(Repository repository, String workspace, String username, String password) { + try { + Session session; + if (username == null) { + session = repository.login(workspace); + } else { + char pass[]; + if (password == null) { + pass = new char[0]; + } else { + pass = password.toCharArray(); + } + session = repository.login(new SimpleCredentials(username, pass), workspace); + } + registerCnd(session); + session.logout(); + } catch (Exception e) { + throw new RuntimeException("Unable to register cnd file", e); + } + } + + /** + * Register the imap CND file + * + * @param session + */ + public static void registerCnd(Session session) { + // Register the custom node types defined in the CND file + InputStream is = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("mailbox-jcr.cnd"); + try { + CndImporter.registerNodeTypes(new InputStreamReader(is), session); + } catch (Exception e) { + throw new RuntimeException("Unable to register cnd file", e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/MailboxSessionJCRRepository.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/MailboxSessionJCRRepository.java.svn-base new file mode 100644 index 0000000..ac0c9d9 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/MailboxSessionJCRRepository.java.svn-base @@ -0,0 +1,111 @@ +/**************************************************************** + * 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 javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSession.User; + +/** + * Manage JCR {@link Session}. It use the username and the password of + * the logged in IMAP user to obtain a JCR {@link Session} from the {@link Repository} + * + */ +public class MailboxSessionJCRRepository { + private final static String JCR_SESSION = "JCR_SESSION"; + private Repository repository; + private String workspace; + + public MailboxSessionJCRRepository(Repository repository ,String workspace) { + this.repository = repository; + this.workspace = workspace; + } + + /** + * If no {@link Session} exists for the {@link MailboxSession} one will get created. + * If one exists it just return the existing. + * + * @param session + * @return jcrSession + * @throws RepositoryException + */ + public Session login(MailboxSession session) throws RepositoryException { + User user = session.getUser(); + String username = user.getUserName(); + String password = user.getPassword(); + char[] pass = null; + if (password != null) { + pass = password.toCharArray(); + } + return login(session, username, pass); + } + + + + protected Session login(MailboxSession mSession, String username, + char[] pass) throws RepositoryException { + Session session = (Session) mSession.getAttributes().get(JCR_SESSION); + if (session == null) { + session = repository.login(new SimpleCredentials(username, pass), + workspace); + mSession.getAttributes().put(JCR_SESSION, session); + } + return session; + } + + /** + * Logout the JCR {@link Session} if one exists + * + * @param mSession + */ + public void logout(MailboxSession mSession) { + if (mSession == null) return; + + Session session = (Session) mSession.getAttributes().remove(JCR_SESSION); + + if (session != null) { + if (session.isLive()) + session.logout(); + session = null; + } + } + + + /** + * Return the {@link Repository} + * + * @return repository + */ + public Repository getRepository() { + return repository; + } + + /** + * Return the workspace + * + * @return workspace + */ + public String getWorkspace() { + return workspace; + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/Persistent.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/Persistent.java.svn-base new file mode 100644 index 0000000..8509ddc --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/.svn/text-base/Persistent.java.svn-base @@ -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.jcr; + +import java.io.IOException; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + + +public interface Persistent { + + /** + * Merge the object with the node + * + * @param node + */ + public void merge(Node node) throws RepositoryException, IOException; + + /** + * Return underlying Node + * + * @return node + */ + public Node getNode(); + + /** + * Return if the object is persistent + * + * @return true if object is persistent else false + */ + public boolean isPersistent(); + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/AbstractJCRScalingMapper.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/AbstractJCRScalingMapper.java new file mode 100644 index 0000000..62fe583 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/AbstractJCRScalingMapper.java @@ -0,0 +1,180 @@ +/**************************************************************** + * 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 javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; +import org.slf4j.Logger; + +/** + * Abstract base class for Mapper's which support scaling. This supports Level 1 Implementations of JCR. So no real transaction management is used. + * + * The Session.save() will get called on commit() method, session.refresh(false) on rollback, and session.refresh(true) on begin() + * + */ +public abstract class AbstractJCRScalingMapper extends TransactionalMapper implements JCRImapConstants { + public final static String MAILBOXES_PATH = "mailboxes"; + + private final MailboxSessionJCRRepository repository; + private final int scaling; + + private MailboxSession mSession; + private final static char PAD ='_'; + + public AbstractJCRScalingMapper(MailboxSessionJCRRepository repository, MailboxSession mSession, int scaling) { + this.scaling = scaling; + + this.mSession = mSession; + this.repository = repository; + } + + /** + * Return the logger + * + * @return logger + */ + protected Logger getLogger() { + return mSession.getLog(); + } + + /** + * Return the JCR Session + * + * @return session + */ + protected Session getSession() throws RepositoryException{ + return repository.login(mSession); + } + + /** + * Begin is not supported by level 1 JCR implementations, however we refresh the session + */ + protected void begin() throws MailboxException { + try { + getSession().refresh(true); + } catch (RepositoryException e) { + // do nothin on refresh + } + // Do nothing + } + + /** + * Just call save on the underlying JCR Session, because level 1 JCR implementation does not offer Transactions + */ + protected void commit() throws MailboxException { + try { + if (getSession().hasPendingChanges()) { + getSession().save(); + } + } catch (RepositoryException e) { + throw new MailboxException("Unable to commit", e); + } + } + + /** + * Rollback is not supported by level 1 JCR implementations, so just do nothing + */ + protected void rollback() throws MailboxException { + try { + // just refresh session and discard all pending changes + getSession().refresh(false); + } catch (RepositoryException e) { + // just catch on rollback by now + } + } + + /** + * Logout from open JCR Session + */ + public void endRequest() { + repository.logout(mSession); + } + + /** + * Construct the user node path part, using the specified scaling factor. + * If the username is not long enough it will fill it with {@link #PAD} + * + * So if you use a scaling of 2 it will look like: + * + * foo: + * f/fo/foo + * + * fo: + * f/fo/fo + * + * f: + * f/f_/f + * + * @param username + * @return path + */ + protected String constructUserPathPart(String username) { + StringBuffer sb = new StringBuffer(); + for (int i = 0 ; i < scaling; i++) { + if (username.length() > i) { + sb.append(username.substring(0,i+1)); + } else { + sb.append(username); + int a = i - username.length(); + for (int b = 0; b < a; b++) { + sb.append(PAD); + } + } + sb.append(NODE_DELIMITER); + } + sb.append(username); + return sb.toString(); + + } + + /** + * Create the needed Node structure for the given username using the given Node as parent. + * + * The method will use {@link #constructUserPathPart(String)} for create the needed node path and split + * it when a NODE_DELIMITER was found + * + * @param parent + * @param username + * @return userNode + * @throws RepositoryException + */ + protected Node createUserPathStructure(Node parent, String username) + throws RepositoryException { + String userpath = constructUserPathPart(username); + + String[] userPathParts = userpath.split(NODE_DELIMITER); + for (int a = 0; a < userPathParts.length; a++) { + parent = JcrUtils.getOrAddNode(parent, userPathParts[a], + "nt:unstructured"); + + // thats the last user node so add the right mixin type + if (a + 1 == userPathParts.length) + parent.addMixin("jamesMailbox:user"); + } + + return parent; + + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/GlobalMailboxSessionJCRRepository.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/GlobalMailboxSessionJCRRepository.java new file mode 100644 index 0000000..3ca4174 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/GlobalMailboxSessionJCRRepository.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.jcr; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.james.mailbox.MailboxSession; + + +/** + * + * Manage JCR {@link Session}. It will use one user and password for all + * + */ +public class GlobalMailboxSessionJCRRepository extends MailboxSessionJCRRepository{ + + private String username; + private char[] pass; + + public GlobalMailboxSessionJCRRepository(Repository repository, String workspace, String username, String password) { + super(repository, workspace); + this.username = username; + if (password == null) { + pass = null; + } else { + pass = password.toCharArray(); + } + } + + /** + * Login in the {@link Repository} with the global configured user and password + */ + @Override + public Session login(MailboxSession session) throws RepositoryException { + return login(session, username, pass); + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRImapConstants.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRImapConstants.java new file mode 100644 index 0000000..1bf31b5 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRImapConstants.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.jcr; + +/** + * Constants for JCR + * + */ +public interface JCRImapConstants { + + + /** + * Delimiter for Nodes + */ + public static final String NODE_DELIMITER = "/"; + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxManager.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxManager.java new file mode 100644 index 0000000..e52f372 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxManager.java @@ -0,0 +1,63 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JCR implementation of a MailboxManager + * + */ +public class JCRMailboxManager extends StoreMailboxManager implements JCRImapConstants { + + private final Logger logger = LoggerFactory.getLogger(JCRMailboxManager.class); + + public JCRMailboxManager(JCRMailboxSessionMapperFactory mapperFactory, final Authenticator authenticator, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + this(mapperFactory, authenticator, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + } + + public JCRMailboxManager(JCRMailboxSessionMapperFactory mapperFactory, final Authenticator authenticator, final MailboxPathLocker locker, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + } + + + @Override + protected StoreMessageManager createMessageManager(Mailbox mailboxEntity, MailboxSession session) throws MailboxException{ + return new JCRMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), (JCRMailbox) mailboxEntity, getAclResolver(), getGroupMembershipResolver(), logger, getDelimiter()); + } + + @Override + protected Mailbox doCreateMailbox(MailboxPath path, MailboxSession session) throws MailboxException { + return new org.apache.james.mailbox.jcr.mail.model.JCRMailbox(path, randomUidValidity(), logger); + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxSessionMapperFactory.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxSessionMapperFactory.java new file mode 100644 index 0000000..de44f88 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMailboxSessionMapperFactory.java @@ -0,0 +1,82 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.jcr.mail.JCRMailboxMapper; +import org.apache.james.mailbox.jcr.mail.JCRMessageMapper; +import org.apache.james.mailbox.jcr.user.JCRSubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * JCR implementation of a {@link MailboxSessionMapperFactory} + * + * + */ +public class JCRMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private final MailboxSessionJCRRepository repository; + private final static int DEFAULT_SCALING = 2; + private final int scaling; + private int messageScaling; + private UidProvider uidProvider; + private ModSeqProvider modSeqProvider; + + public JCRMailboxSessionMapperFactory(final MailboxSessionJCRRepository repository, final UidProvider uidProvider, final ModSeqProvider modSeqProvider) { + this(repository, uidProvider, modSeqProvider, DEFAULT_SCALING, JCRMessageMapper.MESSAGE_SCALE_DAY); + } + + public JCRMailboxSessionMapperFactory(final MailboxSessionJCRRepository repository, final UidProvider uidProvider, final ModSeqProvider modSeqProvider, final int scaling, final int messageScaling) { + this.repository = repository; + this.scaling = scaling; + this.messageScaling = messageScaling; + this.uidProvider= uidProvider; + this.modSeqProvider = modSeqProvider; + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException { + JCRMailboxMapper mapper = new JCRMailboxMapper(repository, session, scaling); + return mapper; + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) throws MailboxException { + JCRMessageMapper messageMapper = new JCRMessageMapper(repository, session, uidProvider, modSeqProvider, messageScaling); + return messageMapper; + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException { + JCRSubscriptionMapper mapper = new JCRSubscriptionMapper(repository, session, DEFAULT_SCALING); + return mapper; + } + + public MailboxSessionJCRRepository getRepository() { + return repository; + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java new file mode 100644 index 0000000..12853dc --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRMessageManager.java @@ -0,0 +1,74 @@ +/**************************************************************** + * 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 java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.jcr.mail.model.JCRMessage; +import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.search.MessageSearchIndex; +import org.slf4j.Logger; + +/** + * JCR implementation of a {@link org.apache.james.mailbox.MessageManager} + * + */ +public class JCRMessageManager extends StoreMessageManager { + + private final Logger log; + + public JCRMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + final MailboxEventDispatcher dispatcher, final MailboxPathLocker locker, final JCRMailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver, final Logger log, final char delimiter) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + this.log = log; + } + + + @Override + protected Message createMessage(Date internalDate, int size, int bodyStartOctet, SharedInputStream content, Flags flags, PropertyBuilder propertyBuilder) throws MailboxException{ + final Message message = new JCRMessage(getMailboxEntity().getMailboxId(), internalDate, + size, flags, content, bodyStartOctet, propertyBuilder, log); + return message; + } + + /** + * This implementation allow to store ANY user flag in a permanent manner + */ + @Override + protected Flags getPermanentFlags(MailboxSession session) { + Flags perm = super.getPermanentFlags(session); + perm.add(Flags.Flag.USER); + return perm; + } + + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRRepositoryAuthenticator.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRRepositoryAuthenticator.java new file mode 100644 index 0000000..f134581 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRRepositoryAuthenticator.java @@ -0,0 +1,61 @@ +/**************************************************************** + * 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 javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.james.mailbox.store.Authenticator; + +/** + * {@link Authenticator} implementation which will try to log in the {@link Repository} and obtain a Session. If this fails it will handle the + * user/pass as invalid. If it success it will logout from the session again and handle the user/pass as valid. + * + * This implementation should be used if you want to handle the IMAP Users transparent to the JCR Users. The use of the implementation only makes + * real sense in conjunction with the direct {@link MailboxSessionJCRRepository} implementation (not a subclass). + * + */ +public class JCRRepositoryAuthenticator implements Authenticator{ + + private MailboxSessionJCRRepository repository; + + public JCRRepositoryAuthenticator(MailboxSessionJCRRepository repository) { + this.repository = repository; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.Authenticator#isAuthentic(java.lang.String, java.lang.CharSequence) + */ + public boolean isAuthentic(String userid, CharSequence passwd) { + Repository repos = repository.getRepository(); + try { + // Try to log in the Repository with the user and password. If this success we can asume that the user/pass is valid. In + // all cases we need to logout again to not bind two JCR Session per thread later + Session session = repos.login(new SimpleCredentials(userid,passwd.toString().toCharArray()), repository.getWorkspace()); + session.logout(); + return true; + } catch (RepositoryException e) { + return false; + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRSubscriptionManager.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRSubscriptionManager.java new file mode 100644 index 0000000..7c5827b --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRSubscriptionManager.java @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.jcr.user.model.JCRSubscription; +import org.apache.james.mailbox.store.StoreSubscriptionManager; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JCR implementation of a SubscriptionManager + */ +public class JCRSubscriptionManager extends StoreSubscriptionManager implements JCRImapConstants { + + private final Logger logger = LoggerFactory.getLogger(JCRSubscriptionManager.class); + + public JCRSubscriptionManager(JCRMailboxSessionMapperFactory mapperFactory) { + super(mapperFactory); + } + + @Override + protected Subscription createSubscription(MailboxSession session, String mailbox) { + return new JCRSubscription(session.getUser().getUserName(), mailbox, logger); + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRUtils.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRUtils.java new file mode 100644 index 0000000..94719db --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/JCRUtils.java @@ -0,0 +1,81 @@ +/**************************************************************** + * 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 java.io.InputStream; +import java.io.InputStreamReader; + +import javax.jcr.Repository; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.jackrabbit.commons.cnd.CndImporter; + +/** + * Utilities used for JCR + * + */ +public class JCRUtils implements JCRImapConstants{ + + /** + * Register the imap CND file in the workspace + * + * @param repository + * @param workspace + * @param username + * @param password + */ + public static void registerCnd(Repository repository, String workspace, String username, String password) { + try { + Session session; + if (username == null) { + session = repository.login(workspace); + } else { + char pass[]; + if (password == null) { + pass = new char[0]; + } else { + pass = password.toCharArray(); + } + session = repository.login(new SimpleCredentials(username, pass), workspace); + } + registerCnd(session); + session.logout(); + } catch (Exception e) { + throw new RuntimeException("Unable to register cnd file", e); + } + } + + /** + * Register the imap CND file + * + * @param session + */ + public static void registerCnd(Session session) { + // Register the custom node types defined in the CND file + InputStream is = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("mailbox-jcr.cnd"); + try { + CndImporter.registerNodeTypes(new InputStreamReader(is), session); + } catch (Exception e) { + throw new RuntimeException("Unable to register cnd file", e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/MailboxSessionJCRRepository.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/MailboxSessionJCRRepository.java new file mode 100644 index 0000000..ac0c9d9 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/MailboxSessionJCRRepository.java @@ -0,0 +1,111 @@ +/**************************************************************** + * 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 javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSession.User; + +/** + * Manage JCR {@link Session}. It use the username and the password of + * the logged in IMAP user to obtain a JCR {@link Session} from the {@link Repository} + * + */ +public class MailboxSessionJCRRepository { + private final static String JCR_SESSION = "JCR_SESSION"; + private Repository repository; + private String workspace; + + public MailboxSessionJCRRepository(Repository repository ,String workspace) { + this.repository = repository; + this.workspace = workspace; + } + + /** + * If no {@link Session} exists for the {@link MailboxSession} one will get created. + * If one exists it just return the existing. + * + * @param session + * @return jcrSession + * @throws RepositoryException + */ + public Session login(MailboxSession session) throws RepositoryException { + User user = session.getUser(); + String username = user.getUserName(); + String password = user.getPassword(); + char[] pass = null; + if (password != null) { + pass = password.toCharArray(); + } + return login(session, username, pass); + } + + + + protected Session login(MailboxSession mSession, String username, + char[] pass) throws RepositoryException { + Session session = (Session) mSession.getAttributes().get(JCR_SESSION); + if (session == null) { + session = repository.login(new SimpleCredentials(username, pass), + workspace); + mSession.getAttributes().put(JCR_SESSION, session); + } + return session; + } + + /** + * Logout the JCR {@link Session} if one exists + * + * @param mSession + */ + public void logout(MailboxSession mSession) { + if (mSession == null) return; + + Session session = (Session) mSession.getAttributes().remove(JCR_SESSION); + + if (session != null) { + if (session.isLive()) + session.logout(); + session = null; + } + } + + + /** + * Return the {@link Repository} + * + * @return repository + */ + public Repository getRepository() { + return repository; + } + + /** + * Return the workspace + * + * @return workspace + */ + public String getWorkspace() { + return workspace; + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/Persistent.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/Persistent.java new file mode 100644 index 0000000..8509ddc --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/Persistent.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.jcr; + +import java.io.IOException; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + + +public interface Persistent { + + /** + * Merge the object with the node + * + * @param node + */ + public void merge(Node node) throws RepositoryException, IOException; + + /** + * Return underlying Node + * + * @return node + */ + public Node getNode(); + + /** + * Return if the object is persistent + * + * @return true if object is persistent else false + */ + public boolean isPersistent(); + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/all-wcprops new file mode 100644 index 0000000..4424453 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 99 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail +END +JCRMailboxMapper.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMailboxMapper.java +END +JCRModSeqProvider.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRModSeqProvider.java +END +JCRMessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMessageMapper.java +END +JCRUidProvider.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRUidProvider.java +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/entries new file mode 100644 index 0000000..4e871a2 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/entries @@ -0,0 +1,167 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JCRMailboxMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +e5dc613a024dbd0d524bd4f4f1b45abb +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +10544 + +model +dir + +JCRModSeqProvider.java +file + + + + +2013-09-02T02:54:38.000000Z +1d8d2aa11cc2e274c2a1a19889b7c835 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3186 + +JCRMessageMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +975e9e870061cc47844504308e1ecc98 +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + + + + + + + + +28713 + +JCRUidProvider.java +file + + + + +2013-09-02T02:54:38.000000Z +cec0cf528b92886fc1321c5f3485fba9 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3151 + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMailboxMapper.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMailboxMapper.java.svn-base new file mode 100644 index 0000000..ca848d4 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMailboxMapper.java.svn-base @@ -0,0 +1,236 @@ +/**************************************************************** + * 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.mail; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.RepositoryException; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.util.ISO9075; +import org.apache.jackrabbit.util.Text; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.jcr.AbstractJCRScalingMapper; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * JCR implementation of a MailboxMapper + * + * + */ +public class JCRMailboxMapper extends AbstractJCRScalingMapper implements MailboxMapper { + + + public JCRMailboxMapper(final MailboxSessionJCRRepository repos, MailboxSession session, final int scaling) { + super(repos, session, scaling); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MailboxMapper#delete(org.apache.james + * .imap.store.mail.model.Mailbox) + */ + public void delete(Mailbox mailbox) throws MailboxException { + try { + Node node = getSession().getNodeByIdentifier(((JCRMailbox) mailbox).getMailboxId()); + + node.remove(); + + } catch (PathNotFoundException e) { + // mailbox does not exists.. + } catch (RepositoryException e) { + throw new MailboxException("Unable to delete mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(org.apache.james.imap.api.MailboxPath) + */ + public Mailbox findMailboxByPath(MailboxPath path) throws MailboxException, MailboxNotFoundException { + try { + String name = Text.escapeIllegalXpathSearchChars(path.getName()); + String user = path.getUser(); + if (user == null ) { + user = ""; + } + user = Text.escapeIllegalXpathSearchChars(user); + String namespace = Text.escapeIllegalXpathSearchChars(path.getNamespace()); + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + + String queryString = "/jcr:root/" + MAILBOXES_PATH + "/" + ISO9075.encodePath(path.getNamespace()) + "//element(*,jamesMailbox:mailbox)[@" + JCRMailbox.NAME_PROPERTY + "='" + name+ "' and @" + JCRMailbox.NAMESPACE_PROPERTY +"='" + namespace + "' and @" + JCRMailbox.USER_PROPERTY + "='" + user + "']"; + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator it = result.getNodes(); + if (it.hasNext()) { + return new JCRMailbox(it.nextNode(), getLogger()); + } + throw new MailboxNotFoundException(path); + } catch (PathNotFoundException e) { + throw new MailboxNotFoundException(path); + } catch (RepositoryException e) { + throw new MailboxException("Unable to find mailbox " + path, e); + } + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(org.apache.james.imap.api.MailboxPath) + */ + public List> findMailboxWithPathLike(MailboxPath path) throws MailboxException { + List> mailboxList = new ArrayList>(); + try { + String name = Text.escapeIllegalXpathSearchChars(path.getName()); + String user = path.getUser(); + if (user == null ) { + user = ""; + } + user = Text.escapeIllegalXpathSearchChars(user); + String namespace = Text.escapeIllegalXpathSearchChars(path.getNamespace()); + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + String queryString = "/jcr:root/" + MAILBOXES_PATH + "/" + ISO9075.encodePath(path.getNamespace()) + "//element(*,jamesMailbox:mailbox)[jcr:like(@" + JCRMailbox.NAME_PROPERTY + ",'%" + name + "%') and @" + JCRMailbox.NAMESPACE_PROPERTY +"='" + namespace + "' and @" + JCRMailbox.USER_PROPERTY + "='" + user + "']"; + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator it = result.getNodes(); + while (it.hasNext()) { + mailboxList.add(new JCRMailbox(it.nextNode(), getLogger())); + } + } catch (PathNotFoundException e) { + // nothing todo + } catch (RepositoryException e) { + throw new MailboxException("Unable to find mailbox " + path, e); + } + return mailboxList; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MailboxMapper#save(org.apache.james. + * imap.store.mail.model.Mailbox) + */ + public void save(Mailbox mailbox) throws MailboxException { + + try { + final JCRMailbox jcrMailbox = (JCRMailbox)mailbox; + Node node = null; + + if (jcrMailbox.isPersistent()) { + node = getSession().getNodeByIdentifier(jcrMailbox.getMailboxId()); + } + if (node == null) { + Node rootNode = getSession().getRootNode(); + Node mailboxNode; + if (rootNode.hasNode(MAILBOXES_PATH) == false) { + mailboxNode = rootNode.addNode(MAILBOXES_PATH); + mailboxNode.addMixin(JcrConstants.MIX_LOCKABLE); + getSession().save(); + } else { + mailboxNode = rootNode.getNode(MAILBOXES_PATH); + } + + node = JcrUtils.getOrAddNode(mailboxNode, Text.escapeIllegalJcrChars(jcrMailbox.getNamespace()), "nt:unstructured"); + if (jcrMailbox.getUser() != null) { + node = createUserPathStructure(node, Text.escapeIllegalJcrChars(jcrMailbox.getUser())); + } + node = JcrUtils.getOrAddNode(node, Text.escapeIllegalJcrChars(jcrMailbox.getName()), "nt:unstructured"); + node.addMixin("jamesMailbox:mailbox"); + + jcrMailbox.merge(node); + + } else { + jcrMailbox.merge(node); + } + + } catch (RepositoryException e) { + throw new MailboxException("Unable to save mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(org.apache.james. + * imap.store.mail.model.Mailbox) + */ + public boolean hasChildren(Mailbox mailbox, char delimiter) + throws MailboxException, MailboxNotFoundException { + try { + String name = Text.escapeIllegalXpathSearchChars(mailbox.getName()); + String user = mailbox.getUser(); + if (user == null ) { + user = ""; + } + user = Text.escapeIllegalXpathSearchChars(user); + String namespace = Text.escapeIllegalXpathSearchChars(mailbox.getNamespace()); + + QueryManager manager = getSession().getWorkspace() + .getQueryManager(); + String queryString = "/jcr:root/" + MAILBOXES_PATH + "/" + ISO9075.encodePath(mailbox.getNamespace()) + + "//element(*,jamesMailbox:mailbox)[jcr:like(@" + + JCRMailbox.NAME_PROPERTY + ",'" + name + delimiter + "%') and @" + JCRMailbox.NAMESPACE_PROPERTY +"='" + namespace + "' and @" + JCRMailbox.USER_PROPERTY + "='" + user + "']"; + QueryResult result = manager.createQuery(queryString, Query.XPATH) + .execute(); + NodeIterator it = result.getNodes(); + return it.hasNext(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to retrieve children for mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + public List> list() throws MailboxException { + try { + List> mList = new ArrayList>(); + QueryManager manager = getSession().getWorkspace().getQueryManager(); + + String queryString = "/jcr:root/" + MAILBOXES_PATH + "//element(*,jamesMailbox:mailbox)"; + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator it = result.getNodes(); + while (it.hasNext()) { + mList.add(new JCRMailbox(it.nextNode(), getLogger())); + } + return mList; + } catch (RepositoryException e) { + throw new MailboxException("Unable to retrieve the list of mailboxes", e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMessageMapper.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMessageMapper.java.svn-base new file mode 100644 index 0000000..a11cf6d --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRMessageMapper.java.svn-base @@ -0,0 +1,705 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.util.ISO9075; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMessage; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * JCR implementation of a {@link MessageMapper}. The implementation store each + * message as a seperate child node under the mailbox + * + */ +public class JCRMessageMapper extends AbstractMessageMapper implements JCRImapConstants { + + /** + * Store the messages directly in the mailbox: .../mailbox/ + */ + public final static int MESSAGE_SCALE_NONE = 0; + + /** + * Store the messages under a year directory in the mailbox: + * .../mailbox/2010/ + */ + public final static int MESSAGE_SCALE_YEAR = 1; + + /** + * Store the messages under a year/month directory in the mailbox: + * .../mailbox/2010/05/ + */ + public final static int MESSAGE_SCALE_MONTH = 2; + + /** + * Store the messages under a year/month/day directory in the mailbox: + * .../mailbox/2010/05/01/ + */ + public final static int MESSAGE_SCALE_DAY = 3; + + /** + * Store the messages under a year/month/day/hour directory in the mailbox: + * .../mailbox/2010/05/02/11 + */ + public final static int MESSAGE_SCALE_HOUR = 4; + + /** + * Store the messages under a year/month/day/hour/min directory in the + * mailbox: .../mailbox/2010/05/02/11/59 + */ + public final static int MESSAGE_SCALE_MINUTE = 5; + + private final int scaleType; + + private final MailboxSessionJCRRepository repository; + + /** + * Construct a new {@link JCRMessageMapper} instance + * + * @param repository + * {@link MailboxSessionJCRRepository} to use + * @param mSession + * {@link MailboxSession} to which the mapper is bound + * @param uidProvider + * {@link UidProvider} to use + * @param modSeqProvider + * {@link ModSeqProvider} to use + * @param scaleType + * message scale type either {@link #MESSAGE_SCALE_DAY}, + * {@link #MESSAGE_SCALE_HOUR}, {@link #MESSAGE_SCALE_MINUTE}, + * {@link #MESSAGE_SCALE_MONTH}, {@link #MESSAGE_SCALE_NONE} or + * {@link #MESSAGE_SCALE_YEAR} + */ + public JCRMessageMapper(final MailboxSessionJCRRepository repository, MailboxSession mSession, + UidProvider uidProvider, ModSeqProvider modSeqProvider, int scaleType) { + super(mSession, uidProvider, modSeqProvider); + this.repository = repository; + this.scaleType = scaleType; + } + + /** + * Construct a new {@link JCRMessageMapper} instance using + * {@link #MESSAGE_SCALE_DAY} as default + * + * @param repos + * {@link MailboxSessionJCRRepository} to use + * @param session + * {@link MailboxSession} to which the mapper is bound + * @param uidProvider + * {@link UidProvider} to use + * @param modSeqProvider + * {@link ModSeqProvider} to use + */ + public JCRMessageMapper(final MailboxSessionJCRRepository repos, MailboxSession session, + UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this(repos, session, uidProvider, modSeqProvider, MESSAGE_SCALE_DAY); + } + + /** + * Return the JCR Session + * + * @return session + */ + protected Session getSession() throws RepositoryException { + return repository.login(mailboxSession); + } + + /** + * Begin is not supported by level 1 JCR implementations, however we refresh + * the session + */ + protected void begin() throws MailboxException { + try { + getSession().refresh(true); + } catch (RepositoryException e) { + // do nothin on refresh + } + // Do nothing + } + + /** + * Just call save on the underlying JCR Session, because level 1 JCR + * implementation does not offer Transactions + */ + protected void commit() throws MailboxException { + try { + if (getSession().hasPendingChanges()) { + getSession().save(); + } + } catch (RepositoryException e) { + throw new MailboxException("Unable to commit", e); + } + } + + /** + * Rollback is not supported by level 1 JCR implementations, so just do + * nothing + */ + protected void rollback() throws MailboxException { + try { + // just refresh session and discard all pending changes + getSession().refresh(false); + } catch (RepositoryException e) { + // just catch on rollback by now + } + } + + /** + * Logout from open JCR Session + */ + public void endRequest() { + repository.logout(mailboxSession); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox + * () + */ + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + try { + // we use order by because without it count will always be 0 in + // jackrabbit + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message) order by @" + + JCRMessage.UID_PROPERTY; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator nodes = result.getNodes(); + long count = nodes.getSize(); + if (count == -1) { + count = 0; + while (nodes.hasNext()) { + nodes.nextNode(); + count++; + } + } + return count; + } catch (RepositoryException e) { + throw new MailboxException("Unable to count messages in mailbox " + mailbox, e); + } + + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper# + * countUnseenMessagesInMailbox () + */ + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + + try { + // we use order by because without it count will always be 0 in + // jackrabbit + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.SEEN_PROPERTY + "='false'] order by @" + JCRMessage.UID_PROPERTY; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator nodes = result.getNodes(); + long count = nodes.getSize(); + + if (count == -1) { + count = 0; + while (nodes.hasNext()) { + nodes.nextNode(); + + count++; + } + } + return count; + } catch (RepositoryException e) { + throw new MailboxException("Unable to count unseen messages in mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james + * .mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + public void delete(Mailbox mailbox, Message message) throws MailboxException { + JCRMessage membership = (JCRMessage) message; + if (membership.isPersistent()) { + try { + + getSession().getNodeByIdentifier(membership.getId()).remove(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to delete message " + message + " in mailbox " + mailbox, e); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache + * .james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) + throws MailboxException { + try { + List> results; + long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, max); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, max); + break; + case ONE: + results = findMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max); + break; + } + return results.iterator(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to search MessageRange " + set + " in mailbox " + mailbox, e); + } + } + + /* + * + * TODO: Maybe we should better use an ItemVisitor and just traverse through + * the child nodes. This could be a way faster + * + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper# + * findRecentMessageUidsInMailbox () + */ + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + + try { + + List list = new ArrayList(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.RECENT_PROPERTY + "='true'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog()).getUid()); + } + return list; + + } catch (RepositoryException e) { + throw new MailboxException("Unable to search recent messages in mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid + * (org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + try { + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.SEEN_PROPERTY + "='false'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + + Query query = manager.createQuery(queryString, Query.XPATH); + query.setLimit(1); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + if (iterator.hasNext()) { + return new JCRMessage(iterator.nextNode(), mailboxSession.getLog()).getUid(); + } else { + return null; + } + } catch (RepositoryException e) { + throw new MailboxException("Unable to find first unseen message in mailbox " + mailbox, e); + } + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + try { + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + switch (type) { + default: + case ALL: + results = findDeletedMessagesInMailbox(mailbox); + break; + case FROM: + results = findDeletedMessagesInMailboxAfterUID(mailbox, from); + break; + case ONE: + results = findDeletedMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + break; + } + Map uids = new HashMap(); + for (int i = 0; i < results.size(); i++) { + Message m = results.get(i); + long uid = m.getUid(); + uids.put(uid, new SimpleMessageMetaData(m)); + delete(mailbox, m); + } + return uids; + } catch (RepositoryException e) { + throw new MailboxException("Unable to search MessageRange " + set + " in mailbox " + mailbox, e); + } + } + + /** + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); + } + + @Override + protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) + throws MailboxException { + try { + String newMessagePath = getSession().getNodeByIdentifier(mailbox.getMailboxId()).getPath() + NODE_DELIMITER + + String.valueOf(uid); + getSession().getWorkspace().copy( + ((JCRMessage) original).getNode().getPath(), + getSession().getNodeByIdentifier(mailbox.getMailboxId()).getPath() + NODE_DELIMITER + + String.valueOf(uid)); + Node node = getSession().getNode(newMessagePath); + node.setProperty(JCRMessage.MAILBOX_UUID_PROPERTY, mailbox.getMailboxId()); + node.setProperty(JCRMessage.UID_PROPERTY, uid); + node.setProperty(JCRMessage.MODSEQ_PROPERTY, modSeq); + // A copy of a message is recent + // See MAILBOX-85 + node.setProperty(JCRMessage.RECENT_PROPERTY, true); + return new SimpleMessageMetaData(new JCRMessage(node, mailboxSession.getLog())); + } catch (RepositoryException e) { + throw new MailboxException("Unable to copy message " + original + " in mailbox " + mailbox, e); + } + } + + @Override + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + final JCRMessage membership = (JCRMessage) message; + try { + + Node messageNode = null; + + if (membership.isPersistent()) { + messageNode = getSession().getNodeByIdentifier(membership.getId()); + } + + if (messageNode == null) { + + Date date = message.getInternalDate(); + if (date == null) { + date = new Date(); + } + + // extracte the date from the message to create node structure + // later + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + final String year = convertIntToString(cal.get(Calendar.YEAR)); + final String month = convertIntToString(cal.get(Calendar.MONTH) + 1); + final String day = convertIntToString(cal.get(Calendar.DAY_OF_MONTH)); + final String hour = convertIntToString(cal.get(Calendar.HOUR_OF_DAY)); + final String min = convertIntToString(cal.get(Calendar.MINUTE)); + + Node mailboxNode = getSession().getNodeByIdentifier(mailbox.getMailboxId()); + Node node = mailboxNode; + + if (scaleType > MESSAGE_SCALE_NONE) { + // we lock the whole mailbox with all its childs while + // adding the folder structure for the date + + if (scaleType >= MESSAGE_SCALE_YEAR) { + node = JcrUtils.getOrAddFolder(node, year); + + if (scaleType >= MESSAGE_SCALE_MONTH) { + node = JcrUtils.getOrAddFolder(node, month); + + if (scaleType >= MESSAGE_SCALE_DAY) { + node = JcrUtils.getOrAddFolder(node, day); + + if (scaleType >= MESSAGE_SCALE_HOUR) { + node = JcrUtils.getOrAddFolder(node, hour); + + if (scaleType >= MESSAGE_SCALE_MINUTE) { + node = JcrUtils.getOrAddFolder(node, min); + } + } + } + } + } + + } + + long uid = membership.getUid(); + messageNode = mailboxNode.addNode(String.valueOf(uid), "nt:file"); + messageNode.addMixin("jamesMailbox:message"); + try { + membership.merge(messageNode); + + } catch (IOException e) { + throw new RepositoryException("Unable to merge message in to tree", e); + } + } else { + membership.merge(messageNode); + } + return new SimpleMessageMetaData(membership); + } catch (RepositoryException e) { + throw new MailboxException("Unable to save message " + message + " in mailbox " + mailbox, e); + } catch (IOException e) { + throw new MailboxException("Unable to save message " + message + " in mailbox " + mailbox, e); + } + } + + /** + * Return the path to the mailbox. This path is escaped to be able to use it + * in xpath queries + * + * See http://wiki.apache.org/jackrabbit/EncodingAndEscaping + * + * @param mailbox + * @return + * @throws ItemNotFoundException + * @throws RepositoryException + */ + private String getMailboxPath(Mailbox mailbox) throws ItemNotFoundException, RepositoryException { + return ISO9075.encodePath(getSession().getNodeByIdentifier(mailbox.getMailboxId()).getPath()); + } + + private List> findMessagesInMailboxAfterUID(Mailbox mailbox, long uid, int batchSize) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + uid + "] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + if (batchSize > 0) + query.setLimit(batchSize); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + "=" + uid + "]"; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + query.setLimit(1); + QueryResult result = query.execute(); + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to, + int batchSize) throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + from + " and @" + JCRMessage.UID_PROPERTY + "<=" + to + + "] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + if (batchSize > 0) + query.setLimit(batchSize); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findMessagesInMailbox(Mailbox mailbox, int batchSize) + throws RepositoryException { + List> list = new ArrayList>(); + + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message) order by @" + + JCRMessage.UID_PROPERTY; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + if (batchSize > 0) + query.setLimit(batchSize); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long uid) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + uid + " and @" + JCRMessage.DELETED_PROPERTY + "='true'] order by @" + + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findDeletedMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + "=" + uid + " and @" + JCRMessage.DELETED_PROPERTY + "='true']"; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + query.setLimit(1); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + JCRMessage member = new JCRMessage(iterator.nextNode(), mailboxSession.getLog()); + list.add(member); + } + return list; + } + + private List> findDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + from + " and @" + JCRMessage.UID_PROPERTY + "<=" + to + " and @" + + JCRMessage.DELETED_PROPERTY + "='true'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findDeletedMessagesInMailbox(Mailbox mailbox) throws RepositoryException { + + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.DELETED_PROPERTY + "='true'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + JCRMessage member = new JCRMessage(iterator.nextNode(), mailboxSession.getLog()); + list.add(member); + } + return list; + } + + /** + * Convert the given int value to a String. If the int value is smaller then + * 9 it will prefix the String with 0. + * + * @param value + * @return stringValue + */ + private String convertIntToString(int value) { + if (value <= 9) { + return "0" + String.valueOf(value); + } else { + return String.valueOf(value); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRModSeqProvider.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRModSeqProvider.java.svn-base new file mode 100644 index 0000000..28331a0 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRModSeqProvider.java.svn-base @@ -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.jcr.mail; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JCRModSeqProvider extends AbstractLockingModSeqProvider{ + + private MailboxSessionJCRRepository repository; + + public JCRModSeqProvider(MailboxPathLocker locker, MailboxSessionJCRRepository repository) { + super(locker); + this.repository = repository; + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(session); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + return node.getProperty(JCRMailbox.HIGHESTMODSEQ_PROPERTY).getLong(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to get highest mod-sequence for mailbox " + mailbox, e); + } + } + + @Override + protected long lockedNextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(session); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + long modseq = node.getProperty(JCRMailbox.HIGHESTMODSEQ_PROPERTY).getLong(); + modseq++; + node.setProperty(JCRMailbox.HIGHESTMODSEQ_PROPERTY, modseq); + s.save(); + return modseq; + } catch (RepositoryException e) { + throw new MailboxException("Unable to consume next uid for mailbox " + mailbox, e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRUidProvider.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRUidProvider.java.svn-base new file mode 100644 index 0000000..3342fdb --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/.svn/text-base/JCRUidProvider.java.svn-base @@ -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.jcr.mail; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingUidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JCRUidProvider extends AbstractLockingUidProvider{ + + private MailboxSessionJCRRepository repository; + + public JCRUidProvider(MailboxPathLocker locker, MailboxSessionJCRRepository repository) { + super(locker); + this.repository = repository; + } + + @Override + public long lastUid(MailboxSession mailboxSession, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(mailboxSession); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + return node.getProperty(JCRMailbox.LASTUID_PROPERTY).getLong(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to get last uid for mailbox " + mailbox, e); + } + + } + + @Override + protected long lockedNextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(session); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + long uid = node.getProperty(JCRMailbox.LASTUID_PROPERTY).getLong(); + uid++; + node.setProperty(JCRMailbox.LASTUID_PROPERTY, uid); + s.save(); + return uid; + } catch (RepositoryException e) { + throw new MailboxException("Unable to consume next uid for mailbox " + mailbox, e); + } + } + + + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMailboxMapper.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMailboxMapper.java new file mode 100644 index 0000000..ca848d4 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMailboxMapper.java @@ -0,0 +1,236 @@ +/**************************************************************** + * 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.mail; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.RepositoryException; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.util.ISO9075; +import org.apache.jackrabbit.util.Text; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.jcr.AbstractJCRScalingMapper; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * JCR implementation of a MailboxMapper + * + * + */ +public class JCRMailboxMapper extends AbstractJCRScalingMapper implements MailboxMapper { + + + public JCRMailboxMapper(final MailboxSessionJCRRepository repos, MailboxSession session, final int scaling) { + super(repos, session, scaling); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MailboxMapper#delete(org.apache.james + * .imap.store.mail.model.Mailbox) + */ + public void delete(Mailbox mailbox) throws MailboxException { + try { + Node node = getSession().getNodeByIdentifier(((JCRMailbox) mailbox).getMailboxId()); + + node.remove(); + + } catch (PathNotFoundException e) { + // mailbox does not exists.. + } catch (RepositoryException e) { + throw new MailboxException("Unable to delete mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(org.apache.james.imap.api.MailboxPath) + */ + public Mailbox findMailboxByPath(MailboxPath path) throws MailboxException, MailboxNotFoundException { + try { + String name = Text.escapeIllegalXpathSearchChars(path.getName()); + String user = path.getUser(); + if (user == null ) { + user = ""; + } + user = Text.escapeIllegalXpathSearchChars(user); + String namespace = Text.escapeIllegalXpathSearchChars(path.getNamespace()); + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + + String queryString = "/jcr:root/" + MAILBOXES_PATH + "/" + ISO9075.encodePath(path.getNamespace()) + "//element(*,jamesMailbox:mailbox)[@" + JCRMailbox.NAME_PROPERTY + "='" + name+ "' and @" + JCRMailbox.NAMESPACE_PROPERTY +"='" + namespace + "' and @" + JCRMailbox.USER_PROPERTY + "='" + user + "']"; + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator it = result.getNodes(); + if (it.hasNext()) { + return new JCRMailbox(it.nextNode(), getLogger()); + } + throw new MailboxNotFoundException(path); + } catch (PathNotFoundException e) { + throw new MailboxNotFoundException(path); + } catch (RepositoryException e) { + throw new MailboxException("Unable to find mailbox " + path, e); + } + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(org.apache.james.imap.api.MailboxPath) + */ + public List> findMailboxWithPathLike(MailboxPath path) throws MailboxException { + List> mailboxList = new ArrayList>(); + try { + String name = Text.escapeIllegalXpathSearchChars(path.getName()); + String user = path.getUser(); + if (user == null ) { + user = ""; + } + user = Text.escapeIllegalXpathSearchChars(user); + String namespace = Text.escapeIllegalXpathSearchChars(path.getNamespace()); + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + String queryString = "/jcr:root/" + MAILBOXES_PATH + "/" + ISO9075.encodePath(path.getNamespace()) + "//element(*,jamesMailbox:mailbox)[jcr:like(@" + JCRMailbox.NAME_PROPERTY + ",'%" + name + "%') and @" + JCRMailbox.NAMESPACE_PROPERTY +"='" + namespace + "' and @" + JCRMailbox.USER_PROPERTY + "='" + user + "']"; + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator it = result.getNodes(); + while (it.hasNext()) { + mailboxList.add(new JCRMailbox(it.nextNode(), getLogger())); + } + } catch (PathNotFoundException e) { + // nothing todo + } catch (RepositoryException e) { + throw new MailboxException("Unable to find mailbox " + path, e); + } + return mailboxList; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MailboxMapper#save(org.apache.james. + * imap.store.mail.model.Mailbox) + */ + public void save(Mailbox mailbox) throws MailboxException { + + try { + final JCRMailbox jcrMailbox = (JCRMailbox)mailbox; + Node node = null; + + if (jcrMailbox.isPersistent()) { + node = getSession().getNodeByIdentifier(jcrMailbox.getMailboxId()); + } + if (node == null) { + Node rootNode = getSession().getRootNode(); + Node mailboxNode; + if (rootNode.hasNode(MAILBOXES_PATH) == false) { + mailboxNode = rootNode.addNode(MAILBOXES_PATH); + mailboxNode.addMixin(JcrConstants.MIX_LOCKABLE); + getSession().save(); + } else { + mailboxNode = rootNode.getNode(MAILBOXES_PATH); + } + + node = JcrUtils.getOrAddNode(mailboxNode, Text.escapeIllegalJcrChars(jcrMailbox.getNamespace()), "nt:unstructured"); + if (jcrMailbox.getUser() != null) { + node = createUserPathStructure(node, Text.escapeIllegalJcrChars(jcrMailbox.getUser())); + } + node = JcrUtils.getOrAddNode(node, Text.escapeIllegalJcrChars(jcrMailbox.getName()), "nt:unstructured"); + node.addMixin("jamesMailbox:mailbox"); + + jcrMailbox.merge(node); + + } else { + jcrMailbox.merge(node); + } + + } catch (RepositoryException e) { + throw new MailboxException("Unable to save mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(org.apache.james. + * imap.store.mail.model.Mailbox) + */ + public boolean hasChildren(Mailbox mailbox, char delimiter) + throws MailboxException, MailboxNotFoundException { + try { + String name = Text.escapeIllegalXpathSearchChars(mailbox.getName()); + String user = mailbox.getUser(); + if (user == null ) { + user = ""; + } + user = Text.escapeIllegalXpathSearchChars(user); + String namespace = Text.escapeIllegalXpathSearchChars(mailbox.getNamespace()); + + QueryManager manager = getSession().getWorkspace() + .getQueryManager(); + String queryString = "/jcr:root/" + MAILBOXES_PATH + "/" + ISO9075.encodePath(mailbox.getNamespace()) + + "//element(*,jamesMailbox:mailbox)[jcr:like(@" + + JCRMailbox.NAME_PROPERTY + ",'" + name + delimiter + "%') and @" + JCRMailbox.NAMESPACE_PROPERTY +"='" + namespace + "' and @" + JCRMailbox.USER_PROPERTY + "='" + user + "']"; + QueryResult result = manager.createQuery(queryString, Query.XPATH) + .execute(); + NodeIterator it = result.getNodes(); + return it.hasNext(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to retrieve children for mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + public List> list() throws MailboxException { + try { + List> mList = new ArrayList>(); + QueryManager manager = getSession().getWorkspace().getQueryManager(); + + String queryString = "/jcr:root/" + MAILBOXES_PATH + "//element(*,jamesMailbox:mailbox)"; + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator it = result.getNodes(); + while (it.hasNext()) { + mList.add(new JCRMailbox(it.nextNode(), getLogger())); + } + return mList; + } catch (RepositoryException e) { + throw new MailboxException("Unable to retrieve the list of mailboxes", e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMessageMapper.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMessageMapper.java new file mode 100644 index 0000000..a11cf6d --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRMessageMapper.java @@ -0,0 +1,705 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.util.ISO9075; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMessage; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * JCR implementation of a {@link MessageMapper}. The implementation store each + * message as a seperate child node under the mailbox + * + */ +public class JCRMessageMapper extends AbstractMessageMapper implements JCRImapConstants { + + /** + * Store the messages directly in the mailbox: .../mailbox/ + */ + public final static int MESSAGE_SCALE_NONE = 0; + + /** + * Store the messages under a year directory in the mailbox: + * .../mailbox/2010/ + */ + public final static int MESSAGE_SCALE_YEAR = 1; + + /** + * Store the messages under a year/month directory in the mailbox: + * .../mailbox/2010/05/ + */ + public final static int MESSAGE_SCALE_MONTH = 2; + + /** + * Store the messages under a year/month/day directory in the mailbox: + * .../mailbox/2010/05/01/ + */ + public final static int MESSAGE_SCALE_DAY = 3; + + /** + * Store the messages under a year/month/day/hour directory in the mailbox: + * .../mailbox/2010/05/02/11 + */ + public final static int MESSAGE_SCALE_HOUR = 4; + + /** + * Store the messages under a year/month/day/hour/min directory in the + * mailbox: .../mailbox/2010/05/02/11/59 + */ + public final static int MESSAGE_SCALE_MINUTE = 5; + + private final int scaleType; + + private final MailboxSessionJCRRepository repository; + + /** + * Construct a new {@link JCRMessageMapper} instance + * + * @param repository + * {@link MailboxSessionJCRRepository} to use + * @param mSession + * {@link MailboxSession} to which the mapper is bound + * @param uidProvider + * {@link UidProvider} to use + * @param modSeqProvider + * {@link ModSeqProvider} to use + * @param scaleType + * message scale type either {@link #MESSAGE_SCALE_DAY}, + * {@link #MESSAGE_SCALE_HOUR}, {@link #MESSAGE_SCALE_MINUTE}, + * {@link #MESSAGE_SCALE_MONTH}, {@link #MESSAGE_SCALE_NONE} or + * {@link #MESSAGE_SCALE_YEAR} + */ + public JCRMessageMapper(final MailboxSessionJCRRepository repository, MailboxSession mSession, + UidProvider uidProvider, ModSeqProvider modSeqProvider, int scaleType) { + super(mSession, uidProvider, modSeqProvider); + this.repository = repository; + this.scaleType = scaleType; + } + + /** + * Construct a new {@link JCRMessageMapper} instance using + * {@link #MESSAGE_SCALE_DAY} as default + * + * @param repos + * {@link MailboxSessionJCRRepository} to use + * @param session + * {@link MailboxSession} to which the mapper is bound + * @param uidProvider + * {@link UidProvider} to use + * @param modSeqProvider + * {@link ModSeqProvider} to use + */ + public JCRMessageMapper(final MailboxSessionJCRRepository repos, MailboxSession session, + UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this(repos, session, uidProvider, modSeqProvider, MESSAGE_SCALE_DAY); + } + + /** + * Return the JCR Session + * + * @return session + */ + protected Session getSession() throws RepositoryException { + return repository.login(mailboxSession); + } + + /** + * Begin is not supported by level 1 JCR implementations, however we refresh + * the session + */ + protected void begin() throws MailboxException { + try { + getSession().refresh(true); + } catch (RepositoryException e) { + // do nothin on refresh + } + // Do nothing + } + + /** + * Just call save on the underlying JCR Session, because level 1 JCR + * implementation does not offer Transactions + */ + protected void commit() throws MailboxException { + try { + if (getSession().hasPendingChanges()) { + getSession().save(); + } + } catch (RepositoryException e) { + throw new MailboxException("Unable to commit", e); + } + } + + /** + * Rollback is not supported by level 1 JCR implementations, so just do + * nothing + */ + protected void rollback() throws MailboxException { + try { + // just refresh session and discard all pending changes + getSession().refresh(false); + } catch (RepositoryException e) { + // just catch on rollback by now + } + } + + /** + * Logout from open JCR Session + */ + public void endRequest() { + repository.logout(mailboxSession); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox + * () + */ + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + try { + // we use order by because without it count will always be 0 in + // jackrabbit + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message) order by @" + + JCRMessage.UID_PROPERTY; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator nodes = result.getNodes(); + long count = nodes.getSize(); + if (count == -1) { + count = 0; + while (nodes.hasNext()) { + nodes.nextNode(); + count++; + } + } + return count; + } catch (RepositoryException e) { + throw new MailboxException("Unable to count messages in mailbox " + mailbox, e); + } + + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper# + * countUnseenMessagesInMailbox () + */ + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + + try { + // we use order by because without it count will always be 0 in + // jackrabbit + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.SEEN_PROPERTY + "='false'] order by @" + JCRMessage.UID_PROPERTY; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + NodeIterator nodes = result.getNodes(); + long count = nodes.getSize(); + + if (count == -1) { + count = 0; + while (nodes.hasNext()) { + nodes.nextNode(); + + count++; + } + } + return count; + } catch (RepositoryException e) { + throw new MailboxException("Unable to count unseen messages in mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james + * .mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + public void delete(Mailbox mailbox, Message message) throws MailboxException { + JCRMessage membership = (JCRMessage) message; + if (membership.isPersistent()) { + try { + + getSession().getNodeByIdentifier(membership.getId()).remove(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to delete message " + message + " in mailbox " + mailbox, e); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache + * .james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) + throws MailboxException { + try { + List> results; + long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, max); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, max); + break; + case ONE: + results = findMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max); + break; + } + return results.iterator(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to search MessageRange " + set + " in mailbox " + mailbox, e); + } + } + + /* + * + * TODO: Maybe we should better use an ItemVisitor and just traverse through + * the child nodes. This could be a way faster + * + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper# + * findRecentMessageUidsInMailbox () + */ + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + + try { + + List list = new ArrayList(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.RECENT_PROPERTY + "='true'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog()).getUid()); + } + return list; + + } catch (RepositoryException e) { + throw new MailboxException("Unable to search recent messages in mailbox " + mailbox, e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid + * (org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + try { + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.SEEN_PROPERTY + "='false'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + + Query query = manager.createQuery(queryString, Query.XPATH); + query.setLimit(1); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + if (iterator.hasNext()) { + return new JCRMessage(iterator.nextNode(), mailboxSession.getLog()).getUid(); + } else { + return null; + } + } catch (RepositoryException e) { + throw new MailboxException("Unable to find first unseen message in mailbox " + mailbox, e); + } + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + try { + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + switch (type) { + default: + case ALL: + results = findDeletedMessagesInMailbox(mailbox); + break; + case FROM: + results = findDeletedMessagesInMailboxAfterUID(mailbox, from); + break; + case ONE: + results = findDeletedMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + break; + } + Map uids = new HashMap(); + for (int i = 0; i < results.size(); i++) { + Message m = results.get(i); + long uid = m.getUid(); + uids.put(uid, new SimpleMessageMetaData(m)); + delete(mailbox, m); + } + return uids; + } catch (RepositoryException e) { + throw new MailboxException("Unable to search MessageRange " + set + " in mailbox " + mailbox, e); + } + } + + /** + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); + } + + @Override + protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) + throws MailboxException { + try { + String newMessagePath = getSession().getNodeByIdentifier(mailbox.getMailboxId()).getPath() + NODE_DELIMITER + + String.valueOf(uid); + getSession().getWorkspace().copy( + ((JCRMessage) original).getNode().getPath(), + getSession().getNodeByIdentifier(mailbox.getMailboxId()).getPath() + NODE_DELIMITER + + String.valueOf(uid)); + Node node = getSession().getNode(newMessagePath); + node.setProperty(JCRMessage.MAILBOX_UUID_PROPERTY, mailbox.getMailboxId()); + node.setProperty(JCRMessage.UID_PROPERTY, uid); + node.setProperty(JCRMessage.MODSEQ_PROPERTY, modSeq); + // A copy of a message is recent + // See MAILBOX-85 + node.setProperty(JCRMessage.RECENT_PROPERTY, true); + return new SimpleMessageMetaData(new JCRMessage(node, mailboxSession.getLog())); + } catch (RepositoryException e) { + throw new MailboxException("Unable to copy message " + original + " in mailbox " + mailbox, e); + } + } + + @Override + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + final JCRMessage membership = (JCRMessage) message; + try { + + Node messageNode = null; + + if (membership.isPersistent()) { + messageNode = getSession().getNodeByIdentifier(membership.getId()); + } + + if (messageNode == null) { + + Date date = message.getInternalDate(); + if (date == null) { + date = new Date(); + } + + // extracte the date from the message to create node structure + // later + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + final String year = convertIntToString(cal.get(Calendar.YEAR)); + final String month = convertIntToString(cal.get(Calendar.MONTH) + 1); + final String day = convertIntToString(cal.get(Calendar.DAY_OF_MONTH)); + final String hour = convertIntToString(cal.get(Calendar.HOUR_OF_DAY)); + final String min = convertIntToString(cal.get(Calendar.MINUTE)); + + Node mailboxNode = getSession().getNodeByIdentifier(mailbox.getMailboxId()); + Node node = mailboxNode; + + if (scaleType > MESSAGE_SCALE_NONE) { + // we lock the whole mailbox with all its childs while + // adding the folder structure for the date + + if (scaleType >= MESSAGE_SCALE_YEAR) { + node = JcrUtils.getOrAddFolder(node, year); + + if (scaleType >= MESSAGE_SCALE_MONTH) { + node = JcrUtils.getOrAddFolder(node, month); + + if (scaleType >= MESSAGE_SCALE_DAY) { + node = JcrUtils.getOrAddFolder(node, day); + + if (scaleType >= MESSAGE_SCALE_HOUR) { + node = JcrUtils.getOrAddFolder(node, hour); + + if (scaleType >= MESSAGE_SCALE_MINUTE) { + node = JcrUtils.getOrAddFolder(node, min); + } + } + } + } + } + + } + + long uid = membership.getUid(); + messageNode = mailboxNode.addNode(String.valueOf(uid), "nt:file"); + messageNode.addMixin("jamesMailbox:message"); + try { + membership.merge(messageNode); + + } catch (IOException e) { + throw new RepositoryException("Unable to merge message in to tree", e); + } + } else { + membership.merge(messageNode); + } + return new SimpleMessageMetaData(membership); + } catch (RepositoryException e) { + throw new MailboxException("Unable to save message " + message + " in mailbox " + mailbox, e); + } catch (IOException e) { + throw new MailboxException("Unable to save message " + message + " in mailbox " + mailbox, e); + } + } + + /** + * Return the path to the mailbox. This path is escaped to be able to use it + * in xpath queries + * + * See http://wiki.apache.org/jackrabbit/EncodingAndEscaping + * + * @param mailbox + * @return + * @throws ItemNotFoundException + * @throws RepositoryException + */ + private String getMailboxPath(Mailbox mailbox) throws ItemNotFoundException, RepositoryException { + return ISO9075.encodePath(getSession().getNodeByIdentifier(mailbox.getMailboxId()).getPath()); + } + + private List> findMessagesInMailboxAfterUID(Mailbox mailbox, long uid, int batchSize) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + uid + "] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + if (batchSize > 0) + query.setLimit(batchSize); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + "=" + uid + "]"; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + query.setLimit(1); + QueryResult result = query.execute(); + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to, + int batchSize) throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + from + " and @" + JCRMessage.UID_PROPERTY + "<=" + to + + "] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + if (batchSize > 0) + query.setLimit(batchSize); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findMessagesInMailbox(Mailbox mailbox, int batchSize) + throws RepositoryException { + List> list = new ArrayList>(); + + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message) order by @" + + JCRMessage.UID_PROPERTY; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + if (batchSize > 0) + query.setLimit(batchSize); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long uid) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + uid + " and @" + JCRMessage.DELETED_PROPERTY + "='true'] order by @" + + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findDeletedMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + "=" + uid + " and @" + JCRMessage.DELETED_PROPERTY + "='true']"; + QueryManager manager = getSession().getWorkspace().getQueryManager(); + Query query = manager.createQuery(queryString, Query.XPATH); + query.setLimit(1); + QueryResult result = query.execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + JCRMessage member = new JCRMessage(iterator.nextNode(), mailboxSession.getLog()); + list.add(member); + } + return list; + } + + private List> findDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to) + throws RepositoryException { + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.UID_PROPERTY + ">=" + from + " and @" + JCRMessage.UID_PROPERTY + "<=" + to + " and @" + + JCRMessage.DELETED_PROPERTY + "='true'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + list.add(new JCRMessage(iterator.nextNode(), mailboxSession.getLog())); + } + return list; + } + + private List> findDeletedMessagesInMailbox(Mailbox mailbox) throws RepositoryException { + + List> list = new ArrayList>(); + String queryString = "/jcr:root" + getMailboxPath(mailbox) + "//element(*,jamesMailbox:message)[@" + + JCRMessage.DELETED_PROPERTY + "='true'] order by @" + JCRMessage.UID_PROPERTY; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator iterator = result.getNodes(); + while (iterator.hasNext()) { + JCRMessage member = new JCRMessage(iterator.nextNode(), mailboxSession.getLog()); + list.add(member); + } + return list; + } + + /** + * Convert the given int value to a String. If the int value is smaller then + * 9 it will prefix the String with 0. + * + * @param value + * @return stringValue + */ + private String convertIntToString(int value) { + if (value <= 9) { + return "0" + String.valueOf(value); + } else { + return String.valueOf(value); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRModSeqProvider.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRModSeqProvider.java new file mode 100644 index 0000000..28331a0 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRModSeqProvider.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.jcr.mail; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JCRModSeqProvider extends AbstractLockingModSeqProvider{ + + private MailboxSessionJCRRepository repository; + + public JCRModSeqProvider(MailboxPathLocker locker, MailboxSessionJCRRepository repository) { + super(locker); + this.repository = repository; + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(session); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + return node.getProperty(JCRMailbox.HIGHESTMODSEQ_PROPERTY).getLong(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to get highest mod-sequence for mailbox " + mailbox, e); + } + } + + @Override + protected long lockedNextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(session); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + long modseq = node.getProperty(JCRMailbox.HIGHESTMODSEQ_PROPERTY).getLong(); + modseq++; + node.setProperty(JCRMailbox.HIGHESTMODSEQ_PROPERTY, modseq); + s.save(); + return modseq; + } catch (RepositoryException e) { + throw new MailboxException("Unable to consume next uid for mailbox " + mailbox, e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRUidProvider.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRUidProvider.java new file mode 100644 index 0000000..3342fdb --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/JCRUidProvider.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.jcr.mail; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.mail.model.JCRMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingUidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JCRUidProvider extends AbstractLockingUidProvider{ + + private MailboxSessionJCRRepository repository; + + public JCRUidProvider(MailboxPathLocker locker, MailboxSessionJCRRepository repository) { + super(locker); + this.repository = repository; + } + + @Override + public long lastUid(MailboxSession mailboxSession, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(mailboxSession); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + return node.getProperty(JCRMailbox.LASTUID_PROPERTY).getLong(); + } catch (RepositoryException e) { + throw new MailboxException("Unable to get last uid for mailbox " + mailbox, e); + } + + } + + @Override + protected long lockedNextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + Session s = repository.login(session); + Node node = s.getNodeByIdentifier(mailbox.getMailboxId()); + long uid = node.getProperty(JCRMailbox.LASTUID_PROPERTY).getLong(); + uid++; + node.setProperty(JCRMailbox.LASTUID_PROPERTY, uid); + s.save(); + return uid; + } catch (RepositoryException e) { + throw new MailboxException("Unable to consume next uid for mailbox " + mailbox, e); + } + } + + + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/all-wcprops new file mode 100644 index 0000000..aaff9db --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 105 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model +END +JCRMessage.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMessage.java +END +JCRProperty.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1137514/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRProperty.java +END +JCRMailbox.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMailbox.java +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/entries new file mode 100644 index 0000000..bef0922 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JCRMessage.java +file + + + + +2013-09-02T02:54:37.000000Z +95e6ee88a7c1821dc84981e9ae71382f +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +25799 + +JCRProperty.java +file + + + + +2013-09-02T02:54:37.000000Z +ddadb137d7f82b7d9df4217aac069c82 +2011-06-20T06:15:30.676685Z +1137514 +norman + + + + + + + + + + + + + + + + + + + + + +7247 + +JCRMailbox.java +file + + + + +2013-09-02T02:54:37.000000Z +85ff0e6340b54ed8cf6826b7b1101ca7 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +10483 + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMailbox.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMailbox.java.svn-base new file mode 100644 index 0000000..65dac70 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMailbox.java.svn-base @@ -0,0 +1,326 @@ +/**************************************************************** + * 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.mail.model; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.util.Text; +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.slf4j.Logger; + + +/** + * JCR implementation of a {@link Mailbox} + */ +public class JCRMailbox implements Mailbox, JCRImapConstants, Persistent{ + + private static final String TAB = " "; + + + public final static String USER_PROPERTY = "jamesMailbox:mailboxUser"; + public final static String NAMESPACE_PROPERTY = "jamesMailbox:mailboxNamespace"; + public final static String NAME_PROPERTY = "jamesMailbox:mailboxName"; + public final static String UIDVALIDITY_PROPERTY = "jamesMailbox:mailboxUidValidity"; + public final static String LASTUID_PROPERTY = "jamesMailbox:mailboxLastUid"; + public final static String HIGHESTMODSEQ_PROPERTY = "jamesMailbox:mailboxHighestModSeq"; + + private String name; + private long uidValidity; + private final Logger logger; + private Node node; + + + private String namespace; + private String user; + private long lastKnownUid; + private long highestKnownModSeq; + + + public JCRMailbox( final MailboxPath path, final long uidValidity, Logger logger) { + this.name = path.getName(); + this.namespace = path.getNamespace(); + this.user = path.getUser(); + this.uidValidity = uidValidity; + this.logger = logger; + } + + public JCRMailbox( final Node node, final Logger logger) { + this.node = node; + this.logger = logger; + } + + public Logger getLog() { + return logger; + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + public String getName() { + if (isPersistent()) { + try { + return node.getProperty(NAME_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAME_PROPERTY, e); + } + } + return name; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + public long getUidValidity() { + if (isPersistent()) { + try { + return node.getProperty(UIDVALIDITY_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + UIDVALIDITY_PROPERTY, e); + } + } + return uidValidity; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String) + */ + public void setName(String name) { + if (isPersistent()) { + try { + node.setProperty(NAME_PROPERTY, name); + // move the node + // See https://issues.apache.org/jira/browse/IMAP-162 + node.getSession().move(node.getPath(), node.getParent().getPath() + NODE_DELIMITER + Text.escapePath(name)); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAME_PROPERTY, e); + } + } else { + this.name = name; + } + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#getNode() + */ + public Node getNode() { + return node; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException { + node.setProperty(NAME_PROPERTY, getName()); + node.setProperty(UIDVALIDITY_PROPERTY, getUidValidity()); + String user = getUser(); + if (user == null) { + user = ""; + } + node.setProperty(USER_PROPERTY, user); + node.setProperty(NAMESPACE_PROPERTY, getNamespace()); + node.setProperty(HIGHESTMODSEQ_PROPERTY, getHighestModSeq()); + node.setProperty(LASTUID_PROPERTY, getLastUid()); + this.node = node; + } + + @Override + public String toString() { + final String retValue = "Mailbox ( " + + "mailboxUID = " + this.getMailboxId() + TAB + + "name = " + this.getName() + TAB + + "uidValidity = " + this.getUidValidity() + TAB + + " )"; + return retValue; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) getMailboxId().hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JCRMailbox other = (JCRMailbox) obj; + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) + return false; + } else { + if (other.getMailboxId() != null) + return false; + } + return true; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + public String getMailboxId() { + if (isPersistent()) { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + JcrConstants.JCR_UUID, e); + } + } + return null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + public String getNamespace() { + if (isPersistent()) { + try { + return node.getProperty(NAMESPACE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAMESPACE_PROPERTY, e); + } + } + return namespace; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + public String getUser() { + if (isPersistent()) { + try { + String user = node.getProperty(USER_PROPERTY).getString(); + if (user.trim().length() == 0) { + return null; + } else { + return user; + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + USER_PROPERTY, e); + } + } + return user; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + public void setNamespace(String namespace) { + if (isPersistent()) { + try { + node.setProperty(NAMESPACE_PROPERTY, namespace); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAMESPACE_PROPERTY, e); + } + } else { + this.namespace = namespace; + } + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + public void setUser(String user) { + if (isPersistent()) { + try { + if (user == null) { + user = ""; + } + node.setProperty(USER_PROPERTY, user); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAME_PROPERTY, e); + } + } else { + this.user = user; + } + } + + private long getLastUid() { + if (isPersistent()) { + try { + return node.getProperty(LASTUID_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + LASTUID_PROPERTY, e); + } + } + return lastKnownUid; + } + + private long getHighestModSeq() { + if (isPersistent()) { + try { + return node.getProperty(HIGHESTMODSEQ_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + HIGHESTMODSEQ_PROPERTY, e); + } + } + return highestKnownModSeq; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + // TODO ACL support + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + // TODO ACL support + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMessage.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMessage.java.svn-base new file mode 100644 index 0000000..e66f663 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRMessage.java.svn-base @@ -0,0 +1,764 @@ +/**************************************************************** + * 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.mail.model; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BoundedInputStream; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.slf4j.Logger; + +/** + * JCR implementation of {@link Message} + * + */ +public class JCRMessage extends AbstractMessage implements JCRImapConstants, Persistent{ + + private Node node; + private final Logger logger; + private SharedInputStream content; + private String mediaType; + private Long textualLineCount; + private String subType; + private List properties; + private int bodyStartOctet; + + private String mailboxUUID; + private long uid; + private Date internalDate; + private long size; + private boolean answered; + private boolean deleted; + private boolean draft; + private boolean flagged; + private boolean recent; + private boolean seen; + private String[] userFlags; + private long modSeq; + + private static final String TOSTRING_SEPARATOR = " "; + + public final static String MAILBOX_UUID_PROPERTY = "jamesMailbox:mailboxUUID"; + public final static String UID_PROPERTY = "jamesMailbox:uid"; + public final static String SIZE_PROPERTY = "jamesMailbox:size"; + public final static String ANSWERED_PROPERTY = "jamesMailbox:answered"; + public final static String DELETED_PROPERTY = "jamesMailbox:deleted"; + public final static String DRAFT_PROPERTY = "jamesMailbox:draft"; + public final static String FLAGGED_PROPERTY = "jamesMailbox:flagged"; + public final static String USERFLAGS_PROPERTY = "jamesMailbox:userFlags"; + + public final static String RECENT_PROPERTY = "jamesMailbox:recent"; + public final static String SEEN_PROPERTY = "jamesMailbox:seen"; + public final static String INTERNAL_DATE_PROPERTY = "jamesMailbox:internalDate"; + + public final static String BODY_START_OCTET_PROPERTY = "jamesMailbox:messageBodyStartOctet"; + public final static String HEADER_NODE_TYPE = "jamesMailbox:messageHeader"; + + public final static String PROPERTY_NODE_TYPE = "jamesMailbox:messageProperty"; + public final static String TEXTUAL_LINE_COUNT_PROPERTY = "jamesMailbox:messageTextualLineCount"; + public final static String SUBTYPE_PROPERTY = "jamesMailbox:messageSubType"; + public final static String MODSEQ_PROPERTY = "jamesMailbox:modSeq"; + + public JCRMessage(Node node, Logger logger) { + this.logger= logger; + this.node = node; + } + + public JCRMessage(String mailboxUUID, Date internalDate, int size, Flags flags, SharedInputStream content, + int bodyStartOctet, final PropertyBuilder propertyBuilder, Logger logger) { + super(); + this.mailboxUUID = mailboxUUID; + this.internalDate = internalDate; + this.size = size; + this.logger = logger; + setFlags(flags); + this.content = content; + + this.bodyStartOctet = bodyStartOctet; + this.textualLineCount = propertyBuilder.getTextualLineCount(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + final List properties = propertyBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + for (final Property property:properties) { + this.properties.add(new JCRProperty(property,logger)); + } + + } + + + /** + * Create a copy of the given message + * + * @param message + * @throws IOException + */ + public JCRMessage(String mailboxUUID, long uid, long modSeq, JCRMessage message, Logger logger) throws MailboxException { + this.mailboxUUID = mailboxUUID; + this.internalDate = message.getInternalDate(); + this.size = message.getFullContentOctets(); + setFlags(message.createFlags()); + this.uid = uid; + this.modSeq = modSeq; + this.logger = logger; + try { + this.content = new SharedByteArrayInputStream(IOUtils.toByteArray(message.getFullContent())); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + + this.bodyStartOctet = (int) (message.getFullContentOctets() - message.getBodyOctets()); + + PropertyBuilder pBuilder = new PropertyBuilder(message.getProperties()); + this.textualLineCount = message.getTextualLineCount(); + this.mediaType = message.getMediaType(); + this.subType = message.getSubType(); + final List properties = pBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + for (final Property property:properties) { + this.properties.add(new JCRProperty(property, logger)); + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getFullContentOctets() + */ + public long getFullContentOctets() { + if (isPersistent()) { + try { + return node.getProperty(SIZE_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve property " + SIZE_PROPERTY, e); + + } + return 0; + } + return size; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getMediaType() + */ + public String getMediaType() { + if (isPersistent()) { + try { + return node.getNode(JcrConstants.JCR_CONTENT).getProperty(JcrConstants.JCR_MIMETYPE).getString(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve node " + JcrConstants.JCR_MIMETYPE, e); + } + return null; + } + return mediaType; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getProperties() + */ + public List getProperties() { + if (isPersistent()) { + try { + List properties = new ArrayList(); + NodeIterator nodeIt = node.getNodes("messageProperty"); + while (nodeIt.hasNext()) { + properties.add(new JCRProperty(nodeIt.nextNode(), logger)); + } + return properties; + } catch (RepositoryException e) { + logger.error("Unable to retrieve nodes messageProperty", e); + } + } + return new ArrayList(properties); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getSubType() + */ + public String getSubType() { + if (isPersistent()) { + try { + return node.getProperty(SUBTYPE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve node " + SUBTYPE_PROPERTY, e); + } + return null; + } + return subType; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getTextualLineCount() + */ + public Long getTextualLineCount() { + if (isPersistent()) { + try { + if (node.hasProperty(TEXTUAL_LINE_COUNT_PROPERTY)) { + return node.getProperty(TEXTUAL_LINE_COUNT_PROPERTY).getLong(); + } + } catch (RepositoryException e) { + logger.error("Unable to retrieve property " + TEXTUAL_LINE_COUNT_PROPERTY, e); + + } + return null; + } + return textualLineCount; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#getNode() + */ + public Node getNode() { + return node; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + public String getUUID() { + if (isPersistent()) { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + logger.error("Unable to access UUID", e); + } + } + return null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException, IOException { + + // update the flags + node.setProperty(ANSWERED_PROPERTY, isAnswered()); + node.setProperty(DELETED_PROPERTY, isDeleted()); + node.setProperty(DRAFT_PROPERTY, isDraft()); + node.setProperty(FLAGGED_PROPERTY, isFlagged()); + node.setProperty(RECENT_PROPERTY, isRecent()); + node.setProperty(SEEN_PROPERTY, isSeen()); + node.setProperty(USERFLAGS_PROPERTY, createFlags().getUserFlags()); + // This stuff is only ever changed on a new message + // so if it is persistent we don'T need to set all the of this. + // + // This also fix https://issues.apache.org/jira/browse/IMAP-159 + if (isPersistent() == false) { + node.setProperty(SIZE_PROPERTY, getFullContentOctets()); + node.setProperty(MAILBOX_UUID_PROPERTY, getMailboxId()); + node.setProperty(UID_PROPERTY, getUid()); + node.setProperty(MODSEQ_PROPERTY, getModSeq()); + + if (getInternalDate() == null) { + internalDate = new Date(); + } + + Calendar cal = Calendar.getInstance(); + + cal.setTime(getInternalDate()); + node.setProperty(INTERNAL_DATE_PROPERTY, cal); + + Node contentNode = JcrUtils.getOrAddNode(node, JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE); + Binary binaryContent = contentNode.getSession().getValueFactory().createBinary(getFullContent()); + contentNode.setProperty(JcrConstants.JCR_DATA, binaryContent); + contentNode.setProperty(JcrConstants.JCR_MIMETYPE, getMediaType()); + + if (getTextualLineCount() != null) { + node.setProperty(TEXTUAL_LINE_COUNT_PROPERTY, getTextualLineCount()); + } + node.setProperty(SUBTYPE_PROPERTY, getSubType()); + node.setProperty(BODY_START_OCTET_PROPERTY, getBodyStartOctet()); + + + List currentProperties = getProperties(); + List newProperites = new ArrayList(); + for (int i = 0; i < currentProperties.size(); i++) { + Property prop = currentProperties.get(i); + newProperites.add(new JCRProperty(prop, logger)); + } + // remove old properties, we will add a bunch of new ones + NodeIterator iterator = node.getNodes("messageProperty"); + while (iterator.hasNext()) { + iterator.nextNode().remove(); + } + + // store new properties + for (int i = 0; i < newProperites.size(); i++) { + JCRProperty prop = (JCRProperty) newProperites.get(i); + Node propNode = node.addNode("messageProperty", "nt:unstructured"); + propNode.addMixin(PROPERTY_NODE_TYPE); + prop.merge(propNode); + } + } + this.node = node; + + } + + @Override + protected String[] createUserFlags() { + return userFlags; + } + + @Override + protected int getBodyStartOctet() { + if (isPersistent()) { + try { + return (int)node.getProperty(BODY_START_OCTET_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve property " + TEXTUAL_LINE_COUNT_PROPERTY, e); + + } + return 0; + } + return bodyStartOctet; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + final JCRMessage other = (JCRMessage) obj; + + if (getUUID() != null) { + if (!getUUID().equals(other.getUUID())) + return false; + } else { + if (other.getUUID() != null) + return false; + } + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) + return false; + } else { + if (other.getMailboxId() != null) + return false; + } + if (getId() != null) { + if (!getId().equals(other.getId())) + return false; + } else { + if (other.getId() != null) + return false; + } + return true; + } + + + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#getInternalDate + * () + */ + public Date getInternalDate() { + if (isPersistent()) { + try { + if (node.hasProperty(INTERNAL_DATE_PROPERTY)) { + return node.getProperty(INTERNAL_DATE_PROPERTY).getDate().getTime(); + } + + } catch (RepositoryException e) { + logger.error("Unable to access property " + FLAGGED_PROPERTY, + e); + } + return null; + } + return internalDate; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#getMailboxId() + */ + public String getMailboxId() { + if (isPersistent()) { + try { + return node.getProperty(MAILBOX_UUID_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + + MAILBOX_UUID_PROPERTY, e); + } + } + return mailboxUUID; + } + + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#getUid() + */ + public long getUid() { + if (isPersistent()) { + try { + return node.getProperty(UID_PROPERTY).getLong(); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + UID_PROPERTY, e); + } + return 0; + } + return uid; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#isAnswered() + */ + public boolean isAnswered() { + if (isPersistent()) { + try { + if (node.hasProperty(ANSWERED_PROPERTY)) { + return node.getProperty(ANSWERED_PROPERTY).getBoolean(); + } + + } catch (RepositoryException e) { + logger.error("Unable to access property " + ANSWERED_PROPERTY, + e); + } + return false; + } + return answered; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isDeleted() + */ + public boolean isDeleted() { + if (isPersistent()) { + try { + if (node.hasProperty(DELETED_PROPERTY)) { + return node.getProperty(DELETED_PROPERTY).getBoolean(); + } + + } catch (RepositoryException e) { + logger.error("Unable to access property " + DELETED_PROPERTY, + e); + } + return false; + } + return deleted; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isDraft() + */ + public boolean isDraft() { + if (isPersistent()) { + try { + if (node.hasProperty(DRAFT_PROPERTY)) { + return node.getProperty(DRAFT_PROPERTY).getBoolean(); + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + DRAFT_PROPERTY, e); + } + return false; + } + return draft; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isFlagged() + */ + public boolean isFlagged() { + if (isPersistent()) { + try { + if (node.hasProperty(FLAGGED_PROPERTY)) { + return node.getProperty(FLAGGED_PROPERTY).getBoolean(); + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + FLAGGED_PROPERTY, + e); + } + return false; + } + return flagged; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isRecent() + */ + public boolean isRecent() { + if (isPersistent()) { + try { + if (node.hasProperty(RECENT_PROPERTY)) { + return node.getProperty(RECENT_PROPERTY).getBoolean(); + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + RECENT_PROPERTY, e); + } + return false; + } + return recent; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isSeen() + */ + public boolean isSeen() { + if (isPersistent()) { + try { + return node.getProperty(SEEN_PROPERTY).getBoolean(); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + SEEN_PROPERTY, e); + } + return false; + } + return seen; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#setFlags(javax + * .mail.Flags) + */ + public void setFlags(Flags flags) { + if (isPersistent()) { + try { + node.setProperty(ANSWERED_PROPERTY, + flags.contains(Flags.Flag.ANSWERED)); + node.setProperty(DELETED_PROPERTY, + flags.contains(Flags.Flag.DELETED)); + node.setProperty(DRAFT_PROPERTY, + flags.contains(Flags.Flag.DRAFT)); + node.setProperty(FLAGGED_PROPERTY, + flags.contains(Flags.Flag.FLAGGED)); + node.setProperty(RECENT_PROPERTY, + flags.contains(Flags.Flag.RECENT)); + node.setProperty(SEEN_PROPERTY, + flags.contains(Flags.Flag.SEEN)); + node.setProperty(USERFLAGS_PROPERTY, flags.getUserFlags()); + } catch (RepositoryException e) { + logger.error("Unable to set flags", e); + } + } else { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + userFlags = flags.getUserFlags(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#unsetRecent() + */ + public void unsetRecent() { + if (isPersistent()) { + try { + node.setProperty(RECENT_PROPERTY, false); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + RECENT_PROPERTY, e); + } + } else { + recent = false; + } + } + + + + public String getId() { + if (isPersistent()) { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + JcrConstants.JCR_UUID, e); + } + } + return null; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + getUUID().hashCode(); + result = PRIME * result + getMailboxId().hashCode(); + return result; + } + + + public String toString() { + final String retValue = + "message(" + + "uuid = " + getUUID() + + "mailboxUUID = " + this.getMailboxId() + TOSTRING_SEPARATOR + + "uuid = " + this.getId() + TOSTRING_SEPARATOR + + "internalDate = " + this.getInternalDate() + TOSTRING_SEPARATOR + + "size = " + this.getFullContentOctets() + TOSTRING_SEPARATOR + + "answered = " + this.isAnswered() + TOSTRING_SEPARATOR + + "deleted = " + this.isDeleted() + TOSTRING_SEPARATOR + + "draft = " + this.isDraft() + TOSTRING_SEPARATOR + + "flagged = " + this.isFlagged() + TOSTRING_SEPARATOR + + "recent = " + this.isRecent() + TOSTRING_SEPARATOR + + "seen = " + this.isSeen() + TOSTRING_SEPARATOR + + " )"; + + return retValue; + } + + + @Override + public InputStream getFullContent() throws IOException { + if (isPersistent()) { + try { + //TODO: Maybe we should cache this somehow... + InputStream contentStream = node.getNode(JcrConstants.JCR_CONTENT).getProperty(JcrConstants.JCR_DATA).getBinary().getStream(); + return contentStream; + } catch (RepositoryException e) { + throw new IOException("Unable to retrieve property " + JcrConstants.JCR_CONTENT, e); + } + } + return content.newStream(0, -1); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + InputStream body = getFullContent(); + IOUtils.skipFully(body, getBodyStartOctet()); + return body; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + public long getModSeq() { + if (isPersistent()) { + try { + return node.getProperty(MODSEQ_PROPERTY).getLong(); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + MODSEQ_PROPERTY, e); + } + return 0; + } + return modSeq; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + public void setModSeq(long modSeq) { + if (isPersistent()) { + try { + node.setProperty(MODSEQ_PROPERTY, modSeq); + } catch (RepositoryException e) { + logger.error("Unable to set mod-sequence", e); + } + } else { + this.modSeq = modSeq; + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#setUid(long) + */ + public void setUid(long uid) { + if (isPersistent()) { + try { + node.setProperty(UID_PROPERTY, uid); + } catch (RepositoryException e) { + logger.error("Unable to set uid", e); + } + } else { + this.uid = uid; + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + long limit = getBodyStartOctet(); + if (limit < 0) { + limit = 0; + } + return new BoundedInputStream(getFullContent(), limit); + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRProperty.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRProperty.java.svn-base new file mode 100644 index 0000000..4a9792e --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/.svn/text-base/JCRProperty.java.svn-base @@ -0,0 +1,231 @@ +/**************************************************************** + * 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.mail.model; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.store.mail.model.Property; +import org.slf4j.Logger; + +/** + * JCR implementation of {@link Property} + * + */ +public class JCRProperty implements JCRImapConstants, Persistent, Property { + + private Node node; + private final Logger logger; + private String namespace; + private String localName; + private String value; + private int order; + + public final static String NAMESPACE_PROPERTY = "jamesMailbox:propertyNamespace"; + public final static String LOCALNAME_PROPERTY = "jamesMailbox:propertyLocalName"; + public final static String VALUE_PROPERTY = "jamesMailbox:propertyValue"; + public final static String ORDER_PROPERTY = "jamesMailbox:propertyOrder"; + + public JCRProperty(final Node node, final Logger logger) { + this.node = node; + this.logger = logger; + } + + public JCRProperty(final String namespace, final String localName, final String value, Logger logger) { + this.namespace = namespace; + this.localName = localName; + this.value = value; + this.logger = logger; + } + + public JCRProperty(Property property, Logger logger) { + this(property.getNamespace(), property.getLocalName(), property.getValue(), logger); + } + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.AbstractComparableProperty#getOrder + * () + */ + public int getOrder() { + if (isPersistent()) { + try { + return (int)node.getProperty(ORDER_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + ORDER_PROPERTY, e); + } + return 0; + } + return order; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.IsPersistent#getNode() + */ + public Node getNode() { + return node; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.Property#getLocalName() + */ + public String getLocalName() { + if (isPersistent()) { + try { + return node.getProperty(LOCALNAME_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + LOCALNAME_PROPERTY, e); + } + return null; + } + return localName; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.Property#getNamespace() + */ + public String getNamespace() { + if (isPersistent()) { + try { + return node.getProperty(NAMESPACE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + NAMESPACE_PROPERTY, e); + } + return null; + } + return namespace; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.Property#getValue() + */ + public String getValue() { + if (isPersistent()) { + try { + return node.getProperty(VALUE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + VALUE_PROPERTY, e); + } + return null; + } + return value; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.IsPersistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.IsPersistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException { + node.setProperty(NAMESPACE_PROPERTY, getNamespace()); + node.setProperty(ORDER_PROPERTY, getOrder()); + node.setProperty(LOCALNAME_PROPERTY, getLocalName()); + node.setProperty(VALUE_PROPERTY, getValue()); + + this.node = node; + /* + namespace = null; + order = 0; + localName = null; + value = null; + */ + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + getLocalName().hashCode(); + result = PRIME * result + getNamespace().hashCode(); + result = PRIME * result + getValue().hashCode(); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JCRProperty other = (JCRProperty) obj; + + if (getLocalName() != null) { + if (!getLocalName().equals(other.getLocalName())) + return false; + } else { + if (other.getLocalName() != null) + return false; + } + if (getNamespace() != null) { + if (!getNamespace().equals(other.getNamespace())) + return false; + } else { + if (other.getNamespace() != null) + return false; + } + if (getValue() != null) { + if (!getValue().equals(other.getValue())) + return false; + } else { + if (other.getValue() != null) + return false; + } + return true; + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() { + final String result = "Property ( " + + "localName = " + this.getLocalName() + " " + + "namespace = " + this.getNamespace() + " " + + "value = " + this.getValue() + + " )"; + + return result; + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMailbox.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMailbox.java new file mode 100644 index 0000000..65dac70 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMailbox.java @@ -0,0 +1,326 @@ +/**************************************************************** + * 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.mail.model; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.util.Text; +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.slf4j.Logger; + + +/** + * JCR implementation of a {@link Mailbox} + */ +public class JCRMailbox implements Mailbox, JCRImapConstants, Persistent{ + + private static final String TAB = " "; + + + public final static String USER_PROPERTY = "jamesMailbox:mailboxUser"; + public final static String NAMESPACE_PROPERTY = "jamesMailbox:mailboxNamespace"; + public final static String NAME_PROPERTY = "jamesMailbox:mailboxName"; + public final static String UIDVALIDITY_PROPERTY = "jamesMailbox:mailboxUidValidity"; + public final static String LASTUID_PROPERTY = "jamesMailbox:mailboxLastUid"; + public final static String HIGHESTMODSEQ_PROPERTY = "jamesMailbox:mailboxHighestModSeq"; + + private String name; + private long uidValidity; + private final Logger logger; + private Node node; + + + private String namespace; + private String user; + private long lastKnownUid; + private long highestKnownModSeq; + + + public JCRMailbox( final MailboxPath path, final long uidValidity, Logger logger) { + this.name = path.getName(); + this.namespace = path.getNamespace(); + this.user = path.getUser(); + this.uidValidity = uidValidity; + this.logger = logger; + } + + public JCRMailbox( final Node node, final Logger logger) { + this.node = node; + this.logger = logger; + } + + public Logger getLog() { + return logger; + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + public String getName() { + if (isPersistent()) { + try { + return node.getProperty(NAME_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAME_PROPERTY, e); + } + } + return name; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + public long getUidValidity() { + if (isPersistent()) { + try { + return node.getProperty(UIDVALIDITY_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + UIDVALIDITY_PROPERTY, e); + } + } + return uidValidity; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String) + */ + public void setName(String name) { + if (isPersistent()) { + try { + node.setProperty(NAME_PROPERTY, name); + // move the node + // See https://issues.apache.org/jira/browse/IMAP-162 + node.getSession().move(node.getPath(), node.getParent().getPath() + NODE_DELIMITER + Text.escapePath(name)); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAME_PROPERTY, e); + } + } else { + this.name = name; + } + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#getNode() + */ + public Node getNode() { + return node; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException { + node.setProperty(NAME_PROPERTY, getName()); + node.setProperty(UIDVALIDITY_PROPERTY, getUidValidity()); + String user = getUser(); + if (user == null) { + user = ""; + } + node.setProperty(USER_PROPERTY, user); + node.setProperty(NAMESPACE_PROPERTY, getNamespace()); + node.setProperty(HIGHESTMODSEQ_PROPERTY, getHighestModSeq()); + node.setProperty(LASTUID_PROPERTY, getLastUid()); + this.node = node; + } + + @Override + public String toString() { + final String retValue = "Mailbox ( " + + "mailboxUID = " + this.getMailboxId() + TAB + + "name = " + this.getName() + TAB + + "uidValidity = " + this.getUidValidity() + TAB + + " )"; + return retValue; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) getMailboxId().hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JCRMailbox other = (JCRMailbox) obj; + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) + return false; + } else { + if (other.getMailboxId() != null) + return false; + } + return true; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + public String getMailboxId() { + if (isPersistent()) { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + JcrConstants.JCR_UUID, e); + } + } + return null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + public String getNamespace() { + if (isPersistent()) { + try { + return node.getProperty(NAMESPACE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAMESPACE_PROPERTY, e); + } + } + return namespace; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + public String getUser() { + if (isPersistent()) { + try { + String user = node.getProperty(USER_PROPERTY).getString(); + if (user.trim().length() == 0) { + return null; + } else { + return user; + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + USER_PROPERTY, e); + } + } + return user; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + public void setNamespace(String namespace) { + if (isPersistent()) { + try { + node.setProperty(NAMESPACE_PROPERTY, namespace); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAMESPACE_PROPERTY, e); + } + } else { + this.namespace = namespace; + } + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + public void setUser(String user) { + if (isPersistent()) { + try { + if (user == null) { + user = ""; + } + node.setProperty(USER_PROPERTY, user); + } catch (RepositoryException e) { + logger.error("Unable to access property " + NAME_PROPERTY, e); + } + } else { + this.user = user; + } + } + + private long getLastUid() { + if (isPersistent()) { + try { + return node.getProperty(LASTUID_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + LASTUID_PROPERTY, e); + } + } + return lastKnownUid; + } + + private long getHighestModSeq() { + if (isPersistent()) { + try { + return node.getProperty(HIGHESTMODSEQ_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + HIGHESTMODSEQ_PROPERTY, e); + } + } + return highestKnownModSeq; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + // TODO ACL support + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + // TODO ACL support + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMessage.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMessage.java new file mode 100644 index 0000000..e66f663 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRMessage.java @@ -0,0 +1,764 @@ +/**************************************************************** + * 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.mail.model; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BoundedInputStream; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.slf4j.Logger; + +/** + * JCR implementation of {@link Message} + * + */ +public class JCRMessage extends AbstractMessage implements JCRImapConstants, Persistent{ + + private Node node; + private final Logger logger; + private SharedInputStream content; + private String mediaType; + private Long textualLineCount; + private String subType; + private List properties; + private int bodyStartOctet; + + private String mailboxUUID; + private long uid; + private Date internalDate; + private long size; + private boolean answered; + private boolean deleted; + private boolean draft; + private boolean flagged; + private boolean recent; + private boolean seen; + private String[] userFlags; + private long modSeq; + + private static final String TOSTRING_SEPARATOR = " "; + + public final static String MAILBOX_UUID_PROPERTY = "jamesMailbox:mailboxUUID"; + public final static String UID_PROPERTY = "jamesMailbox:uid"; + public final static String SIZE_PROPERTY = "jamesMailbox:size"; + public final static String ANSWERED_PROPERTY = "jamesMailbox:answered"; + public final static String DELETED_PROPERTY = "jamesMailbox:deleted"; + public final static String DRAFT_PROPERTY = "jamesMailbox:draft"; + public final static String FLAGGED_PROPERTY = "jamesMailbox:flagged"; + public final static String USERFLAGS_PROPERTY = "jamesMailbox:userFlags"; + + public final static String RECENT_PROPERTY = "jamesMailbox:recent"; + public final static String SEEN_PROPERTY = "jamesMailbox:seen"; + public final static String INTERNAL_DATE_PROPERTY = "jamesMailbox:internalDate"; + + public final static String BODY_START_OCTET_PROPERTY = "jamesMailbox:messageBodyStartOctet"; + public final static String HEADER_NODE_TYPE = "jamesMailbox:messageHeader"; + + public final static String PROPERTY_NODE_TYPE = "jamesMailbox:messageProperty"; + public final static String TEXTUAL_LINE_COUNT_PROPERTY = "jamesMailbox:messageTextualLineCount"; + public final static String SUBTYPE_PROPERTY = "jamesMailbox:messageSubType"; + public final static String MODSEQ_PROPERTY = "jamesMailbox:modSeq"; + + public JCRMessage(Node node, Logger logger) { + this.logger= logger; + this.node = node; + } + + public JCRMessage(String mailboxUUID, Date internalDate, int size, Flags flags, SharedInputStream content, + int bodyStartOctet, final PropertyBuilder propertyBuilder, Logger logger) { + super(); + this.mailboxUUID = mailboxUUID; + this.internalDate = internalDate; + this.size = size; + this.logger = logger; + setFlags(flags); + this.content = content; + + this.bodyStartOctet = bodyStartOctet; + this.textualLineCount = propertyBuilder.getTextualLineCount(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + final List properties = propertyBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + for (final Property property:properties) { + this.properties.add(new JCRProperty(property,logger)); + } + + } + + + /** + * Create a copy of the given message + * + * @param message + * @throws IOException + */ + public JCRMessage(String mailboxUUID, long uid, long modSeq, JCRMessage message, Logger logger) throws MailboxException { + this.mailboxUUID = mailboxUUID; + this.internalDate = message.getInternalDate(); + this.size = message.getFullContentOctets(); + setFlags(message.createFlags()); + this.uid = uid; + this.modSeq = modSeq; + this.logger = logger; + try { + this.content = new SharedByteArrayInputStream(IOUtils.toByteArray(message.getFullContent())); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + + this.bodyStartOctet = (int) (message.getFullContentOctets() - message.getBodyOctets()); + + PropertyBuilder pBuilder = new PropertyBuilder(message.getProperties()); + this.textualLineCount = message.getTextualLineCount(); + this.mediaType = message.getMediaType(); + this.subType = message.getSubType(); + final List properties = pBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + for (final Property property:properties) { + this.properties.add(new JCRProperty(property, logger)); + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getFullContentOctets() + */ + public long getFullContentOctets() { + if (isPersistent()) { + try { + return node.getProperty(SIZE_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve property " + SIZE_PROPERTY, e); + + } + return 0; + } + return size; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getMediaType() + */ + public String getMediaType() { + if (isPersistent()) { + try { + return node.getNode(JcrConstants.JCR_CONTENT).getProperty(JcrConstants.JCR_MIMETYPE).getString(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve node " + JcrConstants.JCR_MIMETYPE, e); + } + return null; + } + return mediaType; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getProperties() + */ + public List getProperties() { + if (isPersistent()) { + try { + List properties = new ArrayList(); + NodeIterator nodeIt = node.getNodes("messageProperty"); + while (nodeIt.hasNext()) { + properties.add(new JCRProperty(nodeIt.nextNode(), logger)); + } + return properties; + } catch (RepositoryException e) { + logger.error("Unable to retrieve nodes messageProperty", e); + } + } + return new ArrayList(properties); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getSubType() + */ + public String getSubType() { + if (isPersistent()) { + try { + return node.getProperty(SUBTYPE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve node " + SUBTYPE_PROPERTY, e); + } + return null; + } + return subType; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Document#getTextualLineCount() + */ + public Long getTextualLineCount() { + if (isPersistent()) { + try { + if (node.hasProperty(TEXTUAL_LINE_COUNT_PROPERTY)) { + return node.getProperty(TEXTUAL_LINE_COUNT_PROPERTY).getLong(); + } + } catch (RepositoryException e) { + logger.error("Unable to retrieve property " + TEXTUAL_LINE_COUNT_PROPERTY, e); + + } + return null; + } + return textualLineCount; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#getNode() + */ + public Node getNode() { + return node; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + public String getUUID() { + if (isPersistent()) { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + logger.error("Unable to access UUID", e); + } + } + return null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.Persistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException, IOException { + + // update the flags + node.setProperty(ANSWERED_PROPERTY, isAnswered()); + node.setProperty(DELETED_PROPERTY, isDeleted()); + node.setProperty(DRAFT_PROPERTY, isDraft()); + node.setProperty(FLAGGED_PROPERTY, isFlagged()); + node.setProperty(RECENT_PROPERTY, isRecent()); + node.setProperty(SEEN_PROPERTY, isSeen()); + node.setProperty(USERFLAGS_PROPERTY, createFlags().getUserFlags()); + // This stuff is only ever changed on a new message + // so if it is persistent we don'T need to set all the of this. + // + // This also fix https://issues.apache.org/jira/browse/IMAP-159 + if (isPersistent() == false) { + node.setProperty(SIZE_PROPERTY, getFullContentOctets()); + node.setProperty(MAILBOX_UUID_PROPERTY, getMailboxId()); + node.setProperty(UID_PROPERTY, getUid()); + node.setProperty(MODSEQ_PROPERTY, getModSeq()); + + if (getInternalDate() == null) { + internalDate = new Date(); + } + + Calendar cal = Calendar.getInstance(); + + cal.setTime(getInternalDate()); + node.setProperty(INTERNAL_DATE_PROPERTY, cal); + + Node contentNode = JcrUtils.getOrAddNode(node, JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE); + Binary binaryContent = contentNode.getSession().getValueFactory().createBinary(getFullContent()); + contentNode.setProperty(JcrConstants.JCR_DATA, binaryContent); + contentNode.setProperty(JcrConstants.JCR_MIMETYPE, getMediaType()); + + if (getTextualLineCount() != null) { + node.setProperty(TEXTUAL_LINE_COUNT_PROPERTY, getTextualLineCount()); + } + node.setProperty(SUBTYPE_PROPERTY, getSubType()); + node.setProperty(BODY_START_OCTET_PROPERTY, getBodyStartOctet()); + + + List currentProperties = getProperties(); + List newProperites = new ArrayList(); + for (int i = 0; i < currentProperties.size(); i++) { + Property prop = currentProperties.get(i); + newProperites.add(new JCRProperty(prop, logger)); + } + // remove old properties, we will add a bunch of new ones + NodeIterator iterator = node.getNodes("messageProperty"); + while (iterator.hasNext()) { + iterator.nextNode().remove(); + } + + // store new properties + for (int i = 0; i < newProperites.size(); i++) { + JCRProperty prop = (JCRProperty) newProperites.get(i); + Node propNode = node.addNode("messageProperty", "nt:unstructured"); + propNode.addMixin(PROPERTY_NODE_TYPE); + prop.merge(propNode); + } + } + this.node = node; + + } + + @Override + protected String[] createUserFlags() { + return userFlags; + } + + @Override + protected int getBodyStartOctet() { + if (isPersistent()) { + try { + return (int)node.getProperty(BODY_START_OCTET_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to retrieve property " + TEXTUAL_LINE_COUNT_PROPERTY, e); + + } + return 0; + } + return bodyStartOctet; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + final JCRMessage other = (JCRMessage) obj; + + if (getUUID() != null) { + if (!getUUID().equals(other.getUUID())) + return false; + } else { + if (other.getUUID() != null) + return false; + } + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) + return false; + } else { + if (other.getMailboxId() != null) + return false; + } + if (getId() != null) { + if (!getId().equals(other.getId())) + return false; + } else { + if (other.getId() != null) + return false; + } + return true; + } + + + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#getInternalDate + * () + */ + public Date getInternalDate() { + if (isPersistent()) { + try { + if (node.hasProperty(INTERNAL_DATE_PROPERTY)) { + return node.getProperty(INTERNAL_DATE_PROPERTY).getDate().getTime(); + } + + } catch (RepositoryException e) { + logger.error("Unable to access property " + FLAGGED_PROPERTY, + e); + } + return null; + } + return internalDate; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#getMailboxId() + */ + public String getMailboxId() { + if (isPersistent()) { + try { + return node.getProperty(MAILBOX_UUID_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + + MAILBOX_UUID_PROPERTY, e); + } + } + return mailboxUUID; + } + + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#getUid() + */ + public long getUid() { + if (isPersistent()) { + try { + return node.getProperty(UID_PROPERTY).getLong(); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + UID_PROPERTY, e); + } + return 0; + } + return uid; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#isAnswered() + */ + public boolean isAnswered() { + if (isPersistent()) { + try { + if (node.hasProperty(ANSWERED_PROPERTY)) { + return node.getProperty(ANSWERED_PROPERTY).getBoolean(); + } + + } catch (RepositoryException e) { + logger.error("Unable to access property " + ANSWERED_PROPERTY, + e); + } + return false; + } + return answered; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isDeleted() + */ + public boolean isDeleted() { + if (isPersistent()) { + try { + if (node.hasProperty(DELETED_PROPERTY)) { + return node.getProperty(DELETED_PROPERTY).getBoolean(); + } + + } catch (RepositoryException e) { + logger.error("Unable to access property " + DELETED_PROPERTY, + e); + } + return false; + } + return deleted; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isDraft() + */ + public boolean isDraft() { + if (isPersistent()) { + try { + if (node.hasProperty(DRAFT_PROPERTY)) { + return node.getProperty(DRAFT_PROPERTY).getBoolean(); + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + DRAFT_PROPERTY, e); + } + return false; + } + return draft; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isFlagged() + */ + public boolean isFlagged() { + if (isPersistent()) { + try { + if (node.hasProperty(FLAGGED_PROPERTY)) { + return node.getProperty(FLAGGED_PROPERTY).getBoolean(); + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + FLAGGED_PROPERTY, + e); + } + return false; + } + return flagged; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isRecent() + */ + public boolean isRecent() { + if (isPersistent()) { + try { + if (node.hasProperty(RECENT_PROPERTY)) { + return node.getProperty(RECENT_PROPERTY).getBoolean(); + } + } catch (RepositoryException e) { + logger.error("Unable to access property " + RECENT_PROPERTY, e); + } + return false; + } + return recent; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.MailboxMembership#isSeen() + */ + public boolean isSeen() { + if (isPersistent()) { + try { + return node.getProperty(SEEN_PROPERTY).getBoolean(); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + SEEN_PROPERTY, e); + } + return false; + } + return seen; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#setFlags(javax + * .mail.Flags) + */ + public void setFlags(Flags flags) { + if (isPersistent()) { + try { + node.setProperty(ANSWERED_PROPERTY, + flags.contains(Flags.Flag.ANSWERED)); + node.setProperty(DELETED_PROPERTY, + flags.contains(Flags.Flag.DELETED)); + node.setProperty(DRAFT_PROPERTY, + flags.contains(Flags.Flag.DRAFT)); + node.setProperty(FLAGGED_PROPERTY, + flags.contains(Flags.Flag.FLAGGED)); + node.setProperty(RECENT_PROPERTY, + flags.contains(Flags.Flag.RECENT)); + node.setProperty(SEEN_PROPERTY, + flags.contains(Flags.Flag.SEEN)); + node.setProperty(USERFLAGS_PROPERTY, flags.getUserFlags()); + } catch (RepositoryException e) { + logger.error("Unable to set flags", e); + } + } else { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + userFlags = flags.getUserFlags(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.MailboxMembership#unsetRecent() + */ + public void unsetRecent() { + if (isPersistent()) { + try { + node.setProperty(RECENT_PROPERTY, false); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + RECENT_PROPERTY, e); + } + } else { + recent = false; + } + } + + + + public String getId() { + if (isPersistent()) { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + logger.error("Unable to access property " + JcrConstants.JCR_UUID, e); + } + } + return null; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + getUUID().hashCode(); + result = PRIME * result + getMailboxId().hashCode(); + return result; + } + + + public String toString() { + final String retValue = + "message(" + + "uuid = " + getUUID() + + "mailboxUUID = " + this.getMailboxId() + TOSTRING_SEPARATOR + + "uuid = " + this.getId() + TOSTRING_SEPARATOR + + "internalDate = " + this.getInternalDate() + TOSTRING_SEPARATOR + + "size = " + this.getFullContentOctets() + TOSTRING_SEPARATOR + + "answered = " + this.isAnswered() + TOSTRING_SEPARATOR + + "deleted = " + this.isDeleted() + TOSTRING_SEPARATOR + + "draft = " + this.isDraft() + TOSTRING_SEPARATOR + + "flagged = " + this.isFlagged() + TOSTRING_SEPARATOR + + "recent = " + this.isRecent() + TOSTRING_SEPARATOR + + "seen = " + this.isSeen() + TOSTRING_SEPARATOR + + " )"; + + return retValue; + } + + + @Override + public InputStream getFullContent() throws IOException { + if (isPersistent()) { + try { + //TODO: Maybe we should cache this somehow... + InputStream contentStream = node.getNode(JcrConstants.JCR_CONTENT).getProperty(JcrConstants.JCR_DATA).getBinary().getStream(); + return contentStream; + } catch (RepositoryException e) { + throw new IOException("Unable to retrieve property " + JcrConstants.JCR_CONTENT, e); + } + } + return content.newStream(0, -1); + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + InputStream body = getFullContent(); + IOUtils.skipFully(body, getBodyStartOctet()); + return body; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + public long getModSeq() { + if (isPersistent()) { + try { + return node.getProperty(MODSEQ_PROPERTY).getLong(); + + } catch (RepositoryException e) { + logger.error("Unable to access property " + MODSEQ_PROPERTY, e); + } + return 0; + } + return modSeq; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + public void setModSeq(long modSeq) { + if (isPersistent()) { + try { + node.setProperty(MODSEQ_PROPERTY, modSeq); + } catch (RepositoryException e) { + logger.error("Unable to set mod-sequence", e); + } + } else { + this.modSeq = modSeq; + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#setUid(long) + */ + public void setUid(long uid) { + if (isPersistent()) { + try { + node.setProperty(UID_PROPERTY, uid); + } catch (RepositoryException e) { + logger.error("Unable to set uid", e); + } + } else { + this.uid = uid; + } + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + long limit = getBodyStartOctet(); + if (limit < 0) { + limit = 0; + } + return new BoundedInputStream(getFullContent(), limit); + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRProperty.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRProperty.java new file mode 100644 index 0000000..4a9792e --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/mail/model/JCRProperty.java @@ -0,0 +1,231 @@ +/**************************************************************** + * 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.mail.model; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.store.mail.model.Property; +import org.slf4j.Logger; + +/** + * JCR implementation of {@link Property} + * + */ +public class JCRProperty implements JCRImapConstants, Persistent, Property { + + private Node node; + private final Logger logger; + private String namespace; + private String localName; + private String value; + private int order; + + public final static String NAMESPACE_PROPERTY = "jamesMailbox:propertyNamespace"; + public final static String LOCALNAME_PROPERTY = "jamesMailbox:propertyLocalName"; + public final static String VALUE_PROPERTY = "jamesMailbox:propertyValue"; + public final static String ORDER_PROPERTY = "jamesMailbox:propertyOrder"; + + public JCRProperty(final Node node, final Logger logger) { + this.node = node; + this.logger = logger; + } + + public JCRProperty(final String namespace, final String localName, final String value, Logger logger) { + this.namespace = namespace; + this.localName = localName; + this.value = value; + this.logger = logger; + } + + public JCRProperty(Property property, Logger logger) { + this(property.getNamespace(), property.getLocalName(), property.getValue(), logger); + } + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.mail.model.AbstractComparableProperty#getOrder + * () + */ + public int getOrder() { + if (isPersistent()) { + try { + return (int)node.getProperty(ORDER_PROPERTY).getLong(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + ORDER_PROPERTY, e); + } + return 0; + } + return order; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.IsPersistent#getNode() + */ + public Node getNode() { + return node; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.Property#getLocalName() + */ + public String getLocalName() { + if (isPersistent()) { + try { + return node.getProperty(LOCALNAME_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + LOCALNAME_PROPERTY, e); + } + return null; + } + return localName; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.Property#getNamespace() + */ + public String getNamespace() { + if (isPersistent()) { + try { + return node.getProperty(NAMESPACE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + NAMESPACE_PROPERTY, e); + } + return null; + } + return namespace; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.model.Property#getValue() + */ + public String getValue() { + if (isPersistent()) { + try { + return node.getProperty(VALUE_PROPERTY).getString(); + } catch (RepositoryException e) { + logger.error("Unable to access Property " + VALUE_PROPERTY, e); + } + return null; + } + return value; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.IsPersistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.IsPersistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException { + node.setProperty(NAMESPACE_PROPERTY, getNamespace()); + node.setProperty(ORDER_PROPERTY, getOrder()); + node.setProperty(LOCALNAME_PROPERTY, getLocalName()); + node.setProperty(VALUE_PROPERTY, getValue()); + + this.node = node; + /* + namespace = null; + order = 0; + localName = null; + value = null; + */ + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + getLocalName().hashCode(); + result = PRIME * result + getNamespace().hashCode(); + result = PRIME * result + getValue().hashCode(); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JCRProperty other = (JCRProperty) obj; + + if (getLocalName() != null) { + if (!getLocalName().equals(other.getLocalName())) + return false; + } else { + if (other.getLocalName() != null) + return false; + } + if (getNamespace() != null) { + if (!getNamespace().equals(other.getNamespace())) + return false; + } else { + if (other.getNamespace() != null) + return false; + } + if (getValue() != null) { + if (!getValue().equals(other.getValue())) + return false; + } else { + if (other.getValue() != null) + return false; + } + return true; + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() { + final String result = "Property ( " + + "localName = " + this.getLocalName() + " " + + "namespace = " + this.getNamespace() + " " + + "value = " + this.getValue() + + " )"; + + return result; + } +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/all-wcprops new file mode 100644 index 0000000..c7fd3b6 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 99 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/user +END +JCRSubscriptionMapper.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/user/JCRSubscriptionMapper.java +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/entries new file mode 100644 index 0000000..e447ffa --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/user +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + +JCRSubscriptionMapper.java +file + + + + +2013-09-02T02:54:37.000000Z +85830c1660ceaccb86d484477f0bd3ca +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +7653 + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/text-base/JCRSubscriptionMapper.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/text-base/JCRSubscriptionMapper.java.svn-base new file mode 100644 index 0000000..19a31ed --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/.svn/text-base/JCRSubscriptionMapper.java.svn-base @@ -0,0 +1,191 @@ +/**************************************************************** + * 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.user; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.util.Text; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.jcr.AbstractJCRScalingMapper; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.user.model.JCRSubscription; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * JCR implementation of a SubscriptionManager + * + */ +public class JCRSubscriptionMapper extends AbstractJCRScalingMapper implements SubscriptionMapper { + + public JCRSubscriptionMapper(final MailboxSessionJCRRepository repos, MailboxSession session, final int scaling) { + super(repos,session, scaling); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.user.SubscriptionMapper#delete(org.apache + * .james.imap.store.user.model.Subscription) + */ + public void delete(Subscription subscription) throws SubscriptionException { + + JCRSubscription sub = (JCRSubscription) subscription; + try { + + Node node = sub.getNode(); + if (node != null) { + Property prop = node.getProperty(JCRSubscription.MAILBOXES_PROPERTY); + Value[] values = prop.getValues(); + List newValues = new ArrayList(); + for (int i = 0; i < values.length; i++) { + String m = values[i].getString(); + if (m.equals(sub.getMailbox()) == false) { + newValues.add(m); + } + } + if (newValues.isEmpty() == false) { + prop.setValue(newValues.toArray(new String[newValues.size()])); + } else { + prop.remove(); + } + } + } catch (PathNotFoundException e) { + // do nothing + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + + } + + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) throws SubscriptionException { + try { + String queryString = "/jcr:root/" + MAILBOXES_PATH + "//element(*,jamesMailbox:user)[@" + JCRSubscription.USERNAME_PROPERTY + "='" + user + "'] AND [@" + JCRSubscription.MAILBOXES_PROPERTY +"='" + mailbox + "']"; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator nodeIt = result.getNodes(); + if (nodeIt.hasNext()) { + JCRSubscription sub = new JCRSubscription(nodeIt.nextNode(), mailbox, getLogger()); + return sub; + } + + } catch (PathNotFoundException e) { + // nothing todo here + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + return null; + + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser + * (java.lang.String) + */ + public List findSubscriptionsForUser(String user) throws SubscriptionException { + List subList = new ArrayList(); + try { + String queryString = "/jcr:root/" + MAILBOXES_PATH + "//element(*,jamesMailbox:user)[@" + JCRSubscription.USERNAME_PROPERTY + "='" + user + "']"; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator nodeIt = result.getNodes(); + while (nodeIt.hasNext()) { + Node node = nodeIt.nextNode(); + if (node.hasProperty(JCRSubscription.MAILBOXES_PROPERTY)) { + Value[] values = node.getProperty(JCRSubscription.MAILBOXES_PROPERTY).getValues(); + for (int i = 0; i < values.length; i++) { + subList.add(new JCRSubscription(node, values[i].getString(), getLogger())); + } + } + } + } catch (PathNotFoundException e) { + // Do nothing just return the empty list later + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + return subList; + + } + + + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.user.SubscriptionMapper#save(org.apache.james + * .imap.store.user.model.Subscription) + */ + public void save(Subscription subscription) throws SubscriptionException { + String username = subscription.getUser(); + String mailbox = subscription.getMailbox(); + try { + + Node node = null; + + JCRSubscription sub = (JCRSubscription) findMailboxSubscriptionForUser(username, mailbox); + + // its a new subscription + if (sub == null) { + node = JcrUtils.getOrAddNode(getSession().getRootNode(), MAILBOXES_PATH); + node = JcrUtils.getOrAddNode(node, Text.escapeIllegalJcrChars(MailboxConstants.USER_NAMESPACE)); + + // This is needed to minimize the child nodes a bit + node = createUserPathStructure(node, Text.escapeIllegalJcrChars(username)); + } else { + node = sub.getNode(); + } + + // Copy new properties to the node + ((JCRSubscription)subscription).merge(node); + + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/JCRSubscriptionMapper.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/JCRSubscriptionMapper.java new file mode 100644 index 0000000..19a31ed --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/JCRSubscriptionMapper.java @@ -0,0 +1,191 @@ +/**************************************************************** + * 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.user; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.util.Text; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.jcr.AbstractJCRScalingMapper; +import org.apache.james.mailbox.jcr.MailboxSessionJCRRepository; +import org.apache.james.mailbox.jcr.user.model.JCRSubscription; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * JCR implementation of a SubscriptionManager + * + */ +public class JCRSubscriptionMapper extends AbstractJCRScalingMapper implements SubscriptionMapper { + + public JCRSubscriptionMapper(final MailboxSessionJCRRepository repos, MailboxSession session, final int scaling) { + super(repos,session, scaling); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.user.SubscriptionMapper#delete(org.apache + * .james.imap.store.user.model.Subscription) + */ + public void delete(Subscription subscription) throws SubscriptionException { + + JCRSubscription sub = (JCRSubscription) subscription; + try { + + Node node = sub.getNode(); + if (node != null) { + Property prop = node.getProperty(JCRSubscription.MAILBOXES_PROPERTY); + Value[] values = prop.getValues(); + List newValues = new ArrayList(); + for (int i = 0; i < values.length; i++) { + String m = values[i].getString(); + if (m.equals(sub.getMailbox()) == false) { + newValues.add(m); + } + } + if (newValues.isEmpty() == false) { + prop.setValue(newValues.toArray(new String[newValues.size()])); + } else { + prop.remove(); + } + } + } catch (PathNotFoundException e) { + // do nothing + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + + } + + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) throws SubscriptionException { + try { + String queryString = "/jcr:root/" + MAILBOXES_PATH + "//element(*,jamesMailbox:user)[@" + JCRSubscription.USERNAME_PROPERTY + "='" + user + "'] AND [@" + JCRSubscription.MAILBOXES_PROPERTY +"='" + mailbox + "']"; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator nodeIt = result.getNodes(); + if (nodeIt.hasNext()) { + JCRSubscription sub = new JCRSubscription(nodeIt.nextNode(), mailbox, getLogger()); + return sub; + } + + } catch (PathNotFoundException e) { + // nothing todo here + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + return null; + + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser + * (java.lang.String) + */ + public List findSubscriptionsForUser(String user) throws SubscriptionException { + List subList = new ArrayList(); + try { + String queryString = "/jcr:root/" + MAILBOXES_PATH + "//element(*,jamesMailbox:user)[@" + JCRSubscription.USERNAME_PROPERTY + "='" + user + "']"; + + QueryManager manager = getSession().getWorkspace().getQueryManager(); + QueryResult result = manager.createQuery(queryString, Query.XPATH).execute(); + + NodeIterator nodeIt = result.getNodes(); + while (nodeIt.hasNext()) { + Node node = nodeIt.nextNode(); + if (node.hasProperty(JCRSubscription.MAILBOXES_PROPERTY)) { + Value[] values = node.getProperty(JCRSubscription.MAILBOXES_PROPERTY).getValues(); + for (int i = 0; i < values.length; i++) { + subList.add(new JCRSubscription(node, values[i].getString(), getLogger())); + } + } + } + } catch (PathNotFoundException e) { + // Do nothing just return the empty list later + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + return subList; + + } + + + + /* + * (non-Javadoc) + * + * @see + * org.apache.james.mailbox.store.user.SubscriptionMapper#save(org.apache.james + * .imap.store.user.model.Subscription) + */ + public void save(Subscription subscription) throws SubscriptionException { + String username = subscription.getUser(); + String mailbox = subscription.getMailbox(); + try { + + Node node = null; + + JCRSubscription sub = (JCRSubscription) findMailboxSubscriptionForUser(username, mailbox); + + // its a new subscription + if (sub == null) { + node = JcrUtils.getOrAddNode(getSession().getRootNode(), MAILBOXES_PATH); + node = JcrUtils.getOrAddNode(node, Text.escapeIllegalJcrChars(MailboxConstants.USER_NAMESPACE)); + + // This is needed to minimize the child nodes a bit + node = createUserPathStructure(node, Text.escapeIllegalJcrChars(username)); + } else { + node = sub.getNode(); + } + + // Copy new properties to the node + ((JCRSubscription)subscription).merge(node); + + } catch (RepositoryException e) { + throw new SubscriptionException(e); + } + } + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/all-wcprops new file mode 100644 index 0000000..6b8d052 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 105 +/repos/asf/!svn/ver/1084344/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model +END +JCRSubscription.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1084344/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java +END diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/entries b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/entries new file mode 100644 index 0000000..b819bb5 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model +http://svn.apache.org/repos/asf + + + +2011-03-22T20:41:30.696945Z +1084344 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JCRSubscription.java +file + + + + +2013-09-02T02:54:37.000000Z +58654870ef878431974658c717b359b2 +2011-03-22T20:41:30.696945Z +1084344 +felixk + + + + + + + + + + + + + + + + + + + + + +5712 + diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/text-base/JCRSubscription.java.svn-base b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/text-base/JCRSubscription.java.svn-base new file mode 100644 index 0000000..79720e4 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/.svn/text-base/JCRSubscription.java.svn-base @@ -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.jcr.user.model; + + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.slf4j.Logger; + +/** + * JCR implementation of a {@link Subscription}. + */ +public class JCRSubscription implements Subscription, Persistent, JCRImapConstants { + private static final String TOSTRING_SEPARATOR = " "; + + public final static String USERNAME_PROPERTY = "jamesMailbox:user"; + public final static String MAILBOXES_PROPERTY = "jamesMailbox:subscriptionMailboxes"; + + private Node node; + private final Logger log; + private String mailbox; + private String username; + + + public JCRSubscription(Node node, String mailbox, Logger log) { + this.node = node; + this.log = log; + this.mailbox = mailbox; + } + + public JCRSubscription(String username, String mailbox, Logger log) { + this.username = username; + this.mailbox = mailbox; + this.log = log; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.user.model.Subscription#getMailbox() + */ + public String getMailbox() { + return mailbox; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.user.model.Subscription#getUser() + */ + public String getUser() { + if (isPersistent()) { + try { + return node.getProperty(USERNAME_PROPERTY).getString(); + } catch (RepositoryException e) { + log.error("Unable to access Property " + USERNAME_PROPERTY, e); + } + return null; + } + return username; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.NodeAware#getNode() + */ + public Node getNode() { + return node; + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.IsPersistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.IsPersistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException{ + node.setProperty(USERNAME_PROPERTY, getUser()); + if (node.hasProperty(MAILBOXES_PROPERTY)) { + Value[] mailboxes = node.getProperty(MAILBOXES_PROPERTY).getValues(); + ListnewMailboxes = new ArrayList(); + for (int i = 0; i< mailboxes.length; i++) { + String m = mailboxes[i].getString(); + newMailboxes.add(m); + } + if (newMailboxes.contains(getMailbox()) == false) { + newMailboxes.add(getMailbox()); + + } + + node.setProperty(MAILBOXES_PROPERTY, newMailboxes.toArray(new String[newMailboxes.size()])); + } else { + node.setProperty(MAILBOXES_PROPERTY, new String[] {getMailbox()}); + } + this.node = node; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + getUser().hashCode(); + result = PRIME * result + getMailbox().hashCode(); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JCRSubscription other = (JCRSubscription) obj; + if (getUser() != null) { + if (!getUser().equals(other.getUser())) + return false; + } else { + if (other.getUser() != null) + return false; + } + if (getMailbox() != null) { + if (!getMailbox().equals(other.getMailbox())) + return false; + } else { + if (other.getMailbox() != null) + return false; + } + return true; + } + + /** + * Renders output suitable for debugging. + * + * @return output suitable for debugging + */ + public String toString() { + final String result = "Subscription ( " + + "user = " + this.getUser() + TOSTRING_SEPARATOR + + "mailbox = " + this.getMailbox() + TOSTRING_SEPARATOR + + " )"; + + return result; + } + + +} diff --git a/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.java new file mode 100644 index 0000000..79720e4 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/java/org/apache/james/mailbox/jcr/user/model/JCRSubscription.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.jcr.user.model; + + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.apache.james.mailbox.jcr.JCRImapConstants; +import org.apache.james.mailbox.jcr.Persistent; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.slf4j.Logger; + +/** + * JCR implementation of a {@link Subscription}. + */ +public class JCRSubscription implements Subscription, Persistent, JCRImapConstants { + private static final String TOSTRING_SEPARATOR = " "; + + public final static String USERNAME_PROPERTY = "jamesMailbox:user"; + public final static String MAILBOXES_PROPERTY = "jamesMailbox:subscriptionMailboxes"; + + private Node node; + private final Logger log; + private String mailbox; + private String username; + + + public JCRSubscription(Node node, String mailbox, Logger log) { + this.node = node; + this.log = log; + this.mailbox = mailbox; + } + + public JCRSubscription(String username, String mailbox, Logger log) { + this.username = username; + this.mailbox = mailbox; + this.log = log; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.user.model.Subscription#getMailbox() + */ + public String getMailbox() { + return mailbox; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.user.model.Subscription#getUser() + */ + public String getUser() { + if (isPersistent()) { + try { + return node.getProperty(USERNAME_PROPERTY).getString(); + } catch (RepositoryException e) { + log.error("Unable to access Property " + USERNAME_PROPERTY, e); + } + return null; + } + return username; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.jcr.NodeAware#getNode() + */ + public Node getNode() { + return node; + } + + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.IsPersistent#isPersistent() + */ + public boolean isPersistent() { + return node != null; + } + + /* + * (non-Javadoc) + * @see org.apache.james.mailbox.jcr.IsPersistent#merge(javax.jcr.Node) + */ + public void merge(Node node) throws RepositoryException{ + node.setProperty(USERNAME_PROPERTY, getUser()); + if (node.hasProperty(MAILBOXES_PROPERTY)) { + Value[] mailboxes = node.getProperty(MAILBOXES_PROPERTY).getValues(); + ListnewMailboxes = new ArrayList(); + for (int i = 0; i< mailboxes.length; i++) { + String m = mailboxes[i].getString(); + newMailboxes.add(m); + } + if (newMailboxes.contains(getMailbox()) == false) { + newMailboxes.add(getMailbox()); + + } + + node.setProperty(MAILBOXES_PROPERTY, newMailboxes.toArray(new String[newMailboxes.size()])); + } else { + node.setProperty(MAILBOXES_PROPERTY, new String[] {getMailbox()}); + } + this.node = node; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + getUser().hashCode(); + result = PRIME * result + getMailbox().hashCode(); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JCRSubscription other = (JCRSubscription) obj; + if (getUser() != null) { + if (!getUser().equals(other.getUser())) + return false; + } else { + if (other.getUser() != null) + return false; + } + if (getMailbox() != null) { + if (!getMailbox().equals(other.getMailbox())) + return false; + } else { + if (other.getMailbox() != null) + return false; + } + return true; + } + + /** + * Renders output suitable for debugging. + * + * @return output suitable for debugging + */ + public String toString() { + final String result = "Subscription ( " + + "user = " + this.getUser() + TOSTRING_SEPARATOR + + "mailbox = " + this.getMailbox() + TOSTRING_SEPARATOR + + " )"; + + return result; + } + + +} diff --git a/james/apache-james-mailbox/jcr/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..6305cad --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jcr/src/main/resources +END +jcr-repository.xml +K 25 +svn:wc:ra_dav:version-url +V 89 +/repos/asf/!svn/ver/1291207/james/mailbox/trunk/jcr/src/main/resources/jcr-repository.xml +END +mailbox-jcr.cnd +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1291197/james/mailbox/trunk/jcr/src/main/resources/mailbox-jcr.cnd +END diff --git a/james/apache-james-mailbox/jcr/src/main/resources/.svn/entries b/james/apache-james-mailbox/jcr/src/main/resources/.svn/entries new file mode 100644 index 0000000..abc6d6c --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/.svn/entries @@ -0,0 +1,99 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/resources +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + +jcr-repository.xml +file + + + + +2013-09-02T02:54:38.000000Z +cf236e625e91354688e8e45bc6529134 +2012-02-20T11:30:24.689576Z +1291207 +eric + + + + + + + + + + + + + + + + + + + + + +3371 + +mailbox-jcr.cnd +file + + + + +2013-09-02T02:54:38.000000Z +55fdea94699f3bb101081476384ecb47 +2012-02-20T11:14:47.341630Z +1291197 +eric + + + + + + + + + + + + + + + + + + + + + +2494 + diff --git a/james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/jcr-repository.xml.svn-base b/james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/jcr-repository.xml.svn-base new file mode 100644 index 0000000..3fa87cf --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/jcr-repository.xml.svn-base @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base b/james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base new file mode 100644 index 0000000..89aae11 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base @@ -0,0 +1,61 @@ +/* + * 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. + */ + + + +[jamesMailbox:user] > mix:created + mixin + - jamesMailbox:user (String) + - jamesMailbox:subscriptionMailboxes (STRING) multiple + + * (nt:unstructured) multiple + +[jamesMailbox:messageProperty] > mix:created + mixin + - jamesMailbox:propertyNamespace (STRING) mandatory + - jamesMailbox:propertyLocalName (STRING) mandatory + - jamesMailbox:propertyValue(STRING) mandatory + - jamesMailbox:propertyOrder (Long) mandatory + +[jamesMailbox:message] > mix:referenceable, mix:created, mix:created + mixin + - jamesMailbox:mailboxUUID (String) mandatory + - jamesMailbox:uid (LONG) mandatory + - jamesMailbox:modSeq (LONG) + - jamesMailbox:size (LONG) mandatory + - jamesMailbox:answered (BOOLEAN) + - jamesMailbox:deleted (BOOLEAN) + - jamesMailbox:draft (BOOLEAN) + - jamesMailbox:flagged (BOOLEAN) + - jamesMailbox:recent (BOOLEAN) + - jamesMailbox:seen (BOOLEAN) + - jamesMailbox:internalDate (DATE) + - jamesMailbox:userFlags (STRING) multiple + - jamesMailbox:messageBodyStartOctet (LONG) mandatory + - jamesMailbox:messageTextualLineCount (LONG) + - jamesMailbox:messageSubType (String) mandatory + + messageProperty (nt:unstructured) multiple + +[jamesMailbox:mailbox] > mix:referenceable, mix:lockable, mix:created + mixin + - jamesMailbox:mailboxUidValidity (LONG) + - jamesMailbox:mailboxName (STRING) + - jamesMailbox:mailboxLastUid (LONG) + - jamesMailbox:mailboxNamespace (STRING) + - jamesMailbox:mailboxUser (STRING) + - jamesMailbox:mailboxLastUid (LONG) + - jamesMailbox:mailboxHighestModSeq (LONG) + + * (nt:unstructured) multiple diff --git a/james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..d1d0e27 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jcr/src/main/resources/META-INF +END diff --git a/james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/entries b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/entries new file mode 100644 index 0000000..42e614d --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + diff --git a/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/all-wcprops new file mode 100644 index 0000000..f8d1ba8 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jcr/src/main/resources/META-INF/spring +END +mailbox-jcr.xml +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml +END diff --git a/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/entries b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/entries new file mode 100644 index 0000000..f2aa16e --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/main/resources/META-INF/spring +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-jcr.xml +file + + + + +2013-09-02T02:54:38.000000Z +a28dbd8d3c2fde6795bca75a258a2b23 +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +4083 + diff --git a/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jcr.xml.svn-base b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jcr.xml.svn-base new file mode 100644 index 0000000..52e229f --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jcr.xml.svn-base @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + registerCnd + + + + james + james + james + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml new file mode 100644 index 0000000..52e229f --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/META-INF/spring/mailbox-jcr.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + registerCnd + + + + james + james + james + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jcr/src/main/resources/jcr-repository.xml b/james/apache-james-mailbox/jcr/src/main/resources/jcr-repository.xml new file mode 100644 index 0000000..3fa87cf --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/jcr-repository.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jcr/src/main/resources/mailbox-jcr.cnd b/james/apache-james-mailbox/jcr/src/main/resources/mailbox-jcr.cnd new file mode 100644 index 0000000..89aae11 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/main/resources/mailbox-jcr.cnd @@ -0,0 +1,61 @@ +/* + * 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. + */ + + + +[jamesMailbox:user] > mix:created + mixin + - jamesMailbox:user (String) + - jamesMailbox:subscriptionMailboxes (STRING) multiple + + * (nt:unstructured) multiple + +[jamesMailbox:messageProperty] > mix:created + mixin + - jamesMailbox:propertyNamespace (STRING) mandatory + - jamesMailbox:propertyLocalName (STRING) mandatory + - jamesMailbox:propertyValue(STRING) mandatory + - jamesMailbox:propertyOrder (Long) mandatory + +[jamesMailbox:message] > mix:referenceable, mix:created, mix:created + mixin + - jamesMailbox:mailboxUUID (String) mandatory + - jamesMailbox:uid (LONG) mandatory + - jamesMailbox:modSeq (LONG) + - jamesMailbox:size (LONG) mandatory + - jamesMailbox:answered (BOOLEAN) + - jamesMailbox:deleted (BOOLEAN) + - jamesMailbox:draft (BOOLEAN) + - jamesMailbox:flagged (BOOLEAN) + - jamesMailbox:recent (BOOLEAN) + - jamesMailbox:seen (BOOLEAN) + - jamesMailbox:internalDate (DATE) + - jamesMailbox:userFlags (STRING) multiple + - jamesMailbox:messageBodyStartOctet (LONG) mandatory + - jamesMailbox:messageTextualLineCount (LONG) + - jamesMailbox:messageSubType (String) mandatory + + messageProperty (nt:unstructured) multiple + +[jamesMailbox:mailbox] > mix:referenceable, mix:lockable, mix:created + mixin + - jamesMailbox:mailboxUidValidity (LONG) + - jamesMailbox:mailboxName (STRING) + - jamesMailbox:mailboxLastUid (LONG) + - jamesMailbox:mailboxNamespace (STRING) + - jamesMailbox:mailboxUser (STRING) + - jamesMailbox:mailboxLastUid (LONG) + - jamesMailbox:mailboxHighestModSeq (LONG) + + * (nt:unstructured) multiple diff --git a/james/apache-james-mailbox/jcr/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..dc5eb65 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/jcr/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/jcr/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/jcr/src/reporting-site/.svn/entries b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/entries new file mode 100644 index 0000000..43d1686 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-03-05T11:38:38.771020Z +1078275 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:38.000000Z +5bde8261948ff1881960902a322aa196 +2011-03-05T11:38:38.771020Z +1078275 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/jcr/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/jcr/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jcr/src/reporting-site/site.xml b/james/apache-james-mailbox/jcr/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jcr/src/test/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/.svn/all-wcprops new file mode 100644 index 0000000..6805fb5 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test +END diff --git a/james/apache-james-mailbox/jcr/src/test/.svn/entries b/james/apache-james-mailbox/jcr/src/test/.svn/entries new file mode 100644 index 0000000..2dc14c9 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/jcr/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..aa45278 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 65 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test/java +END diff --git a/james/apache-james-mailbox/jcr/src/test/java/.svn/entries b/james/apache-james-mailbox/jcr/src/test/java/.svn/entries new file mode 100644 index 0000000..36fe473 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test/java +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..c19e523 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test/java/org +END diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/.svn/entries b/james/apache-james-mailbox/jcr/src/test/java/org/.svn/entries new file mode 100644 index 0000000..a56aed8 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test/java/org +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..ff29089 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 76 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..4cbd7fd --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..72f9b98 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..2ea2573 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..9fb7302 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..6fa5a1a --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +jcr +dir + diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/all-wcprops new file mode 100644 index 0000000..d3b150c --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test/java/org/apache/james/mailbox/jcr +END +JCRStressTest.java +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRStressTest.java +END +JCRMailboxManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1429793/james/mailbox/trunk/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxManagerTest.java +END +JCRSubscriptionManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1429793/james/mailbox/trunk/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRSubscriptionManagerTest.java +END diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/entries b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/entries new file mode 100644 index 0000000..ef2cfef --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test/java/org/apache/james/mailbox/jcr +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JCRStressTest.java +file + + + + +2013-09-02T02:54:37.000000Z +22ec4235413a6ee9ae22e6ca9dc14588 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +4065 + +JCRMailboxManagerTest.java +file + + + + +2013-09-02T02:54:37.000000Z +c670170404014a9f993c5866fb860e3d +2013-01-07T13:21:20.783231Z +1429793 +ieugen + + + + + + + + + + + + + + + + + + + + + +4647 + +JCRSubscriptionManagerTest.java +file + + + + +2013-09-02T02:54:37.000000Z +740e28a212ac81652358cac6a7e76ca9 +2013-01-07T13:21:20.783231Z +1429793 +ieugen + + + + + + + + + + + + + + + + + + + + + +3058 + diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManagerTest.java.svn-base b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManagerTest.java.svn-base new file mode 100644 index 0000000..1794e72 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRMailboxManagerTest.java.svn-base @@ -0,0 +1,102 @@ +/**************************************************************** + * 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.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.ConfigurationException; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.JCRModSeqProvider; +import org.apache.james.mailbox.jcr.mail.JCRUidProvider; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.junit.After; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; + +import javax.jcr.RepositoryException; +import java.io.File; + +/** + * JCRMailboxManagerTest that extends the StoreMailboxManagerTest. + */ +public class JCRMailboxManagerTest extends AbstractMailboxManagerTest { + + private static final String JACKRABBIT_HOME = "target/jackrabbit"; + + public static final String META_DATA_DIRECTORY = "target/user-meta-data"; + + private static RepositoryImpl repository; + + @Before + public void setup() throws Exception { + createMailboxManager(); + } + + @After + public void tearDown() throws MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + repository.shutdown(); + new File(JACKRABBIT_HOME).delete(); + } + + protected void createMailboxManager() throws MailboxException { + + new File(JACKRABBIT_HOME).delete(); + + String user = "user"; + String pass = "pass"; + String workspace = null; + RepositoryConfig config; + try { + config = RepositoryConfig.create(new InputSource(JCRMailboxManagerTest.class.getClassLoader().getResourceAsStream("test-repository.xml")), JACKRABBIT_HOME); + repository = RepositoryImpl.create(config); + } catch (ConfigurationException e) { + e.printStackTrace(); + fail(); + } catch (RepositoryException e) { + e.printStackTrace(); + fail(); + } + + // Register imap cnd file + JCRUtils.registerCnd(repository, workspace, user, pass); + MailboxSessionJCRRepository sessionRepos = new GlobalMailboxSessionJCRRepository(repository, workspace, user, pass); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JCRUidProvider uidProvider = new JCRUidProvider(locker, sessionRepos); + JCRModSeqProvider modSeqProvider = new JCRModSeqProvider(locker, sessionRepos); + JCRMailboxSessionMapperFactory mf = new JCRMailboxSessionMapperFactory(sessionRepos, uidProvider, modSeqProvider); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + JCRMailboxManager manager = new JCRMailboxManager(mf, null, locker, aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + } + +} diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRStressTest.java.svn-base b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRStressTest.java.svn-base new file mode 100644 index 0000000..19163e9 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRStressTest.java.svn-base @@ -0,0 +1,86 @@ +/**************************************************************** + * 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.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.james.mailbox.AbstractStressTest; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.JCRModSeqProvider; +import org.apache.james.mailbox.jcr.mail.JCRUidProvider; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; + +import javax.jcr.RepositoryException; +import java.io.File; + +public class JCRStressTest extends AbstractStressTest { + + private JCRMailboxManager mailboxManager; + private RepositoryImpl repository; + private static final String JACKRABBIT_HOME = "target/jackrabbit"; + + @Before + public void setUp() throws RepositoryException, MailboxException { + + new File(JACKRABBIT_HOME).delete(); + + String user = "user"; + String pass = "pass"; + String workspace = null; + RepositoryConfig config = RepositoryConfig.create(new InputSource(this.getClass().getClassLoader().getResourceAsStream("test-repository.xml")), JACKRABBIT_HOME); + repository = RepositoryImpl.create(config); + + // Register imap cnd file + JCRUtils.registerCnd(repository, workspace, user, pass); + MailboxSessionJCRRepository sessionRepos = new GlobalMailboxSessionJCRRepository(repository, workspace, user, pass); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JCRUidProvider uidProvider = new JCRUidProvider(locker, sessionRepos); + JCRModSeqProvider modSeqProvider = new JCRModSeqProvider(locker, sessionRepos); + JCRMailboxSessionMapperFactory mf = new JCRMailboxSessionMapperFactory(sessionRepos, uidProvider, modSeqProvider); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + mailboxManager = new JCRMailboxManager(mf, null, locker, aclResolver, groupMembershipResolver); + mailboxManager.init(); + } + + @After + public void tearDown() { + MailboxSession session = mailboxManager.createSystemSession("test", LoggerFactory.getLogger(JCRStressTest.class)); + session.close(); + repository.shutdown(); + new File(JACKRABBIT_HOME).delete(); + } + + @Override + protected MailboxManager getMailboxManager() { + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManagerTest.java.svn-base b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManagerTest.java.svn-base new file mode 100644 index 0000000..d0bfb9d --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/.svn/text-base/JCRSubscriptionManagerTest.java.svn-base @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.james.mailbox.AbstractSubscriptionManagerTest; +import org.apache.james.mailbox.SubscriptionManager; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.xml.sax.InputSource; + +import javax.jcr.RepositoryException; +import java.io.File; + +public class JCRSubscriptionManagerTest extends AbstractSubscriptionManagerTest { + private static final String JACKRABBIT_HOME = "target/jackrabbit"; + + public static final String META_DATA_DIRECTORY = "target/user-meta-data"; + + private static RepositoryImpl repository; + private static String user = "user"; + private static String pass = "pass"; + private static String workspace = null; + + @BeforeClass + public static void before() throws RepositoryException { + RepositoryConfig config = RepositoryConfig.create(new InputSource(JCRMailboxManagerTest.class.getClassLoader().getResourceAsStream("test-repository.xml")), JACKRABBIT_HOME); + repository = RepositoryImpl.create(config); + + // Register imap cnd file + JCRUtils.registerCnd(repository, workspace, user, pass); + } + + @AfterClass + public static void after() { + if (repository != null) { + repository.shutdown(); + } + new File(JACKRABBIT_HOME).delete(); + } + + @Override + public SubscriptionManager createSubscriptionManager() { + + new File(JACKRABBIT_HOME).delete(); + MailboxSessionJCRRepository sessionRepos = new GlobalMailboxSessionJCRRepository(repository, workspace, user, pass); + JCRMailboxSessionMapperFactory mf = new JCRMailboxSessionMapperFactory(sessionRepos, null, null); + JCRSubscriptionManager sm = new JCRSubscriptionManager(mf); + return sm; + } + +} diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxManagerTest.java b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxManagerTest.java new file mode 100644 index 0000000..1794e72 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRMailboxManagerTest.java @@ -0,0 +1,102 @@ +/**************************************************************** + * 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.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.ConfigurationException; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.JCRModSeqProvider; +import org.apache.james.mailbox.jcr.mail.JCRUidProvider; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.junit.After; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; + +import javax.jcr.RepositoryException; +import java.io.File; + +/** + * JCRMailboxManagerTest that extends the StoreMailboxManagerTest. + */ +public class JCRMailboxManagerTest extends AbstractMailboxManagerTest { + + private static final String JACKRABBIT_HOME = "target/jackrabbit"; + + public static final String META_DATA_DIRECTORY = "target/user-meta-data"; + + private static RepositoryImpl repository; + + @Before + public void setup() throws Exception { + createMailboxManager(); + } + + @After + public void tearDown() throws MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + repository.shutdown(); + new File(JACKRABBIT_HOME).delete(); + } + + protected void createMailboxManager() throws MailboxException { + + new File(JACKRABBIT_HOME).delete(); + + String user = "user"; + String pass = "pass"; + String workspace = null; + RepositoryConfig config; + try { + config = RepositoryConfig.create(new InputSource(JCRMailboxManagerTest.class.getClassLoader().getResourceAsStream("test-repository.xml")), JACKRABBIT_HOME); + repository = RepositoryImpl.create(config); + } catch (ConfigurationException e) { + e.printStackTrace(); + fail(); + } catch (RepositoryException e) { + e.printStackTrace(); + fail(); + } + + // Register imap cnd file + JCRUtils.registerCnd(repository, workspace, user, pass); + MailboxSessionJCRRepository sessionRepos = new GlobalMailboxSessionJCRRepository(repository, workspace, user, pass); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JCRUidProvider uidProvider = new JCRUidProvider(locker, sessionRepos); + JCRModSeqProvider modSeqProvider = new JCRModSeqProvider(locker, sessionRepos); + JCRMailboxSessionMapperFactory mf = new JCRMailboxSessionMapperFactory(sessionRepos, uidProvider, modSeqProvider); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + JCRMailboxManager manager = new JCRMailboxManager(mf, null, locker, aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + } + +} diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRStressTest.java b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRStressTest.java new file mode 100644 index 0000000..19163e9 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRStressTest.java @@ -0,0 +1,86 @@ +/**************************************************************** + * 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.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.james.mailbox.AbstractStressTest; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jcr.mail.JCRModSeqProvider; +import org.apache.james.mailbox.jcr.mail.JCRUidProvider; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; + +import javax.jcr.RepositoryException; +import java.io.File; + +public class JCRStressTest extends AbstractStressTest { + + private JCRMailboxManager mailboxManager; + private RepositoryImpl repository; + private static final String JACKRABBIT_HOME = "target/jackrabbit"; + + @Before + public void setUp() throws RepositoryException, MailboxException { + + new File(JACKRABBIT_HOME).delete(); + + String user = "user"; + String pass = "pass"; + String workspace = null; + RepositoryConfig config = RepositoryConfig.create(new InputSource(this.getClass().getClassLoader().getResourceAsStream("test-repository.xml")), JACKRABBIT_HOME); + repository = RepositoryImpl.create(config); + + // Register imap cnd file + JCRUtils.registerCnd(repository, workspace, user, pass); + MailboxSessionJCRRepository sessionRepos = new GlobalMailboxSessionJCRRepository(repository, workspace, user, pass); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JCRUidProvider uidProvider = new JCRUidProvider(locker, sessionRepos); + JCRModSeqProvider modSeqProvider = new JCRModSeqProvider(locker, sessionRepos); + JCRMailboxSessionMapperFactory mf = new JCRMailboxSessionMapperFactory(sessionRepos, uidProvider, modSeqProvider); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + mailboxManager = new JCRMailboxManager(mf, null, locker, aclResolver, groupMembershipResolver); + mailboxManager.init(); + } + + @After + public void tearDown() { + MailboxSession session = mailboxManager.createSystemSession("test", LoggerFactory.getLogger(JCRStressTest.class)); + session.close(); + repository.shutdown(); + new File(JACKRABBIT_HOME).delete(); + } + + @Override + protected MailboxManager getMailboxManager() { + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRSubscriptionManagerTest.java b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRSubscriptionManagerTest.java new file mode 100644 index 0000000..d0bfb9d --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/java/org/apache/james/mailbox/jcr/JCRSubscriptionManagerTest.java @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.james.mailbox.AbstractSubscriptionManagerTest; +import org.apache.james.mailbox.SubscriptionManager; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.xml.sax.InputSource; + +import javax.jcr.RepositoryException; +import java.io.File; + +public class JCRSubscriptionManagerTest extends AbstractSubscriptionManagerTest { + private static final String JACKRABBIT_HOME = "target/jackrabbit"; + + public static final String META_DATA_DIRECTORY = "target/user-meta-data"; + + private static RepositoryImpl repository; + private static String user = "user"; + private static String pass = "pass"; + private static String workspace = null; + + @BeforeClass + public static void before() throws RepositoryException { + RepositoryConfig config = RepositoryConfig.create(new InputSource(JCRMailboxManagerTest.class.getClassLoader().getResourceAsStream("test-repository.xml")), JACKRABBIT_HOME); + repository = RepositoryImpl.create(config); + + // Register imap cnd file + JCRUtils.registerCnd(repository, workspace, user, pass); + } + + @AfterClass + public static void after() { + if (repository != null) { + repository.shutdown(); + } + new File(JACKRABBIT_HOME).delete(); + } + + @Override + public SubscriptionManager createSubscriptionManager() { + + new File(JACKRABBIT_HOME).delete(); + MailboxSessionJCRRepository sessionRepos = new GlobalMailboxSessionJCRRepository(repository, workspace, user, pass); + JCRMailboxSessionMapperFactory mf = new JCRMailboxSessionMapperFactory(sessionRepos, null, null); + JCRSubscriptionManager sm = new JCRSubscriptionManager(mf); + return sm; + } + +} diff --git a/james/apache-james-mailbox/jcr/src/test/resources/.svn/all-wcprops b/james/apache-james-mailbox/jcr/src/test/resources/.svn/all-wcprops new file mode 100644 index 0000000..0f58fc7 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/resources/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1056743/james/mailbox/trunk/jcr/src/test/resources +END +test-repository.xml +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1056743/james/mailbox/trunk/jcr/src/test/resources/test-repository.xml +END diff --git a/james/apache-james-mailbox/jcr/src/test/resources/.svn/entries b/james/apache-james-mailbox/jcr/src/test/resources/.svn/entries new file mode 100644 index 0000000..70a7159 --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/resources/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jcr/src/test/resources +http://svn.apache.org/repos/asf + + + +2011-01-08T17:26:31.336887Z +1056743 +norman + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test-repository.xml +file + + + + +2013-09-02T02:54:37.000000Z +96ee38d67ba47b54775060805559e4d7 +2011-01-08T17:26:31.336887Z +1056743 +norman + + + + + + + + + + + + + + + + + + + + + +3893 + diff --git a/james/apache-james-mailbox/jcr/src/test/resources/.svn/text-base/test-repository.xml.svn-base b/james/apache-james-mailbox/jcr/src/test/resources/.svn/text-base/test-repository.xml.svn-base new file mode 100644 index 0000000..8258dcd --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/resources/.svn/text-base/test-repository.xml.svn-base @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jcr/src/test/resources/test-repository.xml b/james/apache-james-mailbox/jcr/src/test/resources/test-repository.xml new file mode 100644 index 0000000..8258dcd --- /dev/null +++ b/james/apache-james-mailbox/jcr/src/test/resources/test-repository.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jpa/.svn/all-wcprops b/james/apache-james-mailbox/jpa/.svn/all-wcprops new file mode 100644 index 0000000..0dc49bc --- /dev/null +++ b/james/apache-james-mailbox/jpa/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 51 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 59 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/jpa/pom.xml +END diff --git a/james/apache-james-mailbox/jpa/.svn/dir-prop-base b/james/apache-james-mailbox/jpa/.svn/dir-prop-base new file mode 100644 index 0000000..258992c --- /dev/null +++ b/james/apache-james-mailbox/jpa/.svn/dir-prop-base @@ -0,0 +1,10 @@ +K 10 +svn:ignore +V 34 +.* +*.log +target +stage +coverage.ec + +END diff --git a/james/apache-james-mailbox/jpa/.svn/entries b/james/apache-james-mailbox/jpa/.svn/entries new file mode 100644 index 0000000..64ab106 --- /dev/null +++ b/james/apache-james-mailbox/jpa/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:40.000000Z +3dd4967bbd2ace78609e2696caf33161 +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +5456 + diff --git a/james/apache-james-mailbox/jpa/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/jpa/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..686b268 --- /dev/null +++ b/james/apache-james-mailbox/jpa/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,129 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-jpa + Apache James :: Mailbox :: JPA + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.openjpa + openjpa + + + org.apache.geronimo.specs + geronimo-jpa_2.0_spec + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.jasypt + jasypt + + + junit + junit + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + com.h2database + h2 + test + + + + org.slf4j + slf4j-api + test + + + org.slf4j + slf4j-simple + test + + + + + + org.codehaus.mojo + openjpa-maven-plugin + 1.1 + + org/apache/james/mailbox/jpa/*/model/**/*.class + org/apache/james/mailbox/jpa/mail/model/openjpa/EncryptDecryptHelper.class + true + true + + + log + TOOL=TRACE + + + metaDataFactory + + jpa(Types=org.apache.james.mailbox.jpa.mail.model.JPAUserFlag;org.apache.james.mailbox.jpa.mail.model.JPAMailbox;org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage;org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMessage;org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage;org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage;org.apache.james.mailbox.jpa.mail.model.JPAProperty;org.apache.james.mailbox.jpa.user.model.JPASubscription) + + + + + + + enhancer + process-classes + + enhance + + + + + + + diff --git a/james/apache-james-mailbox/jpa/pom.xml b/james/apache-james-mailbox/jpa/pom.xml new file mode 100644 index 0000000..686b268 --- /dev/null +++ b/james/apache-james-mailbox/jpa/pom.xml @@ -0,0 +1,129 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-jpa + Apache James :: Mailbox :: JPA + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.openjpa + openjpa + + + org.apache.geronimo.specs + geronimo-jpa_2.0_spec + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.jasypt + jasypt + + + junit + junit + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + com.h2database + h2 + test + + + + org.slf4j + slf4j-api + test + + + org.slf4j + slf4j-simple + test + + + + + + org.codehaus.mojo + openjpa-maven-plugin + 1.1 + + org/apache/james/mailbox/jpa/*/model/**/*.class + org/apache/james/mailbox/jpa/mail/model/openjpa/EncryptDecryptHelper.class + true + true + + + log + TOOL=TRACE + + + metaDataFactory + + jpa(Types=org.apache.james.mailbox.jpa.mail.model.JPAUserFlag;org.apache.james.mailbox.jpa.mail.model.JPAMailbox;org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage;org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMessage;org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage;org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage;org.apache.james.mailbox.jpa.mail.model.JPAProperty;org.apache.james.mailbox.jpa.user.model.JPASubscription) + + + + + + + enhancer + process-classes + + enhance + + + + + + + diff --git a/james/apache-james-mailbox/jpa/src/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/.svn/all-wcprops new file mode 100644 index 0000000..935e471 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 55 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src +END diff --git a/james/apache-james-mailbox/jpa/src/.svn/entries b/james/apache-james-mailbox/jpa/src/.svn/entries new file mode 100644 index 0000000..e094409 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/.svn/all-wcprops new file mode 100644 index 0000000..efa81d7 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main +END diff --git a/james/apache-james-mailbox/jpa/src/main/.svn/entries b/james/apache-james-mailbox/jpa/src/main/.svn/entries new file mode 100644 index 0000000..7ab30ce --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..24921db --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 65 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/.svn/entries new file mode 100644 index 0000000..ad31499 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..76cc007 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/.svn/entries new file mode 100644 index 0000000..0c5e8f8 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..71461d8 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 76 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..e7de1e3 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..d6cf0c5 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..11a0e8e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..90f3a4e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..2909415 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +jpa +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops new file mode 100644 index 0000000..bb0c04a --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa +END +JPAMailboxManager.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxManager.java +END +JPATransactionalMapper.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/JPATransactionalMapper.java +END +JPAMailboxSessionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1139535/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxSessionMapperFactory.java +END +JPAMessageManager.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java +END +JPASubscriptionManager.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1179568/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/JPASubscriptionManager.java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/entries new file mode 100644 index 0000000..3dfa6f7 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/entries @@ -0,0 +1,207 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JPAMailboxManager.java +file + + + + +2013-09-02T02:54:40.000000Z +233cafc9eb2e993857e61c3aa4b57396 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3341 + +mail +dir + +JPATransactionalMapper.java +file + + + + +2013-09-02T02:54:40.000000Z +08ac293bac03247ef3c3b6c305546126 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +3663 + +JPAMailboxSessionMapperFactory.java +file + + + + +2013-09-02T02:54:40.000000Z +947cabe1937903859839e94358539e64 +2011-06-25T12:29:24.868051Z +1139535 +norman + + + + + + + + + + + + + + + + + + + + + +3321 + +JPAMessageManager.java +file + + + + +2013-09-02T02:54:40.000000Z +1eb977dfeeae8d7480ceb07f94f36f84 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3441 + +JPASubscriptionManager.java +file + + + + +2013-09-02T02:54:40.000000Z +12502d5cd6e92b44689739c0241a5672 +2011-10-06T10:58:30.993328Z +1179568 +felixk + + + + + + + + + + + + + + + + + + + + + +2152 + +openjpa +dir + +user +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManager.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManager.java.svn-base new file mode 100644 index 0000000..6ecb0f8 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManager.java.svn-base @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.JPAMailboxMapper; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * JPA implementation of {@link StoreMailboxManager} + */ +public abstract class JPAMailboxManager extends StoreMailboxManager { + + public JPAMailboxManager(JPAMailboxSessionMapperFactory mailboxSessionMapperFactory, + final Authenticator authenticator, final MailboxPathLocker locker, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mailboxSessionMapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + } + + @Override + protected Mailbox doCreateMailbox(MailboxPath path, MailboxSession session) throws MailboxException { + return new JPAMailbox(path, randomUidValidity()); + } + + /** + * Delete all mailboxes + * + * @param mailboxSession + * @throws MailboxException + */ + public void deleteEverything(MailboxSession mailboxSession) throws MailboxException { + final JPAMailboxMapper mapper = (JPAMailboxMapper) getMapperFactory().getMailboxMapper(mailboxSession); + mapper.execute(new TransactionalMapper.VoidTransaction() { + public void runVoid() throws MailboxException { + mapper.deleteAllMemberships(); + } + }); + mapper.execute(new TransactionalMapper.VoidTransaction() { + public void runVoid() throws MailboxException { + mapper.deleteAllMailboxes(); + } + }); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxSessionMapperFactory.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxSessionMapperFactory.java.svn-base new file mode 100644 index 0000000..0c84de3 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxSessionMapperFactory.java.svn-base @@ -0,0 +1,76 @@ +/**************************************************************** + * 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 javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.jpa.mail.JPAMailboxMapper; +import org.apache.james.mailbox.jpa.mail.JPAMessageMapper; +import org.apache.james.mailbox.jpa.user.JPASubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * JPA implementation of {@link MailboxSessionMapperFactory} + * + */ +public class JPAMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private final EntityManagerFactory entityManagerFactory; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + public JPAMailboxSessionMapperFactory(EntityManagerFactory entityManagerFactory, UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this.entityManagerFactory = entityManagerFactory; + this.uidProvider = uidProvider; + this.modSeqProvider = modSeqProvider; + createEntityManager().close(); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) { + return new JPAMailboxMapper(entityManagerFactory); + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) { + return new JPAMessageMapper(session, uidProvider, modSeqProvider, entityManagerFactory); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) { + return new JPASubscriptionMapper(entityManagerFactory); + } + + /** + * Return a new {@link EntityManager} instance + * + * @return manager + */ + private EntityManager createEntityManager() { + return entityManagerFactory.createEntityManager(); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMessageManager.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMessageManager.java.svn-base new file mode 100644 index 0000000..a7fa3ad --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMessageManager.java.svn-base @@ -0,0 +1,69 @@ +/**************************************************************** + * 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 java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.search.MessageSearchIndex; + +/** + * Abstract base class which should be used from JPA implementations. + */ +public class JPAMessageManager extends StoreMessageManager { + + public JPAMessageManager(MailboxSessionMapperFactory mapperFactory, final MessageSearchIndex index, final MailboxEventDispatcher dispatcher, final MailboxPathLocker locker, final Mailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + } + + @Override + protected Message createMessage(Date internalDate, final int size, int bodyStartOctet, final SharedInputStream content, + final Flags flags, PropertyBuilder propertyBuilder) throws MailboxException{ + + final Message message = new JPAMessage((JPAMailbox) getMailboxEntity(), internalDate, size, flags, content, bodyStartOctet, propertyBuilder); + return message; + } + + + /** + * Support user flags + */ + @Override + protected Flags getPermanentFlags(MailboxSession session) { + Flags flags = super.getPermanentFlags(session); + flags.add(Flags.Flag.USER); + return flags; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManager.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManager.java.svn-base new file mode 100644 index 0000000..c02aed0 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManager.java.svn-base @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.StoreSubscriptionManager; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * JPA implementation of {@link StoreSubscriptionManager} + * + */ +public class JPASubscriptionManager extends StoreSubscriptionManager { + + public JPASubscriptionManager(final JPAMailboxSessionMapperFactory mapperFactory) { + super(mapperFactory); + } + + /** + * @see org.apache.james.mailbox.store.StoreSubscriptionManager#createSubscription(org.apache.james.mailbox.MailboxSession, java.lang.String) + */ + protected Subscription createSubscription(final MailboxSession session, final String mailbox) { + final Subscription newSubscription = new JPASubscription(session.getUser().getUserName(), mailbox); + return newSubscription; + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPATransactionalMapper.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPATransactionalMapper.java.svn-base new file mode 100644 index 0000000..6dc0785 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/.svn/text-base/JPATransactionalMapper.java.svn-base @@ -0,0 +1,99 @@ +/**************************************************************** + * 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 javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * JPA implementation of TransactionMapper. This class is not thread-safe! + * + */ +public abstract class JPATransactionalMapper extends TransactionalMapper { + + protected EntityManagerFactory entityManagerFactory; + protected EntityManager entityManager; + + public JPATransactionalMapper(final EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + /** + * Return the currently used {@link EntityManager} or a new one if none exists. + * + * @return entitymanger + */ + public EntityManager getEntityManager() { + if (entityManager != null) + return entityManager; + entityManager = entityManagerFactory.createEntityManager(); + return entityManager; + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin() + */ + protected void begin() throws MailboxException { + try { + getEntityManager().getTransaction().begin(); + } catch (PersistenceException e) { + throw new MailboxException("Begin of transaction failed", e); + } + } + + /** + * Commit the Transaction and close the EntityManager + */ + protected void commit() throws MailboxException { + try { + getEntityManager().getTransaction().commit(); + } catch (PersistenceException e) { + throw new MailboxException("Commit of transaction failed",e); + } + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback() + */ + protected void rollback() throws MailboxException { + EntityTransaction transaction = entityManager.getTransaction(); + // check if we have a transaction to rollback + if (transaction.isActive()) { + getEntityManager().getTransaction().rollback(); + } + } + + /** + * Close open {@link EntityManager} + */ + public void endRequest() { + if (entityManager != null) { + if (entityManager.isOpen()) + entityManager.close(); + entityManager = null; + } + } + + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxManager.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxManager.java new file mode 100644 index 0000000..6ecb0f8 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxManager.java @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.JPAMailboxMapper; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * JPA implementation of {@link StoreMailboxManager} + */ +public abstract class JPAMailboxManager extends StoreMailboxManager { + + public JPAMailboxManager(JPAMailboxSessionMapperFactory mailboxSessionMapperFactory, + final Authenticator authenticator, final MailboxPathLocker locker, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mailboxSessionMapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + } + + @Override + protected Mailbox doCreateMailbox(MailboxPath path, MailboxSession session) throws MailboxException { + return new JPAMailbox(path, randomUidValidity()); + } + + /** + * Delete all mailboxes + * + * @param mailboxSession + * @throws MailboxException + */ + public void deleteEverything(MailboxSession mailboxSession) throws MailboxException { + final JPAMailboxMapper mapper = (JPAMailboxMapper) getMapperFactory().getMailboxMapper(mailboxSession); + mapper.execute(new TransactionalMapper.VoidTransaction() { + public void runVoid() throws MailboxException { + mapper.deleteAllMemberships(); + } + }); + mapper.execute(new TransactionalMapper.VoidTransaction() { + public void runVoid() throws MailboxException { + mapper.deleteAllMailboxes(); + } + }); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxSessionMapperFactory.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxSessionMapperFactory.java new file mode 100644 index 0000000..0c84de3 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMailboxSessionMapperFactory.java @@ -0,0 +1,76 @@ +/**************************************************************** + * 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 javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.jpa.mail.JPAMailboxMapper; +import org.apache.james.mailbox.jpa.mail.JPAMessageMapper; +import org.apache.james.mailbox.jpa.user.JPASubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +/** + * JPA implementation of {@link MailboxSessionMapperFactory} + * + */ +public class JPAMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private final EntityManagerFactory entityManagerFactory; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + public JPAMailboxSessionMapperFactory(EntityManagerFactory entityManagerFactory, UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this.entityManagerFactory = entityManagerFactory; + this.uidProvider = uidProvider; + this.modSeqProvider = modSeqProvider; + createEntityManager().close(); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) { + return new JPAMailboxMapper(entityManagerFactory); + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) { + return new JPAMessageMapper(session, uidProvider, modSeqProvider, entityManagerFactory); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) { + return new JPASubscriptionMapper(entityManagerFactory); + } + + /** + * Return a new {@link EntityManager} instance + * + * @return manager + */ + private EntityManager createEntityManager() { + return entityManagerFactory.createEntityManager(); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java new file mode 100644 index 0000000..a7fa3ad --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPAMessageManager.java @@ -0,0 +1,69 @@ +/**************************************************************** + * 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 java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.store.MailboxEventDispatcher; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.search.MessageSearchIndex; + +/** + * Abstract base class which should be used from JPA implementations. + */ +public class JPAMessageManager extends StoreMessageManager { + + public JPAMessageManager(MailboxSessionMapperFactory mapperFactory, final MessageSearchIndex index, final MailboxEventDispatcher dispatcher, final MailboxPathLocker locker, final Mailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + } + + @Override + protected Message createMessage(Date internalDate, final int size, int bodyStartOctet, final SharedInputStream content, + final Flags flags, PropertyBuilder propertyBuilder) throws MailboxException{ + + final Message message = new JPAMessage((JPAMailbox) getMailboxEntity(), internalDate, size, flags, content, bodyStartOctet, propertyBuilder); + return message; + } + + + /** + * Support user flags + */ + @Override + protected Flags getPermanentFlags(MailboxSession session) { + Flags flags = super.getPermanentFlags(session); + flags.add(Flags.Flag.USER); + return flags; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPASubscriptionManager.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPASubscriptionManager.java new file mode 100644 index 0000000..c02aed0 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPASubscriptionManager.java @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.StoreSubscriptionManager; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * JPA implementation of {@link StoreSubscriptionManager} + * + */ +public class JPASubscriptionManager extends StoreSubscriptionManager { + + public JPASubscriptionManager(final JPAMailboxSessionMapperFactory mapperFactory) { + super(mapperFactory); + } + + /** + * @see org.apache.james.mailbox.store.StoreSubscriptionManager#createSubscription(org.apache.james.mailbox.MailboxSession, java.lang.String) + */ + protected Subscription createSubscription(final MailboxSession session, final String mailbox) { + final Subscription newSubscription = new JPASubscription(session.getUser().getUserName(), mailbox); + return newSubscription; + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPATransactionalMapper.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPATransactionalMapper.java new file mode 100644 index 0000000..6dc0785 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/JPATransactionalMapper.java @@ -0,0 +1,99 @@ +/**************************************************************** + * 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 javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * JPA implementation of TransactionMapper. This class is not thread-safe! + * + */ +public abstract class JPATransactionalMapper extends TransactionalMapper { + + protected EntityManagerFactory entityManagerFactory; + protected EntityManager entityManager; + + public JPATransactionalMapper(final EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + /** + * Return the currently used {@link EntityManager} or a new one if none exists. + * + * @return entitymanger + */ + public EntityManager getEntityManager() { + if (entityManager != null) + return entityManager; + entityManager = entityManagerFactory.createEntityManager(); + return entityManager; + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin() + */ + protected void begin() throws MailboxException { + try { + getEntityManager().getTransaction().begin(); + } catch (PersistenceException e) { + throw new MailboxException("Begin of transaction failed", e); + } + } + + /** + * Commit the Transaction and close the EntityManager + */ + protected void commit() throws MailboxException { + try { + getEntityManager().getTransaction().commit(); + } catch (PersistenceException e) { + throw new MailboxException("Commit of transaction failed",e); + } + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback() + */ + protected void rollback() throws MailboxException { + EntityTransaction transaction = entityManager.getTransaction(); + // check if we have a transaction to rollback + if (transaction.isActive()) { + getEntityManager().getTransaction().rollback(); + } + } + + /** + * Close open {@link EntityManager} + */ + public void endRequest() { + if (entityManager != null) { + if (entityManager.isOpen()) + entityManager.close(); + entityManager = null; + } + } + + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/all-wcprops new file mode 100644 index 0000000..ca4655f --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 99 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail +END +JPAMessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMessageMapper.java +END +JPAUidProvider.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAUidProvider.java +END +JPAMailboxMapper.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMailboxMapper.java +END +JPAModSeqProvider.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAModSeqProvider.java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/entries new file mode 100644 index 0000000..aa258de --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/entries @@ -0,0 +1,167 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + +JPAMessageMapper.java +file + + + + +2013-09-02T02:54:40.000000Z +2716d003bce20804906eee584ce45ef2 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +18045 + +JPAUidProvider.java +file + + + + +2013-09-02T02:54:40.000000Z +62d1f5c23c794760e1471d9cb2876c57 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3744 + +JPAMailboxMapper.java +file + + + + +2013-09-02T02:54:40.000000Z +c0361224b6ff5f925a8301b6b56f61b0 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +8189 + +JPAModSeqProvider.java +file + + + + +2013-09-02T02:54:40.000000Z +c8e51bd73747228c3762019186963c8d +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3803 + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMailboxMapper.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMailboxMapper.java.svn-base new file mode 100644 index 0000000..28430d1 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMailboxMapper.java.svn-base @@ -0,0 +1,170 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mailbox.jpa.mail; + +import java.util.List; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManagerFactory; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceException; +import javax.persistence.RollbackException; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.jpa.JPATransactionalMapper; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Data access management for mailbox. + */ +public class JPAMailboxMapper extends JPATransactionalMapper implements MailboxMapper { + + private static final char SQL_WILDCARD_CHAR = '%'; + private String lastMailboxName; + + public JPAMailboxMapper(EntityManagerFactory entityManagerFactory) { + super(entityManagerFactory); + } + + /** + * Commit the transaction. If the commit fails due a conflict in a unique key constraint a {@link MailboxExistsException} + * will get thrown + */ + @Override + protected void commit() throws MailboxException { + try { + getEntityManager().getTransaction().commit(); + } catch (PersistenceException e) { + if (e instanceof EntityExistsException) + throw new MailboxExistsException(lastMailboxName); + if (e instanceof RollbackException) { + Throwable t = e.getCause(); + if (t != null && t instanceof EntityExistsException) + throw new MailboxExistsException(lastMailboxName); + } + throw new MailboxException("Commit of transaction failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#save(Mailbox) + */ + public void save(Mailbox mailbox) throws MailboxException { + try { + this.lastMailboxName = mailbox.getName(); + getEntityManager().persist(mailbox); + } catch (PersistenceException e) { + throw new MailboxException("Save of mailbox " + mailbox.getName() +" failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(MailboxPath) + */ + public Mailbox findMailboxByPath(MailboxPath mailboxPath) throws MailboxException, MailboxNotFoundException { + try { + if (mailboxPath.getUser() == null) { + return (JPAMailbox) getEntityManager().createNamedQuery("findMailboxByName").setParameter("nameParam", mailboxPath.getName()).setParameter("namespaceParam", mailboxPath.getNamespace()).getSingleResult(); + } else { + return (JPAMailbox) getEntityManager().createNamedQuery("findMailboxByNameWithUser").setParameter("nameParam", mailboxPath.getName()).setParameter("namespaceParam", mailboxPath.getNamespace()).setParameter("userParam", mailboxPath.getUser()).getSingleResult(); + } + } catch (NoResultException e) { + throw new MailboxNotFoundException(mailboxPath); + } catch (PersistenceException e) { + throw new MailboxException("Search of mailbox " + mailboxPath + " failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#delete(Mailbox) + */ + public void delete(Mailbox mailbox) throws MailboxException { + try { + getEntityManager().createNamedQuery("deleteMessages").setParameter("idParam", mailbox.getMailboxId()).executeUpdate(); + getEntityManager().remove(mailbox); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailbox " + mailbox + " failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(MailboxPath) + */ + @SuppressWarnings("unchecked") + public List> findMailboxWithPathLike(MailboxPath path) throws MailboxException { + try { + if (path.getUser() == null) { + return getEntityManager().createNamedQuery("findMailboxWithNameLike").setParameter("nameParam", SQL_WILDCARD_CHAR + path.getName() + SQL_WILDCARD_CHAR).setParameter("namespaceParam", path.getNamespace()).getResultList(); + } else { + return getEntityManager().createNamedQuery("findMailboxWithNameLikeWithUser").setParameter("nameParam", SQL_WILDCARD_CHAR + path.getName() + SQL_WILDCARD_CHAR).setParameter("namespaceParam", path.getNamespace()).setParameter("userParam", path.getUser()).getResultList(); + } + } catch (PersistenceException e) { + throw new MailboxException("Search of mailbox " + path + " failed", e); + } + } + + public void deleteAllMemberships() throws MailboxException { + try { + getEntityManager().createNamedQuery("deleteAllMemberships").executeUpdate(); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailboxes failed", e); + } + } + + public void deleteAllMailboxes() throws MailboxException { + try { + getEntityManager().createNamedQuery("deleteAllMailboxes").executeUpdate(); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailboxes failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(Mailbox, char) + */ + public boolean hasChildren(Mailbox mailbox, char delimiter) throws MailboxException, + MailboxNotFoundException { + final String name = mailbox.getName() + delimiter + SQL_WILDCARD_CHAR; + final Long numberOfChildMailboxes; + if (mailbox.getUser() == null) { + numberOfChildMailboxes = (Long) getEntityManager().createNamedQuery("countMailboxesWithNameLike").setParameter("nameParam", name).setParameter("namespaceParam", mailbox.getNamespace()).getSingleResult(); + } else { + numberOfChildMailboxes = (Long) getEntityManager().createNamedQuery("countMailboxesWithNameLikeWithUser").setParameter("nameParam", name).setParameter("namespaceParam", mailbox.getNamespace()).setParameter("userParam", mailbox.getUser()).getSingleResult(); + } + return numberOfChildMailboxes != null && numberOfChildMailboxes > 0; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + @SuppressWarnings("unchecked") + public List> list() throws MailboxException{ + try { + return getEntityManager().createNamedQuery("listMailboxes").getResultList(); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailboxes failed", e); + } + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMessageMapper.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMessageMapper.java.svn-base new file mode 100644 index 0000000..26e280e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAMessageMapper.java.svn-base @@ -0,0 +1,413 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.PersistenceException; +import javax.persistence.Query; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.openjpa.persistence.ArgumentException; + +/** + * JPA implementation of a {@link MessageMapper}. This class is not thread-safe! + */ +public class JPAMessageMapper extends AbstractMessageMapper implements MessageMapper { + protected EntityManagerFactory entityManagerFactory; + protected EntityManager entityManager; + + public JPAMessageMapper(final MailboxSession session, final UidProvider uidProvider, + ModSeqProvider modSeqProvider, final EntityManagerFactory entityManagerFactory) { + super(session, uidProvider, modSeqProvider); + this.entityManagerFactory = entityManagerFactory; + } + + /** + * Return the currently used {@link EntityManager} or a new one if none + * exists. + * + * @return entitymanger + */ + public EntityManager getEntityManager() { + if (entityManager != null) + return entityManager; + entityManager = entityManagerFactory.createEntityManager(); + return entityManager; + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin() + */ + protected void begin() throws MailboxException { + try { + getEntityManager().getTransaction().begin(); + } catch (PersistenceException e) { + throw new MailboxException("Begin of transaction failed", e); + } + } + + /** + * Commit the Transaction and close the EntityManager + */ + protected void commit() throws MailboxException { + try { + getEntityManager().getTransaction().commit(); + } catch (PersistenceException e) { + throw new MailboxException("Commit of transaction failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback() + */ + protected void rollback() throws MailboxException { + EntityTransaction transaction = entityManager.getTransaction(); + // check if we have a transaction to rollback + if (transaction.isActive()) { + getEntityManager().getTransaction().rollback(); + } + } + + /** + * Close open {@link EntityManager} + */ + public void endRequest() { + if (entityManager != null) { + if (entityManager.isOpen()) + entityManager.close(); + entityManager = null; + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) + throws MailboxException { + try { + List> results; + long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, max); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, max); + break; + case ONE: + results = findMessagesInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max); + break; + } + + return results.iterator(); + + } catch (PersistenceException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(Mailbox) + */ + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + try { + return (Long) getEntityManager().createNamedQuery("countMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + } catch (PersistenceException e) { + throw new MailboxException("Count of messages failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(Mailbox) + */ + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + try { + return (Long) getEntityManager().createNamedQuery("countUnseenMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + } catch (PersistenceException e) { + throw new MailboxException("Count of useen messages failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + public void delete(Mailbox mailbox, Message message) throws MailboxException { + try { + getEntityManager().remove(message); + } catch (PersistenceException e) { + throw new MailboxException("Delete of message " + message + " failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(Mailbox) + */ + @SuppressWarnings("unchecked") + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + try { + Query query = getEntityManager().createNamedQuery("findUnseenMessagesInMailboxOrderByUid").setParameter( + "idParam", mailbox.getMailboxId()); + query.setMaxResults(1); + List> result = query.getResultList(); + if (result.isEmpty()) { + return null; + } else { + return result.get(0).getUid(); + } + } catch (PersistenceException e) { + throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(Mailbox) + */ + @SuppressWarnings("unchecked") + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + try { + Query query = getEntityManager().createNamedQuery("findRecentMessageUidsInMailbox").setParameter("idParam", + mailbox.getMailboxId()); + return query.getResultList(); + } catch (PersistenceException e) { + throw new MailboxException("Search of recent messages failed in mailbox " + mailbox, e); + } + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + try { + final Map data; + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + + switch (set.getType()) { + case ONE: + results = findDeletedMessagesInMailboxWithUID(mailbox, from); + data = createMetaData(results); + deleteDeletedMessagesInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + data = createMetaData(results); + deleteDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + break; + case FROM: + results = findDeletedMessagesInMailboxAfterUID(mailbox, from); + data = createMetaData(results); + deleteDeletedMessagesInMailboxAfterUID(mailbox, from); + break; + default: + case ALL: + results = findDeletedMessagesInMailbox(mailbox); + data = createMetaData(results); + deleteDeletedMessagesInMailbox(mailbox); + break; + } + + return data; + } catch (PersistenceException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + /** + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(Mailbox, + * long, long, Message) + */ + protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) + throws MailboxException { + Message copy; + if (original instanceof JPAStreamingMessage) { + copy = new JPAStreamingMessage((JPAMailbox) mailbox, uid, modSeq, original); + } else if (original instanceof JPAEncryptedMessage) { + copy = new JPAEncryptedMessage((JPAMailbox) mailbox, uid, modSeq, original); + } else { + copy = new JPAMessage((JPAMailbox) mailbox, uid, modSeq, original); + } + return save(mailbox, copy); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(Mailbox, + * Message) + */ + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + + try { + + // We need to reload a "JPA attached" mailbox, because the provide + // mailbox is already "JPA detached" + // If we don't this, we will get an + // org.apache.openjpa.persistence.ArgumentException. + ((AbstractJPAMessage) message) + .setMailbox(getEntityManager().find(JPAMailbox.class, mailbox.getMailboxId())); + + getEntityManager().persist(message); + return new SimpleMessageMetaData(message); + } catch (PersistenceException e) { + throw new MailboxException("Save of message " + message + " failed in mailbox " + mailbox, e); + } catch (ArgumentException e) { + throw new MailboxException("Save of message " + message + " failed in mailbox " + mailbox, e); + } + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailboxAfterUID(Mailbox mailbox, long uid, int batchSize) { + Query query = getEntityManager().createNamedQuery("findMessagesInMailboxAfterUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid); + + if (batchSize > 0) + query.setMaxResults(batchSize); + + return query.getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailboxWithUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("findMessagesInMailboxWithUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).setMaxResults(1) + .getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to, + int batchSize) { + Query query = getEntityManager().createNamedQuery("findMessagesInMailboxBetweenUIDs") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("fromParam", from) + .setParameter("toParam", to); + + if (batchSize > 0) + query.setMaxResults(batchSize); + + return query.getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailbox(Mailbox mailbox, int batchSize) { + Query query = getEntityManager().createNamedQuery("findMessagesInMailbox").setParameter("idParam", + mailbox.getMailboxId()); + if (batchSize > 0) + query.setMaxResults(batchSize); + return query.getResultList(); + } + + private Map createMetaData(List> uids) { + final Map data = new HashMap(); + for (int i = 0; i < uids.size(); i++) { + Message m = uids.get(i); + data.put(m.getUid(), new SimpleMessageMetaData(m)); + } + return data; + } + + private int deleteDeletedMessagesInMailbox(Mailbox mailbox) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).executeUpdate(); + } + + private int deleteDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxAfterUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).executeUpdate(); + } + + private int deleteDeletedMessagesInMailboxWithUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxWithUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).executeUpdate(); + } + + private int deleteDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxBetweenUIDs") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("fromParam", from) + .setParameter("toParam", to).executeUpdate(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailbox(Mailbox mailbox) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxAfterUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailboxWithUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxWithUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).setMaxResults(1) + .getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxBetweenUIDs") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("fromParam", from) + .setParameter("toParam", to).getResultList(); + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAModSeqProvider.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAModSeqProvider.java.svn-base new file mode 100644 index 0000000..0368dde --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAModSeqProvider.java.svn-base @@ -0,0 +1,85 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JPAModSeqProvider extends AbstractLockingModSeqProvider{ + + private EntityManagerFactory factory; + + public JPAModSeqProvider(MailboxPathLocker locker, EntityManagerFactory factory) { + super(locker); + this.factory = factory; + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + long highest = (Long) manager.createNamedQuery("findHighestModSeq").setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + manager.getTransaction().commit(); + return highest; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to get highest mod-sequence for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + + @Override + protected long lockedNextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + JPAMailbox m = manager.find(JPAMailbox.class, mailbox.getMailboxId()); + long modSeq = m.consumeModSeq(); + manager.persist(m); + manager.getTransaction().commit(); + return modSeq; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to save highest mod-sequence for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAUidProvider.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAUidProvider.java.svn-base new file mode 100644 index 0000000..27f5474 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/.svn/text-base/JPAUidProvider.java.svn-base @@ -0,0 +1,86 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingUidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JPAUidProvider extends AbstractLockingUidProvider{ + + private EntityManagerFactory factory; + + public JPAUidProvider(MailboxPathLocker locker, EntityManagerFactory factory) { + super(locker); + this.factory = factory; + } + + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + long uid = (Long) manager.createNamedQuery("findLastUid").setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + manager.getTransaction().commit(); + return uid; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to get last uid for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + + @Override + protected long lockedNextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + JPAMailbox m = manager.find(JPAMailbox.class, mailbox.getMailboxId()); + long uid = m.consumeUid(); + manager.persist(m); + manager.getTransaction().commit(); + return uid; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to save next uid for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMailboxMapper.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMailboxMapper.java new file mode 100644 index 0000000..28430d1 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMailboxMapper.java @@ -0,0 +1,170 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mailbox.jpa.mail; + +import java.util.List; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManagerFactory; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceException; +import javax.persistence.RollbackException; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.jpa.JPATransactionalMapper; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Data access management for mailbox. + */ +public class JPAMailboxMapper extends JPATransactionalMapper implements MailboxMapper { + + private static final char SQL_WILDCARD_CHAR = '%'; + private String lastMailboxName; + + public JPAMailboxMapper(EntityManagerFactory entityManagerFactory) { + super(entityManagerFactory); + } + + /** + * Commit the transaction. If the commit fails due a conflict in a unique key constraint a {@link MailboxExistsException} + * will get thrown + */ + @Override + protected void commit() throws MailboxException { + try { + getEntityManager().getTransaction().commit(); + } catch (PersistenceException e) { + if (e instanceof EntityExistsException) + throw new MailboxExistsException(lastMailboxName); + if (e instanceof RollbackException) { + Throwable t = e.getCause(); + if (t != null && t instanceof EntityExistsException) + throw new MailboxExistsException(lastMailboxName); + } + throw new MailboxException("Commit of transaction failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#save(Mailbox) + */ + public void save(Mailbox mailbox) throws MailboxException { + try { + this.lastMailboxName = mailbox.getName(); + getEntityManager().persist(mailbox); + } catch (PersistenceException e) { + throw new MailboxException("Save of mailbox " + mailbox.getName() +" failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(MailboxPath) + */ + public Mailbox findMailboxByPath(MailboxPath mailboxPath) throws MailboxException, MailboxNotFoundException { + try { + if (mailboxPath.getUser() == null) { + return (JPAMailbox) getEntityManager().createNamedQuery("findMailboxByName").setParameter("nameParam", mailboxPath.getName()).setParameter("namespaceParam", mailboxPath.getNamespace()).getSingleResult(); + } else { + return (JPAMailbox) getEntityManager().createNamedQuery("findMailboxByNameWithUser").setParameter("nameParam", mailboxPath.getName()).setParameter("namespaceParam", mailboxPath.getNamespace()).setParameter("userParam", mailboxPath.getUser()).getSingleResult(); + } + } catch (NoResultException e) { + throw new MailboxNotFoundException(mailboxPath); + } catch (PersistenceException e) { + throw new MailboxException("Search of mailbox " + mailboxPath + " failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#delete(Mailbox) + */ + public void delete(Mailbox mailbox) throws MailboxException { + try { + getEntityManager().createNamedQuery("deleteMessages").setParameter("idParam", mailbox.getMailboxId()).executeUpdate(); + getEntityManager().remove(mailbox); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailbox " + mailbox + " failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(MailboxPath) + */ + @SuppressWarnings("unchecked") + public List> findMailboxWithPathLike(MailboxPath path) throws MailboxException { + try { + if (path.getUser() == null) { + return getEntityManager().createNamedQuery("findMailboxWithNameLike").setParameter("nameParam", SQL_WILDCARD_CHAR + path.getName() + SQL_WILDCARD_CHAR).setParameter("namespaceParam", path.getNamespace()).getResultList(); + } else { + return getEntityManager().createNamedQuery("findMailboxWithNameLikeWithUser").setParameter("nameParam", SQL_WILDCARD_CHAR + path.getName() + SQL_WILDCARD_CHAR).setParameter("namespaceParam", path.getNamespace()).setParameter("userParam", path.getUser()).getResultList(); + } + } catch (PersistenceException e) { + throw new MailboxException("Search of mailbox " + path + " failed", e); + } + } + + public void deleteAllMemberships() throws MailboxException { + try { + getEntityManager().createNamedQuery("deleteAllMemberships").executeUpdate(); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailboxes failed", e); + } + } + + public void deleteAllMailboxes() throws MailboxException { + try { + getEntityManager().createNamedQuery("deleteAllMailboxes").executeUpdate(); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailboxes failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(Mailbox, char) + */ + public boolean hasChildren(Mailbox mailbox, char delimiter) throws MailboxException, + MailboxNotFoundException { + final String name = mailbox.getName() + delimiter + SQL_WILDCARD_CHAR; + final Long numberOfChildMailboxes; + if (mailbox.getUser() == null) { + numberOfChildMailboxes = (Long) getEntityManager().createNamedQuery("countMailboxesWithNameLike").setParameter("nameParam", name).setParameter("namespaceParam", mailbox.getNamespace()).getSingleResult(); + } else { + numberOfChildMailboxes = (Long) getEntityManager().createNamedQuery("countMailboxesWithNameLikeWithUser").setParameter("nameParam", name).setParameter("namespaceParam", mailbox.getNamespace()).setParameter("userParam", mailbox.getUser()).getSingleResult(); + } + return numberOfChildMailboxes != null && numberOfChildMailboxes > 0; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + @SuppressWarnings("unchecked") + public List> list() throws MailboxException{ + try { + return getEntityManager().createNamedQuery("listMailboxes").getResultList(); + } catch (PersistenceException e) { + throw new MailboxException("Delete of mailboxes failed", e); + } + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMessageMapper.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMessageMapper.java new file mode 100644 index 0000000..26e280e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAMessageMapper.java @@ -0,0 +1,413 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.PersistenceException; +import javax.persistence.Query; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.openjpa.persistence.ArgumentException; + +/** + * JPA implementation of a {@link MessageMapper}. This class is not thread-safe! + */ +public class JPAMessageMapper extends AbstractMessageMapper implements MessageMapper { + protected EntityManagerFactory entityManagerFactory; + protected EntityManager entityManager; + + public JPAMessageMapper(final MailboxSession session, final UidProvider uidProvider, + ModSeqProvider modSeqProvider, final EntityManagerFactory entityManagerFactory) { + super(session, uidProvider, modSeqProvider); + this.entityManagerFactory = entityManagerFactory; + } + + /** + * Return the currently used {@link EntityManager} or a new one if none + * exists. + * + * @return entitymanger + */ + public EntityManager getEntityManager() { + if (entityManager != null) + return entityManager; + entityManager = entityManagerFactory.createEntityManager(); + return entityManager; + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin() + */ + protected void begin() throws MailboxException { + try { + getEntityManager().getTransaction().begin(); + } catch (PersistenceException e) { + throw new MailboxException("Begin of transaction failed", e); + } + } + + /** + * Commit the Transaction and close the EntityManager + */ + protected void commit() throws MailboxException { + try { + getEntityManager().getTransaction().commit(); + } catch (PersistenceException e) { + throw new MailboxException("Commit of transaction failed", e); + } + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback() + */ + protected void rollback() throws MailboxException { + EntityTransaction transaction = entityManager.getTransaction(); + // check if we have a transaction to rollback + if (transaction.isActive()) { + getEntityManager().getTransaction().rollback(); + } + } + + /** + * Close open {@link EntityManager} + */ + public void endRequest() { + if (entityManager != null) { + if (entityManager.isOpen()) + entityManager.close(); + entityManager = null; + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) + throws MailboxException { + try { + List> results; + long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, max); + break; + case FROM: + results = findMessagesInMailboxAfterUID(mailbox, from, max); + break; + case ONE: + results = findMessagesInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, from, to, max); + break; + } + + return results.iterator(); + + } catch (PersistenceException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(Mailbox) + */ + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + try { + return (Long) getEntityManager().createNamedQuery("countMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + } catch (PersistenceException e) { + throw new MailboxException("Count of messages failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(Mailbox) + */ + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + try { + return (Long) getEntityManager().createNamedQuery("countUnseenMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + } catch (PersistenceException e) { + throw new MailboxException("Count of useen messages failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + public void delete(Mailbox mailbox, Message message) throws MailboxException { + try { + getEntityManager().remove(message); + } catch (PersistenceException e) { + throw new MailboxException("Delete of message " + message + " failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(Mailbox) + */ + @SuppressWarnings("unchecked") + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + try { + Query query = getEntityManager().createNamedQuery("findUnseenMessagesInMailboxOrderByUid").setParameter( + "idParam", mailbox.getMailboxId()); + query.setMaxResults(1); + List> result = query.getResultList(); + if (result.isEmpty()) { + return null; + } else { + return result.get(0).getUid(); + } + } catch (PersistenceException e) { + throw new MailboxException("Search of first unseen message failed in mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(Mailbox) + */ + @SuppressWarnings("unchecked") + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + try { + Query query = getEntityManager().createNamedQuery("findRecentMessageUidsInMailbox").setParameter("idParam", + mailbox.getMailboxId()); + return query.getResultList(); + } catch (PersistenceException e) { + throw new MailboxException("Search of recent messages failed in mailbox " + mailbox, e); + } + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + try { + final Map data; + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + + switch (set.getType()) { + case ONE: + results = findDeletedMessagesInMailboxWithUID(mailbox, from); + data = createMetaData(results); + deleteDeletedMessagesInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + data = createMetaData(results); + deleteDeletedMessagesInMailboxBetweenUIDs(mailbox, from, to); + break; + case FROM: + results = findDeletedMessagesInMailboxAfterUID(mailbox, from); + data = createMetaData(results); + deleteDeletedMessagesInMailboxAfterUID(mailbox, from); + break; + default: + case ALL: + results = findDeletedMessagesInMailbox(mailbox); + data = createMetaData(results); + deleteDeletedMessagesInMailbox(mailbox); + break; + } + + return data; + } catch (PersistenceException e) { + throw new MailboxException("Search of MessageRange " + set + " failed in mailbox " + mailbox, e); + } + } + + /** + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(Mailbox, + * long, long, Message) + */ + protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) + throws MailboxException { + Message copy; + if (original instanceof JPAStreamingMessage) { + copy = new JPAStreamingMessage((JPAMailbox) mailbox, uid, modSeq, original); + } else if (original instanceof JPAEncryptedMessage) { + copy = new JPAEncryptedMessage((JPAMailbox) mailbox, uid, modSeq, original); + } else { + copy = new JPAMessage((JPAMailbox) mailbox, uid, modSeq, original); + } + return save(mailbox, copy); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(Mailbox, + * Message) + */ + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + + try { + + // We need to reload a "JPA attached" mailbox, because the provide + // mailbox is already "JPA detached" + // If we don't this, we will get an + // org.apache.openjpa.persistence.ArgumentException. + ((AbstractJPAMessage) message) + .setMailbox(getEntityManager().find(JPAMailbox.class, mailbox.getMailboxId())); + + getEntityManager().persist(message); + return new SimpleMessageMetaData(message); + } catch (PersistenceException e) { + throw new MailboxException("Save of message " + message + " failed in mailbox " + mailbox, e); + } catch (ArgumentException e) { + throw new MailboxException("Save of message " + message + " failed in mailbox " + mailbox, e); + } + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailboxAfterUID(Mailbox mailbox, long uid, int batchSize) { + Query query = getEntityManager().createNamedQuery("findMessagesInMailboxAfterUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid); + + if (batchSize > 0) + query.setMaxResults(batchSize); + + return query.getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailboxWithUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("findMessagesInMailboxWithUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).setMaxResults(1) + .getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to, + int batchSize) { + Query query = getEntityManager().createNamedQuery("findMessagesInMailboxBetweenUIDs") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("fromParam", from) + .setParameter("toParam", to); + + if (batchSize > 0) + query.setMaxResults(batchSize); + + return query.getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findMessagesInMailbox(Mailbox mailbox, int batchSize) { + Query query = getEntityManager().createNamedQuery("findMessagesInMailbox").setParameter("idParam", + mailbox.getMailboxId()); + if (batchSize > 0) + query.setMaxResults(batchSize); + return query.getResultList(); + } + + private Map createMetaData(List> uids) { + final Map data = new HashMap(); + for (int i = 0; i < uids.size(); i++) { + Message m = uids.get(i); + data.put(m.getUid(), new SimpleMessageMetaData(m)); + } + return data; + } + + private int deleteDeletedMessagesInMailbox(Mailbox mailbox) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).executeUpdate(); + } + + private int deleteDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxAfterUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).executeUpdate(); + } + + private int deleteDeletedMessagesInMailboxWithUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxWithUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).executeUpdate(); + } + + private int deleteDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to) { + return getEntityManager().createNamedQuery("deleteDeletedMessagesInMailboxBetweenUIDs") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("fromParam", from) + .setParameter("toParam", to).executeUpdate(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailbox(Mailbox mailbox) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailbox") + .setParameter("idParam", mailbox.getMailboxId()).getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailboxAfterUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxAfterUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailboxWithUID(Mailbox mailbox, long uid) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxWithUID") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("uidParam", uid).setMaxResults(1) + .getResultList(); + } + + @SuppressWarnings("unchecked") + private List> findDeletedMessagesInMailboxBetweenUIDs(Mailbox mailbox, long from, long to) { + return getEntityManager().createNamedQuery("findDeletedMessagesInMailboxBetweenUIDs") + .setParameter("idParam", mailbox.getMailboxId()).setParameter("fromParam", from) + .setParameter("toParam", to).getResultList(); + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAModSeqProvider.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAModSeqProvider.java new file mode 100644 index 0000000..0368dde --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAModSeqProvider.java @@ -0,0 +1,85 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JPAModSeqProvider extends AbstractLockingModSeqProvider{ + + private EntityManagerFactory factory; + + public JPAModSeqProvider(MailboxPathLocker locker, EntityManagerFactory factory) { + super(locker); + this.factory = factory; + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + long highest = (Long) manager.createNamedQuery("findHighestModSeq").setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + manager.getTransaction().commit(); + return highest; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to get highest mod-sequence for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + + @Override + protected long lockedNextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + JPAMailbox m = manager.find(JPAMailbox.class, mailbox.getMailboxId()); + long modSeq = m.consumeModSeq(); + manager.persist(m); + manager.getTransaction().commit(); + return modSeq; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to save highest mod-sequence for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAUidProvider.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAUidProvider.java new file mode 100644 index 0000000..27f5474 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/JPAUidProvider.java @@ -0,0 +1,86 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.AbstractLockingUidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class JPAUidProvider extends AbstractLockingUidProvider{ + + private EntityManagerFactory factory; + + public JPAUidProvider(MailboxPathLocker locker, EntityManagerFactory factory) { + super(locker); + this.factory = factory; + } + + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + long uid = (Long) manager.createNamedQuery("findLastUid").setParameter("idParam", mailbox.getMailboxId()).getSingleResult(); + manager.getTransaction().commit(); + return uid; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to get last uid for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + + @Override + protected long lockedNextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + EntityManager manager = null; + try { + manager = factory.createEntityManager(); + manager.getTransaction().begin(); + JPAMailbox m = manager.find(JPAMailbox.class, mailbox.getMailboxId()); + long uid = m.consumeUid(); + manager.persist(m); + manager.getTransaction().commit(); + return uid; + } catch (PersistenceException e) { + if (manager != null && manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new MailboxException("Unable to save next uid for mailbox " + mailbox, e); + } finally { + if (manager != null) { + manager.close(); + } + } + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/all-wcprops new file mode 100644 index 0000000..6f4bd3f --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 105 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model +END +JPAProperty.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1413343/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAProperty.java +END +JPAUserFlag.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1179568/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAUserFlag.java +END +JPAMailbox.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAMailbox.java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/entries new file mode 100644 index 0000000..558b822 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/entries @@ -0,0 +1,133 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JPAProperty.java +file + + + + +2013-09-02T02:54:40.000000Z +1177c75bdc3d971188e63d5701c313a5 +2012-11-25T12:35:43.766304Z +1413343 +eric + + + + + + + + + + + + + + + + + + + + + +5419 + +JPAUserFlag.java +file + + + + +2013-09-02T02:54:40.000000Z +847bf1e2062b445c058394e01ca7e61a +2011-10-06T10:58:30.993328Z +1179568 +felixk + + + + + + + + + + + + + + + + + + + + + +3484 + +JPAMailbox.java +file + + + + +2013-09-02T02:54:40.000000Z +d762f4953c97067068e5a3ec7f30f9b8 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +8002 + +openjpa +dir + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAMailbox.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAMailbox.java.svn-base new file mode 100644 index 0000000..c37ecd7 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAMailbox.java.svn-base @@ -0,0 +1,235 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +@Entity(name="Mailbox") +@Table(name="JAMES_MAILBOX") +@NamedQueries({ + @NamedQuery(name="findMailboxById", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.mailbox.mailboxId = :idParam"), + @NamedQuery(name="findMailboxByName", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name = :nameParam and mailbox.user is NULL and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="findMailboxByNameWithUser", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name = :nameParam and mailbox.user= :userParam and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="deleteAllMailboxes", + query="DELETE FROM Mailbox mailbox"), + @NamedQuery(name="findMailboxWithNameLikeWithUser", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user= :userParam and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="findMailboxWithNameLike", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user is NULL and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="countMailboxesWithNameLikeWithUser", + query="SELECT COUNT(mailbox) FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user= :userParam and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="countMailboxesWithNameLike", + query="SELECT COUNT(mailbox) FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user is NULL and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="listMailboxes", + query="SELECT mailbox FROM Mailbox mailbox"), + @NamedQuery(name="findHighestModSeq", + query="SELECT mailbox.highestModSeq FROM Mailbox mailbox WHERE mailbox.mailboxId = :idParam"), + @NamedQuery(name="findLastUid", + query="SELECT mailbox.lastUid FROM Mailbox mailbox WHERE mailbox.mailboxId = :idParam") +}) +public class JPAMailbox implements Mailbox { + + private static final String TAB = " "; + + /** The value for the mailboxId field */ + @Id + @GeneratedValue + @Column(name = "MAILBOX_ID") + private long mailboxId; + + /** The value for the name field */ + @Basic(optional = false) + @Column(name = "MAILBOX_NAME", nullable = false, length = 200) + private String name; + + /** The value for the uidValidity field */ + @Basic(optional = false) + @Column(name = "MAILBOX_UID_VALIDITY", nullable = false) + private long uidValidity; + + @Basic(optional = false) + @Column(name = "USER_NAME", nullable = false, length = 200) + private String user; + + @Basic(optional = false) + @Column(name = "MAILBOX_NAMESPACE", nullable = false, length = 200) + private String namespace; + + @Basic(optional = false) + @Column(name = "MAILBOX_LAST_UID", nullable = false) + private long lastUid; + + @Basic(optional = false) + @Column(name = "MAILBOX_HIGHEST_MODSEQ", nullable = false) + private long highestModSeq; + + /** + * JPA only + */ + @Deprecated + public JPAMailbox() { + super(); + } + + public JPAMailbox(MailboxPath path, int uidValidity) { + this(); + this.name = path.getName(); + this.user = path.getUser(); + this.namespace = path.getNamespace(); + this.uidValidity = uidValidity; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + public Long getMailboxId() { + return mailboxId; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + public String getName() { + return name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + public long getUidValidity() { + return uidValidity; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String) + */ + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + final String retValue = "Mailbox ( " + + "mailboxId = " + this.mailboxId + TAB + + "name = " + this.name + TAB + + "uidValidity = " + this.uidValidity + TAB + + " )"; + return retValue; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailboxId ^ (mailboxId >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPAMailbox other = (JPAMailbox) obj; + if (mailboxId != other.mailboxId) + return false; + return true; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + public String getNamespace() { + return namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + public String getUser() { + return user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + public void setUser(String user) { + this.user = user; + } + + + public long getLastUid() { + return lastUid; + } + + public long getHighestModSeq() { + return highestModSeq; + } + + public long consumeUid() { + return ++lastUid; + } + + public long consumeModSeq() { + return ++highestModSeq; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + // TODO ACL support + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + // TODO ACL support + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAProperty.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAProperty.java.svn-base new file mode 100644 index 0000000..e27c267 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAProperty.java.svn-base @@ -0,0 +1,180 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.openjpa.persistence.jdbc.Index; + +@Entity(name = "Property") +@Table(name = "JAMES_MAIL_PROPERTY") +public class JPAProperty implements Property { + + /** The system unique key */ + @Id + @GeneratedValue + @Column(name = "PROPERTY_ID", nullable = true) + // TODO The columnNames are not interpreted, see OPENJPA-223 to fix + // MAILBOX-186 + @Index(name = "INDEX_PROPERTY_MSG_ID", columnNames = { "MAILBOX_ID", "MAIL_UID" }) + private long id; + + /** Order within the list of properties */ + @Basic(optional = false) + @Column(name = "PROPERTY_LINE_NUMBER", nullable = false) + @Index(name = "INDEX_PROPERTY_LINE_NUMBER") + private int line; + + /** Local part of the name of this property */ + @Basic(optional = false) + @Column(name = "PROPERTY_LOCAL_NAME", nullable = false, length = 500) + private String localName; + + /** Namespace part of the name of this property */ + @Basic(optional = false) + @Column(name = "PROPERTY_NAME_SPACE", nullable = false, length = 500) + private String namespace; + + /** Value of this property */ + @Basic(optional = false) + @Column(name = "PROPERTY_VALUE", nullable = false, length = 1024) + private String value; + + /** + * @deprecated enhancement only + */ + @Deprecated + public JPAProperty() { + } + + /** + * Constructs a property. + * + * @param localName + * not null + * @param namespace + * not null + * @param value + * not null + */ + public JPAProperty(String namespace, String localName, String value, int order) { + super(); + this.localName = localName; + this.namespace = namespace; + this.value = value; + this.line = order; + } + + /** + * Constructs a property cloned from the given. + * + * @param property + * not null + */ + public JPAProperty(Property property, int order) { + this(property.getNamespace(), property.getLocalName(), property.getValue(), order); + } + + /** + * Create a copy of the give JPAProperty + * + * @param property + */ + public JPAProperty(JPAProperty property) { + this(property.getNamespace(), property.getLocalName(), property.getValue(), property.getOrder()); + } + + /** + * Gets the order of this property. + * + * @return order of this property + */ + public int getOrder() { + return line; + } + + /** + * Gets the local part of the name of the property. + * + * @return not null + */ + public String getLocalName() { + return localName; + } + + /** + * Gets the namespace for the name. + * + * @return not null + */ + public String getNamespace() { + return namespace; + } + + /** + * Gets the value for this property. + * + * @return not null + */ + public String getValue() { + return value; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPAProperty other = (JPAProperty) obj; + if (id != other.id) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String result = "JPAProperty ( " + "id = " + this.id + " " + "localName = " + this.localName + " " + + "namespace = " + this.namespace + " " + "value = " + this.value + " )"; + + return result; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAUserFlag.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAUserFlag.java.svn-base new file mode 100644 index 0000000..403ac72 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/.svn/text-base/JPAUserFlag.java.svn-base @@ -0,0 +1,116 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity(name="UserFlag") +@Table(name="JAMES_MAIL_USERFLAG") +public class JPAUserFlag { + + + /** The system unique key */ + @Id + @GeneratedValue + @Column(name = "USERFLAG_ID", nullable = true) + private long id; + + /** Local part of the name of this property */ + @Basic(optional = false) + @Column(name = "USERFLAG_NAME", nullable = false, length = 500) + private String name; + + + /** + * @deprecated enhancement only + */ + @Deprecated + public JPAUserFlag() {} + + /** + * Constructs a User Flag. + * @param name not null + */ + public JPAUserFlag(String name) { + super(); + this.name = name; + } + + /** + * Constructs a User Flag, cloned from the given. + * @param flag not null + */ + public JPAUserFlag(JPAUserFlag flag) { + this(flag.getName()); + } + + + + /** + * Gets the name. + * @return not null + */ + public String getName() { + return name; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPAUserFlag other = (JPAUserFlag) obj; + if (id != other.id) + return false; + return true; + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() { + final String result = "JPAUserFlag ( " + + "id = " + this.id + " " + + "name = " + this.name + + " )"; + + return result; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAMailbox.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAMailbox.java new file mode 100644 index 0000000..c37ecd7 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAMailbox.java @@ -0,0 +1,235 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +@Entity(name="Mailbox") +@Table(name="JAMES_MAILBOX") +@NamedQueries({ + @NamedQuery(name="findMailboxById", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.mailbox.mailboxId = :idParam"), + @NamedQuery(name="findMailboxByName", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name = :nameParam and mailbox.user is NULL and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="findMailboxByNameWithUser", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name = :nameParam and mailbox.user= :userParam and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="deleteAllMailboxes", + query="DELETE FROM Mailbox mailbox"), + @NamedQuery(name="findMailboxWithNameLikeWithUser", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user= :userParam and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="findMailboxWithNameLike", + query="SELECT mailbox FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user is NULL and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="countMailboxesWithNameLikeWithUser", + query="SELECT COUNT(mailbox) FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user= :userParam and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="countMailboxesWithNameLike", + query="SELECT COUNT(mailbox) FROM Mailbox mailbox WHERE mailbox.name LIKE :nameParam and mailbox.user is NULL and mailbox.namespace= :namespaceParam"), + @NamedQuery(name="listMailboxes", + query="SELECT mailbox FROM Mailbox mailbox"), + @NamedQuery(name="findHighestModSeq", + query="SELECT mailbox.highestModSeq FROM Mailbox mailbox WHERE mailbox.mailboxId = :idParam"), + @NamedQuery(name="findLastUid", + query="SELECT mailbox.lastUid FROM Mailbox mailbox WHERE mailbox.mailboxId = :idParam") +}) +public class JPAMailbox implements Mailbox { + + private static final String TAB = " "; + + /** The value for the mailboxId field */ + @Id + @GeneratedValue + @Column(name = "MAILBOX_ID") + private long mailboxId; + + /** The value for the name field */ + @Basic(optional = false) + @Column(name = "MAILBOX_NAME", nullable = false, length = 200) + private String name; + + /** The value for the uidValidity field */ + @Basic(optional = false) + @Column(name = "MAILBOX_UID_VALIDITY", nullable = false) + private long uidValidity; + + @Basic(optional = false) + @Column(name = "USER_NAME", nullable = false, length = 200) + private String user; + + @Basic(optional = false) + @Column(name = "MAILBOX_NAMESPACE", nullable = false, length = 200) + private String namespace; + + @Basic(optional = false) + @Column(name = "MAILBOX_LAST_UID", nullable = false) + private long lastUid; + + @Basic(optional = false) + @Column(name = "MAILBOX_HIGHEST_MODSEQ", nullable = false) + private long highestModSeq; + + /** + * JPA only + */ + @Deprecated + public JPAMailbox() { + super(); + } + + public JPAMailbox(MailboxPath path, int uidValidity) { + this(); + this.name = path.getName(); + this.user = path.getUser(); + this.namespace = path.getNamespace(); + this.uidValidity = uidValidity; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + public Long getMailboxId() { + return mailboxId; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + public String getName() { + return name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + public long getUidValidity() { + return uidValidity; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String) + */ + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + final String retValue = "Mailbox ( " + + "mailboxId = " + this.mailboxId + TAB + + "name = " + this.name + TAB + + "uidValidity = " + this.uidValidity + TAB + + " )"; + return retValue; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailboxId ^ (mailboxId >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPAMailbox other = (JPAMailbox) obj; + if (mailboxId != other.mailboxId) + return false; + return true; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + public String getNamespace() { + return namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + public String getUser() { + return user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + public void setUser(String user) { + this.user = user; + } + + + public long getLastUid() { + return lastUid; + } + + public long getHighestModSeq() { + return highestModSeq; + } + + public long consumeUid() { + return ++lastUid; + } + + public long consumeModSeq() { + return ++highestModSeq; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + // TODO ACL support + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + // TODO ACL support + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAProperty.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAProperty.java new file mode 100644 index 0000000..e27c267 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAProperty.java @@ -0,0 +1,180 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.openjpa.persistence.jdbc.Index; + +@Entity(name = "Property") +@Table(name = "JAMES_MAIL_PROPERTY") +public class JPAProperty implements Property { + + /** The system unique key */ + @Id + @GeneratedValue + @Column(name = "PROPERTY_ID", nullable = true) + // TODO The columnNames are not interpreted, see OPENJPA-223 to fix + // MAILBOX-186 + @Index(name = "INDEX_PROPERTY_MSG_ID", columnNames = { "MAILBOX_ID", "MAIL_UID" }) + private long id; + + /** Order within the list of properties */ + @Basic(optional = false) + @Column(name = "PROPERTY_LINE_NUMBER", nullable = false) + @Index(name = "INDEX_PROPERTY_LINE_NUMBER") + private int line; + + /** Local part of the name of this property */ + @Basic(optional = false) + @Column(name = "PROPERTY_LOCAL_NAME", nullable = false, length = 500) + private String localName; + + /** Namespace part of the name of this property */ + @Basic(optional = false) + @Column(name = "PROPERTY_NAME_SPACE", nullable = false, length = 500) + private String namespace; + + /** Value of this property */ + @Basic(optional = false) + @Column(name = "PROPERTY_VALUE", nullable = false, length = 1024) + private String value; + + /** + * @deprecated enhancement only + */ + @Deprecated + public JPAProperty() { + } + + /** + * Constructs a property. + * + * @param localName + * not null + * @param namespace + * not null + * @param value + * not null + */ + public JPAProperty(String namespace, String localName, String value, int order) { + super(); + this.localName = localName; + this.namespace = namespace; + this.value = value; + this.line = order; + } + + /** + * Constructs a property cloned from the given. + * + * @param property + * not null + */ + public JPAProperty(Property property, int order) { + this(property.getNamespace(), property.getLocalName(), property.getValue(), order); + } + + /** + * Create a copy of the give JPAProperty + * + * @param property + */ + public JPAProperty(JPAProperty property) { + this(property.getNamespace(), property.getLocalName(), property.getValue(), property.getOrder()); + } + + /** + * Gets the order of this property. + * + * @return order of this property + */ + public int getOrder() { + return line; + } + + /** + * Gets the local part of the name of the property. + * + * @return not null + */ + public String getLocalName() { + return localName; + } + + /** + * Gets the namespace for the name. + * + * @return not null + */ + public String getNamespace() { + return namespace; + } + + /** + * Gets the value for this property. + * + * @return not null + */ + public String getValue() { + return value; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPAProperty other = (JPAProperty) obj; + if (id != other.id) + return false; + return true; + } + + /** + * Constructs a String with all attributes in name = value + * format. + * + * @return a String representation of this object. + */ + public String toString() { + final String result = "JPAProperty ( " + "id = " + this.id + " " + "localName = " + this.localName + " " + + "namespace = " + this.namespace + " " + "value = " + this.value + " )"; + + return result; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAUserFlag.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAUserFlag.java new file mode 100644 index 0000000..403ac72 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/JPAUserFlag.java @@ -0,0 +1,116 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity(name="UserFlag") +@Table(name="JAMES_MAIL_USERFLAG") +public class JPAUserFlag { + + + /** The system unique key */ + @Id + @GeneratedValue + @Column(name = "USERFLAG_ID", nullable = true) + private long id; + + /** Local part of the name of this property */ + @Basic(optional = false) + @Column(name = "USERFLAG_NAME", nullable = false, length = 500) + private String name; + + + /** + * @deprecated enhancement only + */ + @Deprecated + public JPAUserFlag() {} + + /** + * Constructs a User Flag. + * @param name not null + */ + public JPAUserFlag(String name) { + super(); + this.name = name; + } + + /** + * Constructs a User Flag, cloned from the given. + * @param flag not null + */ + public JPAUserFlag(JPAUserFlag flag) { + this(flag.getName()); + } + + + + /** + * Gets the name. + * @return not null + */ + public String getName() { + return name; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPAUserFlag other = (JPAUserFlag) obj; + if (id != other.id) + return false; + return true; + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() { + final String result = "JPAUserFlag ( " + + "id = " + this.id + " " + + "name = " + this.name + + " )"; + + return result; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/all-wcprops new file mode 100644 index 0000000..49f4e8e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa +END +EncryptDecryptHelper.java +K 25 +svn:wc:ra_dav:version-url +V 139 +/repos/asf/!svn/ver/1145691/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/EncryptDecryptHelper.java +END +JPAMessage.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1476170/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAMessage.java +END +AbstractJPAMessage.java +K 25 +svn:wc:ra_dav:version-url +V 137 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMessage.java +END +JPAStreamingMessage.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAStreamingMessage.java +END +JPAEncryptedMessage.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAEncryptedMessage.java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/entries new file mode 100644 index 0000000..263963a --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/entries @@ -0,0 +1,198 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa +http://svn.apache.org/repos/asf + + + +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +EncryptDecryptHelper.java +file + + + + +2013-09-02T02:54:40.000000Z +9fcf7bb1da2de4026c61628eb2a7d2f6 +2011-07-12T17:40:49.429827Z +1145691 +norman + + + + + + + + + + + + + + + + + + + + + +2384 + +JPAMessage.java +file + + + + +2013-09-02T02:54:40.000000Z +c307e1114b1a093abaa4db6bd76741dd +2013-04-26T12:34:08.407673Z +1476170 +eric + + + + + + + + + + + + + + + + + + + + + +4368 + +AbstractJPAMessage.java +file + + + + +2013-09-02T02:54:40.000000Z +3428a7e1e95d6de3821177c5d01c1ed4 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +20408 + +JPAStreamingMessage.java +file + + + + +2013-09-02T02:54:40.000000Z +a47b124b9a08e13461c292284001f8fe +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4674 + +JPAEncryptedMessage.java +file + + + + +2013-09-02T02:54:40.000000Z +027145575eb84a4332d3dad055ff3241 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4940 + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/AbstractJPAMessage.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/AbstractJPAMessage.java.svn-base new file mode 100644 index 0000000..d04b124 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/AbstractJPAMessage.java.svn-base @@ -0,0 +1,559 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.mail.Flags; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.openjpa.persistence.jdbc.ElementJoinColumn; +import org.apache.openjpa.persistence.jdbc.ElementJoinColumns; +import org.apache.openjpa.persistence.jdbc.Index; + +/** + * Abstract base class for JPA based implementations of {@link AbstractMessage} + */ +@IdClass(AbstractJPAMessage.MailboxIdUidKey.class) +@NamedQueries({ + @NamedQuery(name="findRecentMessageUidsInMailbox", + query="SELECT message.uid FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.recent = TRUE"), + @NamedQuery(name="findUnseenMessagesInMailboxOrderByUid", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.seen = FALSE ORDER BY message.uid ASC"), + @NamedQuery(name="findMessagesInMailbox", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam"), + @NamedQuery(name="findMessagesInMailboxBetweenUIDs", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam"), + @NamedQuery(name="findMessagesInMailboxWithUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam"), + @NamedQuery(name="findMessagesInMailboxAfterUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam"), + @NamedQuery(name="findDeletedMessagesInMailbox", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.deleted=TRUE"), + @NamedQuery(name="findDeletedMessagesInMailboxBetweenUIDs", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam AND message.deleted=TRUE"), + @NamedQuery(name="findDeletedMessagesInMailboxWithUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam AND message.deleted=TRUE"), + @NamedQuery(name="findDeletedMessagesInMailboxAfterUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam AND message.deleted=TRUE"), + + @NamedQuery(name="deleteDeletedMessagesInMailbox", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.deleted=TRUE"), + @NamedQuery(name="deleteDeletedMessagesInMailboxBetweenUIDs", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam AND message.deleted=TRUE"), + @NamedQuery(name="deleteDeletedMessagesInMailboxWithUID", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam AND message.deleted=TRUE"), + @NamedQuery(name="deleteDeletedMessagesInMailboxAfterUID", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam AND message.deleted=TRUE"), + + @NamedQuery(name="countUnseenMessagesInMailbox", + query="SELECT COUNT(message) FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.seen=FALSE"), + @NamedQuery(name="countMessagesInMailbox", + query="SELECT COUNT(message) FROM Message message WHERE message.mailbox.mailboxId = :idParam"), + @NamedQuery(name="deleteMessages", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam"), + @NamedQuery(name="findLastUidInMailbox", + query="SELECT message.uid FROM Message message WHERE message.mailbox.mailboxId = :idParam ORDER BY message.uid DESC"), + @NamedQuery(name="findHighestModSeqInMailbox", + query="SELECT message.modSeq FROM Message message WHERE message.mailbox.mailboxId = :idParam ORDER BY message.modSeq DESC"), + @NamedQuery(name="deleteAllMemberships", + query="DELETE FROM Message message") +}) +@MappedSuperclass +public abstract class AbstractJPAMessage extends AbstractMessage { + + + + private static final String TOSTRING_SEPARATOR = " "; + + /** Identifies composite key */ + public static class MailboxIdUidKey implements Serializable { + + private static final long serialVersionUID = 7847632032426660997L; + + public MailboxIdUidKey() {} + + /** The value for the mailbox field */ + public long mailbox; + + /** The value for the uid field */ + public long uid; + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailbox ^ (mailbox >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final MailboxIdUidKey other = (MailboxIdUidKey) obj; + if (mailbox != other.mailbox) + return false; + if (uid != other.uid) + return false; + return true; + } + + } + + /** The value for the mailboxId field */ + @Id + @ManyToOne( + cascade = { + CascadeType.PERSIST, + CascadeType.REFRESH, + CascadeType.MERGE}, + fetch=FetchType.LAZY) + @Column(name = "MAILBOX_ID", nullable = true) + private JPAMailbox mailbox; + + /** The value for the uid field */ + @Id + @Column(name = "MAIL_UID") + private long uid; + + /** The value for the modSeq field */ + @Index + @Column(name = "MAIL_MODSEQ") + private long modSeq; + + /** The value for the internalDate field */ + @Basic(optional = false) + @Column(name = "MAIL_DATE") + private Date internalDate; + + /** The value for the answered field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_ANSWERED", nullable = false) + private boolean answered = false; + + /** The value for the deleted field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_DELETED", nullable = false) + @Index + private boolean deleted = false; + + /** The value for the draft field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_DRAFT", nullable = false) + private boolean draft = false; + + /** The value for the flagged field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_FLAGGED", nullable = false) + private boolean flagged = false; + + /** The value for the recent field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_RECENT", nullable = false) + @Index + private boolean recent = false; + + /** The value for the seen field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_SEEN", nullable = false) + @Index + private boolean seen = false; + + + /** The first body octet */ + @Basic(optional = false) + @Column(name = "MAIL_BODY_START_OCTET", nullable = false) + private int bodyStartOctet; + + /** Number of octets in the full document content */ + @Basic(optional = false) + @Column(name = "MAIL_CONTENT_OCTETS_COUNT", nullable = false) + private long contentOctets; + + /** MIME media type */ + @Basic(optional = true) + @Column(name = "MAIL_MIME_TYPE", nullable = true, length = 200) + private String mediaType; + + /** MIME sub type */ + @Basic(optional = true) + @Column(name = "MAIL_MIME_SUBTYPE", nullable = true, length = 200) + private String subType; + + /** THE CRFL count when this document is textual, null otherwise */ + @Basic(optional = true) + @Column(name = "MAIL_TEXTUAL_LINE_COUNT", nullable = true) + private Long textualLineCount; + + + /** Meta data for this message */ + @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY) + @OrderBy("line") + @ElementJoinColumns({@ElementJoinColumn(name="MAILBOX_ID", referencedColumnName="MAILBOX_ID"), + @ElementJoinColumn(name="MAIL_UID", referencedColumnName="MAIL_UID")}) + private List properties; + + @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY) + @OrderBy("id") + @ElementJoinColumns({@ElementJoinColumn(name="MAILBOX_ID", referencedColumnName="MAILBOX_ID"), + @ElementJoinColumn(name="MAIL_UID", referencedColumnName="MAIL_UID")}) + private List userFlags; + + @Deprecated + public AbstractJPAMessage() {} + + public AbstractJPAMessage(JPAMailbox mailbox, Date internalDate, Flags flags, final long contentOctets, final int bodyStartOctet, final PropertyBuilder propertyBuilder) { + super(); + this.mailbox = mailbox; + this.internalDate = internalDate; + userFlags = new ArrayList(); + + setFlags(flags); + this.contentOctets = contentOctets; + this.bodyStartOctet = bodyStartOctet; + this.textualLineCount = propertyBuilder.getTextualLineCount(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + final List properties = propertyBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + int order = 0; + for (final Property property:properties) { + this.properties.add(new JPAProperty(property, order++)); + } + + } + + /** + * Constructs a copy of the given message. + * All properties are cloned except mailbox and UID. + * @param mailbox new mailbox + * @param uid new UID + * @param modSeq new modSeq + * @param original message to be copied, not null + * @throws IOException + */ + public AbstractJPAMessage(JPAMailbox mailbox, long uid, long modSeq, Message original) throws MailboxException { + super(); + this.mailbox = mailbox; + this.uid = uid; + this.modSeq = modSeq; + this.userFlags = new ArrayList(); + setFlags(original.createFlags()); + + // A copy of a message is recent + // See MAILBOX-85 + this.recent = true; + + this.contentOctets = original.getFullContentOctets(); + this.bodyStartOctet = (int) (original.getFullContentOctets() - original.getBodyOctets()); + this.internalDate = original.getInternalDate(); + + + PropertyBuilder pBuilder = new PropertyBuilder(original.getProperties()); + this.textualLineCount = original.getTextualLineCount(); + this.mediaType = original.getMediaType(); + this.subType = original.getSubType(); + final List properties = pBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + int order = 0; + for (final Property property:properties) { + this.properties.add(new JPAProperty(property, order++)); + } + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (getMailboxId() ^ (getMailboxId() >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final AbstractJPAMessage other = (AbstractJPAMessage) obj; + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) + return false; + } else { + if (other.getMailboxId() != null) + return false; + } + if (uid != other.uid) + return false; + return true; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + public long getModSeq() { + return modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + public String getMediaType() { + return mediaType; + } + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + public String getSubType() { + return subType; + } + + /** + * Gets a read-only list of meta-data properties. + * For properties with multiple values, this list will contain + * several enteries with the same namespace and local name. + * @return unmodifiable list of meta-data, not null + */ + public List getProperties() { + return new ArrayList(properties); + } + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + public Long getTextualLineCount() { + return textualLineCount; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getFullContentOctets() + */ + public long getFullContentOctets() { + return contentOctets; + } + + @Override + protected int getBodyStartOctet() { + return bodyStartOctet; + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getInternalDate() + */ + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getMailboxId() + */ + public Long getMailboxId() { + return getMailbox().getMailboxId(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getUid() + */ + public long getUid() { + return uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isAnswered() + */ + public boolean isAnswered() { + return answered; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDeleted() + */ + public boolean isDeleted() { + return deleted; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDraft() + */ + public boolean isDraft() { + return draft; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isFlagged() + */ + public boolean isFlagged() { + return flagged; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isRecent() + */ + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isSeen() + */ + public boolean isSeen() { + return seen; + } + + public void setUid(long uid) { + this.uid = uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setFlags(javax.mail.Flags) + */ + public void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + + /* + // Loop over the user flags and check which of them needs to get added / removed + List uFlags = Arrays.asList(flags.getUserFlags()); + for (int i = 0; i < userFlags.size(); i++) { + JPAUserFlag f = userFlags.get(i); + if (uFlags.contains(f.getName()) == false) { + userFlags.remove(f); + i++; + } + } + for (int i = 0; i < uFlags.size(); i++) { + boolean found = false; + String uFlag = uFlags.get(i); + for (int a = 0; a < userFlags.size(); a++) { + String userFlag = userFlags.get(a).getName(); + if (userFlag.equals(uFlag)) { + found = true; + break; + } + } + if (found == false) { + userFlags.add(new JPAUserFlag(uFlag)); + } + + + } + */ + String[] userflags = flags.getUserFlags(); + userFlags.clear(); + for (int i = 0 ; i< userflags.length; i++) { + userFlags.add(new JPAUserFlag(userflags[i])); + } + } + + /** + * Utility getter on Mailbox. + */ + public JPAMailbox getMailbox() { + return mailbox; + } + + /** + * This implementation supports user flags + * + * + */ + @Override + protected String[] createUserFlags() { + String[] flags = new String[userFlags.size()]; + for (int i = 0; i < userFlags.size(); i++) { + flags[i] = userFlags.get(i).getName(); + } + return flags; + } + + /** + * Utility setter on Mailbox. + */ + public void setMailbox(JPAMailbox mailbox) { + this.mailbox = mailbox; + } + + public String toString() { + final String retValue = + "message(" + + "mailboxId = " + this.getMailboxId() + TOSTRING_SEPARATOR + + "uid = " + this.uid + TOSTRING_SEPARATOR + + "internalDate = " + this.internalDate + TOSTRING_SEPARATOR + + "answered = " + this.answered + TOSTRING_SEPARATOR + + "deleted = " + this.deleted + TOSTRING_SEPARATOR + + "draft = " + this.draft + TOSTRING_SEPARATOR + + "flagged = " + this.flagged + TOSTRING_SEPARATOR + + "recent = " + this.recent + TOSTRING_SEPARATOR + + "seen = " + this.seen + TOSTRING_SEPARATOR + + " )"; + return retValue; + } + + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/EncryptDecryptHelper.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/EncryptDecryptHelper.java.svn-base new file mode 100644 index 0000000..713be71 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/EncryptDecryptHelper.java.svn-base @@ -0,0 +1,66 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import org.jasypt.encryption.pbe.StandardPBEByteEncryptor; + +/** + * Helper class for encrypt and de-crypt data + * + * + */ +public class EncryptDecryptHelper { + + // Use one static instance as it is thread safe + private static StandardPBEByteEncryptor encryptor = new StandardPBEByteEncryptor(); + + + /** + * Set the password for encrypt / de-crypt. This MUST be done before + * the usage of {@link #getDecrypted(byte[])} and {@link #getEncrypted(byte[])}. + * + * So to be safe its the best to call this in a constructor + * + * @param pass + */ + public static void init(String pass) { + encryptor.setPassword(pass); + } + + /** + * Encrypt the given array and return the encrypted one + * + * @param array + * @return enc-array + */ + public static byte[] getEncrypted(byte[] array) { + return encryptor.encrypt(array); + } + + /** + * Decrypt the given array and return the de-crypted one + * + * @param array + * @return dec-array + */ + public static byte[] getDecrypted(byte[] array) { + return encryptor.decrypt(array); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAEncryptedMessage.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAEncryptedMessage.java.svn-base new file mode 100644 index 0000000..fc2ffcb --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAEncryptedMessage.java.svn-base @@ -0,0 +1,113 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Lob; +import javax.persistence.Table; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.openjpa.persistence.Externalizer; +import org.apache.openjpa.persistence.Factory; + +@Entity(name="Message") +@Table(name="JAMES_MAIL") +public class JPAEncryptedMessage extends AbstractJPAMessage { + + /** The value for the body field. Lazy loaded */ + /** We use a max length to represent 1gb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "MAIL_BYTES", length = 1048576000, nullable = false) + @Externalizer("EncryptDecryptHelper.getEncrypted") + @Factory("EncryptDecryptHelper.getDecrypted") + @Lob private byte[] body; + + + /** The value for the header field. Lazy loaded */ + /** We use a max length to represent 1gb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "HEADER_BYTES", length = 10485760, nullable = false) + @Externalizer("EncryptDecryptHelper.getEncrypted") + @Factory("EncryptDecryptHelper.getDecrypted") + @Lob private byte[] header; + + @Deprecated + public JPAEncryptedMessage() {} + + public JPAEncryptedMessage(JPAMailbox mailbox,Date internalDate, int size, Flags flags, SharedInputStream content, int bodyStartOctet, final PropertyBuilder propertyBuilder) throws MailboxException { + super(mailbox, internalDate, flags, size ,bodyStartOctet, propertyBuilder); + try { + int headerEnd = bodyStartOctet; + if (headerEnd < 0) { + headerEnd = 0; + } + this.header = IOUtils.toByteArray(content.newStream(0, headerEnd)); + this.body = IOUtils.toByteArray(content.newStream(getBodyStartOctet(), -1)); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + /** + * Create a copy of the given message + * + * @param message + * @throws MailboxException + */ + public JPAEncryptedMessage(JPAMailbox mailbox, long uid, long modSeq, Message message) throws MailboxException{ + super(mailbox, uid, modSeq, message); + try { + this.body = IOUtils.toByteArray(message.getBodyContent()); + this.header = IOUtils.toByteArray(message.getHeaderContent()); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return new ByteArrayInputStream(body); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + return new ByteArrayInputStream(header); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAMessage.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAMessage.java.svn-base new file mode 100644 index 0000000..ad03edd --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAMessage.java.svn-base @@ -0,0 +1,107 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Lob; +import javax.persistence.Table; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; + +@Entity(name="Message") +@Table(name="JAMES_MAIL") +public class JPAMessage extends AbstractJPAMessage { + + /** The value for the body field. Lazy loaded */ + /** We use a max length to represent 1gb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "MAIL_BYTES", length = 1048576000, nullable = false) + @Lob private byte[] body; + + + /** The value for the header field. Lazy loaded */ + /** We use a max length to represent 10mb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "HEADER_BYTES", length = 10485760, nullable = false) + @Lob private byte[] header; + + @Deprecated + public JPAMessage() {} + + public JPAMessage(JPAMailbox mailbox,Date internalDate, int size, Flags flags, SharedInputStream content, int bodyStartOctet, final PropertyBuilder propertyBuilder) throws MailboxException { + super(mailbox, internalDate, flags, size ,bodyStartOctet, propertyBuilder); + try { + int headerEnd = bodyStartOctet; + if (headerEnd < 0) { + headerEnd = 0; + } + this.header = IOUtils.toByteArray(content.newStream(0, headerEnd)); + this.body = IOUtils.toByteArray(content.newStream(getBodyStartOctet(), -1)); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + /** + * Create a copy of the given message + * + * @param message + * @throws MailboxException + */ + public JPAMessage(JPAMailbox mailbox, long uid, long modSeq, Message message) throws MailboxException{ + super(mailbox, uid, modSeq, message); + try { + this.body = IOUtils.toByteArray(message.getBodyContent()); + this.header = IOUtils.toByteArray(message.getHeaderContent()); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return new ByteArrayInputStream(body); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + return new ByteArrayInputStream(header); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAStreamingMessage.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAStreamingMessage.java.svn-base new file mode 100644 index 0000000..d4d0cb0 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/.svn/text-base/JPAStreamingMessage.java.svn-base @@ -0,0 +1,118 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Table; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.openjpa.persistence.Persistent; + +/** + * JPA implementation of {@link AbstractJPAMessage} which use openjpas {@link Persistent} type to + * be able to stream the message content without loading it into the memory at all. + * + * This is not supported for all DB's yet. See Additional JPA Mappings + * + * If your DB is not supported by this, use {@link JPAMessage} + * + * TODO: Fix me! + */ +@Entity(name="Message") +@Table(name="JAMES_MAIL") +public class JPAStreamingMessage extends AbstractJPAMessage { + + @SuppressWarnings("unused") + @Persistent(optional = false, fetch = FetchType.LAZY) + @Column(name = "MAIL_BYTES", length = 1048576000, nullable = false) + private InputStream body; + + @SuppressWarnings("unused") + @Persistent(optional = false, fetch = FetchType.LAZY) + @Column(name = "HEADER_BYTES", length = 10485760, nullable = false) + private InputStream header; + + private SharedInputStream content; + + @Deprecated + public JPAStreamingMessage() {} + + public JPAStreamingMessage(JPAMailbox mailbox, Date internalDate, int size, Flags flags, SharedInputStream content, int bodyStartOctet,final PropertyBuilder propertyBuilder) throws MailboxException { + super(mailbox, internalDate, flags, size ,bodyStartOctet, propertyBuilder); + this.content = content; + + try { + this.header = getHeaderContent(); + this.body = getBodyContent(); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + /** + * Create a copy of the given message + * + * @param message + * @throws IOException + */ + public JPAStreamingMessage(JPAMailbox mailbox, long uid, long modSeq, Message message) throws MailboxException { + super(mailbox, uid, modSeq, message); + try { + this.content = new SharedByteArrayInputStream(IOUtils.toByteArray(message.getFullContent())); + this.header = getHeaderContent(); + this.body = getBodyContent(); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return content.newStream(getBodyStartOctet(), -1); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + int headerEnd = getBodyStartOctet() -2; + if (headerEnd < 0) { + headerEnd = 0; + } + return content.newStream(0, headerEnd); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMessage.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMessage.java new file mode 100644 index 0000000..d04b124 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMessage.java @@ -0,0 +1,559 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.mail.Flags; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.openjpa.persistence.jdbc.ElementJoinColumn; +import org.apache.openjpa.persistence.jdbc.ElementJoinColumns; +import org.apache.openjpa.persistence.jdbc.Index; + +/** + * Abstract base class for JPA based implementations of {@link AbstractMessage} + */ +@IdClass(AbstractJPAMessage.MailboxIdUidKey.class) +@NamedQueries({ + @NamedQuery(name="findRecentMessageUidsInMailbox", + query="SELECT message.uid FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.recent = TRUE"), + @NamedQuery(name="findUnseenMessagesInMailboxOrderByUid", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.seen = FALSE ORDER BY message.uid ASC"), + @NamedQuery(name="findMessagesInMailbox", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam"), + @NamedQuery(name="findMessagesInMailboxBetweenUIDs", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam"), + @NamedQuery(name="findMessagesInMailboxWithUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam"), + @NamedQuery(name="findMessagesInMailboxAfterUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam"), + @NamedQuery(name="findDeletedMessagesInMailbox", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.deleted=TRUE"), + @NamedQuery(name="findDeletedMessagesInMailboxBetweenUIDs", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam AND message.deleted=TRUE"), + @NamedQuery(name="findDeletedMessagesInMailboxWithUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam AND message.deleted=TRUE"), + @NamedQuery(name="findDeletedMessagesInMailboxAfterUID", + query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam AND message.deleted=TRUE"), + + @NamedQuery(name="deleteDeletedMessagesInMailbox", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.deleted=TRUE"), + @NamedQuery(name="deleteDeletedMessagesInMailboxBetweenUIDs", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam AND message.deleted=TRUE"), + @NamedQuery(name="deleteDeletedMessagesInMailboxWithUID", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam AND message.deleted=TRUE"), + @NamedQuery(name="deleteDeletedMessagesInMailboxAfterUID", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam AND message.deleted=TRUE"), + + @NamedQuery(name="countUnseenMessagesInMailbox", + query="SELECT COUNT(message) FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.seen=FALSE"), + @NamedQuery(name="countMessagesInMailbox", + query="SELECT COUNT(message) FROM Message message WHERE message.mailbox.mailboxId = :idParam"), + @NamedQuery(name="deleteMessages", + query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam"), + @NamedQuery(name="findLastUidInMailbox", + query="SELECT message.uid FROM Message message WHERE message.mailbox.mailboxId = :idParam ORDER BY message.uid DESC"), + @NamedQuery(name="findHighestModSeqInMailbox", + query="SELECT message.modSeq FROM Message message WHERE message.mailbox.mailboxId = :idParam ORDER BY message.modSeq DESC"), + @NamedQuery(name="deleteAllMemberships", + query="DELETE FROM Message message") +}) +@MappedSuperclass +public abstract class AbstractJPAMessage extends AbstractMessage { + + + + private static final String TOSTRING_SEPARATOR = " "; + + /** Identifies composite key */ + public static class MailboxIdUidKey implements Serializable { + + private static final long serialVersionUID = 7847632032426660997L; + + public MailboxIdUidKey() {} + + /** The value for the mailbox field */ + public long mailbox; + + /** The value for the uid field */ + public long uid; + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailbox ^ (mailbox >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final MailboxIdUidKey other = (MailboxIdUidKey) obj; + if (mailbox != other.mailbox) + return false; + if (uid != other.uid) + return false; + return true; + } + + } + + /** The value for the mailboxId field */ + @Id + @ManyToOne( + cascade = { + CascadeType.PERSIST, + CascadeType.REFRESH, + CascadeType.MERGE}, + fetch=FetchType.LAZY) + @Column(name = "MAILBOX_ID", nullable = true) + private JPAMailbox mailbox; + + /** The value for the uid field */ + @Id + @Column(name = "MAIL_UID") + private long uid; + + /** The value for the modSeq field */ + @Index + @Column(name = "MAIL_MODSEQ") + private long modSeq; + + /** The value for the internalDate field */ + @Basic(optional = false) + @Column(name = "MAIL_DATE") + private Date internalDate; + + /** The value for the answered field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_ANSWERED", nullable = false) + private boolean answered = false; + + /** The value for the deleted field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_DELETED", nullable = false) + @Index + private boolean deleted = false; + + /** The value for the draft field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_DRAFT", nullable = false) + private boolean draft = false; + + /** The value for the flagged field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_FLAGGED", nullable = false) + private boolean flagged = false; + + /** The value for the recent field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_RECENT", nullable = false) + @Index + private boolean recent = false; + + /** The value for the seen field */ + @Basic(optional = false) + @Column(name = "MAIL_IS_SEEN", nullable = false) + @Index + private boolean seen = false; + + + /** The first body octet */ + @Basic(optional = false) + @Column(name = "MAIL_BODY_START_OCTET", nullable = false) + private int bodyStartOctet; + + /** Number of octets in the full document content */ + @Basic(optional = false) + @Column(name = "MAIL_CONTENT_OCTETS_COUNT", nullable = false) + private long contentOctets; + + /** MIME media type */ + @Basic(optional = true) + @Column(name = "MAIL_MIME_TYPE", nullable = true, length = 200) + private String mediaType; + + /** MIME sub type */ + @Basic(optional = true) + @Column(name = "MAIL_MIME_SUBTYPE", nullable = true, length = 200) + private String subType; + + /** THE CRFL count when this document is textual, null otherwise */ + @Basic(optional = true) + @Column(name = "MAIL_TEXTUAL_LINE_COUNT", nullable = true) + private Long textualLineCount; + + + /** Meta data for this message */ + @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY) + @OrderBy("line") + @ElementJoinColumns({@ElementJoinColumn(name="MAILBOX_ID", referencedColumnName="MAILBOX_ID"), + @ElementJoinColumn(name="MAIL_UID", referencedColumnName="MAIL_UID")}) + private List properties; + + @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY) + @OrderBy("id") + @ElementJoinColumns({@ElementJoinColumn(name="MAILBOX_ID", referencedColumnName="MAILBOX_ID"), + @ElementJoinColumn(name="MAIL_UID", referencedColumnName="MAIL_UID")}) + private List userFlags; + + @Deprecated + public AbstractJPAMessage() {} + + public AbstractJPAMessage(JPAMailbox mailbox, Date internalDate, Flags flags, final long contentOctets, final int bodyStartOctet, final PropertyBuilder propertyBuilder) { + super(); + this.mailbox = mailbox; + this.internalDate = internalDate; + userFlags = new ArrayList(); + + setFlags(flags); + this.contentOctets = contentOctets; + this.bodyStartOctet = bodyStartOctet; + this.textualLineCount = propertyBuilder.getTextualLineCount(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + final List properties = propertyBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + int order = 0; + for (final Property property:properties) { + this.properties.add(new JPAProperty(property, order++)); + } + + } + + /** + * Constructs a copy of the given message. + * All properties are cloned except mailbox and UID. + * @param mailbox new mailbox + * @param uid new UID + * @param modSeq new modSeq + * @param original message to be copied, not null + * @throws IOException + */ + public AbstractJPAMessage(JPAMailbox mailbox, long uid, long modSeq, Message original) throws MailboxException { + super(); + this.mailbox = mailbox; + this.uid = uid; + this.modSeq = modSeq; + this.userFlags = new ArrayList(); + setFlags(original.createFlags()); + + // A copy of a message is recent + // See MAILBOX-85 + this.recent = true; + + this.contentOctets = original.getFullContentOctets(); + this.bodyStartOctet = (int) (original.getFullContentOctets() - original.getBodyOctets()); + this.internalDate = original.getInternalDate(); + + + PropertyBuilder pBuilder = new PropertyBuilder(original.getProperties()); + this.textualLineCount = original.getTextualLineCount(); + this.mediaType = original.getMediaType(); + this.subType = original.getSubType(); + final List properties = pBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + int order = 0; + for (final Property property:properties) { + this.properties.add(new JPAProperty(property, order++)); + } + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (getMailboxId() ^ (getMailboxId() >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final AbstractJPAMessage other = (AbstractJPAMessage) obj; + if (getMailboxId() != null) { + if (!getMailboxId().equals(other.getMailboxId())) + return false; + } else { + if (other.getMailboxId() != null) + return false; + } + if (uid != other.uid) + return false; + return true; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + public long getModSeq() { + return modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + public String getMediaType() { + return mediaType; + } + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + public String getSubType() { + return subType; + } + + /** + * Gets a read-only list of meta-data properties. + * For properties with multiple values, this list will contain + * several enteries with the same namespace and local name. + * @return unmodifiable list of meta-data, not null + */ + public List getProperties() { + return new ArrayList(properties); + } + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + public Long getTextualLineCount() { + return textualLineCount; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getFullContentOctets() + */ + public long getFullContentOctets() { + return contentOctets; + } + + @Override + protected int getBodyStartOctet() { + return bodyStartOctet; + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getInternalDate() + */ + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getMailboxId() + */ + public Long getMailboxId() { + return getMailbox().getMailboxId(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getUid() + */ + public long getUid() { + return uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isAnswered() + */ + public boolean isAnswered() { + return answered; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDeleted() + */ + public boolean isDeleted() { + return deleted; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isDraft() + */ + public boolean isDraft() { + return draft; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isFlagged() + */ + public boolean isFlagged() { + return flagged; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isRecent() + */ + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isSeen() + */ + public boolean isSeen() { + return seen; + } + + public void setUid(long uid) { + this.uid = uid; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setFlags(javax.mail.Flags) + */ + public void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + + /* + // Loop over the user flags and check which of them needs to get added / removed + List uFlags = Arrays.asList(flags.getUserFlags()); + for (int i = 0; i < userFlags.size(); i++) { + JPAUserFlag f = userFlags.get(i); + if (uFlags.contains(f.getName()) == false) { + userFlags.remove(f); + i++; + } + } + for (int i = 0; i < uFlags.size(); i++) { + boolean found = false; + String uFlag = uFlags.get(i); + for (int a = 0; a < userFlags.size(); a++) { + String userFlag = userFlags.get(a).getName(); + if (userFlag.equals(uFlag)) { + found = true; + break; + } + } + if (found == false) { + userFlags.add(new JPAUserFlag(uFlag)); + } + + + } + */ + String[] userflags = flags.getUserFlags(); + userFlags.clear(); + for (int i = 0 ; i< userflags.length; i++) { + userFlags.add(new JPAUserFlag(userflags[i])); + } + } + + /** + * Utility getter on Mailbox. + */ + public JPAMailbox getMailbox() { + return mailbox; + } + + /** + * This implementation supports user flags + * + * + */ + @Override + protected String[] createUserFlags() { + String[] flags = new String[userFlags.size()]; + for (int i = 0; i < userFlags.size(); i++) { + flags[i] = userFlags.get(i).getName(); + } + return flags; + } + + /** + * Utility setter on Mailbox. + */ + public void setMailbox(JPAMailbox mailbox) { + this.mailbox = mailbox; + } + + public String toString() { + final String retValue = + "message(" + + "mailboxId = " + this.getMailboxId() + TOSTRING_SEPARATOR + + "uid = " + this.uid + TOSTRING_SEPARATOR + + "internalDate = " + this.internalDate + TOSTRING_SEPARATOR + + "answered = " + this.answered + TOSTRING_SEPARATOR + + "deleted = " + this.deleted + TOSTRING_SEPARATOR + + "draft = " + this.draft + TOSTRING_SEPARATOR + + "flagged = " + this.flagged + TOSTRING_SEPARATOR + + "recent = " + this.recent + TOSTRING_SEPARATOR + + "seen = " + this.seen + TOSTRING_SEPARATOR + + " )"; + return retValue; + } + + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/EncryptDecryptHelper.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/EncryptDecryptHelper.java new file mode 100644 index 0000000..713be71 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/EncryptDecryptHelper.java @@ -0,0 +1,66 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import org.jasypt.encryption.pbe.StandardPBEByteEncryptor; + +/** + * Helper class for encrypt and de-crypt data + * + * + */ +public class EncryptDecryptHelper { + + // Use one static instance as it is thread safe + private static StandardPBEByteEncryptor encryptor = new StandardPBEByteEncryptor(); + + + /** + * Set the password for encrypt / de-crypt. This MUST be done before + * the usage of {@link #getDecrypted(byte[])} and {@link #getEncrypted(byte[])}. + * + * So to be safe its the best to call this in a constructor + * + * @param pass + */ + public static void init(String pass) { + encryptor.setPassword(pass); + } + + /** + * Encrypt the given array and return the encrypted one + * + * @param array + * @return enc-array + */ + public static byte[] getEncrypted(byte[] array) { + return encryptor.encrypt(array); + } + + /** + * Decrypt the given array and return the de-crypted one + * + * @param array + * @return dec-array + */ + public static byte[] getDecrypted(byte[] array) { + return encryptor.decrypt(array); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAEncryptedMessage.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAEncryptedMessage.java new file mode 100644 index 0000000..fc2ffcb --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAEncryptedMessage.java @@ -0,0 +1,113 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Lob; +import javax.persistence.Table; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.openjpa.persistence.Externalizer; +import org.apache.openjpa.persistence.Factory; + +@Entity(name="Message") +@Table(name="JAMES_MAIL") +public class JPAEncryptedMessage extends AbstractJPAMessage { + + /** The value for the body field. Lazy loaded */ + /** We use a max length to represent 1gb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "MAIL_BYTES", length = 1048576000, nullable = false) + @Externalizer("EncryptDecryptHelper.getEncrypted") + @Factory("EncryptDecryptHelper.getDecrypted") + @Lob private byte[] body; + + + /** The value for the header field. Lazy loaded */ + /** We use a max length to represent 1gb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "HEADER_BYTES", length = 10485760, nullable = false) + @Externalizer("EncryptDecryptHelper.getEncrypted") + @Factory("EncryptDecryptHelper.getDecrypted") + @Lob private byte[] header; + + @Deprecated + public JPAEncryptedMessage() {} + + public JPAEncryptedMessage(JPAMailbox mailbox,Date internalDate, int size, Flags flags, SharedInputStream content, int bodyStartOctet, final PropertyBuilder propertyBuilder) throws MailboxException { + super(mailbox, internalDate, flags, size ,bodyStartOctet, propertyBuilder); + try { + int headerEnd = bodyStartOctet; + if (headerEnd < 0) { + headerEnd = 0; + } + this.header = IOUtils.toByteArray(content.newStream(0, headerEnd)); + this.body = IOUtils.toByteArray(content.newStream(getBodyStartOctet(), -1)); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + /** + * Create a copy of the given message + * + * @param message + * @throws MailboxException + */ + public JPAEncryptedMessage(JPAMailbox mailbox, long uid, long modSeq, Message message) throws MailboxException{ + super(mailbox, uid, modSeq, message); + try { + this.body = IOUtils.toByteArray(message.getBodyContent()); + this.header = IOUtils.toByteArray(message.getHeaderContent()); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return new ByteArrayInputStream(body); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + return new ByteArrayInputStream(header); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAMessage.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAMessage.java new file mode 100644 index 0000000..ad03edd --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAMessage.java @@ -0,0 +1,107 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Lob; +import javax.persistence.Table; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; + +@Entity(name="Message") +@Table(name="JAMES_MAIL") +public class JPAMessage extends AbstractJPAMessage { + + /** The value for the body field. Lazy loaded */ + /** We use a max length to represent 1gb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "MAIL_BYTES", length = 1048576000, nullable = false) + @Lob private byte[] body; + + + /** The value for the header field. Lazy loaded */ + /** We use a max length to represent 10mb data. Thats prolly overkill, but who knows */ + @Basic(optional = false, fetch = FetchType.LAZY) + @Column(name = "HEADER_BYTES", length = 10485760, nullable = false) + @Lob private byte[] header; + + @Deprecated + public JPAMessage() {} + + public JPAMessage(JPAMailbox mailbox,Date internalDate, int size, Flags flags, SharedInputStream content, int bodyStartOctet, final PropertyBuilder propertyBuilder) throws MailboxException { + super(mailbox, internalDate, flags, size ,bodyStartOctet, propertyBuilder); + try { + int headerEnd = bodyStartOctet; + if (headerEnd < 0) { + headerEnd = 0; + } + this.header = IOUtils.toByteArray(content.newStream(0, headerEnd)); + this.body = IOUtils.toByteArray(content.newStream(getBodyStartOctet(), -1)); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + /** + * Create a copy of the given message + * + * @param message + * @throws MailboxException + */ + public JPAMessage(JPAMailbox mailbox, long uid, long modSeq, Message message) throws MailboxException{ + super(mailbox, uid, modSeq, message); + try { + this.body = IOUtils.toByteArray(message.getBodyContent()); + this.header = IOUtils.toByteArray(message.getHeaderContent()); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return new ByteArrayInputStream(body); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + return new ByteArrayInputStream(header); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAStreamingMessage.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAStreamingMessage.java new file mode 100644 index 0000000..d4d0cb0 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/JPAStreamingMessage.java @@ -0,0 +1,118 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.mailbox.jpa.mail.model.openjpa; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Table; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.openjpa.persistence.Persistent; + +/** + * JPA implementation of {@link AbstractJPAMessage} which use openjpas {@link Persistent} type to + * be able to stream the message content without loading it into the memory at all. + * + * This is not supported for all DB's yet. See Additional JPA Mappings + * + * If your DB is not supported by this, use {@link JPAMessage} + * + * TODO: Fix me! + */ +@Entity(name="Message") +@Table(name="JAMES_MAIL") +public class JPAStreamingMessage extends AbstractJPAMessage { + + @SuppressWarnings("unused") + @Persistent(optional = false, fetch = FetchType.LAZY) + @Column(name = "MAIL_BYTES", length = 1048576000, nullable = false) + private InputStream body; + + @SuppressWarnings("unused") + @Persistent(optional = false, fetch = FetchType.LAZY) + @Column(name = "HEADER_BYTES", length = 10485760, nullable = false) + private InputStream header; + + private SharedInputStream content; + + @Deprecated + public JPAStreamingMessage() {} + + public JPAStreamingMessage(JPAMailbox mailbox, Date internalDate, int size, Flags flags, SharedInputStream content, int bodyStartOctet,final PropertyBuilder propertyBuilder) throws MailboxException { + super(mailbox, internalDate, flags, size ,bodyStartOctet, propertyBuilder); + this.content = content; + + try { + this.header = getHeaderContent(); + this.body = getBodyContent(); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + /** + * Create a copy of the given message + * + * @param message + * @throws IOException + */ + public JPAStreamingMessage(JPAMailbox mailbox, long uid, long modSeq, Message message) throws MailboxException { + super(mailbox, uid, modSeq, message); + try { + this.content = new SharedByteArrayInputStream(IOUtils.toByteArray(message.getFullContent())); + this.header = getHeaderContent(); + this.body = getBodyContent(); + } catch (IOException e) { + throw new MailboxException("Unable to parse message",e); + } + } + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return content.newStream(getBodyStartOctet(), -1); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + int headerEnd = getBodyStartOctet() -2; + if (headerEnd < 0) { + headerEnd = 0; + } + return content.newStream(0, headerEnd); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/all-wcprops new file mode 100644 index 0000000..00de6c3 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa +END +OpenJPAMailboxManager.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java +END +OpenJPAMessageManager.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/entries new file mode 100644 index 0000000..a9768f0 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +OpenJPAMailboxManager.java +file + + + + +2013-09-02T02:54:40.000000Z +a239e23016fb066ef9b7c5a16782bb3a +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3937 + +OpenJPAMessageManager.java +file + + + + +2013-09-02T02:54:40.000000Z +4675aaae8d8255d518a1aba47a88ea66 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4250 + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMailboxManager.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMailboxManager.java.svn-base new file mode 100644 index 0000000..1c52fa8 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMailboxManager.java.svn-base @@ -0,0 +1,73 @@ +/**************************************************************** + * 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.openjpa; + + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.JPAMailboxManager; +import org.apache.james.mailbox.jpa.JPAMailboxSessionMapperFactory; +import org.apache.james.mailbox.jpa.mail.model.openjpa.EncryptDecryptHelper; +import org.apache.james.mailbox.jpa.openjpa.OpenJPAMessageManager.AdvancedFeature; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * OpenJPA implementation of MailboxManager + * + */ +public class OpenJPAMailboxManager extends JPAMailboxManager { + + private AdvancedFeature feature; + + public OpenJPAMailboxManager(JPAMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxPathLocker locker, boolean useStreaming, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + if (useStreaming) { + feature = AdvancedFeature.Streaming; + } else { + feature = AdvancedFeature.None; + } + } + + public OpenJPAMailboxManager(JPAMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxPathLocker locker, String encryptPass, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + if (encryptPass != null) { + EncryptDecryptHelper.init(encryptPass); + feature = AdvancedFeature.Encryption; + } else { + feature = AdvancedFeature.None; + } + } + + public OpenJPAMailboxManager(JPAMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + this(mapperFactory, authenticator, new JVMMailboxPathLocker(), false, aclResolver, groupMembershipResolver); + } + + @Override + protected StoreMessageManager createMessageManager(Mailbox mailboxRow, MailboxSession session) throws MailboxException { + StoreMessageManager result = new OpenJPAMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), mailboxRow, feature, getAclResolver(), getGroupMembershipResolver()); + return result; + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMessageManager.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMessageManager.java.svn-base new file mode 100644 index 0000000..24f103b --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/.svn/text-base/OpenJPAMessageManager.java.svn-base @@ -0,0 +1,85 @@ +/**************************************************************** + * 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.openjpa; + +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.JPAMessageManager; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage; +import org.apache.james.mailbox.store.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; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.search.MessageSearchIndex; + +/** + * OpenJPA implementation of Mailbox + */ +public class OpenJPAMessageManager extends JPAMessageManager { + + private final AdvancedFeature feature; + + public static enum AdvancedFeature { + None, + Streaming, + Encryption + } + + public OpenJPAMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + MailboxEventDispatcher dispatcher, MailboxPathLocker locker, Mailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + this(mapperFactory, index, dispatcher, locker, mailbox, AdvancedFeature.None, aclResolver, groupMembershipResolver); + } + + public OpenJPAMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + MailboxEventDispatcher dispatcher, MailboxPathLocker locker, Mailbox mailbox, final AdvancedFeature f, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + this.feature = f; + } + + @Override + protected Message createMessage(Date internalDate, int size, int bodyStartOctet, SharedInputStream content, Flags flags, PropertyBuilder propertyBuilder) throws MailboxException { + int headerEnd = bodyStartOctet -2; + if (headerEnd < 0) { + headerEnd = 0; + } + switch (feature) { + case Streaming: + return new JPAStreamingMessage((JPAMailbox) getMailboxEntity(), internalDate, size, flags, content, bodyStartOctet, propertyBuilder); + case Encryption: + return new JPAEncryptedMessage((JPAMailbox) getMailboxEntity(), internalDate, size, flags, content, bodyStartOctet, propertyBuilder); + default: + return super.createMessage(internalDate, size, bodyStartOctet, content, flags, propertyBuilder); + } + + } + + + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java new file mode 100644 index 0000000..1c52fa8 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java @@ -0,0 +1,73 @@ +/**************************************************************** + * 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.openjpa; + + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.JPAMailboxManager; +import org.apache.james.mailbox.jpa.JPAMailboxSessionMapperFactory; +import org.apache.james.mailbox.jpa.mail.model.openjpa.EncryptDecryptHelper; +import org.apache.james.mailbox.jpa.openjpa.OpenJPAMessageManager.AdvancedFeature; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMessageManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * OpenJPA implementation of MailboxManager + * + */ +public class OpenJPAMailboxManager extends JPAMailboxManager { + + private AdvancedFeature feature; + + public OpenJPAMailboxManager(JPAMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxPathLocker locker, boolean useStreaming, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + if (useStreaming) { + feature = AdvancedFeature.Streaming; + } else { + feature = AdvancedFeature.None; + } + } + + public OpenJPAMailboxManager(JPAMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxPathLocker locker, String encryptPass, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + super(mapperFactory, authenticator, locker, aclResolver, groupMembershipResolver); + if (encryptPass != null) { + EncryptDecryptHelper.init(encryptPass); + feature = AdvancedFeature.Encryption; + } else { + feature = AdvancedFeature.None; + } + } + + public OpenJPAMailboxManager(JPAMailboxSessionMapperFactory mapperFactory, Authenticator authenticator, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) { + this(mapperFactory, authenticator, new JVMMailboxPathLocker(), false, aclResolver, groupMembershipResolver); + } + + @Override + protected StoreMessageManager createMessageManager(Mailbox mailboxRow, MailboxSession session) throws MailboxException { + StoreMessageManager result = new OpenJPAMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), mailboxRow, feature, getAclResolver(), getGroupMembershipResolver()); + return result; + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java new file mode 100644 index 0000000..24f103b --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java @@ -0,0 +1,85 @@ +/**************************************************************** + * 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.openjpa; + +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.JPAMessageManager; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAEncryptedMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAStreamingMessage; +import org.apache.james.mailbox.store.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; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.search.MessageSearchIndex; + +/** + * OpenJPA implementation of Mailbox + */ +public class OpenJPAMessageManager extends JPAMessageManager { + + private final AdvancedFeature feature; + + public static enum AdvancedFeature { + None, + Streaming, + Encryption + } + + public OpenJPAMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + MailboxEventDispatcher dispatcher, MailboxPathLocker locker, Mailbox mailbox, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + this(mapperFactory, index, dispatcher, locker, mailbox, AdvancedFeature.None, aclResolver, groupMembershipResolver); + } + + public OpenJPAMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index, + MailboxEventDispatcher dispatcher, MailboxPathLocker locker, Mailbox mailbox, final AdvancedFeature f, MailboxACLResolver aclResolver, GroupMembershipResolver groupMembershipResolver) throws MailboxException { + super(mapperFactory, index, dispatcher, locker, mailbox, aclResolver, groupMembershipResolver); + this.feature = f; + } + + @Override + protected Message createMessage(Date internalDate, int size, int bodyStartOctet, SharedInputStream content, Flags flags, PropertyBuilder propertyBuilder) throws MailboxException { + int headerEnd = bodyStartOctet -2; + if (headerEnd < 0) { + headerEnd = 0; + } + switch (feature) { + case Streaming: + return new JPAStreamingMessage((JPAMailbox) getMailboxEntity(), internalDate, size, flags, content, bodyStartOctet, propertyBuilder); + case Encryption: + return new JPAEncryptedMessage((JPAMailbox) getMailboxEntity(), internalDate, size, flags, content, bodyStartOctet, propertyBuilder); + default: + return super.createMessage(internalDate, size, bodyStartOctet, content, flags, propertyBuilder); + } + + } + + + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/all-wcprops new file mode 100644 index 0000000..a8cb1a3 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 99 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/user +END +JPASubscriptionMapper.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/user/JPASubscriptionMapper.java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/entries new file mode 100644 index 0000000..443aff0 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/user +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + +JPASubscriptionMapper.java +file + + + + +2013-09-02T02:54:40.000000Z +98591d60023358fbcec6e37636ee2ca3 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +4015 + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/text-base/JPASubscriptionMapper.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/text-base/JPASubscriptionMapper.java.svn-base new file mode 100644 index 0000000..d46a7b5 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/.svn/text-base/JPASubscriptionMapper.java.svn-base @@ -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.jpa.user; + +import java.util.List; + +import javax.persistence.EntityManagerFactory; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.jpa.JPATransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * JPA implementation of a {@link SubscriptionMapper}. This class is not thread-safe! + */ +public class JPASubscriptionMapper extends JPATransactionalMapper implements SubscriptionMapper { + + public JPASubscriptionMapper(final EntityManagerFactory entityManagerFactory) { + super(entityManagerFactory); + } + + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + public Subscription findMailboxSubscriptionForUser(final String user, final String mailbox) throws SubscriptionException { + try { + return (Subscription) getEntityManager().createNamedQuery("findFindMailboxSubscriptionForUser") + .setParameter("userParam", user).setParameter("mailboxParam", mailbox).getSingleResult(); + } catch (NoResultException e) { + return null; + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(Subscription) + */ + public void save(Subscription subscription) throws SubscriptionException { + try { + getEntityManager().persist(subscription); + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List findSubscriptionsForUser(String user) throws SubscriptionException { + try { + return (List) getEntityManager().createNamedQuery("findSubscriptionsForUser").setParameter("userParam", user).getResultList(); + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(Subscription) + */ + public void delete(Subscription subscription) throws SubscriptionException { + try { + getEntityManager().remove(subscription); + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/JPASubscriptionMapper.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/JPASubscriptionMapper.java new file mode 100644 index 0000000..d46a7b5 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/JPASubscriptionMapper.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.jpa.user; + +import java.util.List; + +import javax.persistence.EntityManagerFactory; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceException; + +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.jpa.JPATransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * JPA implementation of a {@link SubscriptionMapper}. This class is not thread-safe! + */ +public class JPASubscriptionMapper extends JPATransactionalMapper implements SubscriptionMapper { + + public JPASubscriptionMapper(final EntityManagerFactory entityManagerFactory) { + super(entityManagerFactory); + } + + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + public Subscription findMailboxSubscriptionForUser(final String user, final String mailbox) throws SubscriptionException { + try { + return (Subscription) getEntityManager().createNamedQuery("findFindMailboxSubscriptionForUser") + .setParameter("userParam", user).setParameter("mailboxParam", mailbox).getSingleResult(); + } catch (NoResultException e) { + return null; + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(Subscription) + */ + public void save(Subscription subscription) throws SubscriptionException { + try { + getEntityManager().persist(subscription); + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List findSubscriptionsForUser(String user) throws SubscriptionException { + try { + return (List) getEntityManager().createNamedQuery("findSubscriptionsForUser").setParameter("userParam", user).getResultList(); + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } + + /** + * @throws SubscriptionException + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(Subscription) + */ + public void delete(Subscription subscription) throws SubscriptionException { + try { + getEntityManager().remove(subscription); + } catch (PersistenceException e) { + throw new SubscriptionException(e); + } + } +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/all-wcprops new file mode 100644 index 0000000..f908b46 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 105 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model +END +JPASubscription.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/JPASubscription.java +END diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/entries b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/entries new file mode 100644 index 0000000..d5f64c3 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JPASubscription.java +file + + + + +2013-09-02T02:54:40.000000Z +aaf96bb15bcdea82808b68b869521bbf +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +4552 + diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/text-base/JPASubscription.java.svn-base b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/text-base/JPASubscription.java.svn-base new file mode 100644 index 0000000..251ed03 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/.svn/text-base/JPASubscription.java.svn-base @@ -0,0 +1,138 @@ +/**************************************************************** + * 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.user.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * A subscription to a mailbox by a user. + */ +@Entity(name = "Subscription") +@Table( + name = "JAMES_SUBSCRIPTION", + uniqueConstraints = + @UniqueConstraint( + columnNames={ + "USER_NAME", + "MAILBOX_NAME"}) +) +@NamedQueries({ + @NamedQuery(name = "findFindMailboxSubscriptionForUser", + query = "SELECT subscription FROM Subscription subscription WHERE subscription.username = :userParam AND subscription.mailbox = :mailboxParam"), + @NamedQuery(name = "findSubscriptionsForUser", + query = "SELECT subscription FROM Subscription subscription WHERE subscription.username = :userParam") +}) +public class JPASubscription implements Subscription { + + private static final String TO_STRING_SEPARATOR = " "; + + /** Primary key */ + @GeneratedValue + @Id + @Column(name = "SUBSCRIPTION_ID") + private long id; + + /** Name of the subscribed user */ + @Basic(optional = false) + @Column(name = "USER_NAME", nullable = false, length = 100) + private String username; + + /** Subscribed mailbox */ + @Basic(optional = false) + @Column(name = "MAILBOX_NAME", nullable = false, length = 100) + private String mailbox; + + /** + * Used by JPA + */ + @Deprecated + public JPASubscription() {} + + /** + * Constructs a user subscription. + * @param username not null + * @param mailbox not null + */ + public JPASubscription(String username, String mailbox) { + super(); + this.username = username; + this.mailbox = mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getMailbox() + */ + public String getMailbox() { + return mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getUser() + */ + public String getUser() { + return username; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPASubscription other = (JPASubscription) obj; + if (id != other.id) + return false; + return true; + } + + /** + * Renders output suitable for debugging. + * + * @return output suitable for debugging + */ + public String toString() { + final String result = "Subscription ( " + + "id = " + this.id + TO_STRING_SEPARATOR + + "user = " + this.username + TO_STRING_SEPARATOR + + "mailbox = " + this.mailbox + TO_STRING_SEPARATOR + + " )"; + return result; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/JPASubscription.java b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/JPASubscription.java new file mode 100644 index 0000000..251ed03 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/user/model/JPASubscription.java @@ -0,0 +1,138 @@ +/**************************************************************** + * 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.user.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * A subscription to a mailbox by a user. + */ +@Entity(name = "Subscription") +@Table( + name = "JAMES_SUBSCRIPTION", + uniqueConstraints = + @UniqueConstraint( + columnNames={ + "USER_NAME", + "MAILBOX_NAME"}) +) +@NamedQueries({ + @NamedQuery(name = "findFindMailboxSubscriptionForUser", + query = "SELECT subscription FROM Subscription subscription WHERE subscription.username = :userParam AND subscription.mailbox = :mailboxParam"), + @NamedQuery(name = "findSubscriptionsForUser", + query = "SELECT subscription FROM Subscription subscription WHERE subscription.username = :userParam") +}) +public class JPASubscription implements Subscription { + + private static final String TO_STRING_SEPARATOR = " "; + + /** Primary key */ + @GeneratedValue + @Id + @Column(name = "SUBSCRIPTION_ID") + private long id; + + /** Name of the subscribed user */ + @Basic(optional = false) + @Column(name = "USER_NAME", nullable = false, length = 100) + private String username; + + /** Subscribed mailbox */ + @Basic(optional = false) + @Column(name = "MAILBOX_NAME", nullable = false, length = 100) + private String mailbox; + + /** + * Used by JPA + */ + @Deprecated + public JPASubscription() {} + + /** + * Constructs a user subscription. + * @param username not null + * @param mailbox not null + */ + public JPASubscription(String username, String mailbox) { + super(); + this.username = username; + this.mailbox = mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getMailbox() + */ + public String getMailbox() { + return mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getUser() + */ + public String getUser() { + return username; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (id ^ (id >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final JPASubscription other = (JPASubscription) obj; + if (id != other.id) + return false; + return true; + } + + /** + * Renders output suitable for debugging. + * + * @return output suitable for debugging + */ + public String toString() { + final String result = "Subscription ( " + + "id = " + this.id + TO_STRING_SEPARATOR + + "user = " + this.username + TO_STRING_SEPARATOR + + "mailbox = " + this.mailbox + TO_STRING_SEPARATOR + + " )"; + return result; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..ba5cd97 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jpa/src/main/resources +END +james-database.properties +K 25 +svn:wc:ra_dav:version-url +V 96 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jpa/src/main/resources/james-database.properties +END diff --git a/james/apache-james-mailbox/jpa/src/main/resources/.svn/entries b/james/apache-james-mailbox/jpa/src/main/resources/.svn/entries new file mode 100644 index 0000000..0c8b63d --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/resources +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + +james-database.properties +file + + + + +2013-09-02T02:54:40.000000Z +26ebc0e378ea7f5269e2418f50b37c54 +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +1078 + diff --git a/james/apache-james-mailbox/jpa/src/main/resources/.svn/text-base/james-database.properties.svn-base b/james/apache-james-mailbox/jpa/src/main/resources/.svn/text-base/james-database.properties.svn-base new file mode 100644 index 0000000..0821945 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/.svn/text-base/james-database.properties.svn-base @@ -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. +# + +# See http://james.apache.org/server/3/config.html for usage + +database.driverClassName=org.apache.derby.jdbc.EmbeddedDriver +database.url=jdbc:derby:../var/store/derby;create=true +database.username=app +database.password=app +vendorAdapter.database=DERBY +openjpa.streaming=false diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..1b7b414 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jpa/src/main/resources/META-INF +END +persistence.xml +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jpa/src/main/resources/META-INF/persistence.xml +END diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/entries b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/entries new file mode 100644 index 0000000..4fce968 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + +persistence.xml +file + + + + +2013-09-02T02:54:40.000000Z +c9558e92191552e1caeee58148f719b6 +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +2126 + diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/text-base/persistence.xml.svn-base b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/text-base/persistence.xml.svn-base new file mode 100644 index 0000000..c5e4aa2 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/.svn/text-base/persistence.xml.svn-base @@ -0,0 +1,42 @@ + + + + + + + org.apache.james.mailbox.jpa.mail.model.JPAMailbox + org.apache.james.mailbox.jpa.mail.model.JPAUserFlag + org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage + org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage + org.apache.james.mailbox.jpa.mail.model.JPAProperty + org.apache.james.mailbox.jpa.user.model.JPASubscription + + + + + + + + + + diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/persistence.xml b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..c5e4aa2 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,42 @@ + + + + + + + org.apache.james.mailbox.jpa.mail.model.JPAMailbox + org.apache.james.mailbox.jpa.mail.model.JPAUserFlag + org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage + org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage + org.apache.james.mailbox.jpa.mail.model.JPAProperty + org.apache.james.mailbox.jpa.user.model.JPASubscription + + + + + + + + + + diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/all-wcprops new file mode 100644 index 0000000..07c8ad4 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jpa/src/main/resources/META-INF/spring +END +mailbox-jpa.xml +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml +END diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/entries b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/entries new file mode 100644 index 0000000..61a2f48 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/main/resources/META-INF/spring +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-jpa.xml +file + + + + +2013-09-02T02:54:40.000000Z +0ba847fb96de33f65702099ff31656ef +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +4096 + diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jpa.xml.svn-base b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jpa.xml.svn-base new file mode 100644 index 0000000..cf4aa9f --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/.svn/text-base/mailbox-jpa.xml.svn-base @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml new file mode 100644 index 0000000..cf4aa9f --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jpa/src/main/resources/james-database.properties b/james/apache-james-mailbox/jpa/src/main/resources/james-database.properties new file mode 100644 index 0000000..0821945 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/main/resources/james-database.properties @@ -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. +# + +# See http://james.apache.org/server/3/config.html for usage + +database.driverClassName=org.apache.derby.jdbc.EmbeddedDriver +database.url=jdbc:derby:../var/store/derby;create=true +database.username=app +database.password=app +vendorAdapter.database=DERBY +openjpa.streaming=false diff --git a/james/apache-james-mailbox/jpa/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..358cd2d --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/jpa/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/jpa/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/jpa/src/reporting-site/.svn/entries b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/entries new file mode 100644 index 0000000..850091f --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-03-05T11:38:38.771020Z +1078275 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:40.000000Z +5bde8261948ff1881960902a322aa196 +2011-03-05T11:38:38.771020Z +1078275 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/jpa/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/jpa/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jpa/src/reporting-site/site.xml b/james/apache-james-mailbox/jpa/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/jpa/src/test/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/test/.svn/all-wcprops new file mode 100644 index 0000000..9ab07a2 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test +END diff --git a/james/apache-james-mailbox/jpa/src/test/.svn/entries b/james/apache-james-mailbox/jpa/src/test/.svn/entries new file mode 100644 index 0000000..b5a0088 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/test +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/jpa/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..7e1ead9 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 65 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test/java +END diff --git a/james/apache-james-mailbox/jpa/src/test/java/.svn/entries b/james/apache-james-mailbox/jpa/src/test/java/.svn/entries new file mode 100644 index 0000000..0e2e250 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/test/java +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..8043afb --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test/java/org +END diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/.svn/entries b/james/apache-james-mailbox/jpa/src/test/java/org/.svn/entries new file mode 100644 index 0000000..69b995e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/test/java/org +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..9c6db29 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 76 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..8c33454 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..43a4cbb --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..fcf3a98 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..195e97b --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..cbb87ed --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +jpa +dir + diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops new file mode 100644 index 0000000..41ab0ad --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test/java/org/apache/james/mailbox/jpa +END +JPAMailboxManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java +END +JPASubscriptionManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1139535/james/mailbox/trunk/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java +END +JPAStressTest.java +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAStressTest.java +END diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/entries b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/entries new file mode 100644 index 0000000..dd304a2 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/jpa/src/test/java/org/apache/james/mailbox/jpa +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JPAMailboxManagerTest.java +file + + + + +2013-09-02T02:54:40.000000Z +8279a6c44cc7bbd4196c2c601cab7fff +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +5888 + +JPASubscriptionManagerTest.java +file + + + + +2013-09-02T02:54:40.000000Z +5791f6fbb729e0021337c128a2df02ea +2011-06-25T12:29:24.868051Z +1139535 +norman + + + + + + + + + + + + + + + + + + + + + +3837 + +JPAStressTest.java +file + + + + +2013-09-02T02:54:40.000000Z +65e45e4959aed4cd5f8f0f0bf008ef7a +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +5532 + diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManagerTest.java.svn-base b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManagerTest.java.svn-base new file mode 100644 index 0000000..5453f5e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAMailboxManagerTest.java.svn-base @@ -0,0 +1,128 @@ +/**************************************************************** + * 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 java.util.HashMap; + +import javax.persistence.EntityManagerFactory; + +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.JPAModSeqProvider; +import org.apache.james.mailbox.jpa.mail.JPAUidProvider; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.openjpa.OpenJPAMailboxManager; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * JPAMailboxManagerTest that extends the StoreMailboxManagerTest. + */ +public class JPAMailboxManagerTest extends AbstractMailboxManagerTest { + + /** + * The entity manager factory. + */ + private static EntityManagerFactory entityManagerFactory; + + /** + * Setup the mailboxManager. + * + * @throws Exception + */ + @Before + public void setup() throws Exception { + createMailboxManager(); + } + + /** + * Close the system session and entityManagerFactory + * + * @throws MailboxException + * @throws BadCredentialsException + */ + @After + public void tearDown() throws BadCredentialsException, MailboxException { + deleteAllMailboxes(); + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + entityManagerFactory.close(); + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() throws MailboxException { + + HashMap properties = new HashMap(); + properties.put("openjpa.ConnectionDriverName", "org.h2.Driver"); + properties.put("openjpa.ConnectionURL", "jdbc:h2:mem:imap;DB_CLOSE_DELAY=-1"); + properties.put("openjpa.Log", "JDBC=WARN, SQL=WARN, Runtime=WARN"); + properties.put("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=72"); + properties.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); + properties.put("openjpa.MetaDataFactory", "jpa(Types=" + + JPAMailbox.class.getName() + ";" + + AbstractJPAMessage.class.getName() + ";" + + JPAMessage.class.getName() + ";" + + JPAProperty.class.getName() + ";" + + JPAUserFlag.class.getName() + ";" + + JPASubscription.class.getName() + ")"); + + entityManagerFactory = OpenJPAPersistence.getEntityManagerFactory(properties); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JPAMailboxSessionMapperFactory mf = new JPAMailboxSessionMapperFactory(entityManagerFactory, new JPAUidProvider(locker, entityManagerFactory), new JPAModSeqProvider(locker, entityManagerFactory)); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + JPAMailboxManager mailboxManager = new OpenJPAMailboxManager(mf, null, aclResolver, groupMembershipResolver); + mailboxManager.init(); + + setMailboxManager(mailboxManager); + + deleteAllMailboxes(); + + } + + private void deleteAllMailboxes() throws BadCredentialsException, MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + try { + ((OpenJPAMailboxManager) mailboxManager).deleteEverything(session); + } catch (MailboxException e) { + e.printStackTrace(); + } + session.close(); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAStressTest.java.svn-base b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAStressTest.java.svn-base new file mode 100644 index 0000000..e3f79e9 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPAStressTest.java.svn-base @@ -0,0 +1,118 @@ +/**************************************************************** + * 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 java.util.HashMap; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.apache.james.mailbox.AbstractStressTest; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.JPAModSeqProvider; +import org.apache.james.mailbox.jpa.mail.JPAUidProvider; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.openjpa.OpenJPAMailboxManager; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * Proof of bug https://issues.apache.org/jira/browse/IMAP-137 + */ +public class JPAStressTest extends AbstractStressTest { + + private OpenJPAMailboxManager mailboxManager; + + private long locktimeout = 60000; + + private EntityManagerFactory entityManagerFactory; + + @Before + public void setUp() throws MailboxException { + + HashMap properties = new HashMap(); + properties.put("openjpa.ConnectionDriverName", org.h2.Driver.class.getName()); + properties.put("openjpa.ConnectionURL", "jdbc:h2:mem:mailboxintegrationstress;DB_CLOSE_DELAY=-1"); + properties.put("openjpa.Log", "JDBC=WARN, SQL=WARN, Runtime=WARN"); + properties.put("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=72"); + properties.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); + properties.put("openjpa.MetaDataFactory", "jpa(Types=" + + JPAMailbox.class.getName() + ";" + + AbstractJPAMessage.class.getName() + ";" + + JPAMessage.class.getName() + ";" + + JPAProperty.class.getName() + ";" + + JPAUserFlag.class.getName() + ";" + + JPASubscription.class.getName() + ")"); + properties.put("openjpa.LockTimeout", locktimeout + ""); + + entityManagerFactory = OpenJPAPersistence.getEntityManagerFactory(properties); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JPAMailboxSessionMapperFactory mf = new JPAMailboxSessionMapperFactory(entityManagerFactory, new JPAUidProvider(locker, entityManagerFactory), new JPAModSeqProvider(locker, entityManagerFactory)); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + mailboxManager = new OpenJPAMailboxManager(mf, null, aclResolver, groupMembershipResolver); + mailboxManager.init(); + + // Set the lock timeout via SQL because of a bug in openJPA + // https://issues.apache.org/jira/browse/OPENJPA-1656 + setH2LockTimeout(); + + } + + private void setH2LockTimeout() { + EntityManager manager = entityManagerFactory.createEntityManager(); + manager.getTransaction().begin(); + manager.createNativeQuery("SET DEFAULT_LOCK_TIMEOUT " + locktimeout).executeUpdate(); + manager.getTransaction().commit(); + manager.close(); + } + + @After + public void tearDown() { + MailboxSession session = mailboxManager.createSystemSession("test", LoggerFactory.getLogger("Test")); + try { + mailboxManager.deleteEverything(session); + } catch (MailboxException e) { + e.printStackTrace(); + } + session.close(); + } + + @Override + protected MailboxManager getMailboxManager() { + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManagerTest.java.svn-base b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManagerTest.java.svn-base new file mode 100644 index 0000000..8c7ed59 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/.svn/text-base/JPASubscriptionManagerTest.java.svn-base @@ -0,0 +1,78 @@ +/**************************************************************** + * 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 java.util.HashMap; + +import org.apache.james.mailbox.AbstractSubscriptionManagerTest; +import org.apache.james.mailbox.SubscriptionManager; +import org.apache.james.mailbox.jpa.mail.JPAModSeqProvider; +import org.apache.james.mailbox.jpa.mail.JPAUidProvider; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.junit.After; +import org.junit.Before; + +public class JPASubscriptionManagerTest extends AbstractSubscriptionManagerTest{ + + private OpenJPAEntityManagerFactory entityManagerFactory; + + @Before + public void setUp() { + + HashMap properties = new HashMap(); + properties.put("openjpa.ConnectionDriverName", "org.h2.Driver"); + properties.put("openjpa.ConnectionURL", "jdbc:h2:mem:imap;DB_CLOSE_DELAY=-1"); + properties.put("openjpa.Log", "JDBC=WARN, SQL=WARN, Runtime=WARN"); + properties.put("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=72"); + properties.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); + properties.put("openjpa.MetaDataFactory", "jpa(Types=" + + JPAMailbox.class.getName() + ";" + + AbstractJPAMessage.class.getName() + ";" + + JPAMessage.class.getName() + ";" + + JPAProperty.class.getName() + ";" + + JPAUserFlag.class.getName() + ";" + + JPASubscription.class.getName() + ")"); + + entityManagerFactory = OpenJPAPersistence.getEntityManagerFactory(properties); + } + + @Override + public SubscriptionManager createSubscriptionManager() { + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + + JPAMailboxSessionMapperFactory mf = new JPAMailboxSessionMapperFactory(entityManagerFactory, new JPAUidProvider(locker, entityManagerFactory), new JPAModSeqProvider(locker, entityManagerFactory)); + + JPASubscriptionManager sm = new JPASubscriptionManager(mf); + + return sm; + } + + @After + public void tearDown() { + entityManagerFactory.close(); + } +} diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java new file mode 100644 index 0000000..5453f5e --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java @@ -0,0 +1,128 @@ +/**************************************************************** + * 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 java.util.HashMap; + +import javax.persistence.EntityManagerFactory; + +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.JPAModSeqProvider; +import org.apache.james.mailbox.jpa.mail.JPAUidProvider; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.openjpa.OpenJPAMailboxManager; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * JPAMailboxManagerTest that extends the StoreMailboxManagerTest. + */ +public class JPAMailboxManagerTest extends AbstractMailboxManagerTest { + + /** + * The entity manager factory. + */ + private static EntityManagerFactory entityManagerFactory; + + /** + * Setup the mailboxManager. + * + * @throws Exception + */ + @Before + public void setup() throws Exception { + createMailboxManager(); + } + + /** + * Close the system session and entityManagerFactory + * + * @throws MailboxException + * @throws BadCredentialsException + */ + @After + public void tearDown() throws BadCredentialsException, MailboxException { + deleteAllMailboxes(); + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + entityManagerFactory.close(); + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() throws MailboxException { + + HashMap properties = new HashMap(); + properties.put("openjpa.ConnectionDriverName", "org.h2.Driver"); + properties.put("openjpa.ConnectionURL", "jdbc:h2:mem:imap;DB_CLOSE_DELAY=-1"); + properties.put("openjpa.Log", "JDBC=WARN, SQL=WARN, Runtime=WARN"); + properties.put("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=72"); + properties.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); + properties.put("openjpa.MetaDataFactory", "jpa(Types=" + + JPAMailbox.class.getName() + ";" + + AbstractJPAMessage.class.getName() + ";" + + JPAMessage.class.getName() + ";" + + JPAProperty.class.getName() + ";" + + JPAUserFlag.class.getName() + ";" + + JPASubscription.class.getName() + ")"); + + entityManagerFactory = OpenJPAPersistence.getEntityManagerFactory(properties); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JPAMailboxSessionMapperFactory mf = new JPAMailboxSessionMapperFactory(entityManagerFactory, new JPAUidProvider(locker, entityManagerFactory), new JPAModSeqProvider(locker, entityManagerFactory)); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + JPAMailboxManager mailboxManager = new OpenJPAMailboxManager(mf, null, aclResolver, groupMembershipResolver); + mailboxManager.init(); + + setMailboxManager(mailboxManager); + + deleteAllMailboxes(); + + } + + private void deleteAllMailboxes() throws BadCredentialsException, MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + try { + ((OpenJPAMailboxManager) mailboxManager).deleteEverything(session); + } catch (MailboxException e) { + e.printStackTrace(); + } + session.close(); + } + +} diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAStressTest.java b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAStressTest.java new file mode 100644 index 0000000..e3f79e9 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAStressTest.java @@ -0,0 +1,118 @@ +/**************************************************************** + * 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 java.util.HashMap; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.apache.james.mailbox.AbstractStressTest; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.jpa.mail.JPAModSeqProvider; +import org.apache.james.mailbox.jpa.mail.JPAUidProvider; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.openjpa.OpenJPAMailboxManager; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * Proof of bug https://issues.apache.org/jira/browse/IMAP-137 + */ +public class JPAStressTest extends AbstractStressTest { + + private OpenJPAMailboxManager mailboxManager; + + private long locktimeout = 60000; + + private EntityManagerFactory entityManagerFactory; + + @Before + public void setUp() throws MailboxException { + + HashMap properties = new HashMap(); + properties.put("openjpa.ConnectionDriverName", org.h2.Driver.class.getName()); + properties.put("openjpa.ConnectionURL", "jdbc:h2:mem:mailboxintegrationstress;DB_CLOSE_DELAY=-1"); + properties.put("openjpa.Log", "JDBC=WARN, SQL=WARN, Runtime=WARN"); + properties.put("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=72"); + properties.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); + properties.put("openjpa.MetaDataFactory", "jpa(Types=" + + JPAMailbox.class.getName() + ";" + + AbstractJPAMessage.class.getName() + ";" + + JPAMessage.class.getName() + ";" + + JPAProperty.class.getName() + ";" + + JPAUserFlag.class.getName() + ";" + + JPASubscription.class.getName() + ")"); + properties.put("openjpa.LockTimeout", locktimeout + ""); + + entityManagerFactory = OpenJPAPersistence.getEntityManagerFactory(properties); + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + JPAMailboxSessionMapperFactory mf = new JPAMailboxSessionMapperFactory(entityManagerFactory, new JPAUidProvider(locker, entityManagerFactory), new JPAModSeqProvider(locker, entityManagerFactory)); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + mailboxManager = new OpenJPAMailboxManager(mf, null, aclResolver, groupMembershipResolver); + mailboxManager.init(); + + // Set the lock timeout via SQL because of a bug in openJPA + // https://issues.apache.org/jira/browse/OPENJPA-1656 + setH2LockTimeout(); + + } + + private void setH2LockTimeout() { + EntityManager manager = entityManagerFactory.createEntityManager(); + manager.getTransaction().begin(); + manager.createNativeQuery("SET DEFAULT_LOCK_TIMEOUT " + locktimeout).executeUpdate(); + manager.getTransaction().commit(); + manager.close(); + } + + @After + public void tearDown() { + MailboxSession session = mailboxManager.createSystemSession("test", LoggerFactory.getLogger("Test")); + try { + mailboxManager.deleteEverything(session); + } catch (MailboxException e) { + e.printStackTrace(); + } + session.close(); + } + + @Override + protected MailboxManager getMailboxManager() { + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java new file mode 100644 index 0000000..8c7ed59 --- /dev/null +++ b/james/apache-james-mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java @@ -0,0 +1,78 @@ +/**************************************************************** + * 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 java.util.HashMap; + +import org.apache.james.mailbox.AbstractSubscriptionManagerTest; +import org.apache.james.mailbox.SubscriptionManager; +import org.apache.james.mailbox.jpa.mail.JPAModSeqProvider; +import org.apache.james.mailbox.jpa.mail.JPAUidProvider; +import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; +import org.apache.james.mailbox.jpa.mail.model.JPAProperty; +import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; +import org.apache.james.mailbox.jpa.mail.model.openjpa.AbstractJPAMessage; +import org.apache.james.mailbox.jpa.mail.model.openjpa.JPAMessage; +import org.apache.james.mailbox.jpa.user.model.JPASubscription; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.junit.After; +import org.junit.Before; + +public class JPASubscriptionManagerTest extends AbstractSubscriptionManagerTest{ + + private OpenJPAEntityManagerFactory entityManagerFactory; + + @Before + public void setUp() { + + HashMap properties = new HashMap(); + properties.put("openjpa.ConnectionDriverName", "org.h2.Driver"); + properties.put("openjpa.ConnectionURL", "jdbc:h2:mem:imap;DB_CLOSE_DELAY=-1"); + properties.put("openjpa.Log", "JDBC=WARN, SQL=WARN, Runtime=WARN"); + properties.put("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=72"); + properties.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)"); + properties.put("openjpa.MetaDataFactory", "jpa(Types=" + + JPAMailbox.class.getName() + ";" + + AbstractJPAMessage.class.getName() + ";" + + JPAMessage.class.getName() + ";" + + JPAProperty.class.getName() + ";" + + JPAUserFlag.class.getName() + ";" + + JPASubscription.class.getName() + ")"); + + entityManagerFactory = OpenJPAPersistence.getEntityManagerFactory(properties); + } + + @Override + public SubscriptionManager createSubscriptionManager() { + JVMMailboxPathLocker locker = new JVMMailboxPathLocker(); + + JPAMailboxSessionMapperFactory mf = new JPAMailboxSessionMapperFactory(entityManagerFactory, new JPAUidProvider(locker, entityManagerFactory), new JPAModSeqProvider(locker, entityManagerFactory)); + + JPASubscriptionManager sm = new JPASubscriptionManager(mf); + + return sm; + } + + @After + public void tearDown() { + entityManagerFactory.close(); + } +} diff --git a/james/apache-james-mailbox/lucene/.svn/all-wcprops b/james/apache-james-mailbox/lucene/.svn/all-wcprops new file mode 100644 index 0000000..9f6e489 --- /dev/null +++ b/james/apache-james-mailbox/lucene/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 54 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/lucene +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/lucene/pom.xml +END diff --git a/james/apache-james-mailbox/lucene/.svn/dir-prop-base b/james/apache-james-mailbox/lucene/.svn/dir-prop-base new file mode 100644 index 0000000..0412bba --- /dev/null +++ b/james/apache-james-mailbox/lucene/.svn/dir-prop-base @@ -0,0 +1,8 @@ +K 10 +svn:ignore +V 22 +target +.* +coverage.ec + +END diff --git a/james/apache-james-mailbox/lucene/.svn/entries b/james/apache-james-mailbox/lucene/.svn/entries new file mode 100644 index 0000000..69a0b6f --- /dev/null +++ b/james/apache-james-mailbox/lucene/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene +http://svn.apache.org/repos/asf + + + +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:40.000000Z +e43f71cab03600600775026df8650b89 +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +3424 + diff --git a/james/apache-james-mailbox/lucene/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/lucene/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..cb257fe --- /dev/null +++ b/james/apache-james-mailbox/lucene/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,89 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-lucene + Apache James :: Mailbox :: Lucene Index + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-mime4j-core + + + org.apache.james + apache-mime4j-dom + + + org.slf4j + slf4j-api + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.apache.lucene + lucene-core + + + org.apache.lucene + lucene-analyzers + + + org.apache.lucene + lucene-smartcn + + + junit + junit + test + + + org.apache.james + apache-james-mailbox-api + test-jar + test + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + diff --git a/james/apache-james-mailbox/lucene/pom.xml b/james/apache-james-mailbox/lucene/pom.xml new file mode 100644 index 0000000..cb257fe --- /dev/null +++ b/james/apache-james-mailbox/lucene/pom.xml @@ -0,0 +1,89 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-lucene + Apache James :: Mailbox :: Lucene Index + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-mime4j-core + + + org.apache.james + apache-mime4j-dom + + + org.slf4j + slf4j-api + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.apache.lucene + lucene-core + + + org.apache.lucene + lucene-analyzers + + + org.apache.lucene + lucene-smartcn + + + junit + junit + test + + + org.apache.james + apache-james-mailbox-api + test-jar + test + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + diff --git a/james/apache-james-mailbox/lucene/src/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/.svn/all-wcprops new file mode 100644 index 0000000..7f12533 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/lucene/src +END diff --git a/james/apache-james-mailbox/lucene/src/.svn/entries b/james/apache-james-mailbox/lucene/src/.svn/entries new file mode 100644 index 0000000..f9c2a62 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/.svn/all-wcprops new file mode 100644 index 0000000..13391ab --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/lucene/src/main +END diff --git a/james/apache-james-mailbox/lucene/src/main/.svn/entries b/james/apache-james-mailbox/lucene/src/main/.svn/entries new file mode 100644 index 0000000..a7a0ce1 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..fa5e10e --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 68 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java +END diff --git a/james/apache-james-mailbox/lucene/src/main/java/.svn/entries b/james/apache-james-mailbox/lucene/src/main/java/.svn/entries new file mode 100644 index 0000000..d397842 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/java +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..c5ed366 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java/org +END diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/.svn/entries b/james/apache-james-mailbox/lucene/src/main/java/org/.svn/entries new file mode 100644 index 0000000..ceaed97 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/java/org +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..20b6aed --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..525b161 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..0a38aed --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 85 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..989abc1 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..5e9eb00 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..afe1c0b --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +lucene +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/all-wcprops new file mode 100644 index 0000000..33886e9 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 100 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene +END diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/entries b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/entries new file mode 100644 index 0000000..b2cd312 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +search +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops new file mode 100644 index 0000000..f379ff2 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 107 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene/search +END +LenientImapSearchAnalyzer.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1134921/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LenientImapSearchAnalyzer.java +END +StrictImapSearchAnalyzer.java +K 25 +svn:wc:ra_dav:version-url +V 137 +/repos/asf/!svn/ver/1179570/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene/search/StrictImapSearchAnalyzer.java +END +UpperCaseFilter.java +K 25 +svn:wc:ra_dav:version-url +V 128 +/repos/asf/!svn/ver/1134921/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene/search/UpperCaseFilter.java +END +LuceneMessageSearchIndex.java +K 25 +svn:wc:ra_dav:version-url +V 137 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java +END diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/entries b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/entries new file mode 100644 index 0000000..f0fd115 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/entries @@ -0,0 +1,164 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/java/org/apache/james/mailbox/lucene/search +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +LenientImapSearchAnalyzer.java +file + + + + +2013-09-02T02:54:40.000000Z +e880f002263b09809ce72426aefc2d8f +2011-06-12T12:56:09.906805Z +1134921 +norman + + + + + + + + + + + + + + + + + + + + + +2281 + +StrictImapSearchAnalyzer.java +file + + + + +2013-09-02T02:54:40.000000Z +c20e693337bef3b61a0b74a14ad6d7a0 +2011-10-06T11:11:50.191621Z +1179570 +felixk + + + + + + + + + + + + + + + + + + + + + +2439 + +UpperCaseFilter.java +file + + + + +2013-09-02T02:54:40.000000Z +f69616bb3ba66e17b21f009e8b211871 +2011-06-12T12:56:09.906805Z +1134921 +norman + + + + + + + + + + + + + + + + + + + + + +2104 + +LuceneMessageSearchIndex.java +file + + + + +2013-09-02T02:54:40.000000Z +16bc1a5a1db6212f52d61ef217e6259c +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +59817 + diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LenientImapSearchAnalyzer.java.svn-base b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LenientImapSearchAnalyzer.java.svn-base new file mode 100644 index 0000000..1a46ebb --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LenientImapSearchAnalyzer.java.svn-base @@ -0,0 +1,54 @@ +/**************************************************************** + * 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.lucene.search; + +import java.io.Reader; + + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.WhitespaceTokenizer; +import org.apache.lucene.analysis.shingle.ShingleFilter; +import org.apache.lucene.util.Version; + +/** + * This {@link Analyzer} is not 100% conform with RFC3501 but does + * most times exactly what the user would expect. + * + */ +public final class LenientImapSearchAnalyzer extends Analyzer{ + + public final static int DEFAULT_MAX_TOKEN_LENGTH = 4; + + private final int maxTokenLength; + + + public LenientImapSearchAnalyzer(int maxTokenLength) { + this.maxTokenLength = maxTokenLength; + } + + public LenientImapSearchAnalyzer() { + this(DEFAULT_MAX_TOKEN_LENGTH); + } + + @Override + public TokenStream tokenStream(String arg0, Reader reader) { + return new ShingleFilter(new UpperCaseFilter(new WhitespaceTokenizer(Version.LUCENE_31, reader)), 2, maxTokenLength); + } +} diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndex.java.svn-base b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndex.java.svn-base new file mode 100644 index 0000000..7062bf6 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndex.java.svn-base @@ -0,0 +1,1303 @@ +/**************************************************************** + * 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.lucene.search; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.UnsupportedSearchException; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SearchQuery.AllCriterion; +import org.apache.james.mailbox.model.SearchQuery.ContainsOperator; +import org.apache.james.mailbox.model.SearchQuery.Criterion; +import org.apache.james.mailbox.model.SearchQuery.CustomFlagCriterion; +import org.apache.james.mailbox.model.SearchQuery.DateOperator; +import org.apache.james.mailbox.model.SearchQuery.DateResolution; +import org.apache.james.mailbox.model.SearchQuery.FlagCriterion; +import org.apache.james.mailbox.model.SearchQuery.HeaderCriterion; +import org.apache.james.mailbox.model.SearchQuery.HeaderOperator; +import org.apache.james.mailbox.model.SearchQuery.NumericOperator; +import org.apache.james.mailbox.model.SearchQuery.NumericRange; +import org.apache.james.mailbox.model.SearchQuery.UidCriterion; +import org.apache.james.mailbox.store.mail.MessageMapperFactory; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex; +import org.apache.james.mailbox.store.search.SearchUtil; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.dom.datetime.DateTime; +import org.apache.james.mime4j.dom.field.DateTimeField; +import org.apache.james.mime4j.field.address.AddressFormatter; +import org.apache.james.mime4j.field.address.LenientAddressBuilder; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.message.SimpleContentHandler; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.util.MimeUtil; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.document.DateTools; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.Field.Index; +import org.apache.lucene.document.Field.Store; +import org.apache.lucene.document.NumericField; +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.IndexWriterConfig.OpenMode; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.NumericRangeQuery; +import org.apache.lucene.search.PrefixQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TermRangeQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.WildcardQuery; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.LockObtainFailedException; +import org.apache.lucene.util.Version; + +/** + * Lucene based {@link ListeningMessageSearchIndex} which offers message searching via a Lucene index + * + * + + * @param + */ +public class LuceneMessageSearchIndex extends ListeningMessageSearchIndex{ + private final static Date MAX_DATE; + private final static Date MIN_DATE; + + static { + Calendar cal = Calendar.getInstance(); + cal.set(9999, 11, 31); + MAX_DATE = cal.getTime(); + + cal.set(0000, 0, 1); + MIN_DATE = cal.getTime(); + } + + /** + * Default max query results + */ + public final static int DEFAULT_MAX_QUERY_RESULTS = 100000; + + /** + * {@link Field} which will contain the unique index of the {@link Document} + */ + public final static String ID_FIELD ="id"; + + + /** + * {@link Field} which will contain uid of the {@link Message} + */ + public final static String UID_FIELD = "uid"; + + /** + * {@link Field} which will contain the {@link Flags} of the {@link Message} + */ + public final static String FLAGS_FIELD = "flags"; + + /** + * {@link Field} which will contain the size of the {@link Message} + */ + public final static String SIZE_FIELD = "size"; + + /** + * {@link Field} which will contain the body of the {@link Message} + */ + public final static String BODY_FIELD = "body"; + + + /** + * Prefix which will be used for each message header to store it also in a seperate {@link Field} + */ + public final static String PREFIX_HEADER_FIELD ="header_"; + + /** + * {@link Field} which will contain the whole message header of the {@link Message} + */ + public final static String HEADERS_FIELD ="headers"; + + /** + * {@link Field} which will contain the mod-sequence of the message + */ + public final static String MODSEQ_FIELD = "modSeq"; + + /** + * {@link Field} which will contain the TO-Address of the message + */ + public final static String TO_FIELD ="to"; + + public final static String FIRST_TO_MAILBOX_NAME_FIELD ="firstToMailboxName"; + public final static String FIRST_TO_MAILBOX_DISPLAY_FIELD ="firstToMailboxDisplay"; + + /** + * {@link Field} which will contain the CC-Address of the message + */ + public final static String CC_FIELD ="cc"; + + public final static String FIRST_CC_MAILBOX_NAME_FIELD ="firstCcMailboxName"; + + + /** + * {@link Field} which will contain the FROM-Address of the message + */ + public final static String FROM_FIELD ="from"; + + public final static String FIRST_FROM_MAILBOX_NAME_FIELD ="firstFromMailboxName"; + public final static String FIRST_FROM_MAILBOX_DISPLAY_FIELD ="firstFromMailboxDisplay"; + + /** + * {@link Field} which will contain the BCC-Address of the message + */ + public final static String BCC_FIELD ="bcc"; + + + public final static String BASE_SUBJECT_FIELD = "baseSubject"; + + /** + * {@link Field} which contain the internalDate of the message with YEAR-Resolution + */ + public final static String INTERNAL_DATE_FIELD_YEAR_RESOLUTION ="internaldateYearResolution"; + + + /** + * {@link Field} which contain the internalDate of the message with MONTH-Resolution + */ + public final static String INTERNAL_DATE_FIELD_MONTH_RESOLUTION ="internaldateMonthResolution"; + + /** + * {@link Field} which contain the internalDate of the message with DAY-Resolution + */ + public final static String INTERNAL_DATE_FIELD_DAY_RESOLUTION ="internaldateDayResolution"; + + /** + * {@link Field} which contain the internalDate of the message with HOUR-Resolution + */ + public final static String INTERNAL_DATE_FIELD_HOUR_RESOLUTION ="internaldateHourResolution"; + + /** + * {@link Field} which contain the internalDate of the message with MINUTE-Resolution + */ + public final static String INTERNAL_DATE_FIELD_MINUTE_RESOLUTION ="internaldateMinuteResolution"; + + /** + * {@link Field} which contain the internalDate of the message with SECOND-Resolution + */ + public final static String INTERNAL_DATE_FIELD_SECOND_RESOLUTION ="internaldateSecondResolution"; + + + /** + * {@link Field} which contain the internalDate of the message with MILLISECOND-Resolution + */ + public final static String INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION ="internaldateMillisecondResolution"; + + /** + * {@link Field} which will contain the id of the {@link Mailbox} + */ + public final static String MAILBOX_ID_FIELD ="mailboxid"; + + /** + * {@link Field} which contain the Date header of the message with YEAR-Resolution + */ + public final static String SENT_DATE_FIELD_YEAR_RESOLUTION ="sentdateYearResolution"; + + + /** + * {@link Field} which contain the Date header of the message with MONTH-Resolution + */ + public final static String SENT_DATE_FIELD_MONTH_RESOLUTION ="sentdateMonthResolution"; + + /** + * {@link Field} which contain the Date header of the message with DAY-Resolution + */ + public final static String SENT_DATE_FIELD_DAY_RESOLUTION ="sentdateDayResolution"; + + /** + * {@link Field} which contain the Date header of the message with HOUR-Resolution + */ + public final static String SENT_DATE_FIELD_HOUR_RESOLUTION ="sentdateHourResolution"; + + /** + * {@link Field} which contain the Date header of the message with MINUTE-Resolution + */ + public final static String SENT_DATE_FIELD_MINUTE_RESOLUTION ="sentdateMinuteResolution"; + + /** + * {@link Field} which contain the Date header of the message with SECOND-Resolution + */ + public final static String SENT_DATE_FIELD_SECOND_RESOLUTION ="sentdateSecondResolution"; + + + /** + * {@link Field} which contain the Date header of the message with MILLISECOND-Resolution + */ + public final static String SENT_DATE_FIELD_MILLISECOND_RESOLUTION ="sentdateMillisecondResolution"; + + public final static String SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION ="sentdateSort"; + + public final static String NON_EXIST_FIELD ="nonExistField"; + + + private final static String MEDIA_TYPE_TEXT = "text"; + private final static String MEDIA_TYPE_MESSAGE = "message"; + private final static String DEFAULT_ENCODING = "US-ASCII"; + + private final IndexWriter writer; + + private int maxQueryResults = DEFAULT_MAX_QUERY_RESULTS; + + private boolean suffixMatch = false; + + private final static SortField UID_SORT = new SortField(UID_FIELD, SortField.LONG); + private final static SortField UID_SORT_REVERSE = new SortField(UID_FIELD, SortField.LONG, true); + + private final static SortField SIZE_SORT = new SortField(SIZE_FIELD, SortField.LONG); + private final static SortField SIZE_SORT_REVERSE = new SortField(SIZE_FIELD, SortField.LONG, true); + + private final static SortField FIRST_CC_MAILBOX_SORT = new SortField(FIRST_CC_MAILBOX_NAME_FIELD, SortField.STRING); + private final static SortField FIRST_CC_MAILBOX_SORT_REVERSE = new SortField(FIRST_CC_MAILBOX_NAME_FIELD, SortField.STRING, true); + + private final static SortField FIRST_TO_MAILBOX_SORT = new SortField(FIRST_TO_MAILBOX_NAME_FIELD, SortField.STRING); + private final static SortField FIRST_TO_MAILBOX_SORT_REVERSE = new SortField(FIRST_TO_MAILBOX_NAME_FIELD, SortField.STRING, true); + + private final static SortField FIRST_FROM_MAILBOX_SORT = new SortField(FIRST_FROM_MAILBOX_NAME_FIELD, SortField.STRING); + private final static SortField FIRST_FROM_MAILBOX_SORT_REVERSE = new SortField(FIRST_FROM_MAILBOX_NAME_FIELD, SortField.STRING, true); + + + private final static SortField ARRIVAL_MAILBOX_SORT = new SortField(INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION, SortField.LONG); + private final static SortField ARRIVAL_MAILBOX_SORT_REVERSE = new SortField(INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION, SortField.LONG, true); + + private final static SortField BASE_SUBJECT_SORT = new SortField(BASE_SUBJECT_FIELD, SortField.STRING); + private final static SortField BASE_SUBJECT_SORT_REVERSE = new SortField(BASE_SUBJECT_FIELD, SortField.STRING, true); + + private final static SortField SENT_DATE_SORT = new SortField(SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION, SortField.LONG); + private final static SortField SENT_DATE_SORT_REVERSE = new SortField(SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION, SortField.LONG, true); + + private final static SortField FIRST_TO_MAILBOX_DISPLAY_SORT = new SortField(FIRST_TO_MAILBOX_DISPLAY_FIELD, SortField.STRING); + private final static SortField FIRST_TO_MAILBOX_DISPLAY_SORT_REVERSE = new SortField(FIRST_TO_MAILBOX_DISPLAY_FIELD, SortField.STRING, true); + + private final static SortField FIRST_FROM_MAILBOX_DISPLAY_SORT = new SortField(FIRST_FROM_MAILBOX_DISPLAY_FIELD, SortField.STRING); + private final static SortField FIRST_FROM_MAILBOX_DISPLAY_SORT_REVERSE = new SortField(FIRST_FROM_MAILBOX_DISPLAY_FIELD, SortField.STRING, true); + + + public LuceneMessageSearchIndex(MessageMapperFactory factory, Directory directory) throws CorruptIndexException, LockObtainFailedException, IOException { + this(factory, directory, false, true); + } + + + public LuceneMessageSearchIndex(MessageMapperFactory factory, Directory directory, boolean dropIndexOnStart, boolean lenient) throws CorruptIndexException, LockObtainFailedException, IOException { + super(factory); + this.writer = new IndexWriter(directory, createConfig(createAnalyzer(lenient), dropIndexOnStart)); + } + + + public LuceneMessageSearchIndex(MessageMapperFactory factory, IndexWriter writer) { + super(factory); + this.writer = writer; + } + + /** + * Set the max count of results which will get returned from a query. The default is {@link #DEFAULT_MAX_QUERY_RESULTS} + * + * @param maxQueryResults + */ + public void setMaxQueryResults(int maxQueryResults) { + this.maxQueryResults = maxQueryResults; + } + + protected IndexWriterConfig createConfig(Analyzer analyzer, boolean dropIndexOnStart) { + IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_31, analyzer); + if (dropIndexOnStart) { + config.setOpenMode(OpenMode.CREATE); + } else { + config.setOpenMode(OpenMode.CREATE_OR_APPEND); + } + return config; + } + + /** + * Create a {@link Analyzer} which is used to index the {@link Message}'s + * + * @param lenient + * + * @return analyzer + */ + protected Analyzer createAnalyzer(boolean lenient) { + if (lenient) { + return new LenientImapSearchAnalyzer(); + } else { + return new StrictImapSearchAnalyzer(); + } + } + + /** + * If set to true this implementation will use {@link WildcardQuery} to match suffix and prefix. This is what RFC3501 expects but is often not what the user does. + * It also slow things a lot if you have complex queries which use many "TEXT" arguments. If you want the implementation to behave strict like RFC3501 says, you should + * set this to true. + * + * The default is false for performance reasons + * + * + * @param suffixMatch + */ + public void setEnableSuffixMatch(boolean suffixMatch) { + this.suffixMatch = suffixMatch; + } + + + + /** + * @see org.apache.james.mailbox.store.search.MessageSearchIndex#search(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.model.SearchQuery) + */ + public Iterator search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) throws MailboxException { + Set uids = new LinkedHashSet(); + IndexSearcher searcher = null; + + try { + searcher = new IndexSearcher(IndexReader.open(writer, true)); + BooleanQuery query = new BooleanQuery(); + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + // Not return flags documents + query.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST_NOT); + List crits = searchQuery.getCriterias(); + for (int i = 0; i < crits.size(); i++) { + query.add(createQuery(crits.get(i), mailbox, searchQuery.getRecentMessageUids()), BooleanClause.Occur.MUST); + } + + // query for all the documents sorted as specified in the SearchQuery + TopDocs docs = searcher.search(query, null, maxQueryResults, createSort(searchQuery.getSorts())); + ScoreDoc[] sDocs = docs.scoreDocs; + for (int i = 0; i < sDocs.length; i++) { + long uid = Long.valueOf(searcher.doc(sDocs[i].doc).get(UID_FIELD)); + uids.add(uid); + } + } catch (IOException e) { + throw new MailboxException("Unable to search the mailbox", e); + } finally { + if (searcher != null) { + try { + searcher.close(); + } catch (IOException e) { + // ignore on close + } + } + } + return uids.iterator(); + } + + + /** + * Create a new {@link Document} for the given {@link Message}. This Document does not contain any flags data. The {@link Flags} are stored in a seperate Document. + * + * See {@link #createFlagsDocument(Message)} + * + * @param membership + * @return document + */ + private Document createMessageDocument(final MailboxSession session, final Message membership) throws MailboxException{ + final Document doc = new Document(); + // TODO: Better handling + doc.add(new Field(MAILBOX_ID_FIELD, membership.getMailboxId().toString().toUpperCase(Locale.ENGLISH), Store.YES, Index.NOT_ANALYZED)); + doc.add(new NumericField(UID_FIELD,Store.YES, true).setLongValue(membership.getUid())); + + // create an unqiue key for the document which can be used later on updates to find the document + doc.add(new Field(ID_FIELD, membership.getMailboxId().toString().toUpperCase(Locale.ENGLISH) +"-" + Long.toString(membership.getUid()), Store.YES, Index.NOT_ANALYZED)); + + doc.add(new Field(INTERNAL_DATE_FIELD_YEAR_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.YEAR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_MONTH_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.MONTH), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_DAY_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.DAY), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_HOUR_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.HOUR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_MINUTE_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.MINUTE), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_SECOND_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.SECOND), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.MILLISECOND), Store.NO, Index.NOT_ANALYZED)); + + doc.add(new NumericField(SIZE_FIELD,Store.YES, true).setLongValue(membership.getFullContentOctets())); + + // content handler which will index the headers and the body of the message + SimpleContentHandler handler = new SimpleContentHandler() { + + + public void headers(Header header) { + + Date sentDate = null; + String firstFromMailbox = ""; + String firstToMailbox = ""; + String firstCcMailbox = ""; + String firstFromDisplay = ""; + String firstToDisplay = ""; + + Iterator fields = header.iterator(); + while(fields.hasNext()) { + org.apache.james.mime4j.stream.Field f = fields.next(); + String headerName = f.getName().toUpperCase(Locale.ENGLISH); + String headerValue = f.getBody().toUpperCase(Locale.ENGLISH); + String fullValue = f.toString().toUpperCase(Locale.ENGLISH); + doc.add(new Field(HEADERS_FIELD, fullValue, Store.NO, Index.ANALYZED)); + doc.add(new Field(PREFIX_HEADER_FIELD + headerName, headerValue, Store.NO, Index.ANALYZED)); + + if (f instanceof DateTimeField) { + // We need to make sure we convert it to GMT + final StringReader reader = new StringReader(f.getBody()); + try { + DateTime dateTime = new DateTimeParser(reader).parseAll(); + Calendar cal = getGMT(); + cal.set(dateTime.getYear(), dateTime.getMonth() - 1, dateTime.getDay(), dateTime.getHour(), dateTime.getMinute(), dateTime.getSecond()); + sentDate = cal.getTime(); + + } catch (org.apache.james.mime4j.field.datetime.parser.ParseException e) { + session.getLog().debug("Unable to parse Date header for proper indexing", e); + // This should never happen anyway fallback to the already parsed field + sentDate = ((DateTimeField) f).getDate(); + } + + } + String field = null; + if ("To".equalsIgnoreCase(headerName)) { + field = TO_FIELD; + } else if ("From".equalsIgnoreCase(headerName)) { + field = FROM_FIELD; + } else if ("Cc".equalsIgnoreCase(headerName)) { + field = CC_FIELD; + } else if ("Bcc".equalsIgnoreCase(headerName)) { + field = BCC_FIELD; + } + + + // Check if we can index the the address in the right manner + if (field != null) { + // not sure if we really should reparse it. It maybe be better to check just for the right type. + // But this impl was easier in the first place + AddressList aList = LenientAddressBuilder.DEFAULT.parseAddressList(MimeUtil.unfold(f.getBody())); + for (int i = 0; i < aList.size(); i++) { + Address address = aList.get(i); + if (address instanceof org.apache.james.mime4j.dom.address.Mailbox) { + org.apache.james.mime4j.dom.address.Mailbox mailbox = (org.apache.james.mime4j.dom.address.Mailbox) address; + String value = AddressFormatter.DEFAULT.encode(mailbox).toUpperCase(Locale.ENGLISH); + doc.add(new Field(field, value, Store.NO, Index.ANALYZED)); + if (i == 0) { + String mailboxAddress = SearchUtil.getMailboxAddress(mailbox); + String mailboxDisplay = SearchUtil.getDisplayAddress(mailbox); + + if ("To".equalsIgnoreCase(headerName)) { + firstToMailbox = mailboxAddress; + firstToDisplay = mailboxDisplay; + } else if ("From".equalsIgnoreCase(headerName)) { + firstFromMailbox = mailboxAddress; + firstFromDisplay = mailboxDisplay; + + } else if ("Cc".equalsIgnoreCase(headerName)) { + firstCcMailbox = mailboxAddress; + } + + } + } else if (address instanceof Group) { + MailboxList mList = ((Group) address).getMailboxes(); + for (int a = 0; a < mList.size(); a++) { + org.apache.james.mime4j.dom.address.Mailbox mailbox = mList.get(a); + String value = AddressFormatter.DEFAULT.encode(mailbox).toUpperCase(Locale.ENGLISH); + doc.add(new Field(field, value, Store.NO, Index.ANALYZED)); + + if (i == 0 && a == 0) { + String mailboxAddress = SearchUtil.getMailboxAddress(mailbox); + String mailboxDisplay = SearchUtil.getDisplayAddress(mailbox); + + if ("To".equalsIgnoreCase(headerName)) { + firstToMailbox = mailboxAddress; + firstToDisplay = mailboxDisplay; + } else if ("From".equalsIgnoreCase(headerName)) { + firstFromMailbox = mailboxAddress; + firstFromDisplay = mailboxDisplay; + + } else if ("Cc".equalsIgnoreCase(headerName)) { + firstCcMailbox = mailboxAddress; + } + } + } + } + } + + + doc.add(new Field(field, headerValue, Store.NO, Index.ANALYZED)); + + } else if (headerName.equalsIgnoreCase("Subject")) { + doc.add(new Field(BASE_SUBJECT_FIELD, SearchUtil.getBaseSubject(headerValue), Store.YES, Index.NOT_ANALYZED)); + } + } + if (sentDate == null) { + sentDate = membership.getInternalDate(); + } else { + + doc.add(new Field(SENT_DATE_FIELD_YEAR_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.YEAR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_MONTH_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.MONTH), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_DAY_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.DAY), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_HOUR_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.HOUR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_MINUTE_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.MINUTE), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_SECOND_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.SECOND), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_MILLISECOND_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.MILLISECOND), Store.NO, Index.NOT_ANALYZED)); + + } + doc.add(new Field(SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION,DateTools.dateToString(sentDate, DateTools.Resolution.MILLISECOND), Store.NO, Index.NOT_ANALYZED)); + + doc.add(new Field(FIRST_FROM_MAILBOX_NAME_FIELD, firstFromMailbox, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_TO_MAILBOX_NAME_FIELD, firstToMailbox, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_CC_MAILBOX_NAME_FIELD, firstCcMailbox, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_FROM_MAILBOX_DISPLAY_FIELD, firstFromDisplay, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_TO_MAILBOX_DISPLAY_FIELD, firstToDisplay, Store.YES, Index.NOT_ANALYZED)); + + } + + @Override + public void body(BodyDescriptor desc, InputStream in) throws MimeException, IOException { + String mediaType = desc.getMediaType(); + if (MEDIA_TYPE_TEXT.equalsIgnoreCase(mediaType) || MEDIA_TYPE_MESSAGE.equalsIgnoreCase(mediaType)) { + String cset = desc.getCharset(); + if (cset == null) { + cset = DEFAULT_ENCODING; + } + Charset charset; + try { + charset = Charset.forName(cset); + } catch (Exception e) { + // Invalid charset found so fallback toe the DEFAULT_ENCODING + charset = Charset.forName(DEFAULT_ENCODING); + } + + // Read the content one line after the other and add it to the document + BufferedReader bodyReader = new BufferedReader(new InputStreamReader(in, charset)); + String line = null; + while((line = bodyReader.readLine()) != null) { + doc.add(new Field(BODY_FIELD, line.toUpperCase(Locale.ENGLISH),Store.NO, Index.ANALYZED)); + } + + } + } + + }; + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + //config.setStrictParsing(false); + config.setMaxContentLen(-1); + MimeStreamParser parser = new MimeStreamParser(config); + parser.setContentDecoding(true); + parser.setContentHandler(handler); + + try { + // parse the message to index headers and body + parser.parse(membership.getFullContent()); + } catch (MimeException e) { + // This should never happen as it was parsed before too without problems. + throw new MailboxException("Unable to index content of message", e); + } catch (IOException e) { + // This should never happen as it was parsed before too without problems. + // anyway let us just skip the body and headers in the index + throw new MailboxException("Unable to index content of message", e); + } + + + return doc; + } + + private String toSentDateField(DateResolution res) { + String field; + switch (res) { + case Year: + field = SENT_DATE_FIELD_YEAR_RESOLUTION; + break; + case Month: + field = SENT_DATE_FIELD_MONTH_RESOLUTION; + break; + case Day: + field = SENT_DATE_FIELD_DAY_RESOLUTION; + break; + case Hour: + field = SENT_DATE_FIELD_HOUR_RESOLUTION; + break; + case Minute: + field = SENT_DATE_FIELD_MINUTE_RESOLUTION; + break; + case Second: + field = SENT_DATE_FIELD_SECOND_RESOLUTION; + break; + default: + field = SENT_DATE_FIELD_MILLISECOND_RESOLUTION; + break; + } + return field; + } + + + private static Calendar getGMT() { + return Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.ENGLISH); + } + + + private String toInteralDateField(DateResolution res) { + String field; + switch (res) { + case Year: + field = INTERNAL_DATE_FIELD_YEAR_RESOLUTION; + break; + case Month: + field = INTERNAL_DATE_FIELD_MONTH_RESOLUTION; + break; + case Day: + field = INTERNAL_DATE_FIELD_DAY_RESOLUTION; + break; + case Hour: + field = INTERNAL_DATE_FIELD_HOUR_RESOLUTION; + break; + case Minute: + field = INTERNAL_DATE_FIELD_MINUTE_RESOLUTION; + break; + case Second: + field = INTERNAL_DATE_FIELD_SECOND_RESOLUTION; + break; + default: + field = INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION; + break; + } + return field; + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.InternalDateCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createInternalDateQuery(SearchQuery.InternalDateCriterion crit) throws UnsupportedSearchException { + DateOperator dop = crit.getOperator(); + DateResolution res = dop.getDateResultion(); + String field = toInteralDateField(res); + return createQuery(field, dop); + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.SizeCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createSizeQuery(SearchQuery.SizeCriterion crit) throws UnsupportedSearchException { + NumericOperator op = crit.getOperator(); + switch (op.getType()) { + case EQUALS: + return NumericRangeQuery.newLongRange(SIZE_FIELD, op.getValue(), op.getValue(), true, true); + case GREATER_THAN: + return NumericRangeQuery.newLongRange(SIZE_FIELD, op.getValue(), Long.MAX_VALUE, false, true); + case LESS_THAN: + return NumericRangeQuery.newLongRange(SIZE_FIELD, Long.MIN_VALUE, op.getValue(), true, false); + default: + throw new UnsupportedSearchException(); + } + } + + /** + * This method will return the right {@link Query} depending if {@link #suffixMatch} is enabled + * + * @param fieldName + * @param value + * @return query + */ + private Query createTermQuery(String fieldName, String value) { + if (suffixMatch) { + return new WildcardQuery(new Term(fieldName, "*" + value + "*")); + } else { + return new PrefixQuery(new Term(fieldName, value)); + } + } + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.HeaderCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createHeaderQuery(SearchQuery.HeaderCriterion crit) throws UnsupportedSearchException { + HeaderOperator op = crit.getOperator(); + String name = crit.getHeaderName().toUpperCase(Locale.ENGLISH); + String fieldName = PREFIX_HEADER_FIELD + name; + if (op instanceof SearchQuery.ContainsOperator) { + ContainsOperator cop = (ContainsOperator) op; + return createTermQuery(fieldName, cop.getValue().toUpperCase(Locale.ENGLISH)); + } else if (op instanceof SearchQuery.ExistsOperator){ + return new PrefixQuery(new Term(fieldName, "")); + } else if (op instanceof SearchQuery.DateOperator) { + DateOperator dop = (DateOperator) op; + String field = toSentDateField(dop.getDateResultion()); + return createQuery(field, dop); + } else if (op instanceof SearchQuery.AddressOperator) { + String field = name.toLowerCase(Locale.ENGLISH); + return createTermQuery(field, ((SearchQuery.AddressOperator) op).getAddress().toUpperCase(Locale.ENGLISH)); + } else { + // Operator not supported + throw new UnsupportedSearchException(); + } + } + + + private Query createQuery(String field, DateOperator dop) throws UnsupportedSearchException { + Date date = dop.getDate(); + DateResolution res = dop.getDateResultion(); + DateTools.Resolution dRes = toResolution(res); + String value = DateTools.dateToString(date, dRes); + switch(dop.getType()) { + case ON: + return new TermQuery(new Term(field ,value)); + case BEFORE: + return new TermRangeQuery(field, DateTools.dateToString(MIN_DATE, dRes), value, true, false); + case AFTER: + return new TermRangeQuery(field, value, DateTools.dateToString(MAX_DATE, dRes), false, true); + default: + throw new UnsupportedSearchException(); + } + } + + private DateTools.Resolution toResolution(DateResolution res) { + switch (res) { + case Year: + return DateTools.Resolution.YEAR; + case Month: + return DateTools.Resolution.MONTH; + case Day: + return DateTools.Resolution.DAY; + case Hour: + return DateTools.Resolution.HOUR; + case Minute: + return DateTools.Resolution.MINUTE; + case Second: + return DateTools.Resolution.SECOND; + default: + return DateTools.Resolution.MILLISECOND; + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.UidCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createUidQuery(SearchQuery.UidCriterion crit) throws UnsupportedSearchException { + NumericRange[] ranges = crit.getOperator().getRange(); + if (ranges.length == 1) { + NumericRange range = ranges[0]; + return NumericRangeQuery.newLongRange(UID_FIELD, range.getLowValue(), range.getHighValue(), true, true); + } else { + BooleanQuery rangesQuery = new BooleanQuery(); + for (int i = 0; i < ranges.length; i++) { + NumericRange range = ranges[i]; + rangesQuery.add(NumericRangeQuery.newLongRange(UID_FIELD, range.getLowValue(), range.getHighValue(), true, true), BooleanClause.Occur.SHOULD); + } + return rangesQuery; + } + } + + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.UidCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createModSeqQuery(SearchQuery.ModSeqCriterion crit) throws UnsupportedSearchException { + NumericOperator op = crit.getOperator(); + switch (op.getType()) { + case EQUALS: + return NumericRangeQuery.newLongRange(MODSEQ_FIELD, op.getValue(), op.getValue(), true, true); + case GREATER_THAN: + return NumericRangeQuery.newLongRange(MODSEQ_FIELD, op.getValue(), Long.MAX_VALUE, false, true); + case LESS_THAN: + return NumericRangeQuery.newLongRange(MODSEQ_FIELD, Long.MIN_VALUE, op.getValue(), true, false); + default: + throw new UnsupportedSearchException(); + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.FlagCriterion}. This is kind of a hack + * as it will do a search for the flags in this method and + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createFlagQuery(String flag, boolean isSet, Mailbox mailbox, Collection recentUids) throws MailboxException, UnsupportedSearchException { + BooleanQuery query = new BooleanQuery(); + + if (isSet) { + query.add(new TermQuery(new Term(FLAGS_FIELD, flag)), BooleanClause.Occur.MUST); + } else { + // lucene does not support simple NOT queries so we do some nasty hack here + BooleanQuery bQuery = new BooleanQuery(); + bQuery.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST); + bQuery.add(new TermQuery(new Term(FLAGS_FIELD, flag)),BooleanClause.Occur.MUST_NOT); + + query.add(bQuery, BooleanClause.Occur.MUST); + } + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + + + IndexSearcher searcher = null; + + try { + Set uids = new HashSet(); + searcher = new IndexSearcher(IndexReader.open(writer, true)); + + // query for all the documents sorted by uid + TopDocs docs = searcher.search(query, null, maxQueryResults, new Sort(UID_SORT)); + ScoreDoc[] sDocs = docs.scoreDocs; + for (int i = 0; i < sDocs.length; i++) { + long uid = Long.valueOf(searcher.doc(sDocs[i].doc).get(UID_FIELD)); + uids.add(uid); + } + + // add or remove recent uids + if (flag.equalsIgnoreCase("\\RECENT")){ + if (isSet) { + uids.addAll(recentUids); + } else { + uids.removeAll(recentUids); + } + } + + List ranges = MessageRange.toRanges(new ArrayList(uids)); + NumericRange[] nRanges = new NumericRange[ranges.size()]; + for (int i = 0; i < ranges.size(); i++) { + MessageRange range = ranges.get(i); + nRanges[i] = new NumericRange(range.getUidFrom(), range.getUidTo()); + } + return createUidQuery((UidCriterion) SearchQuery.uid(nRanges)); + } catch (IOException e) { + throw new MailboxException("Unable to search mailbox " + mailbox, e); + } finally { + if (searcher != null) { + try { + searcher.close(); + } catch (IOException e) { + // ignore on close + } + } + } + } + + private Sort createSort(List sorts) { + Sort sort = new Sort(); + List fields = new ArrayList(); + + for (int i = 0; i < sorts.size(); i++) { + SearchQuery.Sort s = sorts.get(i); + boolean reverse = s.isReverse(); + SortField sf = null; + + switch (s.getSortClause()) { + case Arrival: + if (reverse) { + sf = ARRIVAL_MAILBOX_SORT_REVERSE; + } else { + sf = ARRIVAL_MAILBOX_SORT; + } + break; + case SentDate: + if (reverse) { + sf = SENT_DATE_SORT_REVERSE; + } else { + sf = SENT_DATE_SORT; + } + break; + case MailboxCc: + if (reverse) { + sf = FIRST_CC_MAILBOX_SORT_REVERSE; + } else { + sf = FIRST_CC_MAILBOX_SORT; + } + break; + case MailboxFrom: + if (reverse) { + sf = FIRST_FROM_MAILBOX_SORT_REVERSE; + } else { + sf = FIRST_FROM_MAILBOX_SORT; + } + break; + case Size: + if (reverse) { + sf = SIZE_SORT_REVERSE; + } else { + sf = SIZE_SORT; + } + break; + case BaseSubject: + if (reverse) { + sf = BASE_SUBJECT_SORT_REVERSE; + } else { + sf = BASE_SUBJECT_SORT; + } + break; + case MailboxTo: + if (reverse) { + sf = FIRST_TO_MAILBOX_SORT_REVERSE; + } else { + sf = FIRST_TO_MAILBOX_SORT; + } + break; + + case Uid: + if (reverse) { + sf = UID_SORT_REVERSE; + } else { + sf = UID_SORT; + } + break; + case DisplayFrom: + if (reverse) { + sf = FIRST_FROM_MAILBOX_DISPLAY_SORT_REVERSE;; + } else { + sf = FIRST_FROM_MAILBOX_DISPLAY_SORT; + } + break; + case DisplayTo: + if (reverse) { + sf = FIRST_TO_MAILBOX_DISPLAY_SORT_REVERSE; + } else { + sf = FIRST_TO_MAILBOX_DISPLAY_SORT; + } + break; + default: + break; + } + if (sf != null) { + + fields.add(sf); + + // Add the uid sort as tie-breaker + if (sf == SENT_DATE_SORT) { + fields.add(UID_SORT); + } else if (sf == SENT_DATE_SORT_REVERSE) { + fields.add(UID_SORT_REVERSE); + } + } + } + // add the uid sorting as last so if no other sorting was able todo the job it will get sorted by the uid + fields.add(UID_SORT); + sort.setSort(fields.toArray(new SortField[0])); + return sort; + } + + /** + * Convert the given {@link Flag} to a String + * + * @param flag + * @return flagString + */ + private String toString(Flag flag) { + if (Flag.ANSWERED.equals(flag)) { + return "\\ANSWERED"; + } else if (Flag.DELETED.equals(flag)) { + return "\\DELETED"; + } else if (Flag.DRAFT.equals(flag)) { + return "\\DRAFT"; + } else if (Flag.FLAGGED.equals(flag)) { + return "\\FLAGGED"; + } else if (Flag.RECENT.equals(flag)) { + return "\\RECENT"; + } else if (Flag.SEEN.equals(flag)) { + return "\\FLAG"; + } else { + return flag.toString(); + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.TextCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createTextQuery(SearchQuery.TextCriterion crit) throws UnsupportedSearchException { + String value = crit.getOperator().getValue().toUpperCase(Locale.ENGLISH); + switch(crit.getType()) { + case BODY: + return createTermQuery(BODY_FIELD, value); + case FULL: + BooleanQuery query = new BooleanQuery(); + query.add(createTermQuery(BODY_FIELD, value), BooleanClause.Occur.SHOULD); + query.add(createTermQuery(HEADERS_FIELD,value), BooleanClause.Occur.SHOULD); + return query; + default: + throw new UnsupportedSearchException(); + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.AllCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createAllQuery(SearchQuery.AllCriterion crit) throws UnsupportedSearchException{ + BooleanQuery query = new BooleanQuery(); + + query.add(createQuery(MessageRange.all()), BooleanClause.Occur.MUST); + query.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST_NOT); + + return query; + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.ConjunctionCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createConjunctionQuery(SearchQuery.ConjunctionCriterion crit, Mailbox mailbox, Collection recentUids) throws UnsupportedSearchException, MailboxException { + List crits = crit.getCriteria(); + BooleanQuery conQuery = new BooleanQuery(); + switch (crit.getType()) { + case AND: + for (int i = 0; i < crits.size(); i++) { + conQuery.add(createQuery(crits.get(i), mailbox, recentUids), BooleanClause.Occur.MUST); + } + return conQuery; + case OR: + for (int i = 0; i < crits.size(); i++) { + conQuery.add(createQuery(crits.get(i), mailbox, recentUids), BooleanClause.Occur.SHOULD); + } + return conQuery; + case NOR: + BooleanQuery nor = new BooleanQuery(); + for (int i = 0; i < crits.size(); i++) { + conQuery.add(createQuery(crits.get(i), mailbox, recentUids), BooleanClause.Occur.SHOULD); + } + nor.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + + nor.add(conQuery, BooleanClause.Occur.MUST_NOT); + return nor; + default: + throw new UnsupportedSearchException(); + } + + } + + /** + * Return a {@link Query} which is builded based on the given {@link Criterion} + * + * @param criterion + * @return query + * @throws UnsupportedSearchException + */ + private Query createQuery(Criterion criterion, Mailbox mailbox, Collection recentUids) throws UnsupportedSearchException, MailboxException { + if (criterion instanceof SearchQuery.InternalDateCriterion) { + SearchQuery.InternalDateCriterion crit = (SearchQuery.InternalDateCriterion) criterion; + return createInternalDateQuery(crit); + } else if (criterion instanceof SearchQuery.SizeCriterion) { + SearchQuery.SizeCriterion crit = (SearchQuery.SizeCriterion) criterion; + return createSizeQuery(crit); + } else if (criterion instanceof SearchQuery.HeaderCriterion) { + HeaderCriterion crit = (HeaderCriterion) criterion; + return createHeaderQuery(crit); + } else if (criterion instanceof SearchQuery.UidCriterion) { + SearchQuery.UidCriterion crit = (SearchQuery.UidCriterion) criterion; + return createUidQuery(crit); + } else if (criterion instanceof SearchQuery.FlagCriterion) { + FlagCriterion crit = (FlagCriterion) criterion; + return createFlagQuery(toString(crit.getFlag()), crit.getOperator().isSet(), mailbox, recentUids); + } else if (criterion instanceof SearchQuery.CustomFlagCriterion) { + CustomFlagCriterion crit = (CustomFlagCriterion) criterion; + return createFlagQuery(crit.getFlag(), crit.getOperator().isSet(), mailbox, recentUids); + } else if (criterion instanceof SearchQuery.TextCriterion) { + SearchQuery.TextCriterion crit = (SearchQuery.TextCriterion) criterion; + return createTextQuery(crit); + } else if (criterion instanceof SearchQuery.AllCriterion) { + return createAllQuery((AllCriterion) criterion); + } else if (criterion instanceof SearchQuery.ConjunctionCriterion) { + SearchQuery.ConjunctionCriterion crit = (SearchQuery.ConjunctionCriterion) criterion; + return createConjunctionQuery(crit, mailbox, recentUids); + } else if (criterion instanceof SearchQuery.ModSeqCriterion) { + return createModSeqQuery((SearchQuery.ModSeqCriterion) criterion); + } + throw new UnsupportedSearchException(); + + } + + + + /** + * @see org.apache.james.mailbox.store.search.ListeningMessageSearchIndex#add(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + public void add(MailboxSession session, Mailbox mailbox, Message membership) throws MailboxException { + Document doc = createMessageDocument(session, membership); + Document flagsDoc = createFlagsDocument(membership); + + try { + writer.addDocument(doc); + writer.addDocument(flagsDoc); + } catch (CorruptIndexException e) { + throw new MailboxException("Unable to add message to index", e); + } catch (IOException e) { + throw new MailboxException("Unable to add message to index", e); + } + } + + /** + * @see org.apache.james.mailbox.store.search.ListeningMessageSearchIndex#update(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.model.MessageRange, javax.mail.Flags) + */ + public void update(MailboxSession session, Mailbox mailbox, MessageRange range, Flags f) throws MailboxException { + try { + IndexSearcher searcher = new IndexSearcher(IndexReader.open(writer, true)); + BooleanQuery query = new BooleanQuery(); + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + query.add(createQuery(range), BooleanClause.Occur.MUST); + query.add( new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST); + + TopDocs docs = searcher.search(query, 100000); + ScoreDoc[] sDocs = docs.scoreDocs; + for (int i = 0; i < sDocs.length; i++) { + Document doc = searcher.doc(sDocs[i].doc); + + if (doc.getField(FLAGS_FIELD) == null) { + doc.removeFields(FLAGS_FIELD); + indexFlags(doc, f); + + writer.updateDocument(new Term(ID_FIELD, doc.get(ID_FIELD)), doc); + + } + } + } catch (IOException e) { + throw new MailboxException("Unable to add messages in index", e); + + } + + } + + /** + * Index the {@link Flags} and add it to the {@link Document} + * + * @param f + * @param doc + */ + private Document createFlagsDocument(Message message) { + Document doc = new Document(); + doc.add(new Field(ID_FIELD, "flags-" + message.getMailboxId().toString() +"-" + Long.toString(message.getUid()), Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(MAILBOX_ID_FIELD, message.getMailboxId().toString(), Store.YES, Index.NOT_ANALYZED)); + doc.add(new NumericField(UID_FIELD,Store.YES, true).setLongValue(message.getUid())); + + indexFlags(doc, message.createFlags()); + return doc; + } + + /** + * Add the given {@link Flags} to the {@link Document} + * + * @param doc + * @param f + */ + private void indexFlags(Document doc, Flags f) { + List fString = new ArrayList(); + Flag[] flags = f.getSystemFlags(); + for (int a = 0; a < flags.length; a++) { + fString.add(toString(flags[a])); + doc.add(new Field(FLAGS_FIELD, toString(flags[a]),Store.NO, Index.NOT_ANALYZED)); + } + + String[] userFlags = f.getUserFlags(); + for (int a = 0; a < userFlags.length; a++) { + doc.add(new Field(FLAGS_FIELD, userFlags[a],Store.NO, Index.NOT_ANALYZED)); + } + + // if no flags are there we just use a empty field + if (flags.length == 0 && userFlags.length == 0) { + doc.add(new Field(FLAGS_FIELD, "",Store.NO, Index.NOT_ANALYZED)); + } + + } + + private Query createQuery(MessageRange range) { + switch (range.getType()) { + case ONE: + return NumericRangeQuery.newLongRange(UID_FIELD, range.getUidFrom(), range.getUidTo(), true, true); + case FROM: + return NumericRangeQuery.newLongRange(UID_FIELD, range.getUidFrom(), Long.MAX_VALUE, true, true); + default: + return NumericRangeQuery.newLongRange(UID_FIELD, 0L, Long.MAX_VALUE, true, true); + } + } + /** + * @see org.apache.james.mailbox.store.search.ListeningMessageSearchIndex#delete(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.model.MessageRange) + */ + public void delete(MailboxSession session, Mailbox mailbox, MessageRange range) throws MailboxException { + BooleanQuery query = new BooleanQuery(); + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + query.add(createQuery(range), BooleanClause.Occur.MUST); + + try { + writer.deleteDocuments(query); + } catch (CorruptIndexException e) { + throw new MailboxException("Unable to delete message from index", e); + + } catch (IOException e) { + throw new MailboxException("Unable to delete message from index", e); + } + } + + + +} diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictImapSearchAnalyzer.java.svn-base b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictImapSearchAnalyzer.java.svn-base new file mode 100644 index 0000000..ac69f91 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictImapSearchAnalyzer.java.svn-base @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.lucene.search; + +import java.io.Reader; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.cn.smart.SentenceTokenizer; +import org.apache.lucene.analysis.ngram.NGramTokenFilter; + +/** +* +* {@link Analyzer} which match substrings. This is needed because of RFC 3501. +* +* From RFC: +* +* In all search keys that use strings, a message matches the key if +* the string is a substring of the field. The matching is +* case-insensitive. +* +*/ +public final class StrictImapSearchAnalyzer extends Analyzer { + + private final int minTokenLength; + private final int maxTokenLength; + + public StrictImapSearchAnalyzer() { + this(3, 40); + } + public StrictImapSearchAnalyzer(int minTokenLength, int maxTokenLength) { + this.minTokenLength = minTokenLength; + this.maxTokenLength = maxTokenLength; + } + + /** + * @see org.apache.lucene.analysis.Analyzer#tokenStream(java.lang.String, java.io.Reader) + */ + public TokenStream tokenStream(String fieldName, Reader reader) { + return new NGramTokenFilter(new UpperCaseFilter(new SentenceTokenizer(reader)), minTokenLength, maxTokenLength); + } + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/UpperCaseFilter.java.svn-base b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/UpperCaseFilter.java.svn-base new file mode 100644 index 0000000..68aed78 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/.svn/text-base/UpperCaseFilter.java.svn-base @@ -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.lucene.search; + +import java.io.IOException; + +import org.apache.lucene.analysis.TokenFilter; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; + +/** + * Normalizes token text to upper case. + */ +public final class UpperCaseFilter extends TokenFilter { + private CharTermAttribute termAtt; + + public UpperCaseFilter(TokenStream in) { + super(in); + termAtt = addAttribute(CharTermAttribute.class); + } + + + @Override + public final boolean incrementToken() throws IOException { + if (input.incrementToken()) { + + final char[] buffer = termAtt.buffer(); + final int length = termAtt.length(); + for (int i = 0; i < length; i++) + buffer[i] = Character.toUpperCase(buffer[i]); + + return true; + } else + return false; + } +} diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LenientImapSearchAnalyzer.java b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LenientImapSearchAnalyzer.java new file mode 100644 index 0000000..1a46ebb --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LenientImapSearchAnalyzer.java @@ -0,0 +1,54 @@ +/**************************************************************** + * 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.lucene.search; + +import java.io.Reader; + + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.WhitespaceTokenizer; +import org.apache.lucene.analysis.shingle.ShingleFilter; +import org.apache.lucene.util.Version; + +/** + * This {@link Analyzer} is not 100% conform with RFC3501 but does + * most times exactly what the user would expect. + * + */ +public final class LenientImapSearchAnalyzer extends Analyzer{ + + public final static int DEFAULT_MAX_TOKEN_LENGTH = 4; + + private final int maxTokenLength; + + + public LenientImapSearchAnalyzer(int maxTokenLength) { + this.maxTokenLength = maxTokenLength; + } + + public LenientImapSearchAnalyzer() { + this(DEFAULT_MAX_TOKEN_LENGTH); + } + + @Override + public TokenStream tokenStream(String arg0, Reader reader) { + return new ShingleFilter(new UpperCaseFilter(new WhitespaceTokenizer(Version.LUCENE_31, reader)), 2, maxTokenLength); + } +} diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java new file mode 100644 index 0000000..7062bf6 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndex.java @@ -0,0 +1,1303 @@ +/**************************************************************** + * 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.lucene.search; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.UnsupportedSearchException; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SearchQuery.AllCriterion; +import org.apache.james.mailbox.model.SearchQuery.ContainsOperator; +import org.apache.james.mailbox.model.SearchQuery.Criterion; +import org.apache.james.mailbox.model.SearchQuery.CustomFlagCriterion; +import org.apache.james.mailbox.model.SearchQuery.DateOperator; +import org.apache.james.mailbox.model.SearchQuery.DateResolution; +import org.apache.james.mailbox.model.SearchQuery.FlagCriterion; +import org.apache.james.mailbox.model.SearchQuery.HeaderCriterion; +import org.apache.james.mailbox.model.SearchQuery.HeaderOperator; +import org.apache.james.mailbox.model.SearchQuery.NumericOperator; +import org.apache.james.mailbox.model.SearchQuery.NumericRange; +import org.apache.james.mailbox.model.SearchQuery.UidCriterion; +import org.apache.james.mailbox.store.mail.MessageMapperFactory; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex; +import org.apache.james.mailbox.store.search.SearchUtil; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.dom.datetime.DateTime; +import org.apache.james.mime4j.dom.field.DateTimeField; +import org.apache.james.mime4j.field.address.AddressFormatter; +import org.apache.james.mime4j.field.address.LenientAddressBuilder; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.message.SimpleContentHandler; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.util.MimeUtil; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.document.DateTools; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.Field.Index; +import org.apache.lucene.document.Field.Store; +import org.apache.lucene.document.NumericField; +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.IndexWriterConfig.OpenMode; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.NumericRangeQuery; +import org.apache.lucene.search.PrefixQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TermRangeQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.WildcardQuery; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.LockObtainFailedException; +import org.apache.lucene.util.Version; + +/** + * Lucene based {@link ListeningMessageSearchIndex} which offers message searching via a Lucene index + * + * + + * @param + */ +public class LuceneMessageSearchIndex extends ListeningMessageSearchIndex{ + private final static Date MAX_DATE; + private final static Date MIN_DATE; + + static { + Calendar cal = Calendar.getInstance(); + cal.set(9999, 11, 31); + MAX_DATE = cal.getTime(); + + cal.set(0000, 0, 1); + MIN_DATE = cal.getTime(); + } + + /** + * Default max query results + */ + public final static int DEFAULT_MAX_QUERY_RESULTS = 100000; + + /** + * {@link Field} which will contain the unique index of the {@link Document} + */ + public final static String ID_FIELD ="id"; + + + /** + * {@link Field} which will contain uid of the {@link Message} + */ + public final static String UID_FIELD = "uid"; + + /** + * {@link Field} which will contain the {@link Flags} of the {@link Message} + */ + public final static String FLAGS_FIELD = "flags"; + + /** + * {@link Field} which will contain the size of the {@link Message} + */ + public final static String SIZE_FIELD = "size"; + + /** + * {@link Field} which will contain the body of the {@link Message} + */ + public final static String BODY_FIELD = "body"; + + + /** + * Prefix which will be used for each message header to store it also in a seperate {@link Field} + */ + public final static String PREFIX_HEADER_FIELD ="header_"; + + /** + * {@link Field} which will contain the whole message header of the {@link Message} + */ + public final static String HEADERS_FIELD ="headers"; + + /** + * {@link Field} which will contain the mod-sequence of the message + */ + public final static String MODSEQ_FIELD = "modSeq"; + + /** + * {@link Field} which will contain the TO-Address of the message + */ + public final static String TO_FIELD ="to"; + + public final static String FIRST_TO_MAILBOX_NAME_FIELD ="firstToMailboxName"; + public final static String FIRST_TO_MAILBOX_DISPLAY_FIELD ="firstToMailboxDisplay"; + + /** + * {@link Field} which will contain the CC-Address of the message + */ + public final static String CC_FIELD ="cc"; + + public final static String FIRST_CC_MAILBOX_NAME_FIELD ="firstCcMailboxName"; + + + /** + * {@link Field} which will contain the FROM-Address of the message + */ + public final static String FROM_FIELD ="from"; + + public final static String FIRST_FROM_MAILBOX_NAME_FIELD ="firstFromMailboxName"; + public final static String FIRST_FROM_MAILBOX_DISPLAY_FIELD ="firstFromMailboxDisplay"; + + /** + * {@link Field} which will contain the BCC-Address of the message + */ + public final static String BCC_FIELD ="bcc"; + + + public final static String BASE_SUBJECT_FIELD = "baseSubject"; + + /** + * {@link Field} which contain the internalDate of the message with YEAR-Resolution + */ + public final static String INTERNAL_DATE_FIELD_YEAR_RESOLUTION ="internaldateYearResolution"; + + + /** + * {@link Field} which contain the internalDate of the message with MONTH-Resolution + */ + public final static String INTERNAL_DATE_FIELD_MONTH_RESOLUTION ="internaldateMonthResolution"; + + /** + * {@link Field} which contain the internalDate of the message with DAY-Resolution + */ + public final static String INTERNAL_DATE_FIELD_DAY_RESOLUTION ="internaldateDayResolution"; + + /** + * {@link Field} which contain the internalDate of the message with HOUR-Resolution + */ + public final static String INTERNAL_DATE_FIELD_HOUR_RESOLUTION ="internaldateHourResolution"; + + /** + * {@link Field} which contain the internalDate of the message with MINUTE-Resolution + */ + public final static String INTERNAL_DATE_FIELD_MINUTE_RESOLUTION ="internaldateMinuteResolution"; + + /** + * {@link Field} which contain the internalDate of the message with SECOND-Resolution + */ + public final static String INTERNAL_DATE_FIELD_SECOND_RESOLUTION ="internaldateSecondResolution"; + + + /** + * {@link Field} which contain the internalDate of the message with MILLISECOND-Resolution + */ + public final static String INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION ="internaldateMillisecondResolution"; + + /** + * {@link Field} which will contain the id of the {@link Mailbox} + */ + public final static String MAILBOX_ID_FIELD ="mailboxid"; + + /** + * {@link Field} which contain the Date header of the message with YEAR-Resolution + */ + public final static String SENT_DATE_FIELD_YEAR_RESOLUTION ="sentdateYearResolution"; + + + /** + * {@link Field} which contain the Date header of the message with MONTH-Resolution + */ + public final static String SENT_DATE_FIELD_MONTH_RESOLUTION ="sentdateMonthResolution"; + + /** + * {@link Field} which contain the Date header of the message with DAY-Resolution + */ + public final static String SENT_DATE_FIELD_DAY_RESOLUTION ="sentdateDayResolution"; + + /** + * {@link Field} which contain the Date header of the message with HOUR-Resolution + */ + public final static String SENT_DATE_FIELD_HOUR_RESOLUTION ="sentdateHourResolution"; + + /** + * {@link Field} which contain the Date header of the message with MINUTE-Resolution + */ + public final static String SENT_DATE_FIELD_MINUTE_RESOLUTION ="sentdateMinuteResolution"; + + /** + * {@link Field} which contain the Date header of the message with SECOND-Resolution + */ + public final static String SENT_DATE_FIELD_SECOND_RESOLUTION ="sentdateSecondResolution"; + + + /** + * {@link Field} which contain the Date header of the message with MILLISECOND-Resolution + */ + public final static String SENT_DATE_FIELD_MILLISECOND_RESOLUTION ="sentdateMillisecondResolution"; + + public final static String SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION ="sentdateSort"; + + public final static String NON_EXIST_FIELD ="nonExistField"; + + + private final static String MEDIA_TYPE_TEXT = "text"; + private final static String MEDIA_TYPE_MESSAGE = "message"; + private final static String DEFAULT_ENCODING = "US-ASCII"; + + private final IndexWriter writer; + + private int maxQueryResults = DEFAULT_MAX_QUERY_RESULTS; + + private boolean suffixMatch = false; + + private final static SortField UID_SORT = new SortField(UID_FIELD, SortField.LONG); + private final static SortField UID_SORT_REVERSE = new SortField(UID_FIELD, SortField.LONG, true); + + private final static SortField SIZE_SORT = new SortField(SIZE_FIELD, SortField.LONG); + private final static SortField SIZE_SORT_REVERSE = new SortField(SIZE_FIELD, SortField.LONG, true); + + private final static SortField FIRST_CC_MAILBOX_SORT = new SortField(FIRST_CC_MAILBOX_NAME_FIELD, SortField.STRING); + private final static SortField FIRST_CC_MAILBOX_SORT_REVERSE = new SortField(FIRST_CC_MAILBOX_NAME_FIELD, SortField.STRING, true); + + private final static SortField FIRST_TO_MAILBOX_SORT = new SortField(FIRST_TO_MAILBOX_NAME_FIELD, SortField.STRING); + private final static SortField FIRST_TO_MAILBOX_SORT_REVERSE = new SortField(FIRST_TO_MAILBOX_NAME_FIELD, SortField.STRING, true); + + private final static SortField FIRST_FROM_MAILBOX_SORT = new SortField(FIRST_FROM_MAILBOX_NAME_FIELD, SortField.STRING); + private final static SortField FIRST_FROM_MAILBOX_SORT_REVERSE = new SortField(FIRST_FROM_MAILBOX_NAME_FIELD, SortField.STRING, true); + + + private final static SortField ARRIVAL_MAILBOX_SORT = new SortField(INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION, SortField.LONG); + private final static SortField ARRIVAL_MAILBOX_SORT_REVERSE = new SortField(INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION, SortField.LONG, true); + + private final static SortField BASE_SUBJECT_SORT = new SortField(BASE_SUBJECT_FIELD, SortField.STRING); + private final static SortField BASE_SUBJECT_SORT_REVERSE = new SortField(BASE_SUBJECT_FIELD, SortField.STRING, true); + + private final static SortField SENT_DATE_SORT = new SortField(SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION, SortField.LONG); + private final static SortField SENT_DATE_SORT_REVERSE = new SortField(SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION, SortField.LONG, true); + + private final static SortField FIRST_TO_MAILBOX_DISPLAY_SORT = new SortField(FIRST_TO_MAILBOX_DISPLAY_FIELD, SortField.STRING); + private final static SortField FIRST_TO_MAILBOX_DISPLAY_SORT_REVERSE = new SortField(FIRST_TO_MAILBOX_DISPLAY_FIELD, SortField.STRING, true); + + private final static SortField FIRST_FROM_MAILBOX_DISPLAY_SORT = new SortField(FIRST_FROM_MAILBOX_DISPLAY_FIELD, SortField.STRING); + private final static SortField FIRST_FROM_MAILBOX_DISPLAY_SORT_REVERSE = new SortField(FIRST_FROM_MAILBOX_DISPLAY_FIELD, SortField.STRING, true); + + + public LuceneMessageSearchIndex(MessageMapperFactory factory, Directory directory) throws CorruptIndexException, LockObtainFailedException, IOException { + this(factory, directory, false, true); + } + + + public LuceneMessageSearchIndex(MessageMapperFactory factory, Directory directory, boolean dropIndexOnStart, boolean lenient) throws CorruptIndexException, LockObtainFailedException, IOException { + super(factory); + this.writer = new IndexWriter(directory, createConfig(createAnalyzer(lenient), dropIndexOnStart)); + } + + + public LuceneMessageSearchIndex(MessageMapperFactory factory, IndexWriter writer) { + super(factory); + this.writer = writer; + } + + /** + * Set the max count of results which will get returned from a query. The default is {@link #DEFAULT_MAX_QUERY_RESULTS} + * + * @param maxQueryResults + */ + public void setMaxQueryResults(int maxQueryResults) { + this.maxQueryResults = maxQueryResults; + } + + protected IndexWriterConfig createConfig(Analyzer analyzer, boolean dropIndexOnStart) { + IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_31, analyzer); + if (dropIndexOnStart) { + config.setOpenMode(OpenMode.CREATE); + } else { + config.setOpenMode(OpenMode.CREATE_OR_APPEND); + } + return config; + } + + /** + * Create a {@link Analyzer} which is used to index the {@link Message}'s + * + * @param lenient + * + * @return analyzer + */ + protected Analyzer createAnalyzer(boolean lenient) { + if (lenient) { + return new LenientImapSearchAnalyzer(); + } else { + return new StrictImapSearchAnalyzer(); + } + } + + /** + * If set to true this implementation will use {@link WildcardQuery} to match suffix and prefix. This is what RFC3501 expects but is often not what the user does. + * It also slow things a lot if you have complex queries which use many "TEXT" arguments. If you want the implementation to behave strict like RFC3501 says, you should + * set this to true. + * + * The default is false for performance reasons + * + * + * @param suffixMatch + */ + public void setEnableSuffixMatch(boolean suffixMatch) { + this.suffixMatch = suffixMatch; + } + + + + /** + * @see org.apache.james.mailbox.store.search.MessageSearchIndex#search(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.model.SearchQuery) + */ + public Iterator search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) throws MailboxException { + Set uids = new LinkedHashSet(); + IndexSearcher searcher = null; + + try { + searcher = new IndexSearcher(IndexReader.open(writer, true)); + BooleanQuery query = new BooleanQuery(); + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + // Not return flags documents + query.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST_NOT); + List crits = searchQuery.getCriterias(); + for (int i = 0; i < crits.size(); i++) { + query.add(createQuery(crits.get(i), mailbox, searchQuery.getRecentMessageUids()), BooleanClause.Occur.MUST); + } + + // query for all the documents sorted as specified in the SearchQuery + TopDocs docs = searcher.search(query, null, maxQueryResults, createSort(searchQuery.getSorts())); + ScoreDoc[] sDocs = docs.scoreDocs; + for (int i = 0; i < sDocs.length; i++) { + long uid = Long.valueOf(searcher.doc(sDocs[i].doc).get(UID_FIELD)); + uids.add(uid); + } + } catch (IOException e) { + throw new MailboxException("Unable to search the mailbox", e); + } finally { + if (searcher != null) { + try { + searcher.close(); + } catch (IOException e) { + // ignore on close + } + } + } + return uids.iterator(); + } + + + /** + * Create a new {@link Document} for the given {@link Message}. This Document does not contain any flags data. The {@link Flags} are stored in a seperate Document. + * + * See {@link #createFlagsDocument(Message)} + * + * @param membership + * @return document + */ + private Document createMessageDocument(final MailboxSession session, final Message membership) throws MailboxException{ + final Document doc = new Document(); + // TODO: Better handling + doc.add(new Field(MAILBOX_ID_FIELD, membership.getMailboxId().toString().toUpperCase(Locale.ENGLISH), Store.YES, Index.NOT_ANALYZED)); + doc.add(new NumericField(UID_FIELD,Store.YES, true).setLongValue(membership.getUid())); + + // create an unqiue key for the document which can be used later on updates to find the document + doc.add(new Field(ID_FIELD, membership.getMailboxId().toString().toUpperCase(Locale.ENGLISH) +"-" + Long.toString(membership.getUid()), Store.YES, Index.NOT_ANALYZED)); + + doc.add(new Field(INTERNAL_DATE_FIELD_YEAR_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.YEAR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_MONTH_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.MONTH), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_DAY_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.DAY), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_HOUR_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.HOUR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_MINUTE_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.MINUTE), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_SECOND_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.SECOND), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION, DateTools.dateToString(membership.getInternalDate(), DateTools.Resolution.MILLISECOND), Store.NO, Index.NOT_ANALYZED)); + + doc.add(new NumericField(SIZE_FIELD,Store.YES, true).setLongValue(membership.getFullContentOctets())); + + // content handler which will index the headers and the body of the message + SimpleContentHandler handler = new SimpleContentHandler() { + + + public void headers(Header header) { + + Date sentDate = null; + String firstFromMailbox = ""; + String firstToMailbox = ""; + String firstCcMailbox = ""; + String firstFromDisplay = ""; + String firstToDisplay = ""; + + Iterator fields = header.iterator(); + while(fields.hasNext()) { + org.apache.james.mime4j.stream.Field f = fields.next(); + String headerName = f.getName().toUpperCase(Locale.ENGLISH); + String headerValue = f.getBody().toUpperCase(Locale.ENGLISH); + String fullValue = f.toString().toUpperCase(Locale.ENGLISH); + doc.add(new Field(HEADERS_FIELD, fullValue, Store.NO, Index.ANALYZED)); + doc.add(new Field(PREFIX_HEADER_FIELD + headerName, headerValue, Store.NO, Index.ANALYZED)); + + if (f instanceof DateTimeField) { + // We need to make sure we convert it to GMT + final StringReader reader = new StringReader(f.getBody()); + try { + DateTime dateTime = new DateTimeParser(reader).parseAll(); + Calendar cal = getGMT(); + cal.set(dateTime.getYear(), dateTime.getMonth() - 1, dateTime.getDay(), dateTime.getHour(), dateTime.getMinute(), dateTime.getSecond()); + sentDate = cal.getTime(); + + } catch (org.apache.james.mime4j.field.datetime.parser.ParseException e) { + session.getLog().debug("Unable to parse Date header for proper indexing", e); + // This should never happen anyway fallback to the already parsed field + sentDate = ((DateTimeField) f).getDate(); + } + + } + String field = null; + if ("To".equalsIgnoreCase(headerName)) { + field = TO_FIELD; + } else if ("From".equalsIgnoreCase(headerName)) { + field = FROM_FIELD; + } else if ("Cc".equalsIgnoreCase(headerName)) { + field = CC_FIELD; + } else if ("Bcc".equalsIgnoreCase(headerName)) { + field = BCC_FIELD; + } + + + // Check if we can index the the address in the right manner + if (field != null) { + // not sure if we really should reparse it. It maybe be better to check just for the right type. + // But this impl was easier in the first place + AddressList aList = LenientAddressBuilder.DEFAULT.parseAddressList(MimeUtil.unfold(f.getBody())); + for (int i = 0; i < aList.size(); i++) { + Address address = aList.get(i); + if (address instanceof org.apache.james.mime4j.dom.address.Mailbox) { + org.apache.james.mime4j.dom.address.Mailbox mailbox = (org.apache.james.mime4j.dom.address.Mailbox) address; + String value = AddressFormatter.DEFAULT.encode(mailbox).toUpperCase(Locale.ENGLISH); + doc.add(new Field(field, value, Store.NO, Index.ANALYZED)); + if (i == 0) { + String mailboxAddress = SearchUtil.getMailboxAddress(mailbox); + String mailboxDisplay = SearchUtil.getDisplayAddress(mailbox); + + if ("To".equalsIgnoreCase(headerName)) { + firstToMailbox = mailboxAddress; + firstToDisplay = mailboxDisplay; + } else if ("From".equalsIgnoreCase(headerName)) { + firstFromMailbox = mailboxAddress; + firstFromDisplay = mailboxDisplay; + + } else if ("Cc".equalsIgnoreCase(headerName)) { + firstCcMailbox = mailboxAddress; + } + + } + } else if (address instanceof Group) { + MailboxList mList = ((Group) address).getMailboxes(); + for (int a = 0; a < mList.size(); a++) { + org.apache.james.mime4j.dom.address.Mailbox mailbox = mList.get(a); + String value = AddressFormatter.DEFAULT.encode(mailbox).toUpperCase(Locale.ENGLISH); + doc.add(new Field(field, value, Store.NO, Index.ANALYZED)); + + if (i == 0 && a == 0) { + String mailboxAddress = SearchUtil.getMailboxAddress(mailbox); + String mailboxDisplay = SearchUtil.getDisplayAddress(mailbox); + + if ("To".equalsIgnoreCase(headerName)) { + firstToMailbox = mailboxAddress; + firstToDisplay = mailboxDisplay; + } else if ("From".equalsIgnoreCase(headerName)) { + firstFromMailbox = mailboxAddress; + firstFromDisplay = mailboxDisplay; + + } else if ("Cc".equalsIgnoreCase(headerName)) { + firstCcMailbox = mailboxAddress; + } + } + } + } + } + + + doc.add(new Field(field, headerValue, Store.NO, Index.ANALYZED)); + + } else if (headerName.equalsIgnoreCase("Subject")) { + doc.add(new Field(BASE_SUBJECT_FIELD, SearchUtil.getBaseSubject(headerValue), Store.YES, Index.NOT_ANALYZED)); + } + } + if (sentDate == null) { + sentDate = membership.getInternalDate(); + } else { + + doc.add(new Field(SENT_DATE_FIELD_YEAR_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.YEAR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_MONTH_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.MONTH), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_DAY_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.DAY), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_HOUR_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.HOUR), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_MINUTE_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.MINUTE), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_SECOND_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.SECOND), Store.NO, Index.NOT_ANALYZED)); + doc.add(new Field(SENT_DATE_FIELD_MILLISECOND_RESOLUTION, DateTools.dateToString(sentDate, DateTools.Resolution.MILLISECOND), Store.NO, Index.NOT_ANALYZED)); + + } + doc.add(new Field(SENT_DATE_SORT_FIELD_MILLISECOND_RESOLUTION,DateTools.dateToString(sentDate, DateTools.Resolution.MILLISECOND), Store.NO, Index.NOT_ANALYZED)); + + doc.add(new Field(FIRST_FROM_MAILBOX_NAME_FIELD, firstFromMailbox, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_TO_MAILBOX_NAME_FIELD, firstToMailbox, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_CC_MAILBOX_NAME_FIELD, firstCcMailbox, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_FROM_MAILBOX_DISPLAY_FIELD, firstFromDisplay, Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(FIRST_TO_MAILBOX_DISPLAY_FIELD, firstToDisplay, Store.YES, Index.NOT_ANALYZED)); + + } + + @Override + public void body(BodyDescriptor desc, InputStream in) throws MimeException, IOException { + String mediaType = desc.getMediaType(); + if (MEDIA_TYPE_TEXT.equalsIgnoreCase(mediaType) || MEDIA_TYPE_MESSAGE.equalsIgnoreCase(mediaType)) { + String cset = desc.getCharset(); + if (cset == null) { + cset = DEFAULT_ENCODING; + } + Charset charset; + try { + charset = Charset.forName(cset); + } catch (Exception e) { + // Invalid charset found so fallback toe the DEFAULT_ENCODING + charset = Charset.forName(DEFAULT_ENCODING); + } + + // Read the content one line after the other and add it to the document + BufferedReader bodyReader = new BufferedReader(new InputStreamReader(in, charset)); + String line = null; + while((line = bodyReader.readLine()) != null) { + doc.add(new Field(BODY_FIELD, line.toUpperCase(Locale.ENGLISH),Store.NO, Index.ANALYZED)); + } + + } + } + + }; + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + //config.setStrictParsing(false); + config.setMaxContentLen(-1); + MimeStreamParser parser = new MimeStreamParser(config); + parser.setContentDecoding(true); + parser.setContentHandler(handler); + + try { + // parse the message to index headers and body + parser.parse(membership.getFullContent()); + } catch (MimeException e) { + // This should never happen as it was parsed before too without problems. + throw new MailboxException("Unable to index content of message", e); + } catch (IOException e) { + // This should never happen as it was parsed before too without problems. + // anyway let us just skip the body and headers in the index + throw new MailboxException("Unable to index content of message", e); + } + + + return doc; + } + + private String toSentDateField(DateResolution res) { + String field; + switch (res) { + case Year: + field = SENT_DATE_FIELD_YEAR_RESOLUTION; + break; + case Month: + field = SENT_DATE_FIELD_MONTH_RESOLUTION; + break; + case Day: + field = SENT_DATE_FIELD_DAY_RESOLUTION; + break; + case Hour: + field = SENT_DATE_FIELD_HOUR_RESOLUTION; + break; + case Minute: + field = SENT_DATE_FIELD_MINUTE_RESOLUTION; + break; + case Second: + field = SENT_DATE_FIELD_SECOND_RESOLUTION; + break; + default: + field = SENT_DATE_FIELD_MILLISECOND_RESOLUTION; + break; + } + return field; + } + + + private static Calendar getGMT() { + return Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.ENGLISH); + } + + + private String toInteralDateField(DateResolution res) { + String field; + switch (res) { + case Year: + field = INTERNAL_DATE_FIELD_YEAR_RESOLUTION; + break; + case Month: + field = INTERNAL_DATE_FIELD_MONTH_RESOLUTION; + break; + case Day: + field = INTERNAL_DATE_FIELD_DAY_RESOLUTION; + break; + case Hour: + field = INTERNAL_DATE_FIELD_HOUR_RESOLUTION; + break; + case Minute: + field = INTERNAL_DATE_FIELD_MINUTE_RESOLUTION; + break; + case Second: + field = INTERNAL_DATE_FIELD_SECOND_RESOLUTION; + break; + default: + field = INTERNAL_DATE_FIELD_MILLISECOND_RESOLUTION; + break; + } + return field; + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.InternalDateCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createInternalDateQuery(SearchQuery.InternalDateCriterion crit) throws UnsupportedSearchException { + DateOperator dop = crit.getOperator(); + DateResolution res = dop.getDateResultion(); + String field = toInteralDateField(res); + return createQuery(field, dop); + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.SizeCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createSizeQuery(SearchQuery.SizeCriterion crit) throws UnsupportedSearchException { + NumericOperator op = crit.getOperator(); + switch (op.getType()) { + case EQUALS: + return NumericRangeQuery.newLongRange(SIZE_FIELD, op.getValue(), op.getValue(), true, true); + case GREATER_THAN: + return NumericRangeQuery.newLongRange(SIZE_FIELD, op.getValue(), Long.MAX_VALUE, false, true); + case LESS_THAN: + return NumericRangeQuery.newLongRange(SIZE_FIELD, Long.MIN_VALUE, op.getValue(), true, false); + default: + throw new UnsupportedSearchException(); + } + } + + /** + * This method will return the right {@link Query} depending if {@link #suffixMatch} is enabled + * + * @param fieldName + * @param value + * @return query + */ + private Query createTermQuery(String fieldName, String value) { + if (suffixMatch) { + return new WildcardQuery(new Term(fieldName, "*" + value + "*")); + } else { + return new PrefixQuery(new Term(fieldName, value)); + } + } + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.HeaderCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createHeaderQuery(SearchQuery.HeaderCriterion crit) throws UnsupportedSearchException { + HeaderOperator op = crit.getOperator(); + String name = crit.getHeaderName().toUpperCase(Locale.ENGLISH); + String fieldName = PREFIX_HEADER_FIELD + name; + if (op instanceof SearchQuery.ContainsOperator) { + ContainsOperator cop = (ContainsOperator) op; + return createTermQuery(fieldName, cop.getValue().toUpperCase(Locale.ENGLISH)); + } else if (op instanceof SearchQuery.ExistsOperator){ + return new PrefixQuery(new Term(fieldName, "")); + } else if (op instanceof SearchQuery.DateOperator) { + DateOperator dop = (DateOperator) op; + String field = toSentDateField(dop.getDateResultion()); + return createQuery(field, dop); + } else if (op instanceof SearchQuery.AddressOperator) { + String field = name.toLowerCase(Locale.ENGLISH); + return createTermQuery(field, ((SearchQuery.AddressOperator) op).getAddress().toUpperCase(Locale.ENGLISH)); + } else { + // Operator not supported + throw new UnsupportedSearchException(); + } + } + + + private Query createQuery(String field, DateOperator dop) throws UnsupportedSearchException { + Date date = dop.getDate(); + DateResolution res = dop.getDateResultion(); + DateTools.Resolution dRes = toResolution(res); + String value = DateTools.dateToString(date, dRes); + switch(dop.getType()) { + case ON: + return new TermQuery(new Term(field ,value)); + case BEFORE: + return new TermRangeQuery(field, DateTools.dateToString(MIN_DATE, dRes), value, true, false); + case AFTER: + return new TermRangeQuery(field, value, DateTools.dateToString(MAX_DATE, dRes), false, true); + default: + throw new UnsupportedSearchException(); + } + } + + private DateTools.Resolution toResolution(DateResolution res) { + switch (res) { + case Year: + return DateTools.Resolution.YEAR; + case Month: + return DateTools.Resolution.MONTH; + case Day: + return DateTools.Resolution.DAY; + case Hour: + return DateTools.Resolution.HOUR; + case Minute: + return DateTools.Resolution.MINUTE; + case Second: + return DateTools.Resolution.SECOND; + default: + return DateTools.Resolution.MILLISECOND; + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.UidCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createUidQuery(SearchQuery.UidCriterion crit) throws UnsupportedSearchException { + NumericRange[] ranges = crit.getOperator().getRange(); + if (ranges.length == 1) { + NumericRange range = ranges[0]; + return NumericRangeQuery.newLongRange(UID_FIELD, range.getLowValue(), range.getHighValue(), true, true); + } else { + BooleanQuery rangesQuery = new BooleanQuery(); + for (int i = 0; i < ranges.length; i++) { + NumericRange range = ranges[i]; + rangesQuery.add(NumericRangeQuery.newLongRange(UID_FIELD, range.getLowValue(), range.getHighValue(), true, true), BooleanClause.Occur.SHOULD); + } + return rangesQuery; + } + } + + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.UidCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createModSeqQuery(SearchQuery.ModSeqCriterion crit) throws UnsupportedSearchException { + NumericOperator op = crit.getOperator(); + switch (op.getType()) { + case EQUALS: + return NumericRangeQuery.newLongRange(MODSEQ_FIELD, op.getValue(), op.getValue(), true, true); + case GREATER_THAN: + return NumericRangeQuery.newLongRange(MODSEQ_FIELD, op.getValue(), Long.MAX_VALUE, false, true); + case LESS_THAN: + return NumericRangeQuery.newLongRange(MODSEQ_FIELD, Long.MIN_VALUE, op.getValue(), true, false); + default: + throw new UnsupportedSearchException(); + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.FlagCriterion}. This is kind of a hack + * as it will do a search for the flags in this method and + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createFlagQuery(String flag, boolean isSet, Mailbox mailbox, Collection recentUids) throws MailboxException, UnsupportedSearchException { + BooleanQuery query = new BooleanQuery(); + + if (isSet) { + query.add(new TermQuery(new Term(FLAGS_FIELD, flag)), BooleanClause.Occur.MUST); + } else { + // lucene does not support simple NOT queries so we do some nasty hack here + BooleanQuery bQuery = new BooleanQuery(); + bQuery.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST); + bQuery.add(new TermQuery(new Term(FLAGS_FIELD, flag)),BooleanClause.Occur.MUST_NOT); + + query.add(bQuery, BooleanClause.Occur.MUST); + } + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + + + IndexSearcher searcher = null; + + try { + Set uids = new HashSet(); + searcher = new IndexSearcher(IndexReader.open(writer, true)); + + // query for all the documents sorted by uid + TopDocs docs = searcher.search(query, null, maxQueryResults, new Sort(UID_SORT)); + ScoreDoc[] sDocs = docs.scoreDocs; + for (int i = 0; i < sDocs.length; i++) { + long uid = Long.valueOf(searcher.doc(sDocs[i].doc).get(UID_FIELD)); + uids.add(uid); + } + + // add or remove recent uids + if (flag.equalsIgnoreCase("\\RECENT")){ + if (isSet) { + uids.addAll(recentUids); + } else { + uids.removeAll(recentUids); + } + } + + List ranges = MessageRange.toRanges(new ArrayList(uids)); + NumericRange[] nRanges = new NumericRange[ranges.size()]; + for (int i = 0; i < ranges.size(); i++) { + MessageRange range = ranges.get(i); + nRanges[i] = new NumericRange(range.getUidFrom(), range.getUidTo()); + } + return createUidQuery((UidCriterion) SearchQuery.uid(nRanges)); + } catch (IOException e) { + throw new MailboxException("Unable to search mailbox " + mailbox, e); + } finally { + if (searcher != null) { + try { + searcher.close(); + } catch (IOException e) { + // ignore on close + } + } + } + } + + private Sort createSort(List sorts) { + Sort sort = new Sort(); + List fields = new ArrayList(); + + for (int i = 0; i < sorts.size(); i++) { + SearchQuery.Sort s = sorts.get(i); + boolean reverse = s.isReverse(); + SortField sf = null; + + switch (s.getSortClause()) { + case Arrival: + if (reverse) { + sf = ARRIVAL_MAILBOX_SORT_REVERSE; + } else { + sf = ARRIVAL_MAILBOX_SORT; + } + break; + case SentDate: + if (reverse) { + sf = SENT_DATE_SORT_REVERSE; + } else { + sf = SENT_DATE_SORT; + } + break; + case MailboxCc: + if (reverse) { + sf = FIRST_CC_MAILBOX_SORT_REVERSE; + } else { + sf = FIRST_CC_MAILBOX_SORT; + } + break; + case MailboxFrom: + if (reverse) { + sf = FIRST_FROM_MAILBOX_SORT_REVERSE; + } else { + sf = FIRST_FROM_MAILBOX_SORT; + } + break; + case Size: + if (reverse) { + sf = SIZE_SORT_REVERSE; + } else { + sf = SIZE_SORT; + } + break; + case BaseSubject: + if (reverse) { + sf = BASE_SUBJECT_SORT_REVERSE; + } else { + sf = BASE_SUBJECT_SORT; + } + break; + case MailboxTo: + if (reverse) { + sf = FIRST_TO_MAILBOX_SORT_REVERSE; + } else { + sf = FIRST_TO_MAILBOX_SORT; + } + break; + + case Uid: + if (reverse) { + sf = UID_SORT_REVERSE; + } else { + sf = UID_SORT; + } + break; + case DisplayFrom: + if (reverse) { + sf = FIRST_FROM_MAILBOX_DISPLAY_SORT_REVERSE;; + } else { + sf = FIRST_FROM_MAILBOX_DISPLAY_SORT; + } + break; + case DisplayTo: + if (reverse) { + sf = FIRST_TO_MAILBOX_DISPLAY_SORT_REVERSE; + } else { + sf = FIRST_TO_MAILBOX_DISPLAY_SORT; + } + break; + default: + break; + } + if (sf != null) { + + fields.add(sf); + + // Add the uid sort as tie-breaker + if (sf == SENT_DATE_SORT) { + fields.add(UID_SORT); + } else if (sf == SENT_DATE_SORT_REVERSE) { + fields.add(UID_SORT_REVERSE); + } + } + } + // add the uid sorting as last so if no other sorting was able todo the job it will get sorted by the uid + fields.add(UID_SORT); + sort.setSort(fields.toArray(new SortField[0])); + return sort; + } + + /** + * Convert the given {@link Flag} to a String + * + * @param flag + * @return flagString + */ + private String toString(Flag flag) { + if (Flag.ANSWERED.equals(flag)) { + return "\\ANSWERED"; + } else if (Flag.DELETED.equals(flag)) { + return "\\DELETED"; + } else if (Flag.DRAFT.equals(flag)) { + return "\\DRAFT"; + } else if (Flag.FLAGGED.equals(flag)) { + return "\\FLAGGED"; + } else if (Flag.RECENT.equals(flag)) { + return "\\RECENT"; + } else if (Flag.SEEN.equals(flag)) { + return "\\FLAG"; + } else { + return flag.toString(); + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.TextCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createTextQuery(SearchQuery.TextCriterion crit) throws UnsupportedSearchException { + String value = crit.getOperator().getValue().toUpperCase(Locale.ENGLISH); + switch(crit.getType()) { + case BODY: + return createTermQuery(BODY_FIELD, value); + case FULL: + BooleanQuery query = new BooleanQuery(); + query.add(createTermQuery(BODY_FIELD, value), BooleanClause.Occur.SHOULD); + query.add(createTermQuery(HEADERS_FIELD,value), BooleanClause.Occur.SHOULD); + return query; + default: + throw new UnsupportedSearchException(); + } + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.AllCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createAllQuery(SearchQuery.AllCriterion crit) throws UnsupportedSearchException{ + BooleanQuery query = new BooleanQuery(); + + query.add(createQuery(MessageRange.all()), BooleanClause.Occur.MUST); + query.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST_NOT); + + return query; + } + + /** + * Return a {@link Query} which is build based on the given {@link SearchQuery.ConjunctionCriterion} + * + * @param crit + * @return query + * @throws UnsupportedSearchException + */ + private Query createConjunctionQuery(SearchQuery.ConjunctionCriterion crit, Mailbox mailbox, Collection recentUids) throws UnsupportedSearchException, MailboxException { + List crits = crit.getCriteria(); + BooleanQuery conQuery = new BooleanQuery(); + switch (crit.getType()) { + case AND: + for (int i = 0; i < crits.size(); i++) { + conQuery.add(createQuery(crits.get(i), mailbox, recentUids), BooleanClause.Occur.MUST); + } + return conQuery; + case OR: + for (int i = 0; i < crits.size(); i++) { + conQuery.add(createQuery(crits.get(i), mailbox, recentUids), BooleanClause.Occur.SHOULD); + } + return conQuery; + case NOR: + BooleanQuery nor = new BooleanQuery(); + for (int i = 0; i < crits.size(); i++) { + conQuery.add(createQuery(crits.get(i), mailbox, recentUids), BooleanClause.Occur.SHOULD); + } + nor.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + + nor.add(conQuery, BooleanClause.Occur.MUST_NOT); + return nor; + default: + throw new UnsupportedSearchException(); + } + + } + + /** + * Return a {@link Query} which is builded based on the given {@link Criterion} + * + * @param criterion + * @return query + * @throws UnsupportedSearchException + */ + private Query createQuery(Criterion criterion, Mailbox mailbox, Collection recentUids) throws UnsupportedSearchException, MailboxException { + if (criterion instanceof SearchQuery.InternalDateCriterion) { + SearchQuery.InternalDateCriterion crit = (SearchQuery.InternalDateCriterion) criterion; + return createInternalDateQuery(crit); + } else if (criterion instanceof SearchQuery.SizeCriterion) { + SearchQuery.SizeCriterion crit = (SearchQuery.SizeCriterion) criterion; + return createSizeQuery(crit); + } else if (criterion instanceof SearchQuery.HeaderCriterion) { + HeaderCriterion crit = (HeaderCriterion) criterion; + return createHeaderQuery(crit); + } else if (criterion instanceof SearchQuery.UidCriterion) { + SearchQuery.UidCriterion crit = (SearchQuery.UidCriterion) criterion; + return createUidQuery(crit); + } else if (criterion instanceof SearchQuery.FlagCriterion) { + FlagCriterion crit = (FlagCriterion) criterion; + return createFlagQuery(toString(crit.getFlag()), crit.getOperator().isSet(), mailbox, recentUids); + } else if (criterion instanceof SearchQuery.CustomFlagCriterion) { + CustomFlagCriterion crit = (CustomFlagCriterion) criterion; + return createFlagQuery(crit.getFlag(), crit.getOperator().isSet(), mailbox, recentUids); + } else if (criterion instanceof SearchQuery.TextCriterion) { + SearchQuery.TextCriterion crit = (SearchQuery.TextCriterion) criterion; + return createTextQuery(crit); + } else if (criterion instanceof SearchQuery.AllCriterion) { + return createAllQuery((AllCriterion) criterion); + } else if (criterion instanceof SearchQuery.ConjunctionCriterion) { + SearchQuery.ConjunctionCriterion crit = (SearchQuery.ConjunctionCriterion) criterion; + return createConjunctionQuery(crit, mailbox, recentUids); + } else if (criterion instanceof SearchQuery.ModSeqCriterion) { + return createModSeqQuery((SearchQuery.ModSeqCriterion) criterion); + } + throw new UnsupportedSearchException(); + + } + + + + /** + * @see org.apache.james.mailbox.store.search.ListeningMessageSearchIndex#add(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + public void add(MailboxSession session, Mailbox mailbox, Message membership) throws MailboxException { + Document doc = createMessageDocument(session, membership); + Document flagsDoc = createFlagsDocument(membership); + + try { + writer.addDocument(doc); + writer.addDocument(flagsDoc); + } catch (CorruptIndexException e) { + throw new MailboxException("Unable to add message to index", e); + } catch (IOException e) { + throw new MailboxException("Unable to add message to index", e); + } + } + + /** + * @see org.apache.james.mailbox.store.search.ListeningMessageSearchIndex#update(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.model.MessageRange, javax.mail.Flags) + */ + public void update(MailboxSession session, Mailbox mailbox, MessageRange range, Flags f) throws MailboxException { + try { + IndexSearcher searcher = new IndexSearcher(IndexReader.open(writer, true)); + BooleanQuery query = new BooleanQuery(); + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + query.add(createQuery(range), BooleanClause.Occur.MUST); + query.add( new PrefixQuery(new Term(FLAGS_FIELD, "")), BooleanClause.Occur.MUST); + + TopDocs docs = searcher.search(query, 100000); + ScoreDoc[] sDocs = docs.scoreDocs; + for (int i = 0; i < sDocs.length; i++) { + Document doc = searcher.doc(sDocs[i].doc); + + if (doc.getField(FLAGS_FIELD) == null) { + doc.removeFields(FLAGS_FIELD); + indexFlags(doc, f); + + writer.updateDocument(new Term(ID_FIELD, doc.get(ID_FIELD)), doc); + + } + } + } catch (IOException e) { + throw new MailboxException("Unable to add messages in index", e); + + } + + } + + /** + * Index the {@link Flags} and add it to the {@link Document} + * + * @param f + * @param doc + */ + private Document createFlagsDocument(Message message) { + Document doc = new Document(); + doc.add(new Field(ID_FIELD, "flags-" + message.getMailboxId().toString() +"-" + Long.toString(message.getUid()), Store.YES, Index.NOT_ANALYZED)); + doc.add(new Field(MAILBOX_ID_FIELD, message.getMailboxId().toString(), Store.YES, Index.NOT_ANALYZED)); + doc.add(new NumericField(UID_FIELD,Store.YES, true).setLongValue(message.getUid())); + + indexFlags(doc, message.createFlags()); + return doc; + } + + /** + * Add the given {@link Flags} to the {@link Document} + * + * @param doc + * @param f + */ + private void indexFlags(Document doc, Flags f) { + List fString = new ArrayList(); + Flag[] flags = f.getSystemFlags(); + for (int a = 0; a < flags.length; a++) { + fString.add(toString(flags[a])); + doc.add(new Field(FLAGS_FIELD, toString(flags[a]),Store.NO, Index.NOT_ANALYZED)); + } + + String[] userFlags = f.getUserFlags(); + for (int a = 0; a < userFlags.length; a++) { + doc.add(new Field(FLAGS_FIELD, userFlags[a],Store.NO, Index.NOT_ANALYZED)); + } + + // if no flags are there we just use a empty field + if (flags.length == 0 && userFlags.length == 0) { + doc.add(new Field(FLAGS_FIELD, "",Store.NO, Index.NOT_ANALYZED)); + } + + } + + private Query createQuery(MessageRange range) { + switch (range.getType()) { + case ONE: + return NumericRangeQuery.newLongRange(UID_FIELD, range.getUidFrom(), range.getUidTo(), true, true); + case FROM: + return NumericRangeQuery.newLongRange(UID_FIELD, range.getUidFrom(), Long.MAX_VALUE, true, true); + default: + return NumericRangeQuery.newLongRange(UID_FIELD, 0L, Long.MAX_VALUE, true, true); + } + } + /** + * @see org.apache.james.mailbox.store.search.ListeningMessageSearchIndex#delete(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.model.MessageRange) + */ + public void delete(MailboxSession session, Mailbox mailbox, MessageRange range) throws MailboxException { + BooleanQuery query = new BooleanQuery(); + query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST); + query.add(createQuery(range), BooleanClause.Occur.MUST); + + try { + writer.deleteDocuments(query); + } catch (CorruptIndexException e) { + throw new MailboxException("Unable to delete message from index", e); + + } catch (IOException e) { + throw new MailboxException("Unable to delete message from index", e); + } + } + + + +} diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/StrictImapSearchAnalyzer.java b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/StrictImapSearchAnalyzer.java new file mode 100644 index 0000000..ac69f91 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/StrictImapSearchAnalyzer.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.lucene.search; + +import java.io.Reader; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.cn.smart.SentenceTokenizer; +import org.apache.lucene.analysis.ngram.NGramTokenFilter; + +/** +* +* {@link Analyzer} which match substrings. This is needed because of RFC 3501. +* +* From RFC: +* +* In all search keys that use strings, a message matches the key if +* the string is a substring of the field. The matching is +* case-insensitive. +* +*/ +public final class StrictImapSearchAnalyzer extends Analyzer { + + private final int minTokenLength; + private final int maxTokenLength; + + public StrictImapSearchAnalyzer() { + this(3, 40); + } + public StrictImapSearchAnalyzer(int minTokenLength, int maxTokenLength) { + this.minTokenLength = minTokenLength; + this.maxTokenLength = maxTokenLength; + } + + /** + * @see org.apache.lucene.analysis.Analyzer#tokenStream(java.lang.String, java.io.Reader) + */ + public TokenStream tokenStream(String fieldName, Reader reader) { + return new NGramTokenFilter(new UpperCaseFilter(new SentenceTokenizer(reader)), minTokenLength, maxTokenLength); + } + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/UpperCaseFilter.java b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/UpperCaseFilter.java new file mode 100644 index 0000000..68aed78 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/java/org/apache/james/mailbox/lucene/search/UpperCaseFilter.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.lucene.search; + +import java.io.IOException; + +import org.apache.lucene.analysis.TokenFilter; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; + +/** + * Normalizes token text to upper case. + */ +public final class UpperCaseFilter extends TokenFilter { + private CharTermAttribute termAtt; + + public UpperCaseFilter(TokenStream in) { + super(in); + termAtt = addAttribute(CharTermAttribute.class); + } + + + @Override + public final boolean incrementToken() throws IOException { + if (input.incrementToken()) { + + final char[] buffer = termAtt.buffer(); + final int length = termAtt.length(); + for (int i = 0; i < length; i++) + buffer[i] = Character.toUpperCase(buffer[i]); + + return true; + } else + return false; + } +} diff --git a/james/apache-james-mailbox/lucene/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..1f3b143 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/lucene/src/main/resources +END diff --git a/james/apache-james-mailbox/lucene/src/main/resources/.svn/entries b/james/apache-james-mailbox/lucene/src/main/resources/.svn/entries new file mode 100644 index 0000000..cd0275f --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/resources +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..7a088f2 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/lucene/src/main/resources/META-INF +END diff --git a/james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/entries b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/entries new file mode 100644 index 0000000..54e7640 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + diff --git a/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/all-wcprops new file mode 100644 index 0000000..ebad1e4 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 89 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/lucene/src/main/resources/META-INF/spring +END +mailbox-index-lucene.xml +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/lucene/src/main/resources/META-INF/spring/mailbox-index-lucene.xml +END diff --git a/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/entries b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/entries new file mode 100644 index 0000000..f36c525 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/main/resources/META-INF/spring +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-index-lucene.xml +file + + + + +2013-09-02T02:54:40.000000Z +447100d888e11ecaf89c19464ceeac69 +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +1933 + diff --git a/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/text-base/mailbox-index-lucene.xml.svn-base b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/text-base/mailbox-index-lucene.xml.svn-base new file mode 100644 index 0000000..e49bb49 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/.svn/text-base/mailbox-index-lucene.xml.svn-base @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/mailbox-index-lucene.xml b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/mailbox-index-lucene.xml new file mode 100644 index 0000000..e49bb49 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/main/resources/META-INF/spring/mailbox-index-lucene.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/lucene/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..7d3c340 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1162029/james/mailbox/trunk/lucene/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1162029/james/mailbox/trunk/lucene/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/lucene/src/reporting-site/.svn/entries b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/entries new file mode 100644 index 0000000..081b7c5 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-08-26T08:51:50.892761Z +1162029 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:40.000000Z +5bde8261948ff1881960902a322aa196 +2011-08-26T08:51:50.892761Z +1162029 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/lucene/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/lucene/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/lucene/src/reporting-site/site.xml b/james/apache-james-mailbox/lucene/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/lucene/src/test/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/.svn/all-wcprops new file mode 100644 index 0000000..ad94619 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test +END diff --git a/james/apache-james-mailbox/lucene/src/test/.svn/entries b/james/apache-james-mailbox/lucene/src/test/.svn/entries new file mode 100644 index 0000000..6cd6f7d --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/lucene/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..5096c2d --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 68 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java +END diff --git a/james/apache-james-mailbox/lucene/src/test/java/.svn/entries b/james/apache-james-mailbox/lucene/src/test/java/.svn/entries new file mode 100644 index 0000000..3e5c531 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test/java +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..6950bd0 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java/org +END diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/.svn/entries b/james/apache-james-mailbox/lucene/src/test/java/org/.svn/entries new file mode 100644 index 0000000..dd730ec --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test/java/org +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..4e7253f --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..77bf3c6 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..d1f0084 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 85 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..810c0b2 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..bbc89b0 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..43aead8 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +lucene +dir + diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/all-wcprops new file mode 100644 index 0000000..050bf64 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 100 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox/lucene +END diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/entries b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/entries new file mode 100644 index 0000000..bf14647 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox/lucene +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +search +dir + diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops new file mode 100644 index 0000000..6ea28f2 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 107 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox/lucene/search +END +StrictLuceneMessageSearchIndexText.java +K 25 +svn:wc:ra_dav:version-url +V 147 +/repos/asf/!svn/ver/1134921/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox/lucene/search/StrictLuceneMessageSearchIndexText.java +END +LuceneMessageSearchIndexTest.java +K 25 +svn:wc:ra_dav:version-url +V 141 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndexTest.java +END diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/entries b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/entries new file mode 100644 index 0000000..ac716e0 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/lucene/src/test/java/org/apache/james/mailbox/lucene/search +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +StrictLuceneMessageSearchIndexText.java +file + + + + +2013-09-02T02:54:40.000000Z +ee18b89b6e800b46c5a49691060662a0 +2011-06-12T12:56:09.906805Z +1134921 +norman + + + + + + + + + + + + + + + + + + + + + +1435 + +LuceneMessageSearchIndexTest.java +file + + + + +2013-09-02T02:54:40.000000Z +d02cf8211563373f9fa66e8f3223dfe9 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +28796 + diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndexTest.java.svn-base b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndexTest.java.svn-base new file mode 100644 index 0000000..f626eb8 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/LuceneMessageSearchIndexTest.java.svn-base @@ -0,0 +1,765 @@ +/**************************************************************** + * 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.lucene.search; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.SearchQuery.AddressType; +import org.apache.james.mailbox.model.SearchQuery.DateResolution; +import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause; +import org.apache.james.mailbox.store.MessageBuilder; +import org.apache.james.mailbox.store.SimpleMailboxMembership; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.lucene.store.RAMDirectory; +import org.junit.Before; +import org.junit.Test; + +public class LuceneMessageSearchIndexTest { + + private LuceneMessageSearchIndex index; + + private SimpleMailbox mailbox = new SimpleMailbox(0); + private SimpleMailbox mailbox2 = new SimpleMailbox(1); + private SimpleMailbox mailbox3 = new SimpleMailbox(2); + + + private static final String FROM_ADDRESS = "Harry "; + + private static final String SUBJECT_PART = "Mixed"; + + private static final String CUSTARD = "CUSTARD"; + + private static final String RHUBARD = "Rhubard"; + + private static final String BODY = "This is a simple email\r\n " + + "It has " + RHUBARD + ".\r\n" + "It has " + CUSTARD + ".\r\n" + + "It needs naught else.\r\n"; + + Message row; + + protected boolean useLenient() { + return true; + } + + @Before + public void setUp() throws Exception { + index = new LuceneMessageSearchIndex(null, new RAMDirectory(), true, useLenient()); + index.setEnableSuffixMatch(true); + Map headersSubject = new HashMap(); + headersSubject.put("Subject", "test (fwd)"); + headersSubject.put("From", "test99 "); + headersSubject.put("To", "test2 , test3 "); + + Map headersTest = new HashMap(); + headersTest.put("Test", "test"); + headersTest.put("From", "test1 "); + headersTest.put("To", "test3 , test4 "); + headersTest.put("Cc", "test21 , test6 "); + + Map headersTestSubject = new HashMap(); + headersTestSubject.put("Test", "test"); + headersTestSubject.put("Subject", "test2"); + headersTestSubject.put("Date", "Thu, 14 Feb 1990 12:00:00 +0000 (GMT)"); + headersTestSubject.put("From", "test12 "); + headersTestSubject.put("Cc", "test211 , test6 "); + + SimpleMailboxMembership m2 = new SimpleMailboxMembership(mailbox2.getMailboxId(),1, 0, new Date(), 20, new Flags(Flag.ANSWERED), "My Body".getBytes(), headersSubject); + index.add(null, mailbox2, m2); + + SimpleMailboxMembership m = new SimpleMailboxMembership(mailbox.getMailboxId(),1, 0, new Date(), 200, new Flags(Flag.ANSWERED), "My Body".getBytes(), headersSubject); + index.add(null, mailbox, m); + + Calendar cal = Calendar.getInstance(); + cal.set(1980, 2, 10); + SimpleMailboxMembership m3 = new SimpleMailboxMembership(mailbox.getMailboxId(),2, 0, cal.getTime(), 20, new Flags(Flag.DELETED), "My Otherbody".getBytes(), headersTest); + index.add(null, mailbox, m3); + Calendar cal2 = Calendar.getInstance(); + cal2.set(8000, 2, 10); + SimpleMailboxMembership m4 = new SimpleMailboxMembership(mailbox.getMailboxId(),3, 0, cal2.getTime(), 20, new Flags(Flag.DELETED), "My Otherbody2".getBytes(), headersTestSubject); + index.add(null, mailbox, m4); + + MessageBuilder builder = new MessageBuilder(); + builder.header("From", "test "); + builder.header("To", FROM_ADDRESS); + builder.header("Subject", "A " + SUBJECT_PART + " Multipart Mail"); + builder.header("Date", "Thu, 14 Feb 2008 12:00:00 +0000 (GMT)"); + builder.body = Charset.forName("us-ascii").encode(BODY).array(); + builder.uid = 10; + builder.mailboxId = mailbox3.getMailboxId(); + + index.add(null, mailbox3, builder.build()); + + } + + + + @Test + public void testBodyShouldMatchPhraseInBody() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(CUSTARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + + query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(CUSTARD + CUSTARD)); + result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + } + + @Test + public void testBodyMatchShouldBeCaseInsensitive() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(RHUBARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testBodyShouldNotMatchPhraseOnlyInHeader() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(FROM_ADDRESS)); + Iterator result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(SUBJECT_PART)); + result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + } + + @Test + public void testTextShouldMatchPhraseInBody() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(CUSTARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(CUSTARD + CUSTARD)); + result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + } + + @Test + public void testTextMatchShouldBeCaseInsensitive() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(RHUBARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query.andCriteria(SearchQuery.mailContains(RHUBARD.toLowerCase(Locale.US))); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testSearchAddress() throws Exception { + + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.To,FROM_ADDRESS)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.To,"Harry")); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.To,"Harry@example.org")); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testSearchAddressFrom() throws Exception { + + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.From,"ser-from@domain.or")); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + + } + @Test + public void testBodyShouldMatchPhraseOnlyInHeader() throws Exception { + + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(FROM_ADDRESS)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query.andCriteria(SearchQuery.mailContains(SUBJECT_PART)); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testSearchAll() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.all()); + Iterator it2 = index.search(null, mailbox2, query); + assertTrue(it2.hasNext()); + assertEquals(1L, it2.next().longValue()); + assertFalse(it2.hasNext()); + } + + @Test + public void testSearchFlag() throws Exception { + + SearchQuery q = new SearchQuery(); + q.andCriteria(SearchQuery.flagIsSet(Flag.DELETED)); + Iterator it3 = index.search(null, mailbox, q); + assertEquals(2L, it3.next().longValue()); + assertEquals(3L, it3.next().longValue()); + assertFalse(it3.hasNext()); + } + + @Test + public void testSearchBody() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.bodyContains("body")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchMail() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.mailContains("body")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchHeaderContains() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.headerContains("Subject", "test")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchHeaderExists() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.headerExists("Subject")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchFlagUnset() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.flagIsUnSet(Flag.DRAFT)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchInternalDateBefore() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.internalDateBefore(cal.getTime(), DateResolution.Day)); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchInternalDateAfter() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.internalDateAfter(cal.getTime(), DateResolution.Day)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + + + @Test + public void testSearchInternalDateOn() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.internalDateOn(cal.getTime(), DateResolution.Day)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchUidMatch() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.uid(new SearchQuery.NumericRange[] {new SearchQuery.NumericRange(1)})); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchUidRange() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.uid(new SearchQuery.NumericRange[] {new SearchQuery.NumericRange(1), new SearchQuery.NumericRange(2,3)})); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + + @Test + public void testSearchSizeEquals() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.sizeEquals(200)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchSizeLessThan() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.sizeLessThan(200)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchSizeGreaterThan() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.sizeGreaterThan(6)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortUid() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSortUidReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Uid, true))); + q2.andCriteria(SearchQuery.all()); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSortSentDate() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.SentDate, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortSentDateReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.SentDate, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + @Test + public void testSortBaseSubject() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.BaseSubject, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortBaseSubjectReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.BaseSubject, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxFrom() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxFrom, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxFromReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxFrom, true))); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSortMailboxCc() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxCc, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxCcReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxCc, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxTo() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxTo, false))); + + Iterator it4 = index.search(null, mailbox, q2); + + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxToReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxTo, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortDisplayTo() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayTo, false))); + + Iterator it4 = index.search(null, mailbox, q2); + + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortDisplayToReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayTo, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSortDisplayFrom() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayFrom, false))); + + Iterator it4 = index.search(null, mailbox, q2); + + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortDisplayFromReverse() throws Exception { + + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayFrom, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortArrival() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Arrival, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortArrivalReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Arrival, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + @Test + public void testSortSize() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Size, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortSizeReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Size, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testNot() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.not(SearchQuery.uid(new SearchQuery.NumericRange[] { new SearchQuery.NumericRange(1)}))); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + private final class SimpleMailbox implements Mailbox { + private long id; + + public SimpleMailbox(long id) { + this.id = id; + } + + public Long getMailboxId() { + return id; + } + + public String getNamespace() { + throw new UnsupportedOperationException("Not supported"); + } + + public void setNamespace(String namespace) { + throw new UnsupportedOperationException("Not supported"); + } + + public String getUser() { + throw new UnsupportedOperationException("Not supported"); + } + + public void setUser(String user) { + throw new UnsupportedOperationException("Not supported"); + } + + public String getName() { + return Long.toString(id); + } + + public void setName(String name) { + throw new UnsupportedOperationException("Not supported"); + + } + + public long getUidValidity() { + return 0; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + throw new UnsupportedOperationException("Not supported"); + } + + + } +} diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictLuceneMessageSearchIndexText.java.svn-base b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictLuceneMessageSearchIndexText.java.svn-base new file mode 100644 index 0000000..ddae13a --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/.svn/text-base/StrictLuceneMessageSearchIndexText.java.svn-base @@ -0,0 +1,28 @@ +/**************************************************************** + * 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.lucene.search; + +public class StrictLuceneMessageSearchIndexText extends LuceneMessageSearchIndexTest{ + + @Override + protected boolean useLenient() { + return false; + } + +} diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndexTest.java b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndexTest.java new file mode 100644 index 0000000..f626eb8 --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/LuceneMessageSearchIndexTest.java @@ -0,0 +1,765 @@ +/**************************************************************** + * 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.lucene.search; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.SearchQuery.AddressType; +import org.apache.james.mailbox.model.SearchQuery.DateResolution; +import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause; +import org.apache.james.mailbox.store.MessageBuilder; +import org.apache.james.mailbox.store.SimpleMailboxMembership; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.lucene.store.RAMDirectory; +import org.junit.Before; +import org.junit.Test; + +public class LuceneMessageSearchIndexTest { + + private LuceneMessageSearchIndex index; + + private SimpleMailbox mailbox = new SimpleMailbox(0); + private SimpleMailbox mailbox2 = new SimpleMailbox(1); + private SimpleMailbox mailbox3 = new SimpleMailbox(2); + + + private static final String FROM_ADDRESS = "Harry "; + + private static final String SUBJECT_PART = "Mixed"; + + private static final String CUSTARD = "CUSTARD"; + + private static final String RHUBARD = "Rhubard"; + + private static final String BODY = "This is a simple email\r\n " + + "It has " + RHUBARD + ".\r\n" + "It has " + CUSTARD + ".\r\n" + + "It needs naught else.\r\n"; + + Message row; + + protected boolean useLenient() { + return true; + } + + @Before + public void setUp() throws Exception { + index = new LuceneMessageSearchIndex(null, new RAMDirectory(), true, useLenient()); + index.setEnableSuffixMatch(true); + Map headersSubject = new HashMap(); + headersSubject.put("Subject", "test (fwd)"); + headersSubject.put("From", "test99 "); + headersSubject.put("To", "test2 , test3 "); + + Map headersTest = new HashMap(); + headersTest.put("Test", "test"); + headersTest.put("From", "test1 "); + headersTest.put("To", "test3 , test4 "); + headersTest.put("Cc", "test21 , test6 "); + + Map headersTestSubject = new HashMap(); + headersTestSubject.put("Test", "test"); + headersTestSubject.put("Subject", "test2"); + headersTestSubject.put("Date", "Thu, 14 Feb 1990 12:00:00 +0000 (GMT)"); + headersTestSubject.put("From", "test12 "); + headersTestSubject.put("Cc", "test211 , test6 "); + + SimpleMailboxMembership m2 = new SimpleMailboxMembership(mailbox2.getMailboxId(),1, 0, new Date(), 20, new Flags(Flag.ANSWERED), "My Body".getBytes(), headersSubject); + index.add(null, mailbox2, m2); + + SimpleMailboxMembership m = new SimpleMailboxMembership(mailbox.getMailboxId(),1, 0, new Date(), 200, new Flags(Flag.ANSWERED), "My Body".getBytes(), headersSubject); + index.add(null, mailbox, m); + + Calendar cal = Calendar.getInstance(); + cal.set(1980, 2, 10); + SimpleMailboxMembership m3 = new SimpleMailboxMembership(mailbox.getMailboxId(),2, 0, cal.getTime(), 20, new Flags(Flag.DELETED), "My Otherbody".getBytes(), headersTest); + index.add(null, mailbox, m3); + Calendar cal2 = Calendar.getInstance(); + cal2.set(8000, 2, 10); + SimpleMailboxMembership m4 = new SimpleMailboxMembership(mailbox.getMailboxId(),3, 0, cal2.getTime(), 20, new Flags(Flag.DELETED), "My Otherbody2".getBytes(), headersTestSubject); + index.add(null, mailbox, m4); + + MessageBuilder builder = new MessageBuilder(); + builder.header("From", "test "); + builder.header("To", FROM_ADDRESS); + builder.header("Subject", "A " + SUBJECT_PART + " Multipart Mail"); + builder.header("Date", "Thu, 14 Feb 2008 12:00:00 +0000 (GMT)"); + builder.body = Charset.forName("us-ascii").encode(BODY).array(); + builder.uid = 10; + builder.mailboxId = mailbox3.getMailboxId(); + + index.add(null, mailbox3, builder.build()); + + } + + + + @Test + public void testBodyShouldMatchPhraseInBody() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(CUSTARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + + query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(CUSTARD + CUSTARD)); + result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + } + + @Test + public void testBodyMatchShouldBeCaseInsensitive() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(RHUBARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testBodyShouldNotMatchPhraseOnlyInHeader() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(FROM_ADDRESS)); + Iterator result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.bodyContains(SUBJECT_PART)); + result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + } + + @Test + public void testTextShouldMatchPhraseInBody() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(CUSTARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(CUSTARD + CUSTARD)); + result = index.search(null, mailbox3, query); + assertFalse(result.hasNext()); + } + + @Test + public void testTextMatchShouldBeCaseInsensitive() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(RHUBARD)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query.andCriteria(SearchQuery.mailContains(RHUBARD.toLowerCase(Locale.US))); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testSearchAddress() throws Exception { + + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.To,FROM_ADDRESS)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.To,"Harry")); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.To,"Harry@example.org")); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testSearchAddressFrom() throws Exception { + + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.address(AddressType.From,"ser-from@domain.or")); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + + } + @Test + public void testBodyShouldMatchPhraseOnlyInHeader() throws Exception { + + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.mailContains(FROM_ADDRESS)); + Iterator result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + + query.andCriteria(SearchQuery.mailContains(SUBJECT_PART)); + result = index.search(null, mailbox3, query); + assertEquals(10L, result.next().longValue()); + assertFalse(result.hasNext()); + } + + @Test + public void testSearchAll() throws Exception { + SearchQuery query = new SearchQuery(); + query.andCriteria(SearchQuery.all()); + Iterator it2 = index.search(null, mailbox2, query); + assertTrue(it2.hasNext()); + assertEquals(1L, it2.next().longValue()); + assertFalse(it2.hasNext()); + } + + @Test + public void testSearchFlag() throws Exception { + + SearchQuery q = new SearchQuery(); + q.andCriteria(SearchQuery.flagIsSet(Flag.DELETED)); + Iterator it3 = index.search(null, mailbox, q); + assertEquals(2L, it3.next().longValue()); + assertEquals(3L, it3.next().longValue()); + assertFalse(it3.hasNext()); + } + + @Test + public void testSearchBody() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.bodyContains("body")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchMail() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.mailContains("body")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchHeaderContains() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.headerContains("Subject", "test")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchHeaderExists() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.headerExists("Subject")); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchFlagUnset() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.flagIsUnSet(Flag.DRAFT)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchInternalDateBefore() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.internalDateBefore(cal.getTime(), DateResolution.Day)); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchInternalDateAfter() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.internalDateAfter(cal.getTime(), DateResolution.Day)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + + + @Test + public void testSearchInternalDateOn() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.internalDateOn(cal.getTime(), DateResolution.Day)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchUidMatch() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.uid(new SearchQuery.NumericRange[] {new SearchQuery.NumericRange(1)})); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchUidRange() throws Exception { + SearchQuery q2 = new SearchQuery(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + q2.andCriteria(SearchQuery.uid(new SearchQuery.NumericRange[] {new SearchQuery.NumericRange(1), new SearchQuery.NumericRange(2,3)})); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + + @Test + public void testSearchSizeEquals() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.sizeEquals(200)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSearchSizeLessThan() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.sizeLessThan(200)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSearchSizeGreaterThan() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.sizeGreaterThan(6)); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortUid() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSortUidReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Uid, true))); + q2.andCriteria(SearchQuery.all()); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSortSentDate() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.SentDate, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortSentDateReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.SentDate, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + @Test + public void testSortBaseSubject() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.BaseSubject, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortBaseSubjectReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.BaseSubject, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxFrom() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxFrom, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxFromReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxFrom, true))); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSortMailboxCc() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxCc, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxCcReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxCc, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxTo() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxTo, false))); + + Iterator it4 = index.search(null, mailbox, q2); + + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortMailboxToReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.MailboxTo, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortDisplayTo() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayTo, false))); + + Iterator it4 = index.search(null, mailbox, q2); + + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortDisplayToReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayTo, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + + @Test + public void testSortDisplayFrom() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayFrom, false))); + + Iterator it4 = index.search(null, mailbox, q2); + + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortDisplayFromReverse() throws Exception { + + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.DisplayFrom, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortArrival() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Arrival, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortArrivalReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Arrival, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + @Test + public void testSortSize() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Size, false))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertEquals(1L, it4.next().longValue()); + + assertFalse(it4.hasNext()); + } + + @Test + public void testSortSizeReverse() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.all()); + q2.setSorts(Arrays.asList(new SearchQuery.Sort(SortClause.Size, true))); + + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(1L, it4.next().longValue()); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + @Test + public void testNot() throws Exception { + SearchQuery q2 = new SearchQuery(); + q2.andCriteria(SearchQuery.not(SearchQuery.uid(new SearchQuery.NumericRange[] { new SearchQuery.NumericRange(1)}))); + Iterator it4 = index.search(null, mailbox, q2); + assertEquals(2L, it4.next().longValue()); + assertEquals(3L, it4.next().longValue()); + assertFalse(it4.hasNext()); + } + + private final class SimpleMailbox implements Mailbox { + private long id; + + public SimpleMailbox(long id) { + this.id = id; + } + + public Long getMailboxId() { + return id; + } + + public String getNamespace() { + throw new UnsupportedOperationException("Not supported"); + } + + public void setNamespace(String namespace) { + throw new UnsupportedOperationException("Not supported"); + } + + public String getUser() { + throw new UnsupportedOperationException("Not supported"); + } + + public void setUser(String user) { + throw new UnsupportedOperationException("Not supported"); + } + + public String getName() { + return Long.toString(id); + } + + public void setName(String name) { + throw new UnsupportedOperationException("Not supported"); + + } + + public long getUidValidity() { + return 0; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + return SimpleMailboxACL.OWNER_FULL_ACL; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + throw new UnsupportedOperationException("Not supported"); + } + + + } +} diff --git a/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/StrictLuceneMessageSearchIndexText.java b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/StrictLuceneMessageSearchIndexText.java new file mode 100644 index 0000000..ddae13a --- /dev/null +++ b/james/apache-james-mailbox/lucene/src/test/java/org/apache/james/mailbox/lucene/search/StrictLuceneMessageSearchIndexText.java @@ -0,0 +1,28 @@ +/**************************************************************** + * 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.lucene.search; + +public class StrictLuceneMessageSearchIndexText extends LuceneMessageSearchIndexTest{ + + @Override + protected boolean useLenient() { + return false; + } + +} diff --git a/james/apache-james-mailbox/maildir/.svn/all-wcprops b/james/apache-james-mailbox/maildir/.svn/all-wcprops new file mode 100644 index 0000000..af20d21 --- /dev/null +++ b/james/apache-james-mailbox/maildir/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 55 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/maildir/pom.xml +END diff --git a/james/apache-james-mailbox/maildir/.svn/dir-prop-base b/james/apache-james-mailbox/maildir/.svn/dir-prop-base new file mode 100644 index 0000000..0412bba --- /dev/null +++ b/james/apache-james-mailbox/maildir/.svn/dir-prop-base @@ -0,0 +1,8 @@ +K 10 +svn:ignore +V 22 +target +.* +coverage.ec + +END diff --git a/james/apache-james-mailbox/maildir/.svn/entries b/james/apache-james-mailbox/maildir/.svn/entries new file mode 100644 index 0000000..672146c --- /dev/null +++ b/james/apache-james-mailbox/maildir/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:38.000000Z +7501baa8ce06de583f9baea805ac56c7 +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +3425 + diff --git a/james/apache-james-mailbox/maildir/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/maildir/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..e91e2a7 --- /dev/null +++ b/james/apache-james-mailbox/maildir/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,90 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-maildir + Apache James :: Mailbox :: Maildir + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-mime4j-core + + + org.apache.james + apache-mime4j-dom + + + commons-lang + commons-lang + + + commons-io + commons-io + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + junit + junit + test + + + diff --git a/james/apache-james-mailbox/maildir/pom.xml b/james/apache-james-mailbox/maildir/pom.xml new file mode 100644 index 0000000..e91e2a7 --- /dev/null +++ b/james/apache-james-mailbox/maildir/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-maildir + Apache James :: Mailbox :: Maildir + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-mime4j-core + + + org.apache.james + apache-mime4j-dom + + + commons-lang + commons-lang + + + commons-io + commons-io + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + junit + junit + test + + + diff --git a/james/apache-james-mailbox/maildir/src/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/.svn/all-wcprops new file mode 100644 index 0000000..5fa75cc --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 59 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src +END diff --git a/james/apache-james-mailbox/maildir/src/.svn/entries b/james/apache-james-mailbox/maildir/src/.svn/entries new file mode 100644 index 0000000..b3fcecc --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/.svn/all-wcprops new file mode 100644 index 0000000..b3bacca --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 64 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main +END diff --git a/james/apache-james-mailbox/maildir/src/main/.svn/entries b/james/apache-james-mailbox/maildir/src/main/.svn/entries new file mode 100644 index 0000000..70af9d6 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..a403893 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main/java +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/.svn/entries new file mode 100644 index 0000000..5facebf --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..a790a03 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main/java/org +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/.svn/entries new file mode 100644 index 0000000..bba2afb --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..5aa5713 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 80 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..689f7ba --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..b2d2849 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..41c4359 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..fa7a4c8 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..dbb799f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +maildir +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/all-wcprops new file mode 100644 index 0000000..3a70f90 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir +END +UidConstraint.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1041810/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/UidConstraint.java +END +MaildirMessageName.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1428384/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMessageName.java +END +MaildirFolder.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1428384/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java +END +MaildirMailboxSessionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxSessionMapperFactory.java +END +MaildirStore.java +K 25 +svn:wc:ra_dav:version-url +V 120 +/repos/asf/!svn/ver/1428384/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/entries new file mode 100644 index 0000000..e51f831 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/entries @@ -0,0 +1,204 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir +http://svn.apache.org/repos/asf + + + +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mail +dir + +UidConstraint.java +file + + + + +2013-09-02T02:54:38.000000Z +428a728ebc6f13755159166ae794a684 +2010-09-06T18:59:58.520525Z +993124 +norman + + + + + + + + + + + + + + + + + + + + + +3652 + +MaildirMessageName.java +file + + + + +2013-09-02T02:54:38.000000Z +f2fdd18f313f6ccc9944048495e569e0 +2013-01-03T15:03:15.860546Z +1428384 +eric + + + + + + + + + + + + + + + + + + + + + +18255 + +MaildirFolder.java +file + + + + +2013-09-02T02:54:38.000000Z +bf1a0d0944de6d8e490aa87244053bc0 +2013-01-03T15:03:15.860546Z +1428384 +eric + + + + + + + + + + + + + + + + + + + + + +42791 + +user +dir + +MaildirMailboxSessionMapperFactory.java +file + + + + +2013-09-02T02:54:38.000000Z +806f0055e776be3639895a076b341113 +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + + + + + + + + +2704 + +MaildirStore.java +file + + + + +2013-09-02T02:54:38.000000Z +fb150f6a061359c313dea6629a029125 +2013-01-03T15:03:15.860546Z +1428384 +eric + + + + + + + + + + + + + + + + + + + + + +12147 + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirFolder.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirFolder.java.svn-base new file mode 100644 index 0000000..55468fd --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirFolder.java.svn-base @@ -0,0 +1,1018 @@ +/**************************************************************** + * 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 java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.ArrayUtils; +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.MailboxSession; + +public class MaildirFolder { + + public static final String VALIDITY_FILE = "james-uidvalidity"; + public static final String UIDLIST_FILE = "james-uidlist"; + public static final String ACL_FILE = "james-acl"; + public static final String CUR = "cur"; + public static final String NEW = "new"; + public static final String TMP = "tmp"; + + private File rootFolder; + private File curFolder; + private File newFolder; + private File tmpFolder; + private File uidFile; + private File aclFile; + + private long lastUid = -1; + private int messageCount = 0; + private long uidValidity = -1; + private MailboxACL acl; + private boolean messageNameStrictParse = false; + + private final MailboxPathLocker locker; + + private final MailboxPath path; + + /** + * Representation of a maildir folder containing the message folders + * and some special files + * @param absPath The absolute path of the mailbox folder + */ + public MaildirFolder(String absPath, MailboxPath path, MailboxPathLocker locker) { + this.rootFolder = new File(absPath); + this.curFolder = new File(rootFolder, CUR); + this.newFolder = new File(rootFolder, NEW); + this.tmpFolder = new File(rootFolder, TMP); + this.uidFile = new File(rootFolder, UIDLIST_FILE); + this.aclFile = new File(rootFolder, ACL_FILE); + this.locker = locker; + this.path = path; + } + + private MaildirMessageName newMaildirMessageName(MaildirFolder folder, String fullName) { + MaildirMessageName mdn = new MaildirMessageName(folder, fullName); + mdn.setMessageNameStrictParse(isMessageNameStrictParse()); + return mdn; + } + + /** + * Returns whether the names of message files in this folder are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * + * @return + */ + public boolean isMessageNameStrictParse() { + return messageNameStrictParse; + } + + /** + * Specifies whether the names of message files in this folder are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * + * @param messageNameStrictParse + */ + public void setMessageNameStrictParse(boolean messageNameStrictParse) { + this.messageNameStrictParse = messageNameStrictParse; + } + + /** + * Returns the {@link File} of this Maildir folder. + * @return the root folder + */ + public File getRootFile() { + return rootFolder; + } + + /** + * Tests whether the directory belonging to this {@link MaildirFolder} exists + * @return true if the directory belonging to this {@link MaildirFolder} exists ; false otherwise + */ + public boolean exists() { + return rootFolder.isDirectory() && curFolder.isDirectory() && newFolder.isDirectory() && tmpFolder.isDirectory(); + } + + /** + * Checks whether the folder's contents have been changed after + * the uidfile has been created. + * @return true if the contents have been changed. + */ + private boolean isModified() { + long uidListModified = uidFile.lastModified(); + long curModified = curFolder.lastModified(); + long newModified = newFolder.lastModified(); + // because of bad time resolution of file systems we also check "equals" + if (curModified >= uidListModified || newModified >= uidListModified) { + return true; + } + return false; + } + + /** + * Returns the ./cur folder of this Maildir folder. + * @return the ./cur folder + */ + public File getCurFolder() { + return curFolder; + } + + /** + * Returns the ./new folder of this Maildir folder. + * @return the ./new folder + */ + public File getNewFolder() { + return newFolder; + } + + /** + * Returns the ./tmp folder of this Maildir folder. + * @return the ./tmp folder + */ + public File getTmpFolder() { + return tmpFolder; + } + + /** + * Returns the nextUid value and increases it. + * @return nextUid + */ + private long getNextUid() { + return ++lastUid; + } + + /** + * Returns the last uid used in this mailbox + * @param session + * @return lastUid + * @throws MailboxException + */ + public long getLastUid(MailboxSession session) throws MailboxException { + if (lastUid == -1) { + readLastUid(session); + } + return lastUid; + } + + public long getHighestModSeq() throws IOException { + long newModified = getNewFolder().lastModified(); + long curModified = getCurFolder().lastModified(); + if (newModified == 0L && curModified == 0L) { + throw new IOException("Unable to read highest modSeq"); + } + return Math.max(newModified, curModified); + } + + /** + * Read the lastUid of the given mailbox from the file system. + * + * @param session + * @throws MailboxException if there are problems with the uidList file + */ + private void readLastUid(MailboxSession session) throws MailboxException { + locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Void execute() throws MailboxException { + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + try { + if (!uidList.exists()) + createUidFile(); + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + if (line != null) + readUidListHeader(line); + return null; + } catch (IOException e) { + throw new MailboxException("Unable to read last uid", e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + }, true); + + + } + + /** + * Returns the uidValidity of this mailbox + * @return The uidValidity + * @throws IOException + */ + public long getUidValidity() throws IOException { + if (uidValidity == -1) + uidValidity = readUidValidity(); + return uidValidity; + } + + /** + * Sets the uidValidity for this mailbox and writes it to the file system + * @param uidValidity + * @throws IOException + */ + public void setUidValidity(long uidValidity) throws IOException { + saveUidValidity(uidValidity); + this.uidValidity = uidValidity; + } + + /** + * Read the uidValidity of the given mailbox from the file system. + * If the respective file is not yet there, it gets created and + * filled with a brand new uidValidity. + * @return The uidValidity + * @throws IOException if there are problems with the validity file + */ + private long readUidValidity() throws IOException { + File validityFile = new File(rootFolder, VALIDITY_FILE); + if (!validityFile.exists()) { + return resetUidValidity(); + } + FileInputStream fis = null; + InputStreamReader isr = null; + try { + fis = new FileInputStream(validityFile); + isr = new InputStreamReader(fis); + char[] uidValidity = new char[20]; + int len = isr.read(uidValidity); + return Long.parseLong(String.valueOf(uidValidity, 0, len).trim()); + } finally { + IOUtils.closeQuietly(isr); + IOUtils.closeQuietly(fis); + } + } + + /** + * Save the given uidValidity to the file system + * @param uidValidity + * @throws IOException + */ + private void saveUidValidity(long uidValidity) throws IOException { + File validityFile = new File(rootFolder, VALIDITY_FILE); + if (!validityFile.createNewFile()) + throw new IOException("Could not create file " + validityFile); + FileOutputStream fos = new FileOutputStream(validityFile); + try { + fos.write(String.valueOf(uidValidity).getBytes()); + } finally { + IOUtils.closeQuietly(fos); + } + } + + /** + * Sets and returns a new uidValidity for this folder. + * @return the new uidValidity + * @throws IOException + */ + private long resetUidValidity() throws IOException { + // using the timestamp as uidValidity + long timestamp = System.currentTimeMillis(); + setUidValidity(timestamp); + return timestamp; + } + + /** + * Searches the uid list for a certain uid and returns the according {@link MaildirMessageName} + * + * @param session + * @param uid The uid to search for + * @return The {@link MaildirMessageName} that belongs to the uid + * @throws IOException If the uidlist file cannot be found or read + */ + public MaildirMessageName getMessageNameByUid(final MailboxSession session, final Long uid) throws MailboxException { + + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public MaildirMessageName execute() throws MailboxException { + FileReader fileReader = null; + BufferedReader reader = null; + File uidList = uidFile; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String uidString = String.valueOf(uid); + String line = reader.readLine(); // the header + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + if (!line.equals("")) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + continue; + } + + if (line.substring(0, gap).equals(uidString)) { + return newMaildirMessageName(MaildirFolder.this, line.substring(gap + 1)); + } + } + } + + // TODO: Is this right!? + return null; + } catch (IOException e) { + throw new MailboxException("Unable to read messagename for uid " + uid, e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + }, true); + } + + /** + * Reads all uids between the two boundaries from the folder and returns them as + * a sorted map together with their corresponding {@link MaildirMessageName}s. + * + * @param session + * @param from The lower uid limit + * @param to The upper uid limit. -1 disables the upper limit + * @return a {@link Map} whith all uids in the given range and associated {@link MaildirMessageName}s + * @throws MailboxException if there is a problem with the uid list file + */ + public SortedMap getUidMap(final MailboxSession session, final long from, final long to) + throws MailboxException { + return locker.executeWithLock(session, path, new LockAwareExecution>() { + + @Override + public SortedMap execute() throws MailboxException { + final SortedMap uidMap = new TreeMap(); + + File uidList = uidFile; + + if (uidList.isFile()) { + if (isModified()) { + try { + uidMap.putAll(truncateMap(updateUidFile(), from, to)); + } catch (MailboxException e) { + // weird case if someone deleted the uidlist after + // checking its + // existence and before trying to update it. + uidMap.putAll(truncateMap(createUidFile(), from, to)); + } + } else { + // the uidList is up to date + uidMap.putAll(readUidFile(session, from, to)); + } + } else { + // the uidList does not exist + uidMap.putAll(truncateMap(createUidFile(), from, to)); + } + return uidMap; + } + }, true); + } + + public SortedMap getUidMap(MailboxSession session, FilenameFilter filter, long from, long to) + throws MailboxException { + SortedMap allUids = getUidMap(session, from, to); + SortedMap filteredUids = new TreeMap(); + for (Entry entry : allUids.entrySet()) { + if (filter.accept(null, entry.getValue().getFullName())) + filteredUids.put(entry.getKey(), entry.getValue()); + } + return filteredUids; + } + + /** + * Reads all uids from the uid list file which match the given filter + * and returns as many of them as a sorted map as the limit specifies. + * + * + * @param session + * @param filter The file names of all returned items match the filter. + * The dir argument to {@link FilenameFilter}.accept(dir, name) will always be null. + * @param limit The number of items; a limit smaller then 1 disables the limit + * @return A {@link Map} with all uids and associated {@link MaildirMessageName}s + * @throws MailboxException if there is a problem with the uid list file + */ + public SortedMap getUidMap(MailboxSession session, FilenameFilter filter, int limit) throws MailboxException { + SortedMap allUids = getUidMap(session, 0, -1); + SortedMap filteredUids = new TreeMap(); + int theLimit = limit; + if (limit < 1) + theLimit = allUids.size(); + int counter = 0; + for (Entry entry : allUids.entrySet()) { + if (counter >= theLimit) + break; + if (filter.accept(null, entry.getValue().getFullName())) { + filteredUids.put(entry.getKey(), entry.getValue()); + counter++; + } + } + return filteredUids; + } + + /** + * Creates a map of recent messages. + * + * @param session + * @return A {@link Map} with all uids and associated {@link MaildirMessageName}s of recent messages + * @throws MailboxException If there is a problem with the uid list file + */ + public SortedMap getRecentMessages(final MailboxSession session) throws MailboxException { + final String[] recentFiles = getNewFolder().list(); + final LinkedList lines = new LinkedList(); + final int theLimit = recentFiles.length; + return locker.executeWithLock(session, path, new LockAwareExecution>() { + + @Override + public SortedMap execute() throws MailboxException { + final SortedMap recentMessages = new TreeMap(); + + File uidList = uidFile; + + try { + if (!uidList.isFile()) { + if (!uidList.createNewFile()) + throw new IOException("Could not create file " + uidList); + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) + lines.add(String.valueOf(getNextUid()) + " " + file); + PrintWriter pw = new PrintWriter(uidList); + try { + pw.println(createUidListHeader()); + for (String line : lines) + pw.println(line); + } finally { + IOUtils.closeQuietly(pw); + } + } + else { + FileReader fileReader = null; + BufferedReader reader = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message count + while ((line = reader.readLine()) != null) + lines.add(line); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + int counter = 0; + String line; + while (counter < theLimit) { + // walk backwards as recent files are supposedly recent + try { + line = lines.removeLast(); + } catch (NoSuchElementException e) { + break; // the list is empty + } + if (!line.equals("")) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lines.size()); + continue; + } + + Long uid = Long.valueOf(line.substring(0, gap)); + String name = line.substring(gap + 1, line.length()); + for (String recentFile : recentFiles) { + if (recentFile.equals(name)) { + recentMessages.put(uid, newMaildirMessageName(MaildirFolder.this, recentFile)); + counter++; + break; + } + } + } + } + } catch (IOException e) { + throw new MailboxException("Unable to read recent messages", e); + } + return recentMessages; + } + }, true); + } + + + /** + * Creates and returns a uid map (uid -> {@link MaildirMessageName}) and writes it to the disk + * @return The uid map + * @throws MailboxException + */ + private Map createUidFile() throws MailboxException { + final Map uidMap = new TreeMap(); + File uidList = uidFile; + PrintWriter pw = null; + try { + if (!uidList.createNewFile()) + throw new IOException("Could not create file " + uidList); + lastUid = 0; + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) + uidMap.put(getNextUid(), newMaildirMessageName(MaildirFolder.this, file)); + //uidMap = new TreeMap(uidMap); + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (Entry entry : uidMap.entrySet()) + pw.println(String.valueOf(entry.getKey()) + " " + entry.getValue().getFullName()); + } catch (IOException e) { + throw new MailboxException("Unable to create uid file", e); + } finally { + IOUtils.closeQuietly(pw); + } + + return uidMap; + } + + private Map updateUidFile() throws MailboxException { + final Map uidMap = new TreeMap(); + File uidList = uidFile; + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + HashMap reverseUidMap = new HashMap(messageCount); + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter pw = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message count + if (line != null) + readUidListHeader(line); + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + if (!line.equals("")) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + throw new MailboxException("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + } + Long uid = Long.valueOf(line.substring(0, gap)); + String name = line.substring(gap + 1, line.length()); + reverseUidMap.put(stripMetaFromName(name), uid); + } + } + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) { + MaildirMessageName messageName = newMaildirMessageName(MaildirFolder.this, file); + Long uid = reverseUidMap.get(messageName.getBaseName()); + if (uid == null) + uid = getNextUid(); + uidMap.put(uid, messageName); + } + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (Entry entry : uidMap.entrySet()) + pw.println(String.valueOf(entry.getKey()) + " " + entry.getValue().getFullName()); + } catch (IOException e) { + throw new MailboxException("Unable to update uid file", e); + } finally { + IOUtils.closeQuietly(pw); + IOUtils.closeQuietly(fileReader); + IOUtils.closeQuietly(reader); + } + return uidMap; + } + + private Map readUidFile(MailboxSession session, final long from, final long to) throws MailboxException { + final Map uidMap = new HashMap(); + + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message + // count + if (line != null) + readUidListHeader(line); + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + if (!line.equals("")) { + int gap = line.indexOf(" "); + + if (gap == -1) { + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + continue; + } + + Long uid = Long.valueOf(line.substring(0, gap)); + if (uid >= from) { + if (to != -1 && uid > to) + break; + String name = line.substring(gap + 1, line.length()); + uidMap.put(uid, newMaildirMessageName(MaildirFolder.this, name)); + } + } + } + } catch (IOException e) { + throw new MailboxException("Unable to read uid file", e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + messageCount = uidMap.size(); + + return uidMap; + } + + /** + * Sorts the given map and returns a subset which is constricted by a lower and an upper limit. + * @param source The source map + * @param from The lower limit + * @param to The upper limit; -1 disables the upper limit. + * @return The sorted subset + */ + private SortedMap truncateMap(Map source, long from, long to) { + TreeMap sortedMap; + if (source instanceof TreeMap) sortedMap = (TreeMap) source; + else sortedMap = new TreeMap(source); + if (to != -1) + return sortedMap.subMap(from, to + 1); + return sortedMap.tailMap(from); + } + + /** + * Parses the header line in uid list files. + * The format is: version lastUid messageCount (e.g. 1 615 273) + * @param line The raw header line + * @throws IOException + */ + private void readUidListHeader(String line) throws IOException { + if (line == null) + throw new IOException("Header entry in uid-file is null"); + int gap1 = line.indexOf(" "); + if (gap1 == -1) { + // there must be some issues in the file if no gap can be found + throw new IOException("Corrupted header entry in uid-file"); + + } + int version = Integer.valueOf(line.substring(0, gap1)); + if (version != 1) + throw new IOException("Cannot read uidlists with versions other than 1."); + int gap2 = line.indexOf(" ", gap1 + 1); + lastUid = Long.valueOf(line.substring(gap1 + 1, gap2)); + messageCount = Integer.valueOf(line.substring(gap2 + 1, line.length())); + } + + /** + * Creates a line to put as a header in the uid list file. + * @return the line which ought to be the header + */ + private String createUidListHeader() { + return "1 " + String.valueOf(lastUid) + " " + String.valueOf(messageCount); + } + + /** + * Takes the name of a message file and returns only the base name. + * @param fileName The name of the message file + * @return the file name without meta data, the unmodified name if it doesn't have meta data + */ + public static String stripMetaFromName(String fileName) { + int end = fileName.indexOf(",S="); // the size + if (end == -1) + end = fileName.indexOf(":2,"); // the flags + if (end == -1) + return fileName; // there is no meta data to strip + return fileName.substring(0, end); + } + + /** + * Appends a message to the uidlist and returns its uid. + * @param session + * @param name The name of the message's file + * @return The uid of the message + * @throws IOException + */ + public long appendMessage(final MailboxSession session, final String name) throws MailboxException { + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + File uidList = uidFile; + long uid = -1; + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter pw = null; + try { + if (uidList.isFile()) { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message count + if (line != null) + readUidListHeader(line); + ArrayList lines = new ArrayList(); + while ((line = reader.readLine()) != null) + lines.add(line); + uid = getNextUid(); + lines.add(String.valueOf(uid) + " " + name); + messageCount++; + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (String entry : lines) + pw.println(entry); + } + else { + // create the file + if (!uidList.createNewFile()) + throw new IOException("Could not create file " + uidList); + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + ArrayList lines = new ArrayList(); + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) { + long theUid = getNextUid(); + lines.add(String.valueOf(theUid) + " " + file); + // the listed names already include the message to append + if (file.equals(name)) + uid = theUid; + } + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (String line : lines) + pw.println(line); + } + } catch (IOException e) { + throw new MailboxException("Unable to append msg", e); + } finally { + IOUtils.closeQuietly(pw); + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + if (uid == -1) { + throw new MailboxException("Unable to append msg"); + } else { + return uid; + } + } + }, true); + + } + + /** + * Updates an entry in the uid list. + * @param session + * @param uid + * @param messageName + * @throws MailboxException + */ + public void update(final MailboxSession session, final long uid, final String messageName) throws MailboxException { + locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Void execute() throws MailboxException { + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter writer = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + readUidListHeader(line); + ArrayList lines = new ArrayList(); + while ((line = reader.readLine()) != null) { + if (uid == Long.valueOf(line.substring(0, line.indexOf(" ")))) + line = String.valueOf(uid) + " " + messageName; + lines.add(line); + } + writer = new PrintWriter(uidList); + writer.println(createUidListHeader()); + for (String entry : lines) + writer.println(entry); + } catch (IOException e) { + throw new MailboxException("Unable to update msg with uid " + uid, e); + } finally { + IOUtils.closeQuietly(writer); + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + return null; + } + }, true); + + } + + /** + * Retrieves the file belonging to the given uid, deletes it and updates + * the uid list. + * @param uid The uid of the message to delete + * @return The {@link MaildirMessageName} of the deleted message + * @throws MailboxException If the file cannot be deleted of there is a problem with the uid list + */ + public MaildirMessageName delete(final MailboxSession session, final long uid) throws MailboxException { + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public MaildirMessageName execute() throws MailboxException { + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter writer = null; + MaildirMessageName deletedMessage = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + readUidListHeader(reader.readLine()); + + // It may be possible that message count is 0 so we should better not try to calculate the size of the ArrayList + ArrayList lines = new ArrayList(); + String line; + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + continue; + } + + if (uid == Long.valueOf(line.substring(0, line.indexOf(" ")))) { + deletedMessage = newMaildirMessageName(MaildirFolder.this, line.substring(gap + 1, line.length())); + messageCount--; + } + else { + lines.add(line); + } + } + if (deletedMessage != null) { + if (!deletedMessage.getFile().delete()) + throw new IOException("Cannot delete file " + deletedMessage.getFile().getAbsolutePath()); + writer = new PrintWriter(uidList); + writer.println(createUidListHeader()); + for (String entry : lines) + writer.println(entry); + } + return deletedMessage; + + } catch (IOException e) { + throw new MailboxException("Unable to delete msg with uid " + uid, e); + } finally { + IOUtils.closeQuietly(writer); + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + }, true); + + + } + + /** + * The absolute path of this folder. + */ + @Override + public String toString() { + return getRootFile().getAbsolutePath(); + } + + public MailboxACL getACL(final MailboxSession session) throws MailboxException { + if (acl == null) { + acl = readACL(session); + } + return acl; + } + + /** + * Read the ACL of the given mailbox from the file system. + * + * @param session + * @throws MailboxException if there are problems with the aclFile file + */ + private MailboxACL readACL(MailboxSession session) throws MailboxException { + // FIXME Do we need this locking? + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public MailboxACL execute() throws MailboxException { + File f = aclFile; + InputStream in = null; + Properties props = new Properties(); + if (f.exists()) { + try { + in = new FileInputStream(f); + props.load(in); + } catch (FileNotFoundException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } catch (IOException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } + finally { + IOUtils.closeQuietly(in); + } + } + + return new SimpleMailboxACL(props); + + } + }, true); + + } + + public void setACL(final MailboxSession session, final MailboxACL acl) throws MailboxException { + MailboxACL old = this.acl; + if (old != acl && (old == null || !old.equals(acl))) { + /* change only if different */ + saveACL(acl, session); + this.acl = acl; + } + + } + + private void saveACL(final MailboxACL acl, final MailboxSession session) throws MailboxException { + // FIXME Do we need this locking? + locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Void execute() throws MailboxException { + File f = aclFile; + OutputStream out = null; + Properties props = new Properties(); + Map entries = acl.getEntries(); + if (entries != null) { + for (Entry en : entries.entrySet()) { + props.put(en.getKey().serialize(), en.getValue().serialize()); + } + } + if (f.exists()) { + try { + out = new FileOutputStream(f); + props.store(out, "written by "+ getClass().getName()); + } catch (FileNotFoundException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } catch (IOException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } + finally { + IOUtils.closeQuietly(out); + } + } + + return null; + + } + }, true); + } + + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxSessionMapperFactory.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxSessionMapperFactory.java.svn-base new file mode 100644 index 0000000..06a479f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxSessionMapperFactory.java.svn-base @@ -0,0 +1,61 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.maildir.mail.MaildirMailboxMapper; +import org.apache.james.mailbox.maildir.mail.MaildirMessageMapper; +import org.apache.james.mailbox.maildir.user.MaildirSubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +public class MaildirMailboxSessionMapperFactory extends + MailboxSessionMapperFactory { + + private final MaildirStore store; + + + public MaildirMailboxSessionMapperFactory(MaildirStore store) { + this.store = store; + } + + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) + throws MailboxException { + return new MaildirMailboxMapper(store, session); + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) + throws MailboxException { + return new MaildirMessageMapper(session, store); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) + throws SubscriptionException { + return new MaildirSubscriptionMapper(store); + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMessageName.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMessageName.java.svn-base new file mode 100644 index 0000000..527df68 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMessageName.java.svn-base @@ -0,0 +1,473 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Date; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.mail.Flags; + +public class MaildirMessageName { + + // the flags in Maildir message names + public static final String FLAG_DRAFT = "D"; + public static final String FLAG_FLAGGED = "F"; + public static final String FLAG_ANSWERD = "R"; + public static final String FLAG_SEEN = "S"; + public static final String FLAG_DELETED = "T"; + + // patterns + public static final String PATTERN_STRING_MESSAGE_NAME = "\\d+\\.\\w+\\..+?"; + public static final String PATTERN_STRING_FLAGS = ":2,[DFRST]*"; + public static final String PATTERN_STRING_SIZE = ",S=\\d+"; + public static final Pattern PATTERN_MESSAGE = + Pattern.compile(PATTERN_STRING_MESSAGE_NAME + optional(PATTERN_STRING_SIZE) + optional(PATTERN_STRING_FLAGS)); + + public static final Pattern PATTERN_UNSEEN_MESSAGES = + Pattern.compile(PATTERN_STRING_MESSAGE_NAME + PATTERN_STRING_SIZE + optional(":2,[^S]*")); + + public static final FilenameFilter FILTER_UNSEEN_MESSAGES = createRegexFilter(PATTERN_UNSEEN_MESSAGES); + + public static final Pattern PATTERN_DELETED_MESSAGES = + Pattern.compile(PATTERN_STRING_MESSAGE_NAME + PATTERN_STRING_SIZE + ":2,.*" + FLAG_DELETED); + + public static final FilenameFilter FILTER_DELETED_MESSAGES = createRegexFilter(PATTERN_DELETED_MESSAGES); + + /** + * The number of deliveries done by the server since its last start + */ + private static AtomicInteger deliveries = new AtomicInteger(0); + + /** + * A random generator for the random part in the unique message names + */ + private static Random random = new Random(); + + /** + * The process id of the server process + */ + private static String processName = ManagementFactory.getRuntimeMXBean().getName(); + static { + String[] parts = processName.split("@"); + if (parts.length > 1) + processName = parts[0]; + } + + /** + * The host name of the machine the server is running on + */ + private static String currentHostname; + static { + try { + currentHostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + currentHostname = "localhost"; + } + } + + private String fullName; + private File file; + private MaildirFolder parentFolder; + private String timestamp; + private String uniqueString; + private String hostnameAndMeta; // tim-erwin.de,S=1000:2,RS + private String hostname; + private String sizeString; + private String flagsString; + private boolean isSplit; + private Date internalDate; + private Long size; + private Flags flags; + private boolean messageNameStrictParse = false; + + public MaildirMessageName(MaildirFolder parentFolder, String fullName) { + this.parentFolder = parentFolder; + setFullName(fullName); + } + + public boolean isMessageNameStrictParse() { + return messageNameStrictParse; + } + + public void setMessageNameStrictParse(boolean messageNameStrictParse) { + this.messageNameStrictParse = messageNameStrictParse; + } + + /** + * Tests whether the file or directory belonging to this {@link MaildirFolder} exists. + * If the file exists, its absolute path is written to absPath. + * TODO: If the flags have changed or the file doesn't exist any more, the uidlist should be updated + * @return true if the file or directory belonging to this {@link MaildirFolder} exists ; false otherwise + */ + public boolean exists() { + if (file != null && file.isFile()) + return true; + File assumedFile1 = new File(parentFolder.getCurFolder(), fullName); + if (assumedFile1.isFile()) { + file = assumedFile1; + return true; + } + File assumedFile2 = new File(parentFolder.getNewFolder(), fullName); + if (assumedFile2.isFile()) { + file = assumedFile2; + return true; + } + // check if maybe the flags have changed which means + // list the files in the cur and new folder and check if the message is there + FilenameFilter filter = getFilenameFilter(); + File[] matchingFiles1 = parentFolder.getCurFolder().listFiles(filter); + if (matchingFiles1.length == 1) { + setFullName(matchingFiles1[0].getName()); + file = matchingFiles1[0]; + return true; + } + File[] matchingFiles2 = parentFolder.getNewFolder().listFiles(filter); + if (matchingFiles2.length == 1) { + setFullName(matchingFiles2[0].getName()); + file = matchingFiles2[0]; + return true; + } + return false; + } + + /** + * Sets the fullName of this {@link MaildirMessageName} if different from the current one. + * As this invalidates the parsed elements, they are being reset. + * @param fullName A name of a message file in the correct Maildir format + */ + public void setFullName(String fullName) { + if (this.fullName == null || !this.fullName.equals(fullName)) { + this.fullName = fullName; + this.file = null; + this.isSplit = false; + this.internalDate = null; + this.size = null; + this.flags = null; + } + } + + /** + * Returns the full name of this message including size and flags if available. + * @return the full name of this message + */ + public String getFullName() { + if (this.fullName == null) { + StringBuilder fullBuffer = new StringBuilder(); + fullBuffer.append(timestamp); + fullBuffer.append("."); + fullBuffer.append(uniqueString); + fullBuffer.append("."); + fullBuffer.append(hostname); + if (sizeString != null) + fullBuffer.append(sizeString); + if (flagsString != null) + fullBuffer.append(flagsString); + fullName = fullBuffer.toString(); + } + return fullName; + } + + /** + * Returns a {@link File} object of the message denoted by this {@link MaildirMessageName}. + * Also checks for different flags if it cannot be found directly. + * @return A {@link File} object + * @throws FileNotFoundException If there is no file for the given name + */ + public File getFile() throws FileNotFoundException { + if (exists()) + return file; + else + throw new FileNotFoundException("There is no file for message name " + fullName + + " in mailbox " + parentFolder.getRootFile().getAbsolutePath()); + } + + /** + * Creates a filter which matches the message file even if the flags have changed + * @return filter for this message + */ + public FilenameFilter getFilenameFilter() { + split(); + StringBuilder pattern = new StringBuilder(); + pattern.append(timestamp); + pattern.append("\\."); + pattern.append(uniqueString); + pattern.append("\\."); + pattern.append(hostname); + pattern.append(".*"); + return createRegexFilter(Pattern.compile(pattern.toString())); + } + + /** + * Splits up the full file name if necessary. + */ + private void split() { + if (!isSplit) { + splitFullName(); + splitHostNameAndMeta(); + isSplit = true; + } + } + + /** + * Splits up the full file name into its main components timestamp, + * uniqueString and hostNameAndMeta and fills the respective variables. + */ + private void splitFullName() { + int firstEnd = fullName.indexOf('.'); + int secondEnd = fullName.indexOf('.', firstEnd + 1); + timestamp = fullName.substring(0, firstEnd); + uniqueString = fullName.substring(firstEnd + 1, secondEnd); + hostnameAndMeta = fullName.substring(secondEnd + 1, fullName.length()); + } + + /** + * Splits up the third part of the file name (e.g. tim-erwin.de,S=1000:2,RS) + * into its components hostname, size and flags and fills the respective variables. + */ + private void splitHostNameAndMeta() { + String[] hostnamemetaFlags = hostnameAndMeta.split(":", 2); + if (hostnamemetaFlags.length >= 1) { + this.hostnameAndMeta = hostnamemetaFlags[0]; + int firstEnd = hostnameAndMeta.indexOf(','); + + // read size field if existent + if (firstEnd > 0) { + hostname = hostnameAndMeta.substring(0, firstEnd); + String attrStr = hostnameAndMeta.substring(firstEnd); + String[] fields = attrStr.split(","); + for (String field : fields) { + if (field.startsWith("S=")) { + sizeString = "," + field; + } + } + } else { + sizeString = null; + hostname = this.hostnameAndMeta; + } + } + + if (hostnamemetaFlags.length >= 2) { + this.flagsString = ":" + hostnamemetaFlags[1]; + } + if (isMessageNameStrictParse()) { + if (sizeString == null) { + throw new IllegalArgumentException("No message size found in message name: "+ fullName); + } + if (flagsString == null) { + throw new IllegalArgumentException("No flags found in message name: "+ fullName); + } + } + } + + /** + * Sets new flags for this message name. + * @param flags + */ + public void setFlags(Flags flags) { + if (this.flags != flags) { + split(); // save all parts + this.flags = flags; + this.flagsString = encodeFlags(flags); + this.fullName = null; // invalidate the fullName + } + } + + /** + * Decodes the flags part of the file name if necessary and returns the appropriate Flags object. + * @return The {@link Flags} of this message + */ + public Flags getFlags() { + if (flags == null) { + split(); + if (flagsString == null) + return null; + if (flagsString.length() >= 3) + flags = decodeFlags(flagsString.substring(3)); // skip the ":2," part + } + return flags; + } + + /** + * Decodes the size part of the file name if necessary and returns the appropriate Long. + * @return The size of this message as a {@link Long} + */ + public Long getSize() { + if (size == null) { + split(); + if (sizeString == null) + return null; + if (!sizeString.startsWith(",S=")) + return null; + size = Long.valueOf(sizeString.substring(3)); // skip the ",S=" part + } + return size; + } + + /** + * Decodes the time part of the file name if necessary and returns the appropriate Date. + * @return The time of this message as a {@link Date} + */ + public Date getInternalDate() { + if (internalDate == null) { + split(); + if (timestamp == null) + return null; + internalDate = new Date(Long.valueOf(timestamp) * 1000); + } + return internalDate; + } + + /** + * Composes the base name consisting of timestamp, unique string and host name + * witout the size and flags. + * @return The base name + */ + public String getBaseName() { + split(); + StringBuilder baseName = new StringBuilder(); + baseName.append(timestamp); + baseName.append("."); + baseName.append(uniqueString); + baseName.append("."); + baseName.append(hostname); + return baseName.toString(); + } + + /** + * Creates a String that represents the provided Flags + * @param flags The flags to encode + * @return A String valid for Maildir + */ + public String encodeFlags(Flags flags) { + StringBuilder localFlagsString = new StringBuilder(":2,"); + if (flags.contains(Flags.Flag.DRAFT)) + localFlagsString.append(FLAG_DRAFT); + if (flags.contains(Flags.Flag.FLAGGED)) + localFlagsString.append(FLAG_FLAGGED); + if (flags.contains(Flags.Flag.ANSWERED)) + localFlagsString.append(FLAG_ANSWERD); + if (flags.contains(Flags.Flag.SEEN)) + localFlagsString.append(FLAG_SEEN); + if (flags.contains(Flags.Flag.DELETED)) + localFlagsString.append(FLAG_DELETED); + return localFlagsString.toString(); + } + + /** + * Takes a String which is "Maildir encoded" and translates it + * into a {@link Flags} object. + * @param flagsString The String with the flags + * @return A Flags object containing the flags read form the String + */ + public Flags decodeFlags(String flagsString) { + Flags localFlags = new Flags(); + if (flagsString.contains(FLAG_DRAFT)) + localFlags.add(Flags.Flag.DRAFT); + if (flagsString.contains(FLAG_FLAGGED)) + localFlags.add(Flags.Flag.FLAGGED); + if (flagsString.contains(FLAG_ANSWERD)) + localFlags.add(Flags.Flag.ANSWERED); + if (flagsString.contains(FLAG_SEEN)) + localFlags.add(Flags.Flag.SEEN); + if (flagsString.contains(FLAG_DELETED)) + localFlags.add(Flags.Flag.DELETED); + return localFlags; + } + + /** + * Returns and increases a global counter for the number + * of deliveries done since the server has been started. + * This is used for the creation of message names. + * @return The number of (attempted) deliveries until now + */ + static private long getNextDeliveryNumber() { + return deliveries.getAndIncrement(); + } + + /** + * Create a name for a message according to
+ * The following elements are used: + *

+ * "A unique name has three pieces, separated by dots. On the left is the result of time() + * or the second counter from gettimeofday(). On the right is the result of gethostname(). + * (To deal with invalid host names, replace / with \057 and : with \072.) + * In the middle is a delivery identifier, discussed below. + *

+ * Modern delivery identifiers are created by concatenating enough of the following strings + * to guarantee uniqueness: + *

+ * [...]
+ * * Rn, where n is (in hexadecimal) the output of the operating system's unix_cryptorandomnumber() system call, or an equivalent source such as /dev/urandom. Unfortunately, some operating systems don't include cryptographic random number generators.
+ * [...]
+ * * Mn, where n is (in decimal) the microsecond counter from the same gettimeofday() used for the left part of the unique name.
+ * * Pn, where n is (in decimal) the process ID.
+ * * Qn, where n is (in decimal) the number of deliveries made by this process.
+ *
+ * [...]" + * + * @return unique message name + */ + public static MaildirMessageName createUniqueName(MaildirFolder parentFolder, long size) { + String timestamp = String.valueOf(System.currentTimeMillis()); + timestamp = timestamp.substring(0, timestamp.length()-3); // time in seconds + StringBuilder uniquePart = new StringBuilder(); + uniquePart.append(Integer.toHexString(random.nextInt())); // random number as hex + uniquePart.append(timestamp.substring(timestamp.length()-3)); // milliseconds + uniquePart.append(processName); // process name + uniquePart.append(getNextDeliveryNumber()); // delivery number + String sizeString = ",S=" + String.valueOf(size); + String fullName = timestamp + "." + uniquePart.toString() + "." + currentHostname + sizeString; + MaildirMessageName uniqueName = new MaildirMessageName(parentFolder, fullName); + uniqueName.timestamp = timestamp; + uniqueName.uniqueString = uniquePart.toString(); + uniqueName.hostname = currentHostname; + uniqueName.sizeString = sizeString; + uniqueName.isSplit = true; + uniqueName.size = size; + return uniqueName; + } + + public static FilenameFilter createRegexFilter(final Pattern pattern) { + return new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + Matcher matcher = pattern.matcher(name); + return matcher.matches(); + } + }; + } + + public static String optional(String pattern) { + return "(" + pattern + ")?"; + } + + @Override + public String toString() { + return getFullName(); + } +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStore.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStore.java.svn-base new file mode 100644 index 0000000..880cc6a --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStore.java.svn-base @@ -0,0 +1,291 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.IOException; +import java.util.Locale; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.maildir.mail.model.MaildirMailbox; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class MaildirStore implements UidProvider, ModSeqProvider{ + + public static final String PATH_USER = "%user"; + public static final String PATH_DOMAIN = "%domain"; + public static final String PATH_FULLUSER = "%fulluser"; + public static final String WILDCARD = "%"; + + public static final String maildirDelimiter = "."; + + private String maildirLocation; + + private File maildirRootFile; + private final MailboxPathLocker locker; + + private boolean messageNameStrictParse = false; + + /** + * Construct a MaildirStore with a location. The location String + * currently may contain the + * %user, + * %domain, + * %fulluser + * variables. + * @param maildirLocation A String with variables + * @param locker + */ + public MaildirStore(String maildirLocation, MailboxPathLocker locker) { + this.maildirLocation = maildirLocation; + this.locker = locker; + } + + public MaildirStore(String maildirLocation) { + this(maildirLocation, new JVMMailboxPathLocker()); + } + + + public String getMaildirLocation() { + return maildirLocation; + } + /** + * Create a {@link MaildirFolder} for a mailbox + * @param mailbox + * @return The MaildirFolder + */ + public MaildirFolder createMaildirFolder(Mailbox mailbox) { + MaildirFolder mf = new MaildirFolder(getFolderName(mailbox), new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()), locker); + mf.setMessageNameStrictParse(isMessageNameStrictParse()); + return mf; + } + + /** + * Creates a Mailbox object with data loaded from the file system + * @param root The main maildir folder containing the mailbox to load + * @param namespace The namespace to use + * @param user The owner of this mailbox + * @param folderName The name of the mailbox folder + * @return The Mailbox object populated with data from the file system + * @throws MailboxException If the mailbox folder doesn't exist or can't be read + */ + public Mailbox loadMailbox(MailboxSession session, File root, String namespace, String user, String folderName) throws MailboxException { + String mailboxName = getMailboxNameFromFolderName(folderName); + return loadMailbox(session, new File(root, folderName), new MailboxPath(namespace, user, mailboxName)); + } + + /** + * Creates a Mailbox object with data loaded from the file system + * @param mailboxPath The path of the mailbox + * @return The Mailbox object populated with data from the file system + * @throws MailboxNotFoundException If the mailbox folder doesn't exist + * @throws MailboxException If the mailbox folder can't be read + */ + public Mailbox loadMailbox(MailboxSession session, MailboxPath mailboxPath) + throws MailboxNotFoundException, MailboxException { + MaildirFolder folder = new MaildirFolder(getFolderName(mailboxPath), mailboxPath, locker); + folder.setMessageNameStrictParse(isMessageNameStrictParse()); + if (!folder.exists()) + throw new MailboxNotFoundException(mailboxPath); + return loadMailbox(session, folder.getRootFile(), mailboxPath); + } + + /** + * Creates a Mailbox object with data loaded from the file system + * @param mailboxFile File object referencing the folder for the mailbox + * @param mailboxPath The path of the mailbox + * @return The Mailbox object populated with data from the file system + * @throws MailboxException If the mailbox folder doesn't exist or can't be read + */ + private Mailbox loadMailbox(MailboxSession session, File mailboxFile, MailboxPath mailboxPath) throws MailboxException { + MaildirFolder folder = new MaildirFolder(mailboxFile.getAbsolutePath(), mailboxPath, locker); + folder.setMessageNameStrictParse(isMessageNameStrictParse()); + try { + return new MaildirMailbox(session, mailboxPath, folder); + } catch (IOException e) { + throw new MailboxException("Unable to load Mailbox " + mailboxPath, e); + } + } + + /** + * Inserts the user name parts in the general maildir location String + * @param user The user to get the root for. + * @return The name of the folder which contains the specified user's mailbox + */ + public String userRoot(String user) { + String path = maildirLocation.replace(PATH_FULLUSER, user); + String[] userParts = user.split("@"); + String userName = user; + if (userParts.length == 2) { + userName = userParts[0]; + // At least the domain part should not handled in a case-sensitive manner + // See MAILBOX-58 + path = path.replace(PATH_DOMAIN, userParts[1].toLowerCase(Locale.US)); + } + path = path.replace(PATH_USER, userName); + return path; + } + + /** + * The main maildir folder containing all mailboxes for one user + * @param user The user name of a mailbox + * @return A File object referencing the main maildir folder + * @throws MailboxException If the folder does not exist or is no directory + */ + public File getMailboxRootForUser(String user) throws MailboxException { + String path = userRoot(user); + File root = new File(path); + if (!root.isDirectory()) + throw new MailboxException("Unable to load Mailbox for user " + user); + return root; + } + + /** + * Return a File which is the root of all Maidirs. + * The returned maidirRootFile is lazilly constructured. + * + * @return maidirRootFile + */ + public File getMaildirRoot() { + if (maildirRootFile == null) { + String maildirRootLocation = maildirLocation.replaceAll(PATH_FULLUSER, ""); + maildirRootLocation = maildirRootLocation.replaceAll(PATH_DOMAIN, ""); + maildirRootLocation = maildirRootLocation.replaceAll(PATH_USER, ""); + maildirRootFile = new File(maildirRootLocation); + } + return maildirRootFile; + } + + /** + * Transforms a folder name into a mailbox name + * @param folderName The name of the mailbox folder + * @return The complete (namespace) name of a mailbox + */ + public String getMailboxNameFromFolderName(String folderName) { + String mName; + if (folderName.equals("")) mName = MailboxConstants.INBOX; + else + // remove leading dot + mName = folderName.substring(1); + // they are equal, anyways, this might change someday... + //if (maildirDelimiter != MailboxConstants.DEFAULT_DELIMITER_STRING) + // mName = mName.replace(maildirDelimiter, MailboxConstants.DEFAULT_DELIMITER_STRING); + return mName; + } + + /** + * Get the absolute name of the folder for a specific mailbox + * @param namespace The namespace of the mailbox + * @param user The user of the mailbox + * @param name The name of the mailbox + * @return absolute name + */ + public String getFolderName(String namespace, String user, String name) { + String root = userRoot(user); + // if INBOX => location == maildirLocation + if (name.equals(MailboxConstants.INBOX)) + return root; + StringBuilder folder = new StringBuilder(root); + if (!root.endsWith(File.pathSeparator)) + folder.append(File.separator); + folder.append("."); + folder.append(name); + return folder.toString(); + } + + /** + * Get the absolute name of the folder for a specific mailbox + * @param mailbox The mailbox + * @return The absolute path to the folder containing the mailbox + */ + public String getFolderName(Mailbox mailbox) { + return getFolderName(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()); + } + + /** + * Get the absolute name of the folder for a specific mailbox + * @param mailboxPath The MailboxPath + * @return The absolute path to the folder containing the mailbox + */ + public String getFolderName(MailboxPath mailboxPath) { + return getFolderName(mailboxPath.getNamespace(), mailboxPath.getUser(), mailboxPath.getName()); + } + + /** + * @see org.apache.james.mailbox.store.mail.UidProvider#nextUid(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + return createMaildirFolder(mailbox).getLastUid(session) +1; + } catch (MailboxException e) { + throw new MailboxException("Unable to generate next uid", e); + } + } + + @Override + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + return System.currentTimeMillis(); + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + return createMaildirFolder(mailbox).getHighestModSeq(); + } catch (IOException e) { + throw new MailboxException("Unable to get highest mod-sequence for mailbox", e); + } + } + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + return createMaildirFolder(mailbox).getLastUid(session); + } + + /** + * Returns whether the names of message files in this store are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * @return + */ + public boolean isMessageNameStrictParse() { + return messageNameStrictParse; + } + + /** + * Specifies whether the names of message files in this store are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * + * Default is {@code false}. + * + * @param messageNameStrictParse + */ + public void setMessageNameStrictParse(boolean messageNameStrictParse) { + this.messageNameStrictParse = messageNameStrictParse; + } +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/UidConstraint.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/UidConstraint.java.svn-base new file mode 100644 index 0000000..f5e1fd5 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/.svn/text-base/UidConstraint.java.svn-base @@ -0,0 +1,126 @@ +/**************************************************************** + * 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 java.util.LinkedList; + +public class UidConstraint { + + private LinkedList constraints = new LinkedList(); + + public UidConstraint append(Constraint constraint) { + constraints.add(constraint); + return this; + } + + public UidConstraint equals(long uid) { + constraints.add(new Equals(uid)); + return this; + } + + public UidConstraint lessOrEquals(long uid) { + constraints.add(new LessOrEquals(uid)); + return this; + } + + public UidConstraint greaterOrEquals(long uid) { + constraints.add(new GreaterOrEquals(uid)); + return this; + } + + public UidConstraint between(long lower, long upper) { + constraints.add(new Between(lower, upper)); + return this; + } + + public boolean isAllowed(long uid) { + for (Constraint constraint : constraints) + if (!constraint.isAllowed(uid)) + return false; + return true; + } + + public abstract static class Constraint { + + public abstract boolean isAllowed(long uid); + + } + + public static class Equals extends Constraint { + + private long uid; + + public Equals(long uid) { + this.uid = uid; + } + + @Override + public boolean isAllowed(long uid) { + return this.uid == uid; + } + + } + + public static class LessOrEquals extends Constraint { + + private long uid; + + public LessOrEquals(long uid) { + this.uid = uid; + } + + @Override + public boolean isAllowed(long uid) { + return uid <= this.uid; + } + + } + + public static class GreaterOrEquals extends Constraint { + + private long uid; + + public GreaterOrEquals(long uid) { + this.uid = uid; + } + + @Override + public boolean isAllowed(long uid) { + return uid >= this.uid; + } + + } + + public static class Between extends Constraint { + + private long lower; + private long upper; + + public Between(long lower, long upper) { + this.lower = lower; + this.upper = upper; + } + + @Override + public boolean isAllowed(long uid) { + return (uid >= lower) && (uid <= upper); + } + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java new file mode 100644 index 0000000..55468fd --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java @@ -0,0 +1,1018 @@ +/**************************************************************** + * 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 java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.ArrayUtils; +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.MailboxSession; + +public class MaildirFolder { + + public static final String VALIDITY_FILE = "james-uidvalidity"; + public static final String UIDLIST_FILE = "james-uidlist"; + public static final String ACL_FILE = "james-acl"; + public static final String CUR = "cur"; + public static final String NEW = "new"; + public static final String TMP = "tmp"; + + private File rootFolder; + private File curFolder; + private File newFolder; + private File tmpFolder; + private File uidFile; + private File aclFile; + + private long lastUid = -1; + private int messageCount = 0; + private long uidValidity = -1; + private MailboxACL acl; + private boolean messageNameStrictParse = false; + + private final MailboxPathLocker locker; + + private final MailboxPath path; + + /** + * Representation of a maildir folder containing the message folders + * and some special files + * @param absPath The absolute path of the mailbox folder + */ + public MaildirFolder(String absPath, MailboxPath path, MailboxPathLocker locker) { + this.rootFolder = new File(absPath); + this.curFolder = new File(rootFolder, CUR); + this.newFolder = new File(rootFolder, NEW); + this.tmpFolder = new File(rootFolder, TMP); + this.uidFile = new File(rootFolder, UIDLIST_FILE); + this.aclFile = new File(rootFolder, ACL_FILE); + this.locker = locker; + this.path = path; + } + + private MaildirMessageName newMaildirMessageName(MaildirFolder folder, String fullName) { + MaildirMessageName mdn = new MaildirMessageName(folder, fullName); + mdn.setMessageNameStrictParse(isMessageNameStrictParse()); + return mdn; + } + + /** + * Returns whether the names of message files in this folder are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * + * @return + */ + public boolean isMessageNameStrictParse() { + return messageNameStrictParse; + } + + /** + * Specifies whether the names of message files in this folder are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * + * @param messageNameStrictParse + */ + public void setMessageNameStrictParse(boolean messageNameStrictParse) { + this.messageNameStrictParse = messageNameStrictParse; + } + + /** + * Returns the {@link File} of this Maildir folder. + * @return the root folder + */ + public File getRootFile() { + return rootFolder; + } + + /** + * Tests whether the directory belonging to this {@link MaildirFolder} exists + * @return true if the directory belonging to this {@link MaildirFolder} exists ; false otherwise + */ + public boolean exists() { + return rootFolder.isDirectory() && curFolder.isDirectory() && newFolder.isDirectory() && tmpFolder.isDirectory(); + } + + /** + * Checks whether the folder's contents have been changed after + * the uidfile has been created. + * @return true if the contents have been changed. + */ + private boolean isModified() { + long uidListModified = uidFile.lastModified(); + long curModified = curFolder.lastModified(); + long newModified = newFolder.lastModified(); + // because of bad time resolution of file systems we also check "equals" + if (curModified >= uidListModified || newModified >= uidListModified) { + return true; + } + return false; + } + + /** + * Returns the ./cur folder of this Maildir folder. + * @return the ./cur folder + */ + public File getCurFolder() { + return curFolder; + } + + /** + * Returns the ./new folder of this Maildir folder. + * @return the ./new folder + */ + public File getNewFolder() { + return newFolder; + } + + /** + * Returns the ./tmp folder of this Maildir folder. + * @return the ./tmp folder + */ + public File getTmpFolder() { + return tmpFolder; + } + + /** + * Returns the nextUid value and increases it. + * @return nextUid + */ + private long getNextUid() { + return ++lastUid; + } + + /** + * Returns the last uid used in this mailbox + * @param session + * @return lastUid + * @throws MailboxException + */ + public long getLastUid(MailboxSession session) throws MailboxException { + if (lastUid == -1) { + readLastUid(session); + } + return lastUid; + } + + public long getHighestModSeq() throws IOException { + long newModified = getNewFolder().lastModified(); + long curModified = getCurFolder().lastModified(); + if (newModified == 0L && curModified == 0L) { + throw new IOException("Unable to read highest modSeq"); + } + return Math.max(newModified, curModified); + } + + /** + * Read the lastUid of the given mailbox from the file system. + * + * @param session + * @throws MailboxException if there are problems with the uidList file + */ + private void readLastUid(MailboxSession session) throws MailboxException { + locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Void execute() throws MailboxException { + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + try { + if (!uidList.exists()) + createUidFile(); + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + if (line != null) + readUidListHeader(line); + return null; + } catch (IOException e) { + throw new MailboxException("Unable to read last uid", e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + }, true); + + + } + + /** + * Returns the uidValidity of this mailbox + * @return The uidValidity + * @throws IOException + */ + public long getUidValidity() throws IOException { + if (uidValidity == -1) + uidValidity = readUidValidity(); + return uidValidity; + } + + /** + * Sets the uidValidity for this mailbox and writes it to the file system + * @param uidValidity + * @throws IOException + */ + public void setUidValidity(long uidValidity) throws IOException { + saveUidValidity(uidValidity); + this.uidValidity = uidValidity; + } + + /** + * Read the uidValidity of the given mailbox from the file system. + * If the respective file is not yet there, it gets created and + * filled with a brand new uidValidity. + * @return The uidValidity + * @throws IOException if there are problems with the validity file + */ + private long readUidValidity() throws IOException { + File validityFile = new File(rootFolder, VALIDITY_FILE); + if (!validityFile.exists()) { + return resetUidValidity(); + } + FileInputStream fis = null; + InputStreamReader isr = null; + try { + fis = new FileInputStream(validityFile); + isr = new InputStreamReader(fis); + char[] uidValidity = new char[20]; + int len = isr.read(uidValidity); + return Long.parseLong(String.valueOf(uidValidity, 0, len).trim()); + } finally { + IOUtils.closeQuietly(isr); + IOUtils.closeQuietly(fis); + } + } + + /** + * Save the given uidValidity to the file system + * @param uidValidity + * @throws IOException + */ + private void saveUidValidity(long uidValidity) throws IOException { + File validityFile = new File(rootFolder, VALIDITY_FILE); + if (!validityFile.createNewFile()) + throw new IOException("Could not create file " + validityFile); + FileOutputStream fos = new FileOutputStream(validityFile); + try { + fos.write(String.valueOf(uidValidity).getBytes()); + } finally { + IOUtils.closeQuietly(fos); + } + } + + /** + * Sets and returns a new uidValidity for this folder. + * @return the new uidValidity + * @throws IOException + */ + private long resetUidValidity() throws IOException { + // using the timestamp as uidValidity + long timestamp = System.currentTimeMillis(); + setUidValidity(timestamp); + return timestamp; + } + + /** + * Searches the uid list for a certain uid and returns the according {@link MaildirMessageName} + * + * @param session + * @param uid The uid to search for + * @return The {@link MaildirMessageName} that belongs to the uid + * @throws IOException If the uidlist file cannot be found or read + */ + public MaildirMessageName getMessageNameByUid(final MailboxSession session, final Long uid) throws MailboxException { + + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public MaildirMessageName execute() throws MailboxException { + FileReader fileReader = null; + BufferedReader reader = null; + File uidList = uidFile; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String uidString = String.valueOf(uid); + String line = reader.readLine(); // the header + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + if (!line.equals("")) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + continue; + } + + if (line.substring(0, gap).equals(uidString)) { + return newMaildirMessageName(MaildirFolder.this, line.substring(gap + 1)); + } + } + } + + // TODO: Is this right!? + return null; + } catch (IOException e) { + throw new MailboxException("Unable to read messagename for uid " + uid, e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + }, true); + } + + /** + * Reads all uids between the two boundaries from the folder and returns them as + * a sorted map together with their corresponding {@link MaildirMessageName}s. + * + * @param session + * @param from The lower uid limit + * @param to The upper uid limit. -1 disables the upper limit + * @return a {@link Map} whith all uids in the given range and associated {@link MaildirMessageName}s + * @throws MailboxException if there is a problem with the uid list file + */ + public SortedMap getUidMap(final MailboxSession session, final long from, final long to) + throws MailboxException { + return locker.executeWithLock(session, path, new LockAwareExecution>() { + + @Override + public SortedMap execute() throws MailboxException { + final SortedMap uidMap = new TreeMap(); + + File uidList = uidFile; + + if (uidList.isFile()) { + if (isModified()) { + try { + uidMap.putAll(truncateMap(updateUidFile(), from, to)); + } catch (MailboxException e) { + // weird case if someone deleted the uidlist after + // checking its + // existence and before trying to update it. + uidMap.putAll(truncateMap(createUidFile(), from, to)); + } + } else { + // the uidList is up to date + uidMap.putAll(readUidFile(session, from, to)); + } + } else { + // the uidList does not exist + uidMap.putAll(truncateMap(createUidFile(), from, to)); + } + return uidMap; + } + }, true); + } + + public SortedMap getUidMap(MailboxSession session, FilenameFilter filter, long from, long to) + throws MailboxException { + SortedMap allUids = getUidMap(session, from, to); + SortedMap filteredUids = new TreeMap(); + for (Entry entry : allUids.entrySet()) { + if (filter.accept(null, entry.getValue().getFullName())) + filteredUids.put(entry.getKey(), entry.getValue()); + } + return filteredUids; + } + + /** + * Reads all uids from the uid list file which match the given filter + * and returns as many of them as a sorted map as the limit specifies. + * + * + * @param session + * @param filter The file names of all returned items match the filter. + * The dir argument to {@link FilenameFilter}.accept(dir, name) will always be null. + * @param limit The number of items; a limit smaller then 1 disables the limit + * @return A {@link Map} with all uids and associated {@link MaildirMessageName}s + * @throws MailboxException if there is a problem with the uid list file + */ + public SortedMap getUidMap(MailboxSession session, FilenameFilter filter, int limit) throws MailboxException { + SortedMap allUids = getUidMap(session, 0, -1); + SortedMap filteredUids = new TreeMap(); + int theLimit = limit; + if (limit < 1) + theLimit = allUids.size(); + int counter = 0; + for (Entry entry : allUids.entrySet()) { + if (counter >= theLimit) + break; + if (filter.accept(null, entry.getValue().getFullName())) { + filteredUids.put(entry.getKey(), entry.getValue()); + counter++; + } + } + return filteredUids; + } + + /** + * Creates a map of recent messages. + * + * @param session + * @return A {@link Map} with all uids and associated {@link MaildirMessageName}s of recent messages + * @throws MailboxException If there is a problem with the uid list file + */ + public SortedMap getRecentMessages(final MailboxSession session) throws MailboxException { + final String[] recentFiles = getNewFolder().list(); + final LinkedList lines = new LinkedList(); + final int theLimit = recentFiles.length; + return locker.executeWithLock(session, path, new LockAwareExecution>() { + + @Override + public SortedMap execute() throws MailboxException { + final SortedMap recentMessages = new TreeMap(); + + File uidList = uidFile; + + try { + if (!uidList.isFile()) { + if (!uidList.createNewFile()) + throw new IOException("Could not create file " + uidList); + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) + lines.add(String.valueOf(getNextUid()) + " " + file); + PrintWriter pw = new PrintWriter(uidList); + try { + pw.println(createUidListHeader()); + for (String line : lines) + pw.println(line); + } finally { + IOUtils.closeQuietly(pw); + } + } + else { + FileReader fileReader = null; + BufferedReader reader = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message count + while ((line = reader.readLine()) != null) + lines.add(line); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + int counter = 0; + String line; + while (counter < theLimit) { + // walk backwards as recent files are supposedly recent + try { + line = lines.removeLast(); + } catch (NoSuchElementException e) { + break; // the list is empty + } + if (!line.equals("")) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lines.size()); + continue; + } + + Long uid = Long.valueOf(line.substring(0, gap)); + String name = line.substring(gap + 1, line.length()); + for (String recentFile : recentFiles) { + if (recentFile.equals(name)) { + recentMessages.put(uid, newMaildirMessageName(MaildirFolder.this, recentFile)); + counter++; + break; + } + } + } + } + } catch (IOException e) { + throw new MailboxException("Unable to read recent messages", e); + } + return recentMessages; + } + }, true); + } + + + /** + * Creates and returns a uid map (uid -> {@link MaildirMessageName}) and writes it to the disk + * @return The uid map + * @throws MailboxException + */ + private Map createUidFile() throws MailboxException { + final Map uidMap = new TreeMap(); + File uidList = uidFile; + PrintWriter pw = null; + try { + if (!uidList.createNewFile()) + throw new IOException("Could not create file " + uidList); + lastUid = 0; + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) + uidMap.put(getNextUid(), newMaildirMessageName(MaildirFolder.this, file)); + //uidMap = new TreeMap(uidMap); + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (Entry entry : uidMap.entrySet()) + pw.println(String.valueOf(entry.getKey()) + " " + entry.getValue().getFullName()); + } catch (IOException e) { + throw new MailboxException("Unable to create uid file", e); + } finally { + IOUtils.closeQuietly(pw); + } + + return uidMap; + } + + private Map updateUidFile() throws MailboxException { + final Map uidMap = new TreeMap(); + File uidList = uidFile; + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + HashMap reverseUidMap = new HashMap(messageCount); + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter pw = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message count + if (line != null) + readUidListHeader(line); + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + if (!line.equals("")) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + throw new MailboxException("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + } + Long uid = Long.valueOf(line.substring(0, gap)); + String name = line.substring(gap + 1, line.length()); + reverseUidMap.put(stripMetaFromName(name), uid); + } + } + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) { + MaildirMessageName messageName = newMaildirMessageName(MaildirFolder.this, file); + Long uid = reverseUidMap.get(messageName.getBaseName()); + if (uid == null) + uid = getNextUid(); + uidMap.put(uid, messageName); + } + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (Entry entry : uidMap.entrySet()) + pw.println(String.valueOf(entry.getKey()) + " " + entry.getValue().getFullName()); + } catch (IOException e) { + throw new MailboxException("Unable to update uid file", e); + } finally { + IOUtils.closeQuietly(pw); + IOUtils.closeQuietly(fileReader); + IOUtils.closeQuietly(reader); + } + return uidMap; + } + + private Map readUidFile(MailboxSession session, final long from, final long to) throws MailboxException { + final Map uidMap = new HashMap(); + + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message + // count + if (line != null) + readUidListHeader(line); + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + if (!line.equals("")) { + int gap = line.indexOf(" "); + + if (gap == -1) { + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + continue; + } + + Long uid = Long.valueOf(line.substring(0, gap)); + if (uid >= from) { + if (to != -1 && uid > to) + break; + String name = line.substring(gap + 1, line.length()); + uidMap.put(uid, newMaildirMessageName(MaildirFolder.this, name)); + } + } + } + } catch (IOException e) { + throw new MailboxException("Unable to read uid file", e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + messageCount = uidMap.size(); + + return uidMap; + } + + /** + * Sorts the given map and returns a subset which is constricted by a lower and an upper limit. + * @param source The source map + * @param from The lower limit + * @param to The upper limit; -1 disables the upper limit. + * @return The sorted subset + */ + private SortedMap truncateMap(Map source, long from, long to) { + TreeMap sortedMap; + if (source instanceof TreeMap) sortedMap = (TreeMap) source; + else sortedMap = new TreeMap(source); + if (to != -1) + return sortedMap.subMap(from, to + 1); + return sortedMap.tailMap(from); + } + + /** + * Parses the header line in uid list files. + * The format is: version lastUid messageCount (e.g. 1 615 273) + * @param line The raw header line + * @throws IOException + */ + private void readUidListHeader(String line) throws IOException { + if (line == null) + throw new IOException("Header entry in uid-file is null"); + int gap1 = line.indexOf(" "); + if (gap1 == -1) { + // there must be some issues in the file if no gap can be found + throw new IOException("Corrupted header entry in uid-file"); + + } + int version = Integer.valueOf(line.substring(0, gap1)); + if (version != 1) + throw new IOException("Cannot read uidlists with versions other than 1."); + int gap2 = line.indexOf(" ", gap1 + 1); + lastUid = Long.valueOf(line.substring(gap1 + 1, gap2)); + messageCount = Integer.valueOf(line.substring(gap2 + 1, line.length())); + } + + /** + * Creates a line to put as a header in the uid list file. + * @return the line which ought to be the header + */ + private String createUidListHeader() { + return "1 " + String.valueOf(lastUid) + " " + String.valueOf(messageCount); + } + + /** + * Takes the name of a message file and returns only the base name. + * @param fileName The name of the message file + * @return the file name without meta data, the unmodified name if it doesn't have meta data + */ + public static String stripMetaFromName(String fileName) { + int end = fileName.indexOf(",S="); // the size + if (end == -1) + end = fileName.indexOf(":2,"); // the flags + if (end == -1) + return fileName; // there is no meta data to strip + return fileName.substring(0, end); + } + + /** + * Appends a message to the uidlist and returns its uid. + * @param session + * @param name The name of the message's file + * @return The uid of the message + * @throws IOException + */ + public long appendMessage(final MailboxSession session, final String name) throws MailboxException { + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + File uidList = uidFile; + long uid = -1; + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter pw = null; + try { + if (uidList.isFile()) { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + // the first line in the file contains the next uid and message count + if (line != null) + readUidListHeader(line); + ArrayList lines = new ArrayList(); + while ((line = reader.readLine()) != null) + lines.add(line); + uid = getNextUid(); + lines.add(String.valueOf(uid) + " " + name); + messageCount++; + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (String entry : lines) + pw.println(entry); + } + else { + // create the file + if (!uidList.createNewFile()) + throw new IOException("Could not create file " + uidList); + String[] curFiles = curFolder.list(); + String[] newFiles = newFolder.list(); + messageCount = curFiles.length + newFiles.length; + ArrayList lines = new ArrayList(); + String[] allFiles = (String[]) ArrayUtils.addAll(curFiles, newFiles); + for (String file : allFiles) { + long theUid = getNextUid(); + lines.add(String.valueOf(theUid) + " " + file); + // the listed names already include the message to append + if (file.equals(name)) + uid = theUid; + } + pw = new PrintWriter(uidList); + pw.println(createUidListHeader()); + for (String line : lines) + pw.println(line); + } + } catch (IOException e) { + throw new MailboxException("Unable to append msg", e); + } finally { + IOUtils.closeQuietly(pw); + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + if (uid == -1) { + throw new MailboxException("Unable to append msg"); + } else { + return uid; + } + } + }, true); + + } + + /** + * Updates an entry in the uid list. + * @param session + * @param uid + * @param messageName + * @throws MailboxException + */ + public void update(final MailboxSession session, final long uid, final String messageName) throws MailboxException { + locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Void execute() throws MailboxException { + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter writer = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + String line = reader.readLine(); + readUidListHeader(line); + ArrayList lines = new ArrayList(); + while ((line = reader.readLine()) != null) { + if (uid == Long.valueOf(line.substring(0, line.indexOf(" ")))) + line = String.valueOf(uid) + " " + messageName; + lines.add(line); + } + writer = new PrintWriter(uidList); + writer.println(createUidListHeader()); + for (String entry : lines) + writer.println(entry); + } catch (IOException e) { + throw new MailboxException("Unable to update msg with uid " + uid, e); + } finally { + IOUtils.closeQuietly(writer); + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + return null; + } + }, true); + + } + + /** + * Retrieves the file belonging to the given uid, deletes it and updates + * the uid list. + * @param uid The uid of the message to delete + * @return The {@link MaildirMessageName} of the deleted message + * @throws MailboxException If the file cannot be deleted of there is a problem with the uid list + */ + public MaildirMessageName delete(final MailboxSession session, final long uid) throws MailboxException { + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public MaildirMessageName execute() throws MailboxException { + File uidList = uidFile; + FileReader fileReader = null; + BufferedReader reader = null; + PrintWriter writer = null; + MaildirMessageName deletedMessage = null; + try { + fileReader = new FileReader(uidList); + reader = new BufferedReader(fileReader); + readUidListHeader(reader.readLine()); + + // It may be possible that message count is 0 so we should better not try to calculate the size of the ArrayList + ArrayList lines = new ArrayList(); + String line; + int lineNumber = 1; + while ((line = reader.readLine()) != null) { + int gap = line.indexOf(" "); + if (gap == -1) { + // there must be some issues in the file if no gap can be found + session.getLog().info("Corrupted entry in uid-file " + uidList + " line " + lineNumber++); + continue; + } + + if (uid == Long.valueOf(line.substring(0, line.indexOf(" ")))) { + deletedMessage = newMaildirMessageName(MaildirFolder.this, line.substring(gap + 1, line.length())); + messageCount--; + } + else { + lines.add(line); + } + } + if (deletedMessage != null) { + if (!deletedMessage.getFile().delete()) + throw new IOException("Cannot delete file " + deletedMessage.getFile().getAbsolutePath()); + writer = new PrintWriter(uidList); + writer.println(createUidListHeader()); + for (String entry : lines) + writer.println(entry); + } + return deletedMessage; + + } catch (IOException e) { + throw new MailboxException("Unable to delete msg with uid " + uid, e); + } finally { + IOUtils.closeQuietly(writer); + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(fileReader); + } + } + }, true); + + + } + + /** + * The absolute path of this folder. + */ + @Override + public String toString() { + return getRootFile().getAbsolutePath(); + } + + public MailboxACL getACL(final MailboxSession session) throws MailboxException { + if (acl == null) { + acl = readACL(session); + } + return acl; + } + + /** + * Read the ACL of the given mailbox from the file system. + * + * @param session + * @throws MailboxException if there are problems with the aclFile file + */ + private MailboxACL readACL(MailboxSession session) throws MailboxException { + // FIXME Do we need this locking? + return locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public MailboxACL execute() throws MailboxException { + File f = aclFile; + InputStream in = null; + Properties props = new Properties(); + if (f.exists()) { + try { + in = new FileInputStream(f); + props.load(in); + } catch (FileNotFoundException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } catch (IOException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } + finally { + IOUtils.closeQuietly(in); + } + } + + return new SimpleMailboxACL(props); + + } + }, true); + + } + + public void setACL(final MailboxSession session, final MailboxACL acl) throws MailboxException { + MailboxACL old = this.acl; + if (old != acl && (old == null || !old.equals(acl))) { + /* change only if different */ + saveACL(acl, session); + this.acl = acl; + } + + } + + private void saveACL(final MailboxACL acl, final MailboxSession session) throws MailboxException { + // FIXME Do we need this locking? + locker.executeWithLock(session, path, new LockAwareExecution() { + + @Override + public Void execute() throws MailboxException { + File f = aclFile; + OutputStream out = null; + Properties props = new Properties(); + Map entries = acl.getEntries(); + if (entries != null) { + for (Entry en : entries.entrySet()) { + props.put(en.getKey().serialize(), en.getValue().serialize()); + } + } + if (f.exists()) { + try { + out = new FileOutputStream(f); + props.store(out, "written by "+ getClass().getName()); + } catch (FileNotFoundException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } catch (IOException e) { + throw new MailboxException("Unable to read last ACL from "+ f.getAbsolutePath(), e); + } + finally { + IOUtils.closeQuietly(out); + } + } + + return null; + + } + }, true); + } + + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxSessionMapperFactory.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxSessionMapperFactory.java new file mode 100644 index 0000000..06a479f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMailboxSessionMapperFactory.java @@ -0,0 +1,61 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.maildir.mail.MaildirMailboxMapper; +import org.apache.james.mailbox.maildir.mail.MaildirMessageMapper; +import org.apache.james.mailbox.maildir.user.MaildirSubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +public class MaildirMailboxSessionMapperFactory extends + MailboxSessionMapperFactory { + + private final MaildirStore store; + + + public MaildirMailboxSessionMapperFactory(MaildirStore store) { + this.store = store; + } + + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) + throws MailboxException { + return new MaildirMailboxMapper(store, session); + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) + throws MailboxException { + return new MaildirMessageMapper(session, store); + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) + throws SubscriptionException { + return new MaildirSubscriptionMapper(store); + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMessageName.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMessageName.java new file mode 100644 index 0000000..527df68 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirMessageName.java @@ -0,0 +1,473 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Date; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.mail.Flags; + +public class MaildirMessageName { + + // the flags in Maildir message names + public static final String FLAG_DRAFT = "D"; + public static final String FLAG_FLAGGED = "F"; + public static final String FLAG_ANSWERD = "R"; + public static final String FLAG_SEEN = "S"; + public static final String FLAG_DELETED = "T"; + + // patterns + public static final String PATTERN_STRING_MESSAGE_NAME = "\\d+\\.\\w+\\..+?"; + public static final String PATTERN_STRING_FLAGS = ":2,[DFRST]*"; + public static final String PATTERN_STRING_SIZE = ",S=\\d+"; + public static final Pattern PATTERN_MESSAGE = + Pattern.compile(PATTERN_STRING_MESSAGE_NAME + optional(PATTERN_STRING_SIZE) + optional(PATTERN_STRING_FLAGS)); + + public static final Pattern PATTERN_UNSEEN_MESSAGES = + Pattern.compile(PATTERN_STRING_MESSAGE_NAME + PATTERN_STRING_SIZE + optional(":2,[^S]*")); + + public static final FilenameFilter FILTER_UNSEEN_MESSAGES = createRegexFilter(PATTERN_UNSEEN_MESSAGES); + + public static final Pattern PATTERN_DELETED_MESSAGES = + Pattern.compile(PATTERN_STRING_MESSAGE_NAME + PATTERN_STRING_SIZE + ":2,.*" + FLAG_DELETED); + + public static final FilenameFilter FILTER_DELETED_MESSAGES = createRegexFilter(PATTERN_DELETED_MESSAGES); + + /** + * The number of deliveries done by the server since its last start + */ + private static AtomicInteger deliveries = new AtomicInteger(0); + + /** + * A random generator for the random part in the unique message names + */ + private static Random random = new Random(); + + /** + * The process id of the server process + */ + private static String processName = ManagementFactory.getRuntimeMXBean().getName(); + static { + String[] parts = processName.split("@"); + if (parts.length > 1) + processName = parts[0]; + } + + /** + * The host name of the machine the server is running on + */ + private static String currentHostname; + static { + try { + currentHostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + currentHostname = "localhost"; + } + } + + private String fullName; + private File file; + private MaildirFolder parentFolder; + private String timestamp; + private String uniqueString; + private String hostnameAndMeta; // tim-erwin.de,S=1000:2,RS + private String hostname; + private String sizeString; + private String flagsString; + private boolean isSplit; + private Date internalDate; + private Long size; + private Flags flags; + private boolean messageNameStrictParse = false; + + public MaildirMessageName(MaildirFolder parentFolder, String fullName) { + this.parentFolder = parentFolder; + setFullName(fullName); + } + + public boolean isMessageNameStrictParse() { + return messageNameStrictParse; + } + + public void setMessageNameStrictParse(boolean messageNameStrictParse) { + this.messageNameStrictParse = messageNameStrictParse; + } + + /** + * Tests whether the file or directory belonging to this {@link MaildirFolder} exists. + * If the file exists, its absolute path is written to absPath. + * TODO: If the flags have changed or the file doesn't exist any more, the uidlist should be updated + * @return true if the file or directory belonging to this {@link MaildirFolder} exists ; false otherwise + */ + public boolean exists() { + if (file != null && file.isFile()) + return true; + File assumedFile1 = new File(parentFolder.getCurFolder(), fullName); + if (assumedFile1.isFile()) { + file = assumedFile1; + return true; + } + File assumedFile2 = new File(parentFolder.getNewFolder(), fullName); + if (assumedFile2.isFile()) { + file = assumedFile2; + return true; + } + // check if maybe the flags have changed which means + // list the files in the cur and new folder and check if the message is there + FilenameFilter filter = getFilenameFilter(); + File[] matchingFiles1 = parentFolder.getCurFolder().listFiles(filter); + if (matchingFiles1.length == 1) { + setFullName(matchingFiles1[0].getName()); + file = matchingFiles1[0]; + return true; + } + File[] matchingFiles2 = parentFolder.getNewFolder().listFiles(filter); + if (matchingFiles2.length == 1) { + setFullName(matchingFiles2[0].getName()); + file = matchingFiles2[0]; + return true; + } + return false; + } + + /** + * Sets the fullName of this {@link MaildirMessageName} if different from the current one. + * As this invalidates the parsed elements, they are being reset. + * @param fullName A name of a message file in the correct Maildir format + */ + public void setFullName(String fullName) { + if (this.fullName == null || !this.fullName.equals(fullName)) { + this.fullName = fullName; + this.file = null; + this.isSplit = false; + this.internalDate = null; + this.size = null; + this.flags = null; + } + } + + /** + * Returns the full name of this message including size and flags if available. + * @return the full name of this message + */ + public String getFullName() { + if (this.fullName == null) { + StringBuilder fullBuffer = new StringBuilder(); + fullBuffer.append(timestamp); + fullBuffer.append("."); + fullBuffer.append(uniqueString); + fullBuffer.append("."); + fullBuffer.append(hostname); + if (sizeString != null) + fullBuffer.append(sizeString); + if (flagsString != null) + fullBuffer.append(flagsString); + fullName = fullBuffer.toString(); + } + return fullName; + } + + /** + * Returns a {@link File} object of the message denoted by this {@link MaildirMessageName}. + * Also checks for different flags if it cannot be found directly. + * @return A {@link File} object + * @throws FileNotFoundException If there is no file for the given name + */ + public File getFile() throws FileNotFoundException { + if (exists()) + return file; + else + throw new FileNotFoundException("There is no file for message name " + fullName + + " in mailbox " + parentFolder.getRootFile().getAbsolutePath()); + } + + /** + * Creates a filter which matches the message file even if the flags have changed + * @return filter for this message + */ + public FilenameFilter getFilenameFilter() { + split(); + StringBuilder pattern = new StringBuilder(); + pattern.append(timestamp); + pattern.append("\\."); + pattern.append(uniqueString); + pattern.append("\\."); + pattern.append(hostname); + pattern.append(".*"); + return createRegexFilter(Pattern.compile(pattern.toString())); + } + + /** + * Splits up the full file name if necessary. + */ + private void split() { + if (!isSplit) { + splitFullName(); + splitHostNameAndMeta(); + isSplit = true; + } + } + + /** + * Splits up the full file name into its main components timestamp, + * uniqueString and hostNameAndMeta and fills the respective variables. + */ + private void splitFullName() { + int firstEnd = fullName.indexOf('.'); + int secondEnd = fullName.indexOf('.', firstEnd + 1); + timestamp = fullName.substring(0, firstEnd); + uniqueString = fullName.substring(firstEnd + 1, secondEnd); + hostnameAndMeta = fullName.substring(secondEnd + 1, fullName.length()); + } + + /** + * Splits up the third part of the file name (e.g. tim-erwin.de,S=1000:2,RS) + * into its components hostname, size and flags and fills the respective variables. + */ + private void splitHostNameAndMeta() { + String[] hostnamemetaFlags = hostnameAndMeta.split(":", 2); + if (hostnamemetaFlags.length >= 1) { + this.hostnameAndMeta = hostnamemetaFlags[0]; + int firstEnd = hostnameAndMeta.indexOf(','); + + // read size field if existent + if (firstEnd > 0) { + hostname = hostnameAndMeta.substring(0, firstEnd); + String attrStr = hostnameAndMeta.substring(firstEnd); + String[] fields = attrStr.split(","); + for (String field : fields) { + if (field.startsWith("S=")) { + sizeString = "," + field; + } + } + } else { + sizeString = null; + hostname = this.hostnameAndMeta; + } + } + + if (hostnamemetaFlags.length >= 2) { + this.flagsString = ":" + hostnamemetaFlags[1]; + } + if (isMessageNameStrictParse()) { + if (sizeString == null) { + throw new IllegalArgumentException("No message size found in message name: "+ fullName); + } + if (flagsString == null) { + throw new IllegalArgumentException("No flags found in message name: "+ fullName); + } + } + } + + /** + * Sets new flags for this message name. + * @param flags + */ + public void setFlags(Flags flags) { + if (this.flags != flags) { + split(); // save all parts + this.flags = flags; + this.flagsString = encodeFlags(flags); + this.fullName = null; // invalidate the fullName + } + } + + /** + * Decodes the flags part of the file name if necessary and returns the appropriate Flags object. + * @return The {@link Flags} of this message + */ + public Flags getFlags() { + if (flags == null) { + split(); + if (flagsString == null) + return null; + if (flagsString.length() >= 3) + flags = decodeFlags(flagsString.substring(3)); // skip the ":2," part + } + return flags; + } + + /** + * Decodes the size part of the file name if necessary and returns the appropriate Long. + * @return The size of this message as a {@link Long} + */ + public Long getSize() { + if (size == null) { + split(); + if (sizeString == null) + return null; + if (!sizeString.startsWith(",S=")) + return null; + size = Long.valueOf(sizeString.substring(3)); // skip the ",S=" part + } + return size; + } + + /** + * Decodes the time part of the file name if necessary and returns the appropriate Date. + * @return The time of this message as a {@link Date} + */ + public Date getInternalDate() { + if (internalDate == null) { + split(); + if (timestamp == null) + return null; + internalDate = new Date(Long.valueOf(timestamp) * 1000); + } + return internalDate; + } + + /** + * Composes the base name consisting of timestamp, unique string and host name + * witout the size and flags. + * @return The base name + */ + public String getBaseName() { + split(); + StringBuilder baseName = new StringBuilder(); + baseName.append(timestamp); + baseName.append("."); + baseName.append(uniqueString); + baseName.append("."); + baseName.append(hostname); + return baseName.toString(); + } + + /** + * Creates a String that represents the provided Flags + * @param flags The flags to encode + * @return A String valid for Maildir + */ + public String encodeFlags(Flags flags) { + StringBuilder localFlagsString = new StringBuilder(":2,"); + if (flags.contains(Flags.Flag.DRAFT)) + localFlagsString.append(FLAG_DRAFT); + if (flags.contains(Flags.Flag.FLAGGED)) + localFlagsString.append(FLAG_FLAGGED); + if (flags.contains(Flags.Flag.ANSWERED)) + localFlagsString.append(FLAG_ANSWERD); + if (flags.contains(Flags.Flag.SEEN)) + localFlagsString.append(FLAG_SEEN); + if (flags.contains(Flags.Flag.DELETED)) + localFlagsString.append(FLAG_DELETED); + return localFlagsString.toString(); + } + + /** + * Takes a String which is "Maildir encoded" and translates it + * into a {@link Flags} object. + * @param flagsString The String with the flags + * @return A Flags object containing the flags read form the String + */ + public Flags decodeFlags(String flagsString) { + Flags localFlags = new Flags(); + if (flagsString.contains(FLAG_DRAFT)) + localFlags.add(Flags.Flag.DRAFT); + if (flagsString.contains(FLAG_FLAGGED)) + localFlags.add(Flags.Flag.FLAGGED); + if (flagsString.contains(FLAG_ANSWERD)) + localFlags.add(Flags.Flag.ANSWERED); + if (flagsString.contains(FLAG_SEEN)) + localFlags.add(Flags.Flag.SEEN); + if (flagsString.contains(FLAG_DELETED)) + localFlags.add(Flags.Flag.DELETED); + return localFlags; + } + + /** + * Returns and increases a global counter for the number + * of deliveries done since the server has been started. + * This is used for the creation of message names. + * @return The number of (attempted) deliveries until now + */ + static private long getNextDeliveryNumber() { + return deliveries.getAndIncrement(); + } + + /** + * Create a name for a message according to

+ * The following elements are used: + *

+ * "A unique name has three pieces, separated by dots. On the left is the result of time() + * or the second counter from gettimeofday(). On the right is the result of gethostname(). + * (To deal with invalid host names, replace / with \057 and : with \072.) + * In the middle is a delivery identifier, discussed below. + *

+ * Modern delivery identifiers are created by concatenating enough of the following strings + * to guarantee uniqueness: + *

+ * [...]
+ * * Rn, where n is (in hexadecimal) the output of the operating system's unix_cryptorandomnumber() system call, or an equivalent source such as /dev/urandom. Unfortunately, some operating systems don't include cryptographic random number generators.
+ * [...]
+ * * Mn, where n is (in decimal) the microsecond counter from the same gettimeofday() used for the left part of the unique name.
+ * * Pn, where n is (in decimal) the process ID.
+ * * Qn, where n is (in decimal) the number of deliveries made by this process.
+ *
+ * [...]" + * + * @return unique message name + */ + public static MaildirMessageName createUniqueName(MaildirFolder parentFolder, long size) { + String timestamp = String.valueOf(System.currentTimeMillis()); + timestamp = timestamp.substring(0, timestamp.length()-3); // time in seconds + StringBuilder uniquePart = new StringBuilder(); + uniquePart.append(Integer.toHexString(random.nextInt())); // random number as hex + uniquePart.append(timestamp.substring(timestamp.length()-3)); // milliseconds + uniquePart.append(processName); // process name + uniquePart.append(getNextDeliveryNumber()); // delivery number + String sizeString = ",S=" + String.valueOf(size); + String fullName = timestamp + "." + uniquePart.toString() + "." + currentHostname + sizeString; + MaildirMessageName uniqueName = new MaildirMessageName(parentFolder, fullName); + uniqueName.timestamp = timestamp; + uniqueName.uniqueString = uniquePart.toString(); + uniqueName.hostname = currentHostname; + uniqueName.sizeString = sizeString; + uniqueName.isSplit = true; + uniqueName.size = size; + return uniqueName; + } + + public static FilenameFilter createRegexFilter(final Pattern pattern) { + return new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + Matcher matcher = pattern.matcher(name); + return matcher.matches(); + } + }; + } + + public static String optional(String pattern) { + return "(" + pattern + ")?"; + } + + @Override + public String toString() { + return getFullName(); + } +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java new file mode 100644 index 0000000..880cc6a --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java @@ -0,0 +1,291 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.IOException; +import java.util.Locale; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.maildir.mail.model.MaildirMailbox; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class MaildirStore implements UidProvider, ModSeqProvider{ + + public static final String PATH_USER = "%user"; + public static final String PATH_DOMAIN = "%domain"; + public static final String PATH_FULLUSER = "%fulluser"; + public static final String WILDCARD = "%"; + + public static final String maildirDelimiter = "."; + + private String maildirLocation; + + private File maildirRootFile; + private final MailboxPathLocker locker; + + private boolean messageNameStrictParse = false; + + /** + * Construct a MaildirStore with a location. The location String + * currently may contain the + * %user, + * %domain, + * %fulluser + * variables. + * @param maildirLocation A String with variables + * @param locker + */ + public MaildirStore(String maildirLocation, MailboxPathLocker locker) { + this.maildirLocation = maildirLocation; + this.locker = locker; + } + + public MaildirStore(String maildirLocation) { + this(maildirLocation, new JVMMailboxPathLocker()); + } + + + public String getMaildirLocation() { + return maildirLocation; + } + /** + * Create a {@link MaildirFolder} for a mailbox + * @param mailbox + * @return The MaildirFolder + */ + public MaildirFolder createMaildirFolder(Mailbox mailbox) { + MaildirFolder mf = new MaildirFolder(getFolderName(mailbox), new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()), locker); + mf.setMessageNameStrictParse(isMessageNameStrictParse()); + return mf; + } + + /** + * Creates a Mailbox object with data loaded from the file system + * @param root The main maildir folder containing the mailbox to load + * @param namespace The namespace to use + * @param user The owner of this mailbox + * @param folderName The name of the mailbox folder + * @return The Mailbox object populated with data from the file system + * @throws MailboxException If the mailbox folder doesn't exist or can't be read + */ + public Mailbox loadMailbox(MailboxSession session, File root, String namespace, String user, String folderName) throws MailboxException { + String mailboxName = getMailboxNameFromFolderName(folderName); + return loadMailbox(session, new File(root, folderName), new MailboxPath(namespace, user, mailboxName)); + } + + /** + * Creates a Mailbox object with data loaded from the file system + * @param mailboxPath The path of the mailbox + * @return The Mailbox object populated with data from the file system + * @throws MailboxNotFoundException If the mailbox folder doesn't exist + * @throws MailboxException If the mailbox folder can't be read + */ + public Mailbox loadMailbox(MailboxSession session, MailboxPath mailboxPath) + throws MailboxNotFoundException, MailboxException { + MaildirFolder folder = new MaildirFolder(getFolderName(mailboxPath), mailboxPath, locker); + folder.setMessageNameStrictParse(isMessageNameStrictParse()); + if (!folder.exists()) + throw new MailboxNotFoundException(mailboxPath); + return loadMailbox(session, folder.getRootFile(), mailboxPath); + } + + /** + * Creates a Mailbox object with data loaded from the file system + * @param mailboxFile File object referencing the folder for the mailbox + * @param mailboxPath The path of the mailbox + * @return The Mailbox object populated with data from the file system + * @throws MailboxException If the mailbox folder doesn't exist or can't be read + */ + private Mailbox loadMailbox(MailboxSession session, File mailboxFile, MailboxPath mailboxPath) throws MailboxException { + MaildirFolder folder = new MaildirFolder(mailboxFile.getAbsolutePath(), mailboxPath, locker); + folder.setMessageNameStrictParse(isMessageNameStrictParse()); + try { + return new MaildirMailbox(session, mailboxPath, folder); + } catch (IOException e) { + throw new MailboxException("Unable to load Mailbox " + mailboxPath, e); + } + } + + /** + * Inserts the user name parts in the general maildir location String + * @param user The user to get the root for. + * @return The name of the folder which contains the specified user's mailbox + */ + public String userRoot(String user) { + String path = maildirLocation.replace(PATH_FULLUSER, user); + String[] userParts = user.split("@"); + String userName = user; + if (userParts.length == 2) { + userName = userParts[0]; + // At least the domain part should not handled in a case-sensitive manner + // See MAILBOX-58 + path = path.replace(PATH_DOMAIN, userParts[1].toLowerCase(Locale.US)); + } + path = path.replace(PATH_USER, userName); + return path; + } + + /** + * The main maildir folder containing all mailboxes for one user + * @param user The user name of a mailbox + * @return A File object referencing the main maildir folder + * @throws MailboxException If the folder does not exist or is no directory + */ + public File getMailboxRootForUser(String user) throws MailboxException { + String path = userRoot(user); + File root = new File(path); + if (!root.isDirectory()) + throw new MailboxException("Unable to load Mailbox for user " + user); + return root; + } + + /** + * Return a File which is the root of all Maidirs. + * The returned maidirRootFile is lazilly constructured. + * + * @return maidirRootFile + */ + public File getMaildirRoot() { + if (maildirRootFile == null) { + String maildirRootLocation = maildirLocation.replaceAll(PATH_FULLUSER, ""); + maildirRootLocation = maildirRootLocation.replaceAll(PATH_DOMAIN, ""); + maildirRootLocation = maildirRootLocation.replaceAll(PATH_USER, ""); + maildirRootFile = new File(maildirRootLocation); + } + return maildirRootFile; + } + + /** + * Transforms a folder name into a mailbox name + * @param folderName The name of the mailbox folder + * @return The complete (namespace) name of a mailbox + */ + public String getMailboxNameFromFolderName(String folderName) { + String mName; + if (folderName.equals("")) mName = MailboxConstants.INBOX; + else + // remove leading dot + mName = folderName.substring(1); + // they are equal, anyways, this might change someday... + //if (maildirDelimiter != MailboxConstants.DEFAULT_DELIMITER_STRING) + // mName = mName.replace(maildirDelimiter, MailboxConstants.DEFAULT_DELIMITER_STRING); + return mName; + } + + /** + * Get the absolute name of the folder for a specific mailbox + * @param namespace The namespace of the mailbox + * @param user The user of the mailbox + * @param name The name of the mailbox + * @return absolute name + */ + public String getFolderName(String namespace, String user, String name) { + String root = userRoot(user); + // if INBOX => location == maildirLocation + if (name.equals(MailboxConstants.INBOX)) + return root; + StringBuilder folder = new StringBuilder(root); + if (!root.endsWith(File.pathSeparator)) + folder.append(File.separator); + folder.append("."); + folder.append(name); + return folder.toString(); + } + + /** + * Get the absolute name of the folder for a specific mailbox + * @param mailbox The mailbox + * @return The absolute path to the folder containing the mailbox + */ + public String getFolderName(Mailbox mailbox) { + return getFolderName(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()); + } + + /** + * Get the absolute name of the folder for a specific mailbox + * @param mailboxPath The MailboxPath + * @return The absolute path to the folder containing the mailbox + */ + public String getFolderName(MailboxPath mailboxPath) { + return getFolderName(mailboxPath.getNamespace(), mailboxPath.getUser(), mailboxPath.getName()); + } + + /** + * @see org.apache.james.mailbox.store.mail.UidProvider#nextUid(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + return createMaildirFolder(mailbox).getLastUid(session) +1; + } catch (MailboxException e) { + throw new MailboxException("Unable to generate next uid", e); + } + } + + @Override + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + return System.currentTimeMillis(); + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + try { + return createMaildirFolder(mailbox).getHighestModSeq(); + } catch (IOException e) { + throw new MailboxException("Unable to get highest mod-sequence for mailbox", e); + } + } + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + return createMaildirFolder(mailbox).getLastUid(session); + } + + /** + * Returns whether the names of message files in this store are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * @return + */ + public boolean isMessageNameStrictParse() { + return messageNameStrictParse; + } + + /** + * Specifies whether the names of message files in this store are parsed in + * a strict manner ({@code true}), which means a size field and flags are + * expected. + * + * Default is {@code false}. + * + * @param messageNameStrictParse + */ + public void setMessageNameStrictParse(boolean messageNameStrictParse) { + this.messageNameStrictParse = messageNameStrictParse; + } +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/UidConstraint.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/UidConstraint.java new file mode 100644 index 0000000..f5e1fd5 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/UidConstraint.java @@ -0,0 +1,126 @@ +/**************************************************************** + * 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 java.util.LinkedList; + +public class UidConstraint { + + private LinkedList constraints = new LinkedList(); + + public UidConstraint append(Constraint constraint) { + constraints.add(constraint); + return this; + } + + public UidConstraint equals(long uid) { + constraints.add(new Equals(uid)); + return this; + } + + public UidConstraint lessOrEquals(long uid) { + constraints.add(new LessOrEquals(uid)); + return this; + } + + public UidConstraint greaterOrEquals(long uid) { + constraints.add(new GreaterOrEquals(uid)); + return this; + } + + public UidConstraint between(long lower, long upper) { + constraints.add(new Between(lower, upper)); + return this; + } + + public boolean isAllowed(long uid) { + for (Constraint constraint : constraints) + if (!constraint.isAllowed(uid)) + return false; + return true; + } + + public abstract static class Constraint { + + public abstract boolean isAllowed(long uid); + + } + + public static class Equals extends Constraint { + + private long uid; + + public Equals(long uid) { + this.uid = uid; + } + + @Override + public boolean isAllowed(long uid) { + return this.uid == uid; + } + + } + + public static class LessOrEquals extends Constraint { + + private long uid; + + public LessOrEquals(long uid) { + this.uid = uid; + } + + @Override + public boolean isAllowed(long uid) { + return uid <= this.uid; + } + + } + + public static class GreaterOrEquals extends Constraint { + + private long uid; + + public GreaterOrEquals(long uid) { + this.uid = uid; + } + + @Override + public boolean isAllowed(long uid) { + return uid >= this.uid; + } + + } + + public static class Between extends Constraint { + + private long lower; + private long upper; + + public Between(long lower, long upper) { + this.lower = lower; + this.upper = upper; + } + + @Override + public boolean isAllowed(long uid) { + return (uid >= lower) && (uid <= upper); + } + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/all-wcprops new file mode 100644 index 0000000..25209fa --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 107 +/repos/asf/!svn/ver/1428384/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail +END +MaildirMailboxMapper.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java +END +MaildirMessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/entries new file mode 100644 index 0000000..dd6a7c0 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/entries @@ -0,0 +1,99 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail +http://svn.apache.org/repos/asf + + + +2013-01-03T15:03:15.860546Z +1428384 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + +MaildirMailboxMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +b34e84b6e591abce82bd8d8676dce576 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +15484 + +MaildirMessageMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +40aaa68f77df492ae9fd9327d83941ec +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + + + + + + + + +21400 + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMailboxMapper.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMailboxMapper.java.svn-base new file mode 100644 index 0000000..fa0a03e --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMailboxMapper.java.svn-base @@ -0,0 +1,323 @@ +/**************************************************************** + * 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.mail; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.maildir.MaildirMessageName; +import org.apache.james.mailbox.maildir.MaildirStore; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; + +public class MaildirMailboxMapper extends NonTransactionalMapper implements MailboxMapper { + + /** + * The {@link MaildirStore} the mailboxes reside in + */ + private final MaildirStore maildirStore; + + /** + * A request-scoped list of mailboxes in order to refer to them via id + */ + private ArrayList> mailboxCache = new ArrayList>(); + + private final MailboxSession session; + + public MaildirMailboxMapper(MaildirStore maildirStore, MailboxSession session) { + this.maildirStore = maildirStore; + this.session = session; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public void delete(Mailbox mailbox) throws MailboxException { + + String folderName = maildirStore.getFolderName(mailbox); + File folder = new File(folderName); + if (folder.isDirectory()) { + try { + if (mailbox.getName().equals(MailboxConstants.INBOX)) { + // We must only delete cur, new, tmp and metadata for top INBOX mailbox. + FileUtils.deleteDirectory(new File(folder, MaildirFolder.CUR)); + FileUtils.deleteDirectory(new File(folder, MaildirFolder.NEW)); + FileUtils.deleteDirectory(new File(folder, MaildirFolder.TMP)); + File uidListFile = new File(folder, MaildirFolder.UIDLIST_FILE); + uidListFile.delete(); + File validityFile = new File(folder, MaildirFolder.VALIDITY_FILE); + validityFile.delete(); + } + else { + // We simply delete all the folder for non INBOX mailboxes. + FileUtils.deleteDirectory(folder); + } + } catch (IOException e) { + e.printStackTrace(); + throw new MailboxException("Unable to delete Mailbox " + mailbox, e); + } + } + else + throw new MailboxNotFoundException(mailbox.getName()); + } + + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(org.apache.james.mailbox.model.MailboxPath) + */ + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxPath) + throws MailboxException, MailboxNotFoundException { + Mailbox mailbox = maildirStore.loadMailbox(session, mailboxPath); + return cacheMailbox(mailbox); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(org.apache.james.mailbox.model.MailboxPath) + */ + @Override + public List> findMailboxWithPathLike(MailboxPath mailboxPath) + throws MailboxException { + final Pattern searchPattern = Pattern.compile("[" + MaildirStore.maildirDelimiter + "]" + + mailboxPath.getName().replace(".", "\\.").replace(MaildirStore.WILDCARD, ".*")); + FilenameFilter filter = MaildirMessageName.createRegexFilter(searchPattern); + File root = maildirStore.getMailboxRootForUser(mailboxPath.getUser()); + File[] folders = root.listFiles(filter); + ArrayList> mailboxList = new ArrayList>(); + for (File folder : folders) + if (folder.isDirectory()) { + Mailbox mailbox = maildirStore.loadMailbox(session, root, mailboxPath.getNamespace(), mailboxPath.getUser(), folder.getName()); + mailboxList.add(cacheMailbox(mailbox)); + } + // INBOX is in the root of the folder + if (Pattern.matches(mailboxPath.getName().replace(MaildirStore.WILDCARD, ".*"), MailboxConstants.INBOX)) { + Mailbox mailbox = maildirStore.loadMailbox(session, root, mailboxPath.getNamespace(), mailboxPath.getUser(), ""); + mailboxList.add(0, cacheMailbox(mailbox)); + } + return mailboxList; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(org.apache.james.mailbox.store.mail.model.Mailbox, char) + */ + @Override + public boolean hasChildren(Mailbox mailbox, char delimiter) throws MailboxException, MailboxNotFoundException { + String searchString = mailbox.getName() + MaildirStore.maildirDelimiter + MaildirStore.WILDCARD; + List> mailboxes = findMailboxWithPathLike( + new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), searchString)); + return (mailboxes.size() > 0); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public void save(Mailbox mailbox) throws MailboxException { + try { + Mailbox originalMailbox = getCachedMailbox(mailbox.getMailboxId()); + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + // equals with null check + if (originalMailbox.getName() == null ? mailbox.getName() != null : !originalMailbox.getName().equals(mailbox.getName())) { + if (folder.exists()) + throw new MailboxExistsException(mailbox.getName()); + + MaildirFolder originalFolder = maildirStore.createMaildirFolder(originalMailbox); + // renaming the INBOX means to move its contents to the new folder + if (originalMailbox.getName().equals(MailboxConstants.INBOX)) { + try { + File inboxFolder = originalFolder.getRootFile(); + File newFolder = folder.getRootFile(); + if (!newFolder.mkdirs()) + throw new IOException("Could not create folder " + newFolder); + if (!originalFolder.getCurFolder().renameTo(folder.getCurFolder())) + throw new IOException("Could not rename folder " + originalFolder.getCurFolder() + " to " + folder.getCurFolder()); + if (!originalFolder.getNewFolder().renameTo(folder.getNewFolder())) + throw new IOException("Could not rename folder " + originalFolder.getNewFolder() + " to " + folder.getNewFolder()); + if (!originalFolder.getTmpFolder().renameTo(folder.getTmpFolder())) + throw new IOException("Could not rename folder " + originalFolder.getTmpFolder() + " to " + folder.getTmpFolder()); + File oldUidListFile = new File(inboxFolder, MaildirFolder.UIDLIST_FILE); + File newUidListFile = new File(newFolder, MaildirFolder.UIDLIST_FILE); + if (!oldUidListFile.renameTo(newUidListFile)) + throw new IOException("Could not rename file " + oldUidListFile + " to " + newUidListFile); + File oldValidityFile = new File(inboxFolder, MaildirFolder.VALIDITY_FILE); + File newValidityFile = new File(newFolder, MaildirFolder.VALIDITY_FILE); + if (!oldValidityFile.renameTo(newValidityFile)) + throw new IOException("Could not rename file " + oldValidityFile + " to " + newValidityFile); + // recreate the INBOX folders, uidvalidity and uidlist will + // automatically be recreated later + if (!originalFolder.getCurFolder().mkdir()) + throw new IOException("Could not create folder " + originalFolder.getCurFolder()); + if (!originalFolder.getNewFolder().mkdir()) + throw new IOException("Could not create folder " + originalFolder.getNewFolder()); + if (!originalFolder.getTmpFolder().mkdir()) + throw new IOException("Could not create folder " + originalFolder.getTmpFolder()); + } catch (IOException e) { + throw new MailboxException("Failed to save Mailbox " + mailbox, e); + } + } + else { + if (!originalFolder.getRootFile().renameTo(folder.getRootFile())) + throw new MailboxException("Failed to save Mailbox " + mailbox, + new IOException("Could not rename folder " + originalFolder)); + } + } + } catch (MailboxNotFoundException e) { + // it cannot be found and is thus new + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + if (!folder.exists()) { + boolean success = folder.getRootFile().exists(); + if (!success) success = folder.getRootFile().mkdirs(); + if (!success) + throw new MailboxException("Failed to save Mailbox " + mailbox); + success = folder.getCurFolder().mkdir(); + success = success && folder.getNewFolder().mkdir(); + success = success && folder.getTmpFolder().mkdir(); + if (!success) + throw new MailboxException("Failed to save Mailbox " + mailbox, new IOException("Needed folder structure can not be created")); + + } + try { + folder.setUidValidity(mailbox.getUidValidity()); + } catch (IOException ioe) { + throw new MailboxException("Failed to save Mailbox " + mailbox, ioe); + + } + } + + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + @Override + public List> list() throws MailboxException { + + File maildirRoot = maildirStore.getMaildirRoot(); + List> mailboxList = new ArrayList>(); + + if (maildirStore.getMaildirLocation().endsWith("/" + MaildirStore.PATH_FULLUSER)) { + File[] users = maildirRoot.listFiles(); + visitUsersForMailboxList(null, users, mailboxList); + return mailboxList; + } + + File[] domains = maildirRoot.listFiles(); + for (File domain: domains) { + File[] users = domain.listFiles(); + visitUsersForMailboxList(domain, users, mailboxList); + } + return mailboxList; + + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() + */ + @Override + public void endRequest() { + mailboxCache.clear(); + } + + /** + * Stores a copy of a mailbox in a cache valid for one request. This is to enable + * referring to renamed mailboxes via id. + * @param mailbox The mailbox to cache + * @return The id of the cached mailbox + */ + private Mailbox cacheMailbox(Mailbox mailbox) { + mailboxCache.add(new SimpleMailbox(mailbox)); + int id = mailboxCache.size() - 1; + ((SimpleMailbox) mailbox).setMailboxId(id); + return mailbox; + } + + /** + * Retrieves a mailbox from the cache + * @param mailboxId The id of the mailbox to retrieve + * @return The mailbox + * @throws MailboxNotFoundException If the mailboxId is not in the cache + */ + private Mailbox getCachedMailbox(Integer mailboxId) throws MailboxNotFoundException { + if (mailboxId == null) + throw new MailboxNotFoundException("null"); + try { + return mailboxCache.get(mailboxId); + } catch (IndexOutOfBoundsException e) { + throw new MailboxNotFoundException(String.valueOf(mailboxId)); + } + } + + private void visitUsersForMailboxList(File domain, File[] users, List> mailboxList) throws MailboxException { + + String userName = null; + + for (File user: users) { + + + if (domain == null) { + userName = user.getName(); + } + else { + userName = user.getName() + "@" + domain.getName(); + } + + // Special case for INBOX: Let's use the user's folder. + MailboxPath inboxMailboxPath = new MailboxPath(session.getPersonalSpace(), userName, MailboxConstants.INBOX); + mailboxList.add(maildirStore.loadMailbox(session, inboxMailboxPath)); + + // List all INBOX sub folders. + + File[] mailboxes = user.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.getName().startsWith("."); + } + }); + + for (File mailbox: mailboxes) { + + + MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, + userName, + mailbox.getName().substring(1)); + mailboxList.add(maildirStore.loadMailbox(session, mailboxPath)); + + } + + } + + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMessageMapper.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMessageMapper.java.svn-base new file mode 100644 index 0000000..db09e75 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/.svn/text-base/MaildirMessageMapper.java.svn-base @@ -0,0 +1,494 @@ +/**************************************************************** + * 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.mail; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.maildir.MaildirMessageName; +import org.apache.james.mailbox.maildir.MaildirStore; +import org.apache.james.mailbox.maildir.mail.model.MaildirMessage; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; + +public class MaildirMessageMapper extends AbstractMessageMapper { + + private final MaildirStore maildirStore; + private final static int BUF_SIZE = 2048; + + public MaildirMessageMapper(MailboxSession session, MaildirStore maildirStore) { + super(session, maildirStore, maildirStore); + this.maildirStore = maildirStore; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + File newFolder = folder.getNewFolder(); + File curFolder = folder.getCurFolder(); + File[] newFiles = newFolder.listFiles(); + File[] curFiles = curFolder.listFiles(); + if (newFiles == null || curFiles == null) + throw new MailboxException("Unable to count messages in Mailbox " + mailbox, new IOException( + "Not a valid Maildir folder: " + maildirStore.getFolderName(mailbox))); + int count = newFiles.length + curFiles.length; + return count; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + File newFolder = folder.getNewFolder(); + File curFolder = folder.getCurFolder(); + String[] unseenMessages = curFolder.list(MaildirMessageName.FILTER_UNSEEN_MESSAGES); + String[] newUnseenMessages = newFolder.list(MaildirMessageName.FILTER_UNSEEN_MESSAGES); + if (newUnseenMessages == null || unseenMessages == null) + throw new MailboxException("Unable to count unseen messages in Mailbox " + mailbox, new IOException( + "Not a valid Maildir folder: " + maildirStore.getFolderName(mailbox))); + int count = newUnseenMessages.length + unseenMessages.length; + return count; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public void delete(Mailbox mailbox, Message message) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + folder.delete(mailboxSession, message.getUid()); + } catch (MailboxException e) { + throw new MailboxException("Unable to delete Message " + message + " in Mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + @Override + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) + throws MailboxException { + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + switch (type) { + default: + case ALL: + results = findMessagesInMailboxBetweenUIDs(mailbox, null, 0, -1, max); + break; + case FROM: + results = findMessagesInMailboxBetweenUIDs(mailbox, null, from, -1, max); + break; + case ONE: + results = findMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, null, from, to, max); + break; + } + return results.iterator(); + + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + SortedMap recentMessageNames = folder.getRecentMessages(mailboxSession); + return new ArrayList(recentMessageNames.keySet()); + + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + List> result = findMessagesInMailbox(mailbox, MaildirMessageName.FILTER_UNSEEN_MESSAGES, 1); + if (result.isEmpty()) { + return null; + } else { + return result.get(0).getUid(); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, + * javax.mail.Flags, boolean, boolean, + * org.apache.james.mailbox.model.MessageRange) + */ + @Override + public Iterator updateFlags(final Mailbox mailbox, final Flags flags, final boolean value, + final boolean replace, final MessageRange set) throws MailboxException { + final List updatedFlags = new ArrayList(); + final MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + + Iterator> it = findInMailbox(mailbox, set, FetchType.Metadata, -1); + while (it.hasNext()) { + final Message member = it.next(); + Flags originalFlags = member.createFlags(); + if (replace) { + member.setFlags(flags); + } else { + Flags current = member.createFlags(); + if (value) { + current.add(flags); + } else { + current.remove(flags); + } + member.setFlags(current); + } + Flags newFlags = member.createFlags(); + + try { + MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, member.getUid()); + if (messageName != null) { + File messageFile = messageName.getFile(); + // System.out.println("save existing " + message + + // " as " + messageFile.getName()); + messageName.setFlags(member.createFlags()); + // this automatically moves messages from new to cur if + // needed + String newMessageName = messageName.getFullName(); + + File newMessageFile; + + // See MAILBOX-57 + if (newFlags.contains(Flag.RECENT)) { + // message is recent so save it in the new folder + newMessageFile = new File(folder.getNewFolder(), newMessageName); + } else { + newMessageFile = new File(folder.getCurFolder(), newMessageName); + } + long modSeq; + // if the flags don't have change we should not try to move + // the file + if (newMessageFile.equals(messageFile) == false) { + FileUtils.moveFile(messageFile, newMessageFile); + modSeq = newMessageFile.lastModified(); + + } else { + modSeq = messageFile.lastModified(); + } + member.setModSeq(modSeq); + + updatedFlags.add(new UpdatedFlags(member.getUid(), modSeq, originalFlags, newFlags)); + + long uid = member.getUid(); + folder.update(mailboxSession, uid, newMessageName); + } + } catch (IOException e) { + throw new MailboxException("Failure while save Message " + member + " in Mailbox " + mailbox, e); + } + + } + return updatedFlags.iterator(); + + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + List> results = new ArrayList>(); + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, -1); + break; + case FROM: + results = findMessagesInMailboxBetweenUIDs(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, from, -1, + -1); + break; + case ONE: + results = findDeletedMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, from, to, + -1); + break; + } + Map uids = new HashMap(); + for (int i = 0; i < results.size(); i++) { + Message m = results.get(i); + long uid = m.getUid(); + uids.put(uid, new SimpleMessageMetaData(m)); + delete(mailbox, m); + } + + return uids; + } + + /** + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(org.apache + * .james.mailbox.store.mail.model.Mailbox, long, long, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) + throws MailboxException { + SimpleMessage theCopy = new SimpleMessage(mailbox, original); + Flags flags = theCopy.createFlags(); + flags.add(Flag.RECENT); + theCopy.setFlags(flags); + return save(mailbox, theCopy); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + long uid = 0; + // a new message + // save file to "tmp" folder + File tmpFolder = folder.getTmpFolder(); + // The only case in which we could get problems with clashing names + // is if the system clock + // has been set backwards, then the server is restarted with the + // same pid, delivers the same + // number of messages since its start in the exact same millisecond + // as done before and the + // random number generator returns the same number. + // In order to prevent this case we would need to check ALL files in + // all folders and compare + // them to this message name. We rather let this happen once in a + // billion years... + MaildirMessageName messageName = MaildirMessageName.createUniqueName(folder, message.getFullContentOctets()); + File messageFile = new File(tmpFolder, messageName.getFullName()); + FileOutputStream fos = null; + InputStream input = null; + try { + if (!messageFile.createNewFile()) + throw new IOException("Could not create file " + messageFile); + fos = new FileOutputStream(messageFile); + input = message.getFullContent(); + byte[] b = new byte[BUF_SIZE]; + int len = 0; + while ((len = input.read(b)) != -1) + fos.write(b, 0, len); + } catch (IOException ioe) { + throw new MailboxException("Failure while save Message " + message + " in Mailbox " + mailbox, ioe); + } finally { + try { + if (fos != null) + fos.close(); + } catch (IOException e) { + } + try { + if (input != null) + input.close(); + } catch (IOException e) { + } + } + File newMessageFile = null; + // delivered via SMTP, goes to ./new without flags + if (message.isRecent()) { + messageName.setFlags(message.createFlags()); + newMessageFile = new File(folder.getNewFolder(), messageName.getFullName()); + // System.out.println("save new recent " + message + " as " + + // newMessageFile.getName()); + } + // appended via IMAP (might already have flags etc, goes to ./cur + // directly) + else { + messageName.setFlags(message.createFlags()); + newMessageFile = new File(folder.getCurFolder(), messageName.getFullName()); + // System.out.println("save new not recent " + message + " as " + // + newMessageFile.getName()); + } + try { + FileUtils.moveFile(messageFile, newMessageFile); + } catch (IOException e) { + // TODO: Try copy and delete + throw new MailboxException("Failure while save Message " + message + " in Mailbox " + mailbox, e); + } + try { + uid = folder.appendMessage(mailboxSession, newMessageFile.getName()); + message.setUid(uid); + message.setModSeq(newMessageFile.lastModified()); + return new SimpleMessageMetaData(message); + } catch (MailboxException e) { + throw new MailboxException("Failure while save Message " + message + " in Mailbox " + mailbox, e); + } + + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() + */ + @Override + public void endRequest() { + // not used + + } + + private List> findMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, uid); + + ArrayList> messages = new ArrayList>(); + if (messageName != null && messageName.getFile().exists()) { + messages.add(new MaildirMessage(mailbox, uid, messageName)); + } + return messages; + + } catch (IOException e) { + throw new MailboxException("Failure while search for Message with uid " + uid + " in Mailbox " + mailbox, e); + } + } + + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, FilenameFilter filter, + long from, long to, int max) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + int cur = 0; + SortedMap uidMap = null; + try { + if (filter != null) + uidMap = folder.getUidMap(mailboxSession, filter, from, to); + else + uidMap = folder.getUidMap(mailboxSession, from, to); + + ArrayList> messages = new ArrayList>(); + for (Entry entry : uidMap.entrySet()) { + messages.add(new MaildirMessage(mailbox, entry.getKey(), entry.getValue())); + if (max != -1) { + cur++; + if (cur >= max) + break; + } + } + return messages; + } catch (IOException e) { + throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); + } + + } + + private List> findMessagesInMailbox(Mailbox mailbox, FilenameFilter filter, int limit) + throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + SortedMap uidMap = folder.getUidMap(mailboxSession, filter, limit); + + ArrayList> filtered = new ArrayList>(uidMap.size()); + for (Entry entry : uidMap.entrySet()) + filtered.add(new MaildirMessage(mailbox, entry.getKey(), entry.getValue())); + return filtered; + } catch (IOException e) { + throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); + } + + } + + private List> findDeletedMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, uid); + ArrayList> messages = new ArrayList>(); + if (MaildirMessageName.FILTER_DELETED_MESSAGES.accept(null, messageName.getFullName())) { + messages.add(new MaildirMessage(mailbox, uid, messageName)); + } + return messages; + + } catch (IOException e) { + throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); + } + + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin() + */ + @Override + protected void begin() throws MailboxException { + // nothing todo + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#commit() + */ + @Override + protected void commit() throws MailboxException { + // nothing todo + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback() + */ + @Override + protected void rollback() throws MailboxException { + // nothing todo + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java new file mode 100644 index 0000000..fa0a03e --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java @@ -0,0 +1,323 @@ +/**************************************************************** + * 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.mail; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.maildir.MaildirMessageName; +import org.apache.james.mailbox.maildir.MaildirStore; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; + +public class MaildirMailboxMapper extends NonTransactionalMapper implements MailboxMapper { + + /** + * The {@link MaildirStore} the mailboxes reside in + */ + private final MaildirStore maildirStore; + + /** + * A request-scoped list of mailboxes in order to refer to them via id + */ + private ArrayList> mailboxCache = new ArrayList>(); + + private final MailboxSession session; + + public MaildirMailboxMapper(MaildirStore maildirStore, MailboxSession session) { + this.maildirStore = maildirStore; + this.session = session; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public void delete(Mailbox mailbox) throws MailboxException { + + String folderName = maildirStore.getFolderName(mailbox); + File folder = new File(folderName); + if (folder.isDirectory()) { + try { + if (mailbox.getName().equals(MailboxConstants.INBOX)) { + // We must only delete cur, new, tmp and metadata for top INBOX mailbox. + FileUtils.deleteDirectory(new File(folder, MaildirFolder.CUR)); + FileUtils.deleteDirectory(new File(folder, MaildirFolder.NEW)); + FileUtils.deleteDirectory(new File(folder, MaildirFolder.TMP)); + File uidListFile = new File(folder, MaildirFolder.UIDLIST_FILE); + uidListFile.delete(); + File validityFile = new File(folder, MaildirFolder.VALIDITY_FILE); + validityFile.delete(); + } + else { + // We simply delete all the folder for non INBOX mailboxes. + FileUtils.deleteDirectory(folder); + } + } catch (IOException e) { + e.printStackTrace(); + throw new MailboxException("Unable to delete Mailbox " + mailbox, e); + } + } + else + throw new MailboxNotFoundException(mailbox.getName()); + } + + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(org.apache.james.mailbox.model.MailboxPath) + */ + @Override + public Mailbox findMailboxByPath(MailboxPath mailboxPath) + throws MailboxException, MailboxNotFoundException { + Mailbox mailbox = maildirStore.loadMailbox(session, mailboxPath); + return cacheMailbox(mailbox); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(org.apache.james.mailbox.model.MailboxPath) + */ + @Override + public List> findMailboxWithPathLike(MailboxPath mailboxPath) + throws MailboxException { + final Pattern searchPattern = Pattern.compile("[" + MaildirStore.maildirDelimiter + "]" + + mailboxPath.getName().replace(".", "\\.").replace(MaildirStore.WILDCARD, ".*")); + FilenameFilter filter = MaildirMessageName.createRegexFilter(searchPattern); + File root = maildirStore.getMailboxRootForUser(mailboxPath.getUser()); + File[] folders = root.listFiles(filter); + ArrayList> mailboxList = new ArrayList>(); + for (File folder : folders) + if (folder.isDirectory()) { + Mailbox mailbox = maildirStore.loadMailbox(session, root, mailboxPath.getNamespace(), mailboxPath.getUser(), folder.getName()); + mailboxList.add(cacheMailbox(mailbox)); + } + // INBOX is in the root of the folder + if (Pattern.matches(mailboxPath.getName().replace(MaildirStore.WILDCARD, ".*"), MailboxConstants.INBOX)) { + Mailbox mailbox = maildirStore.loadMailbox(session, root, mailboxPath.getNamespace(), mailboxPath.getUser(), ""); + mailboxList.add(0, cacheMailbox(mailbox)); + } + return mailboxList; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(org.apache.james.mailbox.store.mail.model.Mailbox, char) + */ + @Override + public boolean hasChildren(Mailbox mailbox, char delimiter) throws MailboxException, MailboxNotFoundException { + String searchString = mailbox.getName() + MaildirStore.maildirDelimiter + MaildirStore.WILDCARD; + List> mailboxes = findMailboxWithPathLike( + new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), searchString)); + return (mailboxes.size() > 0); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public void save(Mailbox mailbox) throws MailboxException { + try { + Mailbox originalMailbox = getCachedMailbox(mailbox.getMailboxId()); + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + // equals with null check + if (originalMailbox.getName() == null ? mailbox.getName() != null : !originalMailbox.getName().equals(mailbox.getName())) { + if (folder.exists()) + throw new MailboxExistsException(mailbox.getName()); + + MaildirFolder originalFolder = maildirStore.createMaildirFolder(originalMailbox); + // renaming the INBOX means to move its contents to the new folder + if (originalMailbox.getName().equals(MailboxConstants.INBOX)) { + try { + File inboxFolder = originalFolder.getRootFile(); + File newFolder = folder.getRootFile(); + if (!newFolder.mkdirs()) + throw new IOException("Could not create folder " + newFolder); + if (!originalFolder.getCurFolder().renameTo(folder.getCurFolder())) + throw new IOException("Could not rename folder " + originalFolder.getCurFolder() + " to " + folder.getCurFolder()); + if (!originalFolder.getNewFolder().renameTo(folder.getNewFolder())) + throw new IOException("Could not rename folder " + originalFolder.getNewFolder() + " to " + folder.getNewFolder()); + if (!originalFolder.getTmpFolder().renameTo(folder.getTmpFolder())) + throw new IOException("Could not rename folder " + originalFolder.getTmpFolder() + " to " + folder.getTmpFolder()); + File oldUidListFile = new File(inboxFolder, MaildirFolder.UIDLIST_FILE); + File newUidListFile = new File(newFolder, MaildirFolder.UIDLIST_FILE); + if (!oldUidListFile.renameTo(newUidListFile)) + throw new IOException("Could not rename file " + oldUidListFile + " to " + newUidListFile); + File oldValidityFile = new File(inboxFolder, MaildirFolder.VALIDITY_FILE); + File newValidityFile = new File(newFolder, MaildirFolder.VALIDITY_FILE); + if (!oldValidityFile.renameTo(newValidityFile)) + throw new IOException("Could not rename file " + oldValidityFile + " to " + newValidityFile); + // recreate the INBOX folders, uidvalidity and uidlist will + // automatically be recreated later + if (!originalFolder.getCurFolder().mkdir()) + throw new IOException("Could not create folder " + originalFolder.getCurFolder()); + if (!originalFolder.getNewFolder().mkdir()) + throw new IOException("Could not create folder " + originalFolder.getNewFolder()); + if (!originalFolder.getTmpFolder().mkdir()) + throw new IOException("Could not create folder " + originalFolder.getTmpFolder()); + } catch (IOException e) { + throw new MailboxException("Failed to save Mailbox " + mailbox, e); + } + } + else { + if (!originalFolder.getRootFile().renameTo(folder.getRootFile())) + throw new MailboxException("Failed to save Mailbox " + mailbox, + new IOException("Could not rename folder " + originalFolder)); + } + } + } catch (MailboxNotFoundException e) { + // it cannot be found and is thus new + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + if (!folder.exists()) { + boolean success = folder.getRootFile().exists(); + if (!success) success = folder.getRootFile().mkdirs(); + if (!success) + throw new MailboxException("Failed to save Mailbox " + mailbox); + success = folder.getCurFolder().mkdir(); + success = success && folder.getNewFolder().mkdir(); + success = success && folder.getTmpFolder().mkdir(); + if (!success) + throw new MailboxException("Failed to save Mailbox " + mailbox, new IOException("Needed folder structure can not be created")); + + } + try { + folder.setUidValidity(mailbox.getUidValidity()); + } catch (IOException ioe) { + throw new MailboxException("Failed to save Mailbox " + mailbox, ioe); + + } + } + + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + @Override + public List> list() throws MailboxException { + + File maildirRoot = maildirStore.getMaildirRoot(); + List> mailboxList = new ArrayList>(); + + if (maildirStore.getMaildirLocation().endsWith("/" + MaildirStore.PATH_FULLUSER)) { + File[] users = maildirRoot.listFiles(); + visitUsersForMailboxList(null, users, mailboxList); + return mailboxList; + } + + File[] domains = maildirRoot.listFiles(); + for (File domain: domains) { + File[] users = domain.listFiles(); + visitUsersForMailboxList(domain, users, mailboxList); + } + return mailboxList; + + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() + */ + @Override + public void endRequest() { + mailboxCache.clear(); + } + + /** + * Stores a copy of a mailbox in a cache valid for one request. This is to enable + * referring to renamed mailboxes via id. + * @param mailbox The mailbox to cache + * @return The id of the cached mailbox + */ + private Mailbox cacheMailbox(Mailbox mailbox) { + mailboxCache.add(new SimpleMailbox(mailbox)); + int id = mailboxCache.size() - 1; + ((SimpleMailbox) mailbox).setMailboxId(id); + return mailbox; + } + + /** + * Retrieves a mailbox from the cache + * @param mailboxId The id of the mailbox to retrieve + * @return The mailbox + * @throws MailboxNotFoundException If the mailboxId is not in the cache + */ + private Mailbox getCachedMailbox(Integer mailboxId) throws MailboxNotFoundException { + if (mailboxId == null) + throw new MailboxNotFoundException("null"); + try { + return mailboxCache.get(mailboxId); + } catch (IndexOutOfBoundsException e) { + throw new MailboxNotFoundException(String.valueOf(mailboxId)); + } + } + + private void visitUsersForMailboxList(File domain, File[] users, List> mailboxList) throws MailboxException { + + String userName = null; + + for (File user: users) { + + + if (domain == null) { + userName = user.getName(); + } + else { + userName = user.getName() + "@" + domain.getName(); + } + + // Special case for INBOX: Let's use the user's folder. + MailboxPath inboxMailboxPath = new MailboxPath(session.getPersonalSpace(), userName, MailboxConstants.INBOX); + mailboxList.add(maildirStore.loadMailbox(session, inboxMailboxPath)); + + // List all INBOX sub folders. + + File[] mailboxes = user.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.getName().startsWith("."); + } + }); + + for (File mailbox: mailboxes) { + + + MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, + userName, + mailbox.getName().substring(1)); + mailboxList.add(maildirStore.loadMailbox(session, mailboxPath)); + + } + + } + + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java new file mode 100644 index 0000000..db09e75 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java @@ -0,0 +1,494 @@ +/**************************************************************** + * 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.mail; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.maildir.MaildirMessageName; +import org.apache.james.mailbox.maildir.MaildirStore; +import org.apache.james.mailbox.maildir.mail.model.MaildirMessage; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; + +public class MaildirMessageMapper extends AbstractMessageMapper { + + private final MaildirStore maildirStore; + private final static int BUF_SIZE = 2048; + + public MaildirMessageMapper(MailboxSession session, MaildirStore maildirStore) { + super(session, maildirStore, maildirStore); + this.maildirStore = maildirStore; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + File newFolder = folder.getNewFolder(); + File curFolder = folder.getCurFolder(); + File[] newFiles = newFolder.listFiles(); + File[] curFiles = curFolder.listFiles(); + if (newFiles == null || curFiles == null) + throw new MailboxException("Unable to count messages in Mailbox " + mailbox, new IOException( + "Not a valid Maildir folder: " + maildirStore.getFolderName(mailbox))); + int count = newFiles.length + curFiles.length; + return count; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + File newFolder = folder.getNewFolder(); + File curFolder = folder.getCurFolder(); + String[] unseenMessages = curFolder.list(MaildirMessageName.FILTER_UNSEEN_MESSAGES); + String[] newUnseenMessages = newFolder.list(MaildirMessageName.FILTER_UNSEEN_MESSAGES); + if (newUnseenMessages == null || unseenMessages == null) + throw new MailboxException("Unable to count unseen messages in Mailbox " + mailbox, new IOException( + "Not a valid Maildir folder: " + maildirStore.getFolderName(mailbox))); + int count = newUnseenMessages.length + unseenMessages.length; + return count; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public void delete(Mailbox mailbox, Message message) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + folder.delete(mailboxSession, message.getUid()); + } catch (MailboxException e) { + throw new MailboxException("Unable to delete Message " + message + " in Mailbox " + mailbox, e); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + @Override + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) + throws MailboxException { + final List> results; + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + switch (type) { + default: + case ALL: + results = findMessagesInMailboxBetweenUIDs(mailbox, null, 0, -1, max); + break; + case FROM: + results = findMessagesInMailboxBetweenUIDs(mailbox, null, from, -1, max); + break; + case ONE: + results = findMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, null, from, to, max); + break; + } + return results.iterator(); + + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + SortedMap recentMessageNames = folder.getRecentMessages(mailboxSession); + return new ArrayList(recentMessageNames.keySet()); + + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + List> result = findMessagesInMailbox(mailbox, MaildirMessageName.FILTER_UNSEEN_MESSAGES, 1); + if (result.isEmpty()) { + return null; + } else { + return result.get(0).getUid(); + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, + * javax.mail.Flags, boolean, boolean, + * org.apache.james.mailbox.model.MessageRange) + */ + @Override + public Iterator updateFlags(final Mailbox mailbox, final Flags flags, final boolean value, + final boolean replace, final MessageRange set) throws MailboxException { + final List updatedFlags = new ArrayList(); + final MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + + Iterator> it = findInMailbox(mailbox, set, FetchType.Metadata, -1); + while (it.hasNext()) { + final Message member = it.next(); + Flags originalFlags = member.createFlags(); + if (replace) { + member.setFlags(flags); + } else { + Flags current = member.createFlags(); + if (value) { + current.add(flags); + } else { + current.remove(flags); + } + member.setFlags(current); + } + Flags newFlags = member.createFlags(); + + try { + MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, member.getUid()); + if (messageName != null) { + File messageFile = messageName.getFile(); + // System.out.println("save existing " + message + + // " as " + messageFile.getName()); + messageName.setFlags(member.createFlags()); + // this automatically moves messages from new to cur if + // needed + String newMessageName = messageName.getFullName(); + + File newMessageFile; + + // See MAILBOX-57 + if (newFlags.contains(Flag.RECENT)) { + // message is recent so save it in the new folder + newMessageFile = new File(folder.getNewFolder(), newMessageName); + } else { + newMessageFile = new File(folder.getCurFolder(), newMessageName); + } + long modSeq; + // if the flags don't have change we should not try to move + // the file + if (newMessageFile.equals(messageFile) == false) { + FileUtils.moveFile(messageFile, newMessageFile); + modSeq = newMessageFile.lastModified(); + + } else { + modSeq = messageFile.lastModified(); + } + member.setModSeq(modSeq); + + updatedFlags.add(new UpdatedFlags(member.getUid(), modSeq, originalFlags, newFlags)); + + long uid = member.getUid(); + folder.update(mailboxSession, uid, newMessageName); + } + } catch (IOException e) { + throw new MailboxException("Failure while save Message " + member + " in Mailbox " + mailbox, e); + } + + } + return updatedFlags.iterator(); + + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + List> results = new ArrayList>(); + final long from = set.getUidFrom(); + final long to = set.getUidTo(); + final Type type = set.getType(); + switch (type) { + default: + case ALL: + results = findMessagesInMailbox(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, -1); + break; + case FROM: + results = findMessagesInMailboxBetweenUIDs(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, from, -1, + -1); + break; + case ONE: + results = findDeletedMessageInMailboxWithUID(mailbox, from); + break; + case RANGE: + results = findMessagesInMailboxBetweenUIDs(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, from, to, + -1); + break; + } + Map uids = new HashMap(); + for (int i = 0; i < results.size(); i++) { + Message m = results.get(i); + long uid = m.getUid(); + uids.put(uid, new SimpleMessageMetaData(m)); + delete(mailbox, m); + } + + return uids; + } + + /** + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(org.apache + * .james.mailbox.store.mail.model.Mailbox, long, long, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) + throws MailboxException { + SimpleMessage theCopy = new SimpleMessage(mailbox, original); + Flags flags = theCopy.createFlags(); + flags.add(Flag.RECENT); + theCopy.setFlags(flags); + return save(mailbox, theCopy); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + long uid = 0; + // a new message + // save file to "tmp" folder + File tmpFolder = folder.getTmpFolder(); + // The only case in which we could get problems with clashing names + // is if the system clock + // has been set backwards, then the server is restarted with the + // same pid, delivers the same + // number of messages since its start in the exact same millisecond + // as done before and the + // random number generator returns the same number. + // In order to prevent this case we would need to check ALL files in + // all folders and compare + // them to this message name. We rather let this happen once in a + // billion years... + MaildirMessageName messageName = MaildirMessageName.createUniqueName(folder, message.getFullContentOctets()); + File messageFile = new File(tmpFolder, messageName.getFullName()); + FileOutputStream fos = null; + InputStream input = null; + try { + if (!messageFile.createNewFile()) + throw new IOException("Could not create file " + messageFile); + fos = new FileOutputStream(messageFile); + input = message.getFullContent(); + byte[] b = new byte[BUF_SIZE]; + int len = 0; + while ((len = input.read(b)) != -1) + fos.write(b, 0, len); + } catch (IOException ioe) { + throw new MailboxException("Failure while save Message " + message + " in Mailbox " + mailbox, ioe); + } finally { + try { + if (fos != null) + fos.close(); + } catch (IOException e) { + } + try { + if (input != null) + input.close(); + } catch (IOException e) { + } + } + File newMessageFile = null; + // delivered via SMTP, goes to ./new without flags + if (message.isRecent()) { + messageName.setFlags(message.createFlags()); + newMessageFile = new File(folder.getNewFolder(), messageName.getFullName()); + // System.out.println("save new recent " + message + " as " + + // newMessageFile.getName()); + } + // appended via IMAP (might already have flags etc, goes to ./cur + // directly) + else { + messageName.setFlags(message.createFlags()); + newMessageFile = new File(folder.getCurFolder(), messageName.getFullName()); + // System.out.println("save new not recent " + message + " as " + // + newMessageFile.getName()); + } + try { + FileUtils.moveFile(messageFile, newMessageFile); + } catch (IOException e) { + // TODO: Try copy and delete + throw new MailboxException("Failure while save Message " + message + " in Mailbox " + mailbox, e); + } + try { + uid = folder.appendMessage(mailboxSession, newMessageFile.getName()); + message.setUid(uid); + message.setModSeq(newMessageFile.lastModified()); + return new SimpleMessageMetaData(message); + } catch (MailboxException e) { + throw new MailboxException("Failure while save Message " + message + " in Mailbox " + mailbox, e); + } + + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() + */ + @Override + public void endRequest() { + // not used + + } + + private List> findMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, uid); + + ArrayList> messages = new ArrayList>(); + if (messageName != null && messageName.getFile().exists()) { + messages.add(new MaildirMessage(mailbox, uid, messageName)); + } + return messages; + + } catch (IOException e) { + throw new MailboxException("Failure while search for Message with uid " + uid + " in Mailbox " + mailbox, e); + } + } + + private List> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, FilenameFilter filter, + long from, long to, int max) throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + int cur = 0; + SortedMap uidMap = null; + try { + if (filter != null) + uidMap = folder.getUidMap(mailboxSession, filter, from, to); + else + uidMap = folder.getUidMap(mailboxSession, from, to); + + ArrayList> messages = new ArrayList>(); + for (Entry entry : uidMap.entrySet()) { + messages.add(new MaildirMessage(mailbox, entry.getKey(), entry.getValue())); + if (max != -1) { + cur++; + if (cur >= max) + break; + } + } + return messages; + } catch (IOException e) { + throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); + } + + } + + private List> findMessagesInMailbox(Mailbox mailbox, FilenameFilter filter, int limit) + throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + SortedMap uidMap = folder.getUidMap(mailboxSession, filter, limit); + + ArrayList> filtered = new ArrayList>(uidMap.size()); + for (Entry entry : uidMap.entrySet()) + filtered.add(new MaildirMessage(mailbox, entry.getKey(), entry.getValue())); + return filtered; + } catch (IOException e) { + throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); + } + + } + + private List> findDeletedMessageInMailboxWithUID(Mailbox mailbox, long uid) + throws MailboxException { + MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); + try { + MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, uid); + ArrayList> messages = new ArrayList>(); + if (MaildirMessageName.FILTER_DELETED_MESSAGES.accept(null, messageName.getFullName())) { + messages.add(new MaildirMessage(mailbox, uid, messageName)); + } + return messages; + + } catch (IOException e) { + throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); + } + + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin() + */ + @Override + protected void begin() throws MailboxException { + // nothing todo + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#commit() + */ + @Override + protected void commit() throws MailboxException { + // nothing todo + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback() + */ + @Override + protected void rollback() throws MailboxException { + // nothing todo + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/all-wcprops new file mode 100644 index 0000000..110be12 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1428384/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model +END +MaildirMessage.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1428384/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java +END +MaildirMailbox.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMailbox.java +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/entries new file mode 100644 index 0000000..8a8898a --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model +http://svn.apache.org/repos/asf + + + +2013-01-03T15:03:15.860546Z +1428384 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MaildirMessage.java +file + + + + +2013-09-02T02:54:38.000000Z +dd49fe50fb968876f7016cf90fd0d050 +2013-01-03T15:03:15.860546Z +1428384 +eric + + + + + + + + + + + + + + + + + + + + + +15409 + +MaildirMailbox.java +file + + + + +2013-09-02T02:54:38.000000Z +6b0f34b6aaf8e694399bc6e76dc134cf +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1189 + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/prop-base/MaildirMailbox.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/prop-base/MaildirMailbox.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/prop-base/MaildirMailbox.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMailbox.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMailbox.java.svn-base new file mode 100644 index 0000000..4d0bb9f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMailbox.java.svn-base @@ -0,0 +1,41 @@ +package org.apache.james.mailbox.maildir.mail.model; + +import java.io.IOException; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; + +public class MaildirMailbox extends SimpleMailbox { + + private MaildirFolder folder; + private MailboxSession session; + + public MaildirMailbox(MailboxSession session, MailboxPath path, MaildirFolder folder) throws IOException { + super(path, folder.getUidValidity()); + this.folder = folder; + this.session = session; + } + + @Override + public MailboxACL getACL() { + try { + return folder.getACL(session); + } catch (MailboxException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setACL(MailboxACL acl) { + try { + folder.setACL(session, acl); + } catch (MailboxException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMessage.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMessage.java.svn-base new file mode 100644 index 0000000..aafed31 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/.svn/text-base/MaildirMessage.java.svn-base @@ -0,0 +1,456 @@ +/**************************************************************** + * 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.mail.model; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.maildir.MaildirMessageName; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.streaming.CountingInputStream; +import org.apache.james.mailbox.store.streaming.LimitingFileInputStream; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder; +import org.apache.james.mime4j.message.MaximalBodyDescriptor; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + +import javax.mail.Flags; +import javax.mail.util.SharedFileInputStream; +import java.io.*; +import java.util.Date; +import java.util.List; + +public class MaildirMessage extends AbstractMessage { + + private MaildirMessageName messageName; + private int bodyStartOctet; + private final PropertyBuilder propertyBuilder = new PropertyBuilder(); + private boolean parsed; + private boolean answered; + private boolean deleted; + private boolean draft; + private boolean flagged; + private boolean recent; + private boolean seen; + private Mailbox mailbox; + private long uid; + protected boolean newMessage; + private long modSeq; + + public MaildirMessage(Mailbox mailbox, long uid, MaildirMessageName messageName) throws IOException { + this.mailbox = mailbox; + setUid(uid); + setModSeq(messageName.getFile().lastModified()); + Flags flags = messageName.getFlags(); + + // Set the flags for the message and respect if its RECENT + // See MAILBOX-84 + File file = messageName.getFile(); + if (!file.exists()) { + throw new FileNotFoundException("Unable to read file " + file.getAbsolutePath() + " for the message"); + } else { + // if the message resist in the new folder its RECENT + if (file.getParentFile().getName().equals(MaildirFolder.NEW)) { + if (flags == null) + flags = new Flags(); + flags.add(Flags.Flag.RECENT); + } + } + setFlags(flags); + this.messageName = messageName; + } + + + @Override + public Integer getMailboxId() { + return mailbox.getMailboxId(); + } + + @Override + public long getUid() { + return uid; + } + + @Override + public void setUid(long uid) { + this.uid = uid; + } + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#setFlags( + * javax.mail.Flags) + */ + @Override + public void setFlags(Flags flags) { + if (flags != null) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + } + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isAnswered() + */ + @Override + public boolean isAnswered() { + return answered; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isDeleted() + */ + @Override + public boolean isDeleted() { + return deleted; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isDraft() + */ + @Override + public boolean isDraft() { + return draft; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isFlagged() + */ + @Override + public boolean isFlagged() { + return flagged; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isRecent() + */ + @Override + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isSeen() + */ + @Override + public boolean isSeen() { + return seen; + } + + /** + * Indicates whether this MaildirMessage reflects a new message or one that already + * exists in the file system. + * @return true if it is new, false if it already exists + */ + public boolean isNew() { + return newMessage; + } + + + @Override + public String toString() { + StringBuilder theString = new StringBuilder("MaildirMessage "); + theString.append(getUid()); + theString.append(" {"); + Flags flags = createFlags(); + if (flags.contains(Flags.Flag.DRAFT)) + theString.append(MaildirMessageName.FLAG_DRAFT); + if (flags.contains(Flags.Flag.FLAGGED)) + theString.append(MaildirMessageName.FLAG_FLAGGED); + if (flags.contains(Flags.Flag.ANSWERED)) + theString.append(MaildirMessageName.FLAG_ANSWERD); + if (flags.contains(Flags.Flag.SEEN)) + theString.append(MaildirMessageName.FLAG_SEEN); + if (flags.contains(Flags.Flag.DELETED)) + theString.append(MaildirMessageName.FLAG_DELETED); + theString.append("} "); + theString.append(getInternalDate()); + return theString.toString(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + @Override + public long getModSeq() { + return modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + @Override + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + /** + * Parse message if needed + */ + private synchronized void parseMessage() { + if (parsed) + return; + SharedFileInputStream tmpMsgIn = null; + try { + tmpMsgIn = new SharedFileInputStream(messageName.getFile()); + + bodyStartOctet = bodyStartOctet(tmpMsgIn); + + // Disable line length... This should be handled by the smtp server + // component and not the parser itself + // https://issues.apache.org/jira/browse/IMAP-122 + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + final MimeTokenStream parser = new MimeTokenStream(config, new DefaultBodyDescriptorBuilder()); + parser.setRecursionMode(RecursionMode.M_NO_RECURSE); + parser.parse(tmpMsgIn.newStream(0, -1)); + + EntityState next = parser.next(); + while (next != EntityState.T_BODY && next != EntityState.T_END_OF_STREAM && next != EntityState.T_START_MULTIPART) { + next = parser.next(); + } + final MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser.getBodyDescriptor(); + final String mediaType; + final String mediaTypeFromHeader = descriptor.getMediaType(); + final String subType; + if (mediaTypeFromHeader == null) { + mediaType = "text"; + subType = "plain"; + } else { + mediaType = mediaTypeFromHeader; + subType = descriptor.getSubType(); + } + propertyBuilder.setMediaType(mediaType); + propertyBuilder.setSubType(subType); + propertyBuilder.setContentID(descriptor.getContentId()); + propertyBuilder.setContentDescription(descriptor.getContentDescription()); + propertyBuilder.setContentLocation(descriptor.getContentLocation()); + propertyBuilder.setContentMD5(descriptor.getContentMD5Raw()); + propertyBuilder.setContentTransferEncoding(descriptor.getTransferEncoding()); + propertyBuilder.setContentLanguage(descriptor.getContentLanguage()); + propertyBuilder.setContentDispositionType(descriptor.getContentDispositionType()); + propertyBuilder.setContentDispositionParameters(descriptor.getContentDispositionParameters()); + propertyBuilder.setContentTypeParameters(descriptor.getContentTypeParameters()); + // Add missing types + final String codeset = descriptor.getCharset(); + if (codeset == null) { + if ("TEXT".equalsIgnoreCase(mediaType)) { + propertyBuilder.setCharset("us-ascii"); + } + } else { + propertyBuilder.setCharset(codeset); + } + + final String boundary = descriptor.getBoundary(); + if (boundary != null) { + propertyBuilder.setBoundary(boundary); + } + if ("text".equalsIgnoreCase(mediaType)) { + long lines = -1; + final CountingInputStream bodyStream = new CountingInputStream(parser.getInputStream()); + try { + bodyStream.readAll(); + lines = bodyStream.getLineCount(); + } finally { + IOUtils.closeQuietly(bodyStream); + } + + next = parser.next(); + if (next == EntityState.T_EPILOGUE) { + final CountingInputStream epilogueStream = new CountingInputStream(parser.getInputStream()); + try { + epilogueStream.readAll(); + lines += epilogueStream.getLineCount(); + } finally { + IOUtils.closeQuietly(epilogueStream); + } + } + propertyBuilder.setTextualLineCount(lines); + } + } catch (IOException e) { + // has successfully been parsen when appending, shouldn't give any + // problems + } catch (MimeException e) { + // has successfully been parsen when appending, shouldn't give any + // problems + } finally { + if (tmpMsgIn != null) { + try { + tmpMsgIn.close(); + } catch (IOException e) { + // ignore on close + } + } + parsed = true; + } + } + + /** + * Return the position in the given {@link InputStream} at which the Body of + * the Message starts + * + * @param msgIn + * @return bodyStartOctet + * @throws IOException + */ + private int bodyStartOctet(InputStream msgIn) throws IOException { + // we need to pushback maximal 3 bytes + PushbackInputStream in = new PushbackInputStream(msgIn, 3); + int localBodyStartOctet = in.available(); + int i = -1; + int count = 0; + while ((i = in.read()) != -1 && in.available() > 4) { + if (i == 0x0D) { + int a = in.read(); + if (a == 0x0A) { + int b = in.read(); + + if (b == 0x0D) { + int c = in.read(); + + if (c == 0x0A) { + localBodyStartOctet = count + 4; + break; + } + in.unread(c); + } + in.unread(b); + } + in.unread(a); + } + count++; + } + return localBodyStartOctet; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getMediaType() + */ + @Override + public String getMediaType() { + parseMessage(); + return propertyBuilder.getMediaType(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getSubType() + */ + @Override + public String getSubType() { + parseMessage(); + return propertyBuilder.getSubType(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getFullContentOctets() + */ + @Override + public long getFullContentOctets() { + Long size = messageName.getSize(); + if (size != null) { + return size; + } else { + try { + return messageName.getFile().length(); + } catch (FileNotFoundException e) { + return -1; + } + } + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getTextualLineCount() + */ + @Override + public Long getTextualLineCount() { + parseMessage(); + return propertyBuilder.getTextualLineCount(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getProperties() + */ + @Override + public List getProperties() { + parseMessage(); + return propertyBuilder.toProperties(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getInternalDate() + */ + @Override + public Date getInternalDate() { + return messageName.getInternalDate(); + } + + /** + * Return the full content of the message via a {@link FileInputStream} + */ + @Override + public InputStream getFullContent() throws IOException { + return new FileInputStream(messageName.getFile()); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + @Override + public InputStream getBodyContent() throws IOException { + parseMessage(); + FileInputStream body = new FileInputStream(messageName.getFile()); + IOUtils.skipFully(body, bodyStartOctet); + return body; + + } + + /** + * @see org.apache.james.mailbox.store.mail.model.AbstractMessage#getBodyStartOctet() + */ + @Override + protected int getBodyStartOctet() { + parseMessage(); + return bodyStartOctet; + } + + @Override + public InputStream getHeaderContent() throws IOException { + parseMessage(); + long limit = getBodyStartOctet(); + if (limit < 0) { + limit = 0; + } + return new LimitingFileInputStream(messageName.getFile(), limit); + + } + + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMailbox.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMailbox.java new file mode 100644 index 0000000..4d0bb9f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMailbox.java @@ -0,0 +1,41 @@ +package org.apache.james.mailbox.maildir.mail.model; + +import java.io.IOException; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; + +public class MaildirMailbox extends SimpleMailbox { + + private MaildirFolder folder; + private MailboxSession session; + + public MaildirMailbox(MailboxSession session, MailboxPath path, MaildirFolder folder) throws IOException { + super(path, folder.getUidValidity()); + this.folder = folder; + this.session = session; + } + + @Override + public MailboxACL getACL() { + try { + return folder.getACL(session); + } catch (MailboxException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setACL(MailboxACL acl) { + try { + folder.setACL(session, acl); + } catch (MailboxException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java new file mode 100644 index 0000000..aafed31 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java @@ -0,0 +1,456 @@ +/**************************************************************** + * 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.mail.model; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.maildir.MaildirFolder; +import org.apache.james.mailbox.maildir.MaildirMessageName; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.streaming.CountingInputStream; +import org.apache.james.mailbox.store.streaming.LimitingFileInputStream; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder; +import org.apache.james.mime4j.message.MaximalBodyDescriptor; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + +import javax.mail.Flags; +import javax.mail.util.SharedFileInputStream; +import java.io.*; +import java.util.Date; +import java.util.List; + +public class MaildirMessage extends AbstractMessage { + + private MaildirMessageName messageName; + private int bodyStartOctet; + private final PropertyBuilder propertyBuilder = new PropertyBuilder(); + private boolean parsed; + private boolean answered; + private boolean deleted; + private boolean draft; + private boolean flagged; + private boolean recent; + private boolean seen; + private Mailbox mailbox; + private long uid; + protected boolean newMessage; + private long modSeq; + + public MaildirMessage(Mailbox mailbox, long uid, MaildirMessageName messageName) throws IOException { + this.mailbox = mailbox; + setUid(uid); + setModSeq(messageName.getFile().lastModified()); + Flags flags = messageName.getFlags(); + + // Set the flags for the message and respect if its RECENT + // See MAILBOX-84 + File file = messageName.getFile(); + if (!file.exists()) { + throw new FileNotFoundException("Unable to read file " + file.getAbsolutePath() + " for the message"); + } else { + // if the message resist in the new folder its RECENT + if (file.getParentFile().getName().equals(MaildirFolder.NEW)) { + if (flags == null) + flags = new Flags(); + flags.add(Flags.Flag.RECENT); + } + } + setFlags(flags); + this.messageName = messageName; + } + + + @Override + public Integer getMailboxId() { + return mailbox.getMailboxId(); + } + + @Override + public long getUid() { + return uid; + } + + @Override + public void setUid(long uid) { + this.uid = uid; + } + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#setFlags( + * javax.mail.Flags) + */ + @Override + public void setFlags(Flags flags) { + if (flags != null) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + } + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isAnswered() + */ + @Override + public boolean isAnswered() { + return answered; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isDeleted() + */ + @Override + public boolean isDeleted() { + return deleted; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isDraft() + */ + @Override + public boolean isDraft() { + return draft; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isFlagged() + */ + @Override + public boolean isFlagged() { + return flagged; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Message#isRecent() + */ + @Override + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#isSeen() + */ + @Override + public boolean isSeen() { + return seen; + } + + /** + * Indicates whether this MaildirMessage reflects a new message or one that already + * exists in the file system. + * @return true if it is new, false if it already exists + */ + public boolean isNew() { + return newMessage; + } + + + @Override + public String toString() { + StringBuilder theString = new StringBuilder("MaildirMessage "); + theString.append(getUid()); + theString.append(" {"); + Flags flags = createFlags(); + if (flags.contains(Flags.Flag.DRAFT)) + theString.append(MaildirMessageName.FLAG_DRAFT); + if (flags.contains(Flags.Flag.FLAGGED)) + theString.append(MaildirMessageName.FLAG_FLAGGED); + if (flags.contains(Flags.Flag.ANSWERED)) + theString.append(MaildirMessageName.FLAG_ANSWERD); + if (flags.contains(Flags.Flag.SEEN)) + theString.append(MaildirMessageName.FLAG_SEEN); + if (flags.contains(Flags.Flag.DELETED)) + theString.append(MaildirMessageName.FLAG_DELETED); + theString.append("} "); + theString.append(getInternalDate()); + return theString.toString(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + @Override + public long getModSeq() { + return modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + @Override + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + /** + * Parse message if needed + */ + private synchronized void parseMessage() { + if (parsed) + return; + SharedFileInputStream tmpMsgIn = null; + try { + tmpMsgIn = new SharedFileInputStream(messageName.getFile()); + + bodyStartOctet = bodyStartOctet(tmpMsgIn); + + // Disable line length... This should be handled by the smtp server + // component and not the parser itself + // https://issues.apache.org/jira/browse/IMAP-122 + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + final MimeTokenStream parser = new MimeTokenStream(config, new DefaultBodyDescriptorBuilder()); + parser.setRecursionMode(RecursionMode.M_NO_RECURSE); + parser.parse(tmpMsgIn.newStream(0, -1)); + + EntityState next = parser.next(); + while (next != EntityState.T_BODY && next != EntityState.T_END_OF_STREAM && next != EntityState.T_START_MULTIPART) { + next = parser.next(); + } + final MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser.getBodyDescriptor(); + final String mediaType; + final String mediaTypeFromHeader = descriptor.getMediaType(); + final String subType; + if (mediaTypeFromHeader == null) { + mediaType = "text"; + subType = "plain"; + } else { + mediaType = mediaTypeFromHeader; + subType = descriptor.getSubType(); + } + propertyBuilder.setMediaType(mediaType); + propertyBuilder.setSubType(subType); + propertyBuilder.setContentID(descriptor.getContentId()); + propertyBuilder.setContentDescription(descriptor.getContentDescription()); + propertyBuilder.setContentLocation(descriptor.getContentLocation()); + propertyBuilder.setContentMD5(descriptor.getContentMD5Raw()); + propertyBuilder.setContentTransferEncoding(descriptor.getTransferEncoding()); + propertyBuilder.setContentLanguage(descriptor.getContentLanguage()); + propertyBuilder.setContentDispositionType(descriptor.getContentDispositionType()); + propertyBuilder.setContentDispositionParameters(descriptor.getContentDispositionParameters()); + propertyBuilder.setContentTypeParameters(descriptor.getContentTypeParameters()); + // Add missing types + final String codeset = descriptor.getCharset(); + if (codeset == null) { + if ("TEXT".equalsIgnoreCase(mediaType)) { + propertyBuilder.setCharset("us-ascii"); + } + } else { + propertyBuilder.setCharset(codeset); + } + + final String boundary = descriptor.getBoundary(); + if (boundary != null) { + propertyBuilder.setBoundary(boundary); + } + if ("text".equalsIgnoreCase(mediaType)) { + long lines = -1; + final CountingInputStream bodyStream = new CountingInputStream(parser.getInputStream()); + try { + bodyStream.readAll(); + lines = bodyStream.getLineCount(); + } finally { + IOUtils.closeQuietly(bodyStream); + } + + next = parser.next(); + if (next == EntityState.T_EPILOGUE) { + final CountingInputStream epilogueStream = new CountingInputStream(parser.getInputStream()); + try { + epilogueStream.readAll(); + lines += epilogueStream.getLineCount(); + } finally { + IOUtils.closeQuietly(epilogueStream); + } + } + propertyBuilder.setTextualLineCount(lines); + } + } catch (IOException e) { + // has successfully been parsen when appending, shouldn't give any + // problems + } catch (MimeException e) { + // has successfully been parsen when appending, shouldn't give any + // problems + } finally { + if (tmpMsgIn != null) { + try { + tmpMsgIn.close(); + } catch (IOException e) { + // ignore on close + } + } + parsed = true; + } + } + + /** + * Return the position in the given {@link InputStream} at which the Body of + * the Message starts + * + * @param msgIn + * @return bodyStartOctet + * @throws IOException + */ + private int bodyStartOctet(InputStream msgIn) throws IOException { + // we need to pushback maximal 3 bytes + PushbackInputStream in = new PushbackInputStream(msgIn, 3); + int localBodyStartOctet = in.available(); + int i = -1; + int count = 0; + while ((i = in.read()) != -1 && in.available() > 4) { + if (i == 0x0D) { + int a = in.read(); + if (a == 0x0A) { + int b = in.read(); + + if (b == 0x0D) { + int c = in.read(); + + if (c == 0x0A) { + localBodyStartOctet = count + 4; + break; + } + in.unread(c); + } + in.unread(b); + } + in.unread(a); + } + count++; + } + return localBodyStartOctet; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getMediaType() + */ + @Override + public String getMediaType() { + parseMessage(); + return propertyBuilder.getMediaType(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getSubType() + */ + @Override + public String getSubType() { + parseMessage(); + return propertyBuilder.getSubType(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getFullContentOctets() + */ + @Override + public long getFullContentOctets() { + Long size = messageName.getSize(); + if (size != null) { + return size; + } else { + try { + return messageName.getFile().length(); + } catch (FileNotFoundException e) { + return -1; + } + } + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getTextualLineCount() + */ + @Override + public Long getTextualLineCount() { + parseMessage(); + return propertyBuilder.getTextualLineCount(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getProperties() + */ + @Override + public List getProperties() { + parseMessage(); + return propertyBuilder.toProperties(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getInternalDate() + */ + @Override + public Date getInternalDate() { + return messageName.getInternalDate(); + } + + /** + * Return the full content of the message via a {@link FileInputStream} + */ + @Override + public InputStream getFullContent() throws IOException { + return new FileInputStream(messageName.getFile()); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getBodyContent() + */ + @Override + public InputStream getBodyContent() throws IOException { + parseMessage(); + FileInputStream body = new FileInputStream(messageName.getFile()); + IOUtils.skipFully(body, bodyStartOctet); + return body; + + } + + /** + * @see org.apache.james.mailbox.store.mail.model.AbstractMessage#getBodyStartOctet() + */ + @Override + protected int getBodyStartOctet() { + parseMessage(); + return bodyStartOctet; + } + + @Override + public InputStream getHeaderContent() throws IOException { + parseMessage(); + long limit = getBodyStartOctet(); + if (limit < 0) { + limit = 0; + } + return new LimitingFileInputStream(messageName.getFile(), limit); + + } + + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/all-wcprops new file mode 100644 index 0000000..930d940 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 107 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/user +END +MaildirSubscriptionMapper.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/user/MaildirSubscriptionMapper.java +END diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/entries b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/entries new file mode 100644 index 0000000..542d41b --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/java/org/apache/james/mailbox/maildir/user +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MaildirSubscriptionMapper.java +file + + + + +2013-09-02T02:54:38.000000Z +442ff9bb160cf2f7154c93bb943270f3 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +7944 + diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/text-base/MaildirSubscriptionMapper.java.svn-base b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/text-base/MaildirSubscriptionMapper.java.svn-base new file mode 100644 index 0000000..7c1c7f8 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/.svn/text-base/MaildirSubscriptionMapper.java.svn-base @@ -0,0 +1,188 @@ +/**************************************************************** + * 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.user; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.maildir.MaildirStore; +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; + +public class MaildirSubscriptionMapper extends NonTransactionalMapper implements SubscriptionMapper { + + private static final String FILE_SUBSCRIPTION = "subscriptions"; + private MaildirStore store; + + public MaildirSubscriptionMapper(MaildirStore store) { + this.store = store; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(org.apache.james.mailbox.store.user.model.Subscription) + */ + @Override + public void delete(Subscription subscription) throws SubscriptionException { + // TODO: we need some kind of file locking here + Set subscriptionNames = readSubscriptionsForUser(subscription.getUser()); + boolean changed = subscriptionNames.remove(subscription.getMailbox()); + if (changed) { + try { + writeSubscriptions(new File(store.userRoot(subscription.getUser())), subscriptionNames); + } catch (IOException e) { + throw new SubscriptionException(e); + } + } + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @Override + public List findSubscriptionsForUser(String user) throws SubscriptionException { + Set subscriptionNames = readSubscriptionsForUser(user); + ArrayList subscriptions = new ArrayList(); + for (String subscription : subscriptionNames) { + subscriptions.add(new SimpleSubscription(user, subscription)); + } + return subscriptions; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + @Override + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) throws SubscriptionException { + File userRoot = new File(store.userRoot(user)); + Set subscriptionNames; + try { + subscriptionNames = readSubscriptions(userRoot); + } catch (IOException e) { + throw new SubscriptionException(e); + } + if (subscriptionNames.contains(mailbox)) + return new SimpleSubscription(user, mailbox); + return null; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(org.apache.james.mailbox.store.user.model.Subscription) + */ + @Override + public void save(Subscription subscription) throws SubscriptionException { + // TODO: we need some kind of file locking here + Set subscriptionNames = readSubscriptionsForUser(subscription.getUser()); + boolean changed = subscriptionNames.add(subscription.getMailbox()); + if (changed) { + try { + writeSubscriptions(new File(store.userRoot(subscription.getUser())), subscriptionNames); + } catch (IOException e) { + throw new SubscriptionException(e); + } + } + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() + */ + @Override + public void endRequest() { + // nothing to do + } + + + /** + * Read the subscriptions for a particular user + * @param user The user to get the subscriptions for + * @return A Set of names of subscribed mailboxes of the user + * @throws SubscriptionException + */ + private Set readSubscriptionsForUser(String user) throws SubscriptionException { + File userRoot = new File(store.userRoot(user)); + Set subscriptionNames; + try { + subscriptionNames = readSubscriptions(userRoot); + } catch (IOException e) { + throw new SubscriptionException(e); + } + return subscriptionNames; + } + + /** + * Read the names of the mailboxes which are subscribed from the specified folder + * @param mailboxFolder The folder which contains the subscription file + * @return A Set of names of subscribed mailboxes + * @throws IOException + */ + private Set readSubscriptions(File mailboxFolder) throws IOException { + File subscriptionFile = new File(mailboxFolder, FILE_SUBSCRIPTION); + HashSet subscriptions = new HashSet(); + if (!subscriptionFile.exists()) { + return subscriptions; + } + FileReader fileReader = new FileReader(subscriptionFile); + BufferedReader reader = new BufferedReader(fileReader); + String subscription; + while ((subscription = reader.readLine()) != null) + if (!subscription.equals("")) + subscriptions.add(subscription); + reader.close(); + fileReader.close(); + return subscriptions; + } + + /** + * Write the set of mailbox names into the subscriptions file in the specified folder + * @param mailboxFolder Folder which contains the subscriptions file + * @param subscriptions Set of names of subscribed mailboxes + * @throws IOException + */ + private void writeSubscriptions(File mailboxFolder, final Set subscriptions) throws IOException { + List sortedSubscriptions = new ArrayList(subscriptions); + Collections.sort(sortedSubscriptions); + if (!mailboxFolder.exists()) + if (!mailboxFolder.mkdirs()) + throw new IOException("Could not create folder " + mailboxFolder); + + File subscriptionFile = new File(mailboxFolder, FILE_SUBSCRIPTION); + if (!subscriptionFile.exists()) + if (!subscriptionFile.createNewFile()) + throw new IOException("Could not create file " + subscriptionFile); + + FileWriter fileWriter = new FileWriter(subscriptionFile); + PrintWriter writer = new PrintWriter(fileWriter); + for (String subscription : sortedSubscriptions) + writer.println(subscription); + writer.close(); + fileWriter.close(); + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/MaildirSubscriptionMapper.java b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/MaildirSubscriptionMapper.java new file mode 100644 index 0000000..7c1c7f8 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/user/MaildirSubscriptionMapper.java @@ -0,0 +1,188 @@ +/**************************************************************** + * 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.user; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.maildir.MaildirStore; +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; + +public class MaildirSubscriptionMapper extends NonTransactionalMapper implements SubscriptionMapper { + + private static final String FILE_SUBSCRIPTION = "subscriptions"; + private MaildirStore store; + + public MaildirSubscriptionMapper(MaildirStore store) { + this.store = store; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(org.apache.james.mailbox.store.user.model.Subscription) + */ + @Override + public void delete(Subscription subscription) throws SubscriptionException { + // TODO: we need some kind of file locking here + Set subscriptionNames = readSubscriptionsForUser(subscription.getUser()); + boolean changed = subscriptionNames.remove(subscription.getMailbox()); + if (changed) { + try { + writeSubscriptions(new File(store.userRoot(subscription.getUser())), subscriptionNames); + } catch (IOException e) { + throw new SubscriptionException(e); + } + } + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @Override + public List findSubscriptionsForUser(String user) throws SubscriptionException { + Set subscriptionNames = readSubscriptionsForUser(user); + ArrayList subscriptions = new ArrayList(); + for (String subscription : subscriptionNames) { + subscriptions.add(new SimpleSubscription(user, subscription)); + } + return subscriptions; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + @Override + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) throws SubscriptionException { + File userRoot = new File(store.userRoot(user)); + Set subscriptionNames; + try { + subscriptionNames = readSubscriptions(userRoot); + } catch (IOException e) { + throw new SubscriptionException(e); + } + if (subscriptionNames.contains(mailbox)) + return new SimpleSubscription(user, mailbox); + return null; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(org.apache.james.mailbox.store.user.model.Subscription) + */ + @Override + public void save(Subscription subscription) throws SubscriptionException { + // TODO: we need some kind of file locking here + Set subscriptionNames = readSubscriptionsForUser(subscription.getUser()); + boolean changed = subscriptionNames.add(subscription.getMailbox()); + if (changed) { + try { + writeSubscriptions(new File(store.userRoot(subscription.getUser())), subscriptionNames); + } catch (IOException e) { + throw new SubscriptionException(e); + } + } + } + + /** + * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() + */ + @Override + public void endRequest() { + // nothing to do + } + + + /** + * Read the subscriptions for a particular user + * @param user The user to get the subscriptions for + * @return A Set of names of subscribed mailboxes of the user + * @throws SubscriptionException + */ + private Set readSubscriptionsForUser(String user) throws SubscriptionException { + File userRoot = new File(store.userRoot(user)); + Set subscriptionNames; + try { + subscriptionNames = readSubscriptions(userRoot); + } catch (IOException e) { + throw new SubscriptionException(e); + } + return subscriptionNames; + } + + /** + * Read the names of the mailboxes which are subscribed from the specified folder + * @param mailboxFolder The folder which contains the subscription file + * @return A Set of names of subscribed mailboxes + * @throws IOException + */ + private Set readSubscriptions(File mailboxFolder) throws IOException { + File subscriptionFile = new File(mailboxFolder, FILE_SUBSCRIPTION); + HashSet subscriptions = new HashSet(); + if (!subscriptionFile.exists()) { + return subscriptions; + } + FileReader fileReader = new FileReader(subscriptionFile); + BufferedReader reader = new BufferedReader(fileReader); + String subscription; + while ((subscription = reader.readLine()) != null) + if (!subscription.equals("")) + subscriptions.add(subscription); + reader.close(); + fileReader.close(); + return subscriptions; + } + + /** + * Write the set of mailbox names into the subscriptions file in the specified folder + * @param mailboxFolder Folder which contains the subscriptions file + * @param subscriptions Set of names of subscribed mailboxes + * @throws IOException + */ + private void writeSubscriptions(File mailboxFolder, final Set subscriptions) throws IOException { + List sortedSubscriptions = new ArrayList(subscriptions); + Collections.sort(sortedSubscriptions); + if (!mailboxFolder.exists()) + if (!mailboxFolder.mkdirs()) + throw new IOException("Could not create folder " + mailboxFolder); + + File subscriptionFile = new File(mailboxFolder, FILE_SUBSCRIPTION); + if (!subscriptionFile.exists()) + if (!subscriptionFile.createNewFile()) + throw new IOException("Could not create file " + subscriptionFile); + + FileWriter fileWriter = new FileWriter(subscriptionFile); + PrintWriter writer = new PrintWriter(fileWriter); + for (String subscription : sortedSubscriptions) + writer.println(subscription); + writer.close(); + fileWriter.close(); + } + +} diff --git a/james/apache-james-mailbox/maildir/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..9728542 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 74 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/maildir/src/main/resources +END +mailbox-maildir.properties +K 25 +svn:wc:ra_dav:version-url +V 101 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/maildir/src/main/resources/mailbox-maildir.properties +END diff --git a/james/apache-james-mailbox/maildir/src/main/resources/.svn/entries b/james/apache-james-mailbox/maildir/src/main/resources/.svn/entries new file mode 100644 index 0000000..2bf2cb3 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/resources +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + +mailbox-maildir.properties +file + + + + +2013-09-02T02:54:38.000000Z +fef5ce63092160a4d39a2af455462f59 +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +895 + diff --git a/james/apache-james-mailbox/maildir/src/main/resources/.svn/text-base/mailbox-maildir.properties.svn-base b/james/apache-james-mailbox/maildir/src/main/resources/.svn/text-base/mailbox-maildir.properties.svn-base new file mode 100644 index 0000000..a0903fe --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/.svn/text-base/mailbox-maildir.properties.svn-base @@ -0,0 +1,20 @@ +# 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. +# + +maildir.folder=../var/store/maildir/%domain/%user +maildir.messageNameParser.strictMode=false diff --git a/james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..d492048 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 83 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/maildir/src/main/resources/META-INF +END diff --git a/james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/entries b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/entries new file mode 100644 index 0000000..5c847e8 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + diff --git a/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/all-wcprops new file mode 100644 index 0000000..214be34 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/maildir/src/main/resources/META-INF/spring +END +mailbox-maildir.xml +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml +END diff --git a/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/entries b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/entries new file mode 100644 index 0000000..70f61f3 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/main/resources/META-INF/spring +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-maildir.xml +file + + + + +2013-09-02T02:54:38.000000Z +872d13a4ad66046692b01e917a3594a6 +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + + + + + + + + +2737 + diff --git a/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/text-base/mailbox-maildir.xml.svn-base b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/text-base/mailbox-maildir.xml.svn-base new file mode 100644 index 0000000..9372948 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/.svn/text-base/mailbox-maildir.xml.svn-base @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml new file mode 100644 index 0000000..9372948 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/META-INF/spring/mailbox-maildir.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/maildir/src/main/resources/mailbox-maildir.properties b/james/apache-james-mailbox/maildir/src/main/resources/mailbox-maildir.properties new file mode 100644 index 0000000..a0903fe --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/main/resources/mailbox-maildir.properties @@ -0,0 +1,20 @@ +# 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. +# + +maildir.folder=../var/store/maildir/%domain/%user +maildir.messageNameParser.strictMode=false diff --git a/james/apache-james-mailbox/maildir/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..ba955ac --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 74 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/maildir/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 83 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/maildir/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/maildir/src/reporting-site/.svn/entries b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/entries new file mode 100644 index 0000000..881aff7 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-03-05T11:38:38.771020Z +1078275 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:38.000000Z +5bde8261948ff1881960902a322aa196 +2011-03-05T11:38:38.771020Z +1078275 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/maildir/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/maildir/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + +
+ + + + + diff --git a/james/apache-james-mailbox/maildir/src/reporting-site/site.xml b/james/apache-james-mailbox/maildir/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/maildir/src/test/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/test/.svn/all-wcprops new file mode 100644 index 0000000..5fd665d --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 64 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test +END diff --git a/james/apache-james-mailbox/maildir/src/test/.svn/entries b/james/apache-james-mailbox/maildir/src/test/.svn/entries new file mode 100644 index 0000000..2283ead --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/test +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/maildir/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..90eaa46 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 69 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test/java +END diff --git a/james/apache-james-mailbox/maildir/src/test/java/.svn/entries b/james/apache-james-mailbox/maildir/src/test/java/.svn/entries new file mode 100644 index 0000000..9699e5d --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/test/java +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..c1f8728 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test/java/org +END diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/.svn/entries b/james/apache-james-mailbox/maildir/src/test/java/org/.svn/entries new file mode 100644 index 0000000..eb8110e --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/test/java/org +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..2d6cad9 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 80 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..957161e --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..93fb3c4 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..fb50396 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..3e29d6f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..e466381 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +maildir +dir + diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/all-wcprops b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/all-wcprops new file mode 100644 index 0000000..1988ac9 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox/maildir +END +MaildirSubscriptionManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1137510/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirSubscriptionManagerTest.java +END +MailderMessageNameTest.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1428384/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox/maildir/MailderMessageNameTest.java +END +MaildirStressTest.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirStressTest.java +END +MaildirMailboxManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxManagerTest.java +END +OsDetector.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1056661/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox/maildir/OsDetector.java +END diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/entries b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/entries new file mode 100644 index 0000000..f116798 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/entries @@ -0,0 +1,198 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/maildir/src/test/java/org/apache/james/mailbox/maildir +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MaildirSubscriptionManagerTest.java +file + + + + +2013-09-02T02:54:38.000000Z +90244f25ebca83ee73d6b182ade9c859 +2011-06-20T06:08:50.947714Z +1137510 +norman + + + + + + + + + + + + + + + + + + + + + +1948 + +MailderMessageNameTest.java +file + + + + +2013-09-02T02:54:38.000000Z +52f006e08c0bbc143406ff7bb6dd0071 +2013-01-03T15:03:15.860546Z +1428384 +eric + + + + + + + + + + + + + + + + + + + + + +8288 + +MaildirStressTest.java +file + + + + +2013-09-02T02:54:38.000000Z +5200198430a2c2770e24b8269846a9ff +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +3220 + +MaildirMailboxManagerTest.java +file + + + + +2013-09-02T02:54:38.000000Z +62fb4cc26c965741b25593a38c27d0d3 +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +8581 + +OsDetector.java +file + + + + +2013-09-02T02:54:38.000000Z +35c8f52e75da92993f05beb1673693a2 +2011-01-08T08:46:54.031493Z +1056661 +eric + + + + + + + + + + + + + + + + + + + + + +1518 + diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/prop-base/MaildirMailboxManagerTest.java.svn-base b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/prop-base/MaildirMailboxManagerTest.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/prop-base/MaildirMailboxManagerTest.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MailderMessageNameTest.java.svn-base b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MailderMessageNameTest.java.svn-base new file mode 100644 index 0000000..10e5116 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MailderMessageNameTest.java.svn-base @@ -0,0 +1,235 @@ +/**************************************************************** + * 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 junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import javax.mail.Flags; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(value = Parameterized.class) +public class MailderMessageNameTest { + + private String invalidName; + private String validName; + private Parts parts; + + public MailderMessageNameTest(String invalidName, Parts parts) { + this.invalidName = invalidName; + this.validName = parts.fullName; + this.parts = parts; + } + + @Parameterized.Parameters + public static List testData() { + List args = new ArrayList(); + // no size, two flags + Parts parts = Parts.fullName("1328026049.19146_0.km1111:2,RS").timeSeconds(1328026049) + .baseName("1328026049.19146_0.km1111").flagAnswered().flagSeen(); + args.add(valid(parts)); + + // size and flag + parts = Parts.fullName("1328613172.M569643P1862V0000000000000902I00EE42CE_0.km1111,S=13103:2,S") + .timeSeconds(1328613172).size(13103L) + .baseName("1328613172.M569643P1862V0000000000000902I00EE42CE_0.km1111").flagSeen(); + args.add(valid(parts)); + + // size, no flags + parts = Parts.fullName("1340124194.M723289P3184V0000000000000902I006780E9_6.km1111,S=1344:2,") + .baseName("1340124194.M723289P3184V0000000000000902I006780E9_6.km1111").timeSeconds(1340124194) + .size(1344L); + args.add(valid(parts)); + + // three flags, no size + parts = Parts.fullName("1106685752.12132_0.km1111:2,FRS").baseName("1106685752.12132_0.km1111") + .timeSeconds(1106685752).flagFlagged().flagAnswered().flagSeen(); + args.add(valid(parts)); + + // with dovecot attributes + parts = Parts.fullName("1035478339.27041_118.foo.org,S=1000,W=1030:2,S") + .baseName("1035478339.27041_118.foo.org").timeSeconds(1035478339).size(1000L).flagSeen(); + args.add(valid(parts)); + + parts = parts.copy(); + parts.fullName = "1035478339.27041_118.foo.org,W=1030,S=1000:2,S"; + args.add(valid(parts)); + + // new mail, no info part at all. found in courier maildirs + parts = Parts.fullName("1355543030.15049_0.foo.org").baseName("1355543030.15049_0.foo.org") + .timeSeconds(1355543030).noFlags(); + args.add(valid(parts)); + + // new mail, generated by james + parts = Parts.fullName("1356001301.e563087e30181513.foohost,S=629:2,") + .baseName("1356001301.e563087e30181513.foohost").timeSeconds(1356001301).size(629L); + args.add(valid(parts)); + + parts = Parts.fullName("1355675588.5c7e107958851103.foohost,S=654:2,S").timeSeconds(1355675588) + .baseName("1355675588.5c7e107958851103.foohost").size(654L).flagSeen(); + args.add(valid(parts)); + + parts = Parts.fullName("1355675651.f3dd564265174501.foohost,S=661:2,") + .baseName("1355675651.f3dd564265174501.foohost").timeSeconds(1355675651).size(661L); + args.add(valid(parts)); + + return args; + } + + private static Object[] valid(Parts parts) { + return invalidAndValid(null, parts); + } + + private static Object[] invalid(String invalidName) { + return invalidAndValid(invalidName, null); + } + + private static Object[] invalidAndValid(String invalidName, Parts validName) { + return new Object[] { invalidName, validName }; + } + + @Test + public void testParsing() throws Exception { + if (validName != null) { + try { + parseValidName(validName, parts); + } catch (Throwable e) { + throw new Exception("Valid name '" + validName + "' failed.", e); + } + } + if (invalidName != null) { + try { + parseInvalidName(invalidName); + fail("No error reported for invalid name: " + invalidName); + } catch (Exception e) { + // test successful + } + } + } + + private void parseValidName(String name, Parts parts) throws Exception { + MaildirMessageName mn = new MaildirMessageName(null, name); + mn.setMessageNameStrictParse(false); + if (parts.time == null) { + assertNull("date", mn.getInternalDate()); + } else { + assertEquals("date", mn.getInternalDate().getTime(), parts.time.longValue()); + } + assertEquals("fullName", parts.fullName, mn.getFullName()); + assertEquals("flags", parts.flags, mn.getFlags()); + assertEquals("size", parts.size, mn.getSize()); + assertEquals("baseName", parts.baseName, mn.getBaseName()); + } + + private void parseInvalidName(String name) throws Exception { + } + + static class Parts { + public Long time; + public String fullName; + public String baseName; + public Long size; + public Flags flags = new Flags(); + + private Parts(String fullName) { + this.fullName = fullName; + } + + public static Parts fullName(String fullName) { + return new Parts(fullName); + } + + public Parts noFlags() { + this.flags = null; + return this; + } + + public Parts flagSeen() { + this.flags.add(Flags.Flag.SEEN); + return this; + } + + public Parts flagRecent() { + this.flags.add(Flags.Flag.RECENT); + return this; + } + + public Parts flagAnswered() { + this.flags.add(Flags.Flag.ANSWERED); + return this; + } + + public Parts flagFlagged() { + this.flags.add(Flags.Flag.FLAGGED); + return this; + } + + public Parts flagDeleted() { + this.flags.add(Flags.Flag.DELETED); + return this; + } + + public Parts flagDraft() { + this.flags.add(Flags.Flag.DRAFT); + return this; + } + + public Parts baseName(String baseName) { + this.baseName = baseName; + return this; + } + + public Parts size(Long size) { + this.size = size; + return this; + } + + public Parts timeSeconds(Long time) { + if (time != null) { + this.time = time * 1000; + } else { + this.time = null; + } + return this; + } + + public Parts timeMillis(Long time) { + this.time = time; + return this; + } + + public Parts timeSeconds(Integer time) { + return timeSeconds(time != null ? time.longValue() : null); + } + + public Parts copy() { + Parts p = Parts.fullName(fullName).baseName(baseName).size(size).timeMillis(time); + p.flags = (Flags) this.flags.clone(); + return p; + } + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxManagerTest.java.svn-base b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxManagerTest.java.svn-base new file mode 100644 index 0000000..b0fb8fd --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirMailboxManagerTest.java.svn-base @@ -0,0 +1,217 @@ +/**************************************************************** + * 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.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * MaildirMailboxManagerTest that extends the StoreMailboxManagerTest. + */ +public class MaildirMailboxManagerTest extends AbstractMailboxManagerTest { + + private static final String MAILDIR_HOME = "target/Maildir"; + + /** + * Setup the mailboxManager. + * + * @throws Exception + */ + @Before + public void setup() throws Exception { + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + deleteMaildirTestDirectory(); + } + } + + /** + * Delete Maildir directory after test. + * + * @throws IOException + */ + @After + public void tearDown() throws IOException { + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + deleteMaildirTestDirectory(); + } + } + + /** + * @see org.apache.james.mailbox.AbstractMailboxManagerTest#testList() + */ + @Test + @Override + public void testList() throws MailboxException, UnsupportedEncodingException { + + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + + doTestListWithMaildirStoreConfiguration("/%domain/%user"); + + // TODO Tests fail with /%user configuration + try { + doTestListWithMaildirStoreConfiguration("/%user"); + fail(); + } catch (MailboxExistsException e) { + // This is expected as the there are many users which have the same localpart + } + // TODO Tests fail with /%fulluser configuration + doTestListWithMaildirStoreConfiguration("/%fulluser"); + + } + + } + + /** + * @see org.apache.james.mailbox.AbstractMailboxManagerTest#testBasicOperations() + */ + @Test + @Override + public void testBasicOperations() throws BadCredentialsException, MailboxException, UnsupportedEncodingException { + + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + + MaildirStore store = new MaildirStore(MAILDIR_HOME + "/%domain/%user", new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager manager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + try { + super.testBasicOperations(); + } finally { + try { + deleteMaildirTestDirectory(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + } + + /** + * @see org.apache.james.mailbox.AbstractMailboxManagerTest#testCreateSubFolderDirectly() + */ + @Test + @Override + public void testCreateSubFolderDirectly() throws BadCredentialsException, MailboxException { + + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + + MaildirStore store = new MaildirStore(MAILDIR_HOME + "/%domain/%user", new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager manager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + try { + super.testCreateSubFolderDirectly(); + } finally { + try { + deleteMaildirTestDirectory(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + } + + } + + /** + * Create the maildirStore with the provided configuration and executes the list() tests. + * Cleans the generated artifacts. + * + * @param maildirStoreConfiguration + * @throws MailboxException + * @throws UnsupportedEncodingException + */ + private void doTestListWithMaildirStoreConfiguration(String maildirStoreConfiguration) throws MailboxException, UnsupportedEncodingException { + MaildirStore store = new MaildirStore(MAILDIR_HOME + maildirStoreConfiguration, new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager manager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + try { + super.testList(); + } finally { + try { + deleteMaildirTestDirectory(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() { + // Do nothing, the maildir mailboxManager is created in the test method. + } + + /** + * Utility method to delete the test Maildir Directory. + * + * @throws IOException + */ + private void deleteMaildirTestDirectory() throws IOException { + FileUtils.deleteDirectory(new File(MAILDIR_HOME)); + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStressTest.java.svn-base b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStressTest.java.svn-base new file mode 100644 index 0000000..232976f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirStressTest.java.svn-base @@ -0,0 +1,75 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.AbstractStressTest; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.After; +import org.junit.Before; + +public class MaildirStressTest extends AbstractStressTest { + + private static final String MAILDIR_HOME = "target/Maildir"; + + private StoreMailboxManager mailboxManager; + + @Before + public void setUp() throws MailboxException { + MaildirStore store = new MaildirStore(MAILDIR_HOME + "/%user", new JVMMailboxPathLocker()); + + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + mailboxManager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + mailboxManager.init(); + + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(new File(MAILDIR_HOME)); + } + + @Override + public void testStessTest() throws InterruptedException, MailboxException { + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + super.testStessTest(); + } + } + + @Override + protected MailboxManager getMailboxManager() { + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirSubscriptionManagerTest.java.svn-base b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirSubscriptionManagerTest.java.svn-base new file mode 100644 index 0000000..c451305 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/MaildirSubscriptionManagerTest.java.svn-base @@ -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.maildir; + +import org.apache.james.mailbox.AbstractSubscriptionManagerTest; +import org.apache.james.mailbox.SubscriptionManager; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreSubscriptionManager; + +public class MaildirSubscriptionManagerTest extends AbstractSubscriptionManagerTest{ + + @Override + public SubscriptionManager createSubscriptionManager() { + MaildirStore store = new MaildirStore("target/Maildir/%domain/%user", new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory factory = new MaildirMailboxSessionMapperFactory(store); + StoreSubscriptionManager sm = new StoreSubscriptionManager(factory); + return sm; + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/OsDetector.java.svn-base b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/OsDetector.java.svn-base new file mode 100644 index 0000000..c301a34 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/.svn/text-base/OsDetector.java.svn-base @@ -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; + +public class OsDetector { + + /** + * Return if the Test is run on windows + * + * @return windows + */ + public static boolean isWindows() { + String os = System.getProperty("os.name").toLowerCase(); + return (os.indexOf( "win" ) >= 0); + } + + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MailderMessageNameTest.java b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MailderMessageNameTest.java new file mode 100644 index 0000000..10e5116 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MailderMessageNameTest.java @@ -0,0 +1,235 @@ +/**************************************************************** + * 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 junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import javax.mail.Flags; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(value = Parameterized.class) +public class MailderMessageNameTest { + + private String invalidName; + private String validName; + private Parts parts; + + public MailderMessageNameTest(String invalidName, Parts parts) { + this.invalidName = invalidName; + this.validName = parts.fullName; + this.parts = parts; + } + + @Parameterized.Parameters + public static List testData() { + List args = new ArrayList(); + // no size, two flags + Parts parts = Parts.fullName("1328026049.19146_0.km1111:2,RS").timeSeconds(1328026049) + .baseName("1328026049.19146_0.km1111").flagAnswered().flagSeen(); + args.add(valid(parts)); + + // size and flag + parts = Parts.fullName("1328613172.M569643P1862V0000000000000902I00EE42CE_0.km1111,S=13103:2,S") + .timeSeconds(1328613172).size(13103L) + .baseName("1328613172.M569643P1862V0000000000000902I00EE42CE_0.km1111").flagSeen(); + args.add(valid(parts)); + + // size, no flags + parts = Parts.fullName("1340124194.M723289P3184V0000000000000902I006780E9_6.km1111,S=1344:2,") + .baseName("1340124194.M723289P3184V0000000000000902I006780E9_6.km1111").timeSeconds(1340124194) + .size(1344L); + args.add(valid(parts)); + + // three flags, no size + parts = Parts.fullName("1106685752.12132_0.km1111:2,FRS").baseName("1106685752.12132_0.km1111") + .timeSeconds(1106685752).flagFlagged().flagAnswered().flagSeen(); + args.add(valid(parts)); + + // with dovecot attributes + parts = Parts.fullName("1035478339.27041_118.foo.org,S=1000,W=1030:2,S") + .baseName("1035478339.27041_118.foo.org").timeSeconds(1035478339).size(1000L).flagSeen(); + args.add(valid(parts)); + + parts = parts.copy(); + parts.fullName = "1035478339.27041_118.foo.org,W=1030,S=1000:2,S"; + args.add(valid(parts)); + + // new mail, no info part at all. found in courier maildirs + parts = Parts.fullName("1355543030.15049_0.foo.org").baseName("1355543030.15049_0.foo.org") + .timeSeconds(1355543030).noFlags(); + args.add(valid(parts)); + + // new mail, generated by james + parts = Parts.fullName("1356001301.e563087e30181513.foohost,S=629:2,") + .baseName("1356001301.e563087e30181513.foohost").timeSeconds(1356001301).size(629L); + args.add(valid(parts)); + + parts = Parts.fullName("1355675588.5c7e107958851103.foohost,S=654:2,S").timeSeconds(1355675588) + .baseName("1355675588.5c7e107958851103.foohost").size(654L).flagSeen(); + args.add(valid(parts)); + + parts = Parts.fullName("1355675651.f3dd564265174501.foohost,S=661:2,") + .baseName("1355675651.f3dd564265174501.foohost").timeSeconds(1355675651).size(661L); + args.add(valid(parts)); + + return args; + } + + private static Object[] valid(Parts parts) { + return invalidAndValid(null, parts); + } + + private static Object[] invalid(String invalidName) { + return invalidAndValid(invalidName, null); + } + + private static Object[] invalidAndValid(String invalidName, Parts validName) { + return new Object[] { invalidName, validName }; + } + + @Test + public void testParsing() throws Exception { + if (validName != null) { + try { + parseValidName(validName, parts); + } catch (Throwable e) { + throw new Exception("Valid name '" + validName + "' failed.", e); + } + } + if (invalidName != null) { + try { + parseInvalidName(invalidName); + fail("No error reported for invalid name: " + invalidName); + } catch (Exception e) { + // test successful + } + } + } + + private void parseValidName(String name, Parts parts) throws Exception { + MaildirMessageName mn = new MaildirMessageName(null, name); + mn.setMessageNameStrictParse(false); + if (parts.time == null) { + assertNull("date", mn.getInternalDate()); + } else { + assertEquals("date", mn.getInternalDate().getTime(), parts.time.longValue()); + } + assertEquals("fullName", parts.fullName, mn.getFullName()); + assertEquals("flags", parts.flags, mn.getFlags()); + assertEquals("size", parts.size, mn.getSize()); + assertEquals("baseName", parts.baseName, mn.getBaseName()); + } + + private void parseInvalidName(String name) throws Exception { + } + + static class Parts { + public Long time; + public String fullName; + public String baseName; + public Long size; + public Flags flags = new Flags(); + + private Parts(String fullName) { + this.fullName = fullName; + } + + public static Parts fullName(String fullName) { + return new Parts(fullName); + } + + public Parts noFlags() { + this.flags = null; + return this; + } + + public Parts flagSeen() { + this.flags.add(Flags.Flag.SEEN); + return this; + } + + public Parts flagRecent() { + this.flags.add(Flags.Flag.RECENT); + return this; + } + + public Parts flagAnswered() { + this.flags.add(Flags.Flag.ANSWERED); + return this; + } + + public Parts flagFlagged() { + this.flags.add(Flags.Flag.FLAGGED); + return this; + } + + public Parts flagDeleted() { + this.flags.add(Flags.Flag.DELETED); + return this; + } + + public Parts flagDraft() { + this.flags.add(Flags.Flag.DRAFT); + return this; + } + + public Parts baseName(String baseName) { + this.baseName = baseName; + return this; + } + + public Parts size(Long size) { + this.size = size; + return this; + } + + public Parts timeSeconds(Long time) { + if (time != null) { + this.time = time * 1000; + } else { + this.time = null; + } + return this; + } + + public Parts timeMillis(Long time) { + this.time = time; + return this; + } + + public Parts timeSeconds(Integer time) { + return timeSeconds(time != null ? time.longValue() : null); + } + + public Parts copy() { + Parts p = Parts.fullName(fullName).baseName(baseName).size(size).timeMillis(time); + p.flags = (Flags) this.flags.clone(); + return p; + } + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxManagerTest.java b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxManagerTest.java new file mode 100644 index 0000000..b0fb8fd --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirMailboxManagerTest.java @@ -0,0 +1,217 @@ +/**************************************************************** + * 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.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.AbstractMailboxManagerTest; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * MaildirMailboxManagerTest that extends the StoreMailboxManagerTest. + */ +public class MaildirMailboxManagerTest extends AbstractMailboxManagerTest { + + private static final String MAILDIR_HOME = "target/Maildir"; + + /** + * Setup the mailboxManager. + * + * @throws Exception + */ + @Before + public void setup() throws Exception { + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + deleteMaildirTestDirectory(); + } + } + + /** + * Delete Maildir directory after test. + * + * @throws IOException + */ + @After + public void tearDown() throws IOException { + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + deleteMaildirTestDirectory(); + } + } + + /** + * @see org.apache.james.mailbox.AbstractMailboxManagerTest#testList() + */ + @Test + @Override + public void testList() throws MailboxException, UnsupportedEncodingException { + + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + + doTestListWithMaildirStoreConfiguration("/%domain/%user"); + + // TODO Tests fail with /%user configuration + try { + doTestListWithMaildirStoreConfiguration("/%user"); + fail(); + } catch (MailboxExistsException e) { + // This is expected as the there are many users which have the same localpart + } + // TODO Tests fail with /%fulluser configuration + doTestListWithMaildirStoreConfiguration("/%fulluser"); + + } + + } + + /** + * @see org.apache.james.mailbox.AbstractMailboxManagerTest#testBasicOperations() + */ + @Test + @Override + public void testBasicOperations() throws BadCredentialsException, MailboxException, UnsupportedEncodingException { + + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + + MaildirStore store = new MaildirStore(MAILDIR_HOME + "/%domain/%user", new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager manager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + try { + super.testBasicOperations(); + } finally { + try { + deleteMaildirTestDirectory(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + } + + /** + * @see org.apache.james.mailbox.AbstractMailboxManagerTest#testCreateSubFolderDirectly() + */ + @Test + @Override + public void testCreateSubFolderDirectly() throws BadCredentialsException, MailboxException { + + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + + MaildirStore store = new MaildirStore(MAILDIR_HOME + "/%domain/%user", new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager manager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + try { + super.testCreateSubFolderDirectly(); + } finally { + try { + deleteMaildirTestDirectory(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + } + + } + + /** + * Create the maildirStore with the provided configuration and executes the list() tests. + * Cleans the generated artifacts. + * + * @param maildirStoreConfiguration + * @throws MailboxException + * @throws UnsupportedEncodingException + */ + private void doTestListWithMaildirStoreConfiguration(String maildirStoreConfiguration) throws MailboxException, UnsupportedEncodingException { + MaildirStore store = new MaildirStore(MAILDIR_HOME + maildirStoreConfiguration, new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager manager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + manager.init(); + setMailboxManager(manager); + try { + super.testList(); + } finally { + try { + deleteMaildirTestDirectory(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() { + // Do nothing, the maildir mailboxManager is created in the test method. + } + + /** + * Utility method to delete the test Maildir Directory. + * + * @throws IOException + */ + private void deleteMaildirTestDirectory() throws IOException { + FileUtils.deleteDirectory(new File(MAILDIR_HOME)); + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirStressTest.java b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirStressTest.java new file mode 100644 index 0000000..232976f --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirStressTest.java @@ -0,0 +1,75 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.apache.james.mailbox.AbstractStressTest; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.After; +import org.junit.Before; + +public class MaildirStressTest extends AbstractStressTest { + + private static final String MAILDIR_HOME = "target/Maildir"; + + private StoreMailboxManager mailboxManager; + + @Before + public void setUp() throws MailboxException { + MaildirStore store = new MaildirStore(MAILDIR_HOME + "/%user", new JVMMailboxPathLocker()); + + MaildirMailboxSessionMapperFactory mf = new MaildirMailboxSessionMapperFactory(store); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + mailboxManager = new StoreMailboxManager(mf, null, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + mailboxManager.init(); + + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(new File(MAILDIR_HOME)); + } + + @Override + public void testStessTest() throws InterruptedException, MailboxException { + if (OsDetector.isWindows()) { + System.out.println("Maildir tests work only on non-windows systems. So skip the test"); + } else { + super.testStessTest(); + } + } + + @Override + protected MailboxManager getMailboxManager() { + return mailboxManager; + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirSubscriptionManagerTest.java b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirSubscriptionManagerTest.java new file mode 100644 index 0000000..c451305 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/MaildirSubscriptionManagerTest.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.maildir; + +import org.apache.james.mailbox.AbstractSubscriptionManagerTest; +import org.apache.james.mailbox.SubscriptionManager; +import org.apache.james.mailbox.store.JVMMailboxPathLocker; +import org.apache.james.mailbox.store.StoreSubscriptionManager; + +public class MaildirSubscriptionManagerTest extends AbstractSubscriptionManagerTest{ + + @Override + public SubscriptionManager createSubscriptionManager() { + MaildirStore store = new MaildirStore("target/Maildir/%domain/%user", new JVMMailboxPathLocker()); + MaildirMailboxSessionMapperFactory factory = new MaildirMailboxSessionMapperFactory(store); + StoreSubscriptionManager sm = new StoreSubscriptionManager(factory); + return sm; + } + +} diff --git a/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/OsDetector.java b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/OsDetector.java new file mode 100644 index 0000000..c301a34 --- /dev/null +++ b/james/apache-james-mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/OsDetector.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; + +public class OsDetector { + + /** + * Return if the Test is run on windows + * + * @return windows + */ + public static boolean isWindows() { + String os = System.getProperty("os.name").toLowerCase(); + return (os.indexOf( "win" ) >= 0); + } + + +} diff --git a/james/apache-james-mailbox/memory/.svn/all-wcprops b/james/apache-james-mailbox/memory/.svn/all-wcprops new file mode 100644 index 0000000..bb7e89a --- /dev/null +++ b/james/apache-james-mailbox/memory/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 54 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/memory +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/memory/pom.xml +END diff --git a/james/apache-james-mailbox/memory/.svn/dir-prop-base b/james/apache-james-mailbox/memory/.svn/dir-prop-base new file mode 100644 index 0000000..0412bba --- /dev/null +++ b/james/apache-james-mailbox/memory/.svn/dir-prop-base @@ -0,0 +1,8 @@ +K 10 +svn:ignore +V 22 +target +.* +coverage.ec + +END diff --git a/james/apache-james-mailbox/memory/.svn/entries b/james/apache-james-mailbox/memory/.svn/entries new file mode 100644 index 0000000..1db2fbe --- /dev/null +++ b/james/apache-james-mailbox/memory/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory +http://svn.apache.org/repos/asf + + + +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:40.000000Z +6b23789057914c3b2ddf697241b481dd +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +2863 + diff --git a/james/apache-james-mailbox/memory/.svn/prop-base/pom.xml.svn-base b/james/apache-james-mailbox/memory/.svn/prop-base/pom.xml.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/memory/.svn/prop-base/pom.xml.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/memory/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/memory/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..25b6150 --- /dev/null +++ b/james/apache-james-mailbox/memory/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,74 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-memory + Apache James :: Mailbox :: In Memory + bundle + + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.slf4j + slf4j-api + test + + + org.slf4j + slf4j-simple + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + junit + junit + + + diff --git a/james/apache-james-mailbox/memory/build b/james/apache-james-mailbox/memory/build new file mode 100755 index 0000000..b259276 --- /dev/null +++ b/james/apache-james-mailbox/memory/build @@ -0,0 +1 @@ +mvn clean package -DskipTests=true $1 $2 $3 diff --git a/james/apache-james-mailbox/memory/pom.xml b/james/apache-james-mailbox/memory/pom.xml new file mode 100644 index 0000000..f4bc3f5 --- /dev/null +++ b/james/apache-james-mailbox/memory/pom.xml @@ -0,0 +1,109 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-memory + Apache James :: Mailbox :: In Memory + bundle + + + + + src/main/java + + core/server/srp/db/sql/* + core/server/mailextra/sql/* + + + + + + + + com.javapns + com-javapns-api + 2.2 + + + org.log4j + org-log4j-sdk + 1.2.15 + + + com.amazonaws + aws-java-sdk + 1.3.20 + + + com.dropbox + dropbox-java-sdk + 1.3 + + + org.apache.james + apache-mailet + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.james + apache-james-mailbox-store + test-jar + test + + + junit + junit + + + diff --git a/james/apache-james-mailbox/memory/src/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/.svn/all-wcprops new file mode 100644 index 0000000..0414688 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/memory/src +END diff --git a/james/apache-james-mailbox/memory/src/.svn/entries b/james/apache-james-mailbox/memory/src/.svn/entries new file mode 100644 index 0000000..f8396b2 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src +http://svn.apache.org/repos/asf + + + +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/memory/src/main/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/.svn/all-wcprops new file mode 100644 index 0000000..d9c7220 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/memory/src/main +END diff --git a/james/apache-james-mailbox/memory/src/main/.svn/entries b/james/apache-james-mailbox/memory/src/main/.svn/entries new file mode 100644 index 0000000..fb2200d --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main +http://svn.apache.org/repos/asf + + + +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/memory/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..da6a97a --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 68 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java +END diff --git a/james/apache-james-mailbox/memory/src/main/java/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/.svn/entries new file mode 100644 index 0000000..be5ff8c --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/memory/src/main/java/core/app b/james/apache-james-mailbox/memory/src/main/java/core/app new file mode 120000 index 0000000..dc6a340 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/app @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/app \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/callback b/james/apache-james-mailbox/memory/src/main/java/core/callback new file mode 120000 index 0000000..67f5f72 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/callback @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/callback \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/callbacks/Memory.java b/james/apache-james-mailbox/memory/src/main/java/core/callbacks/Memory.java new file mode 120000 index 0000000..8ba701e --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/callbacks/Memory.java @@ -0,0 +1 @@ +../../../../../../../../java/core/src/core/callbacks/Memory.java \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/ConnectorException.java b/james/apache-james-mailbox/memory/src/main/java/core/connector/ConnectorException.java new file mode 120000 index 0000000..9d654e8 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/ConnectorException.java @@ -0,0 +1 @@ +../../../../../../../../java/core/src/core/connector/ConnectorException.java \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/FileInfo.java b/james/apache-james-mailbox/memory/src/main/java/core/connector/FileInfo.java new file mode 120000 index 0000000..8f4edf3 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/FileInfo.java @@ -0,0 +1 @@ +../../../../../../../../java/core/src/core/connector/FileInfo.java \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/ClientInfoDropbox.java b/james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/ClientInfoDropbox.java new file mode 120000 index 0000000..4473728 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/ClientInfoDropbox.java @@ -0,0 +1 @@ +../../../../../../../../../java/core/src/core/connector/dropbox/ClientInfoDropbox.java \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/sync b/james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/sync new file mode 120000 index 0000000..488e0df --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/dropbox/sync @@ -0,0 +1 @@ +../../../../../../../../../java/core/src/core/connector/dropbox/sync \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/s3/ClientInfoS3.java b/james/apache-james-mailbox/memory/src/main/java/core/connector/s3/ClientInfoS3.java new file mode 120000 index 0000000..904692e --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/s3/ClientInfoS3.java @@ -0,0 +1 @@ +../../../../../../../../../java/core/src/core/connector/s3/ClientInfoS3.java \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/s3/sync b/james/apache-james-mailbox/memory/src/main/java/core/connector/s3/sync new file mode 120000 index 0000000..1a5deeb --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/s3/sync @@ -0,0 +1 @@ +../../../../../../../../../java/core/src/core/connector/s3/sync \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/sync/EncryptedStoreConnector.java b/james/apache-james-mailbox/memory/src/main/java/core/connector/sync/EncryptedStoreConnector.java new file mode 120000 index 0000000..88cc797 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/sync/EncryptedStoreConnector.java @@ -0,0 +1 @@ +../../../../../../../../../java/core/src/core/connector/sync/EncryptedStoreConnector.java \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/connector/sync/StoreConnector.java b/james/apache-james-mailbox/memory/src/main/java/core/connector/sync/StoreConnector.java new file mode 120000 index 0000000..411147f --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/connector/sync/StoreConnector.java @@ -0,0 +1 @@ +../../../../../../../../../java/core/src/core/connector/sync/StoreConnector.java \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/constants b/james/apache-james-mailbox/memory/src/main/java/core/constants new file mode 120000 index 0000000..362b362 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/constants @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/constants \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/crypt b/james/apache-james-mailbox/memory/src/main/java/core/crypt new file mode 120000 index 0000000..7dcd134 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/crypt @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/crypt \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/exceptions b/james/apache-james-mailbox/memory/src/main/java/core/exceptions new file mode 120000 index 0000000..f278a43 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/exceptions @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/exceptions \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/io b/james/apache-james-mailbox/memory/src/main/java/core/io new file mode 120000 index 0000000..34a15ed --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/io @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/io \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/server b/james/apache-james-mailbox/memory/src/main/java/core/server new file mode 120000 index 0000000..b44d921 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/server @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/server \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/core/util b/james/apache-james-mailbox/memory/src/main/java/core/util new file mode 120000 index 0000000..b584457 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/core/util @@ -0,0 +1 @@ +../../../../../../../java/core/src/core/util \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/mail/core b/james/apache-james-mailbox/memory/src/main/java/mail/core new file mode 120000 index 0000000..62e4b7e --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/mail/core @@ -0,0 +1 @@ +../../../../../../../java/core/src/mail/core \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/mail/server/db b/james/apache-james-mailbox/memory/src/main/java/mail/server/db new file mode 120000 index 0000000..51f6a55 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/mail/server/db @@ -0,0 +1 @@ +../../../../../../../../java/core/src/mail/server/db \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/mail/server/handler b/james/apache-james-mailbox/memory/src/main/java/mail/server/handler new file mode 120000 index 0000000..c1df2ad --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/mail/server/handler @@ -0,0 +1 @@ +../../../../../../../../java/core/src/mail/server/handler \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/mail/server/push b/james/apache-james-mailbox/memory/src/main/java/mail/server/push new file mode 120000 index 0000000..5cdf448 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/mail/server/push @@ -0,0 +1 @@ +../../../../../../../../java/core/src/mail/server/push \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/mail/server/util b/james/apache-james-mailbox/memory/src/main/java/mail/server/util new file mode 120000 index 0000000..02ea335 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/mail/server/util @@ -0,0 +1 @@ +../../../../../../../../java/core/src/mail/server/util \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..f09577c --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java/org +END diff --git a/james/apache-james-mailbox/memory/src/main/java/org/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/org/.svn/entries new file mode 100644 index 0000000..f820664 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java/org +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..677b40f --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..81e02bf --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..70df5cd --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 85 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..645dd4d --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..f169735 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..dc38854 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +inmemory +dir + diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops new file mode 100644 index 0000000..9d1bd26 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory +END +InMemoryMailboxSessionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 143 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java +END diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/entries new file mode 100644 index 0000000..8562341 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/entries @@ -0,0 +1,68 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mail +dir + +InMemoryMailboxSessionMapperFactory.java +file + + + + +2013-09-02T02:54:40.000000Z +c3ea86c4fb91c99508e14db1e4162771 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3264 + +user +dir + diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxSessionMapperFactory.java.svn-base b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxSessionMapperFactory.java.svn-base new file mode 100644 index 0000000..1ecf377 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxSessionMapperFactory.java.svn-base @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.inmemory.mail.InMemoryMailboxMapper; +import org.apache.james.mailbox.inmemory.mail.InMemoryMessageMapper; +import org.apache.james.mailbox.inmemory.mail.InMemoryModSeqProvider; +import org.apache.james.mailbox.inmemory.mail.InMemoryUidProvider; +import org.apache.james.mailbox.inmemory.user.InMemorySubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; + +public class InMemoryMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private MailboxMapper mailboxMapper; + private MessageMapper messageMapper; + private SubscriptionMapper subscriptionMapper; + + public InMemoryMailboxSessionMapperFactory() { + mailboxMapper = new InMemoryMailboxMapper(); + messageMapper = new InMemoryMessageMapper(null, new InMemoryUidProvider(), new InMemoryModSeqProvider()); + subscriptionMapper = new InMemorySubscriptionMapper(); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException { + return mailboxMapper; + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) throws MailboxException { + return messageMapper; + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException { + return subscriptionMapper; + } + + public void deleteAll() throws MailboxException { + ((InMemoryMailboxMapper) mailboxMapper).deleteAll(); + ((InMemoryMessageMapper) messageMapper).deleteAll(); + ((InMemorySubscriptionMapper) subscriptionMapper).deleteAll(); + } + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java new file mode 100644 index 0000000..35c3eb9 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java @@ -0,0 +1,88 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.inmemory.mail.InMemoryMailboxMapper; +import org.apache.james.mailbox.inmemory.mail.InMemoryMessageMapper; +import org.apache.james.mailbox.inmemory.mail.InMemoryModSeqProvider; +import org.apache.james.mailbox.inmemory.mail.InMemoryUidProvider; +import org.apache.james.mailbox.inmemory.user.InMemorySubscriptionMapper; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.mailet.MailetContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import core.util.ProxySelectorRegex; + +import java.lang.reflect.Method; +import java.net.Proxy; +import java.net.InetSocketAddress; + +public class InMemoryMailboxSessionMapperFactory extends MailboxSessionMapperFactory { + + private MailetContext mailetContext; + private MailboxMapper mailboxMapper; + private MessageMapper messageMapper; + private SubscriptionMapper subscriptionMapper; + + static Logger log = LoggerFactory.getLogger(InMemoryMailboxSessionMapperFactory.class); + + public InMemoryMailboxSessionMapperFactory() throws MailboxException + { + log.info("Installing proxy"); + ProxySelectorRegex proxySelector = new ProxySelectorRegex(); + proxySelector.excludeHosts(ProxySelectorRegex.DEFAULT_EXCLUDED_HOSTS); + proxySelector.addProxy ("socket", new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 23232))); + proxySelector.install(); + log.info("Finished Installing proxy"); + + mailboxMapper = new InMemoryMailboxMapper(mailetContext); + messageMapper = new InMemoryMessageMapper(mailetContext, null, new InMemoryUidProvider(), new InMemoryModSeqProvider()); + subscriptionMapper = new InMemorySubscriptionMapper(); + } + + @Override + public MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException { + return mailboxMapper; + } + + @Override + public MessageMapper createMessageMapper(MailboxSession session) throws MailboxException { + return messageMapper; + } + + @Override + public SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException { + return subscriptionMapper; + } + + public void deleteAll() throws MailboxException { + ((InMemoryMailboxMapper) mailboxMapper).deleteAll(); + ((InMemoryMessageMapper) messageMapper).deleteAll(); + ((InMemorySubscriptionMapper) subscriptionMapper).deleteAll(); + } + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/all-wcprops new file mode 100644 index 0000000..f0e81a4 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 107 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/mail +END +InMemoryMailboxMapper.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMailboxMapper.java +END +InMemoryModSeqProvider.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryModSeqProvider.java +END +InMemoryMessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java +END +InMemoryUidProvider.java +K 25 +svn:wc:ra_dav:version-url +V 132 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryUidProvider.java +END diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/entries new file mode 100644 index 0000000..6bb6e6b --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/entries @@ -0,0 +1,164 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/mail +http://svn.apache.org/repos/asf + + + +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +InMemoryMailboxMapper.java +file + + + + +2013-09-02T02:54:40.000000Z +a12992498c9bf87ea5af9a152bc9f19c +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +5248 + +InMemoryModSeqProvider.java +file + + + + +2013-09-02T02:54:40.000000Z +e692057e1454575b3d78fae9eecca2c3 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2449 + +InMemoryMessageMapper.java +file + + + + +2013-09-02T02:54:40.000000Z +4236fdebc5c4a1eb6f062a8637925172 +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + + + + + + + + +10356 + +InMemoryUidProvider.java +file + + + + +2013-09-02T02:54:40.000000Z +f9b445c8a1a6e2f7014255d10866b073 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2432 + diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMailboxMapper.java.svn-base b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMailboxMapper.java.svn-base new file mode 100644 index 0000000..d477ec3 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMailboxMapper.java.svn-base @@ -0,0 +1,132 @@ +/**************************************************************** + * 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.mail; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; + +public class InMemoryMailboxMapper implements MailboxMapper { + + private static final int INITIAL_SIZE = 128; + private final Map> mailboxesById; + private final static AtomicLong IDS = new AtomicLong(); + + public InMemoryMailboxMapper() { + mailboxesById = new ConcurrentHashMap>(INITIAL_SIZE); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public void delete(Mailbox mailbox) throws MailboxException { + mailboxesById.remove(mailbox.getMailboxId()); + } + + public void deleteAll() throws MailboxException { + mailboxesById.clear(); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(org.apache.james.mailbox.model.MailboxPath) + */ + public synchronized Mailbox findMailboxByPath(MailboxPath path) throws MailboxException, MailboxNotFoundException { + Mailbox result = null; + for (final Mailbox mailbox:mailboxesById.values()) { + MailboxPath mp = new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()); + if (mp.equals(path)) { + result = mailbox; + break; + } + } + if (result == null) { + throw new MailboxNotFoundException(path); + } else { + return result; + } + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(org.apache.james.mailbox.model.MailboxPath) + */ + public List> findMailboxWithPathLike(MailboxPath path) throws MailboxException { + final String regex = path.getName().replace("%", ".*"); + List> results = new ArrayList>(); + for (final Mailbox mailbox:mailboxesById.values()) { + if (mailbox.getName().matches(regex)) { + results.add(mailbox); + } + } + return results; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public void save(Mailbox mailbox) throws MailboxException { + Long id = mailbox.getMailboxId(); + if (id == null) { + id = IDS.incrementAndGet(); + ((SimpleMailbox) mailbox).setMailboxId(id); + } + mailboxesById.put(id, mailbox); + } + + /** + * Do nothing + */ + public void endRequest() { + // Do nothing + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(org.apache.james.mailbox.store.mail.model.Mailbox, char) + */ + public boolean hasChildren(Mailbox mailbox, char delimiter) throws MailboxException, + MailboxNotFoundException { + String mailboxName = mailbox.getName() + delimiter; + for (final Mailbox box:mailboxesById.values()) { + if (box.getName().startsWith(mailboxName)) { + return true; + } + } + return false; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + public List> list() throws MailboxException { + return new ArrayList>(mailboxesById.values()); + } + + public T execute(Transaction transaction) throws MailboxException { + return transaction.run(); + } + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMessageMapper.java.svn-base b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMessageMapper.java.svn-base new file mode 100644 index 0000000..7ee3220 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryMessageMapper.java.svn-base @@ -0,0 +1,260 @@ +/**************************************************************** + * 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.mail; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; + +public class InMemoryMessageMapper extends AbstractMessageMapper { + + private Map>> mailboxByUid; + private static final int INITIAL_SIZE = 256; + + public InMemoryMessageMapper(MailboxSession session, UidProvider uidProvider, + ModSeqProvider modSeqProvider) { + super(session, uidProvider, modSeqProvider); + this.mailboxByUid = new ConcurrentHashMap>>(INITIAL_SIZE); + } + + private Map> getMembershipByUidForMailbox(Mailbox mailbox) { + Map> membershipByUid = mailboxByUid.get(mailbox.getMailboxId()); + if (membershipByUid == null) { + membershipByUid = new ConcurrentHashMap>(INITIAL_SIZE); + mailboxByUid.put(mailbox.getMailboxId(), membershipByUid); + } + return membershipByUid; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + return getMembershipByUidForMailbox(mailbox).size(); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + long count = 0; + for (Message member : getMembershipByUidForMailbox(mailbox).values()) { + if (!member.isSeen()) { + count++; + } + } + return count; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + public void delete(Mailbox mailbox, Message message) throws MailboxException { + getMembershipByUidForMailbox(mailbox).remove(message.getUid()); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType ftype, int max) + throws MailboxException { + List> results; + final MessageRange.Type type = set.getType(); + switch (type) { + case ALL: + results = new ArrayList>(getMembershipByUidForMailbox(mailbox).values()); + break; + case FROM: + results = new ArrayList>(getMembershipByUidForMailbox(mailbox).values()); + for (final Iterator> it = results.iterator(); it.hasNext();) { + if (it.next().getUid() < set.getUidFrom()) { + it.remove(); + } + } + break; + case RANGE: + results = new ArrayList>(getMembershipByUidForMailbox(mailbox).values()); + for (final Iterator> it = results.iterator(); it.hasNext();) { + final long uid = it.next().getUid(); + if (uid < set.getUidFrom() || uid > set.getUidTo()) { + it.remove(); + } + } + break; + case ONE: + results = new ArrayList>(1); + final Message member = getMembershipByUidForMailbox(mailbox).get(set.getUidFrom()); + if (member != null) { + results.add(member); + } + break; + default: + results = new ArrayList>(); + break; + } + Collections.sort(results); + + if (max > 0 && results.size() > max) { + results = results.subList(0, max - 1); + } + return results.iterator(); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + final List results = new ArrayList(); + for (Message member : getMembershipByUidForMailbox(mailbox).values()) { + if (member.isRecent()) { + results.add(member.getUid()); + } + } + Collections.sort(results); + + return results; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + List> memberships = new ArrayList>(getMembershipByUidForMailbox(mailbox).values()); + Collections.sort(memberships); + for (int i = 0; i < memberships.size(); i++) { + Message m = memberships.get(i); + if (m.isSeen() == false) { + return m.getUid(); + } + } + return null; + } + + @Override + public Map expungeMarkedForDeletionInMailbox(final Mailbox mailbox, MessageRange set) + throws MailboxException { + final Map filteredResult = new HashMap(); + + Iterator> it = findInMailbox(mailbox, set, FetchType.Metadata, -1); + while (it.hasNext()) { + Message member = it.next(); + if (member.isDeleted()) { + filteredResult.put(member.getUid(), new SimpleMessageMetaData(member)); + + delete(mailbox, member); + } + } + return filteredResult; + } + + public void deleteAll() { + mailboxByUid.clear(); + } + + /** + * (non-Javadoc) + * + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException("Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); + } + + /** + * Do nothing + */ + public void endRequest() { + // Do nothing + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, + * long, long, org.apache.james.mailbox.store.mail.model.Message) + */ + protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) + throws MailboxException { + SimpleMessage message = new SimpleMessage(mailbox, original); + message.setUid(uid); + message.setModSeq(modSeq); + Flags flags = original.createFlags(); + + // Mark message as recent as it is a copy + flags.add(Flag.RECENT); + message.setFlags(flags); + return save(mailbox, message); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + protected MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException { + SimpleMessage copy = new SimpleMessage(mailbox, message); + copy.setUid(message.getUid()); + copy.setModSeq(message.getModSeq()); + getMembershipByUidForMailbox(mailbox).put(message.getUid(), copy); + + return new SimpleMessageMetaData(message); + } + + /** + * Do nothing + */ + protected void begin() throws MailboxException { + + } + + /** + * Do nothing + */ + protected void commit() throws MailboxException { + + } + + /** + * Do nothing + */ + protected void rollback() throws MailboxException { + } +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryModSeqProvider.java.svn-base b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryModSeqProvider.java.svn-base new file mode 100644 index 0000000..96441af --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryModSeqProvider.java.svn-base @@ -0,0 +1,55 @@ +/**************************************************************** + * 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.mail; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class InMemoryModSeqProvider implements ModSeqProvider{ + private final ConcurrentMap map = new ConcurrentHashMap(); + + @Override + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + return getHighest(mailbox.getMailboxId()).incrementAndGet(); + + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + return getHighest(mailbox.getMailboxId()).get(); + } + private AtomicLong getHighest(Long id) { + AtomicLong uid = map.get(id); + if (uid == null) { + uid = new AtomicLong(0); + AtomicLong u = map.putIfAbsent(id, uid); + if (u != null) { + uid = u; + } + } + return uid; + } +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryUidProvider.java.svn-base b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryUidProvider.java.svn-base new file mode 100644 index 0000000..74863ca --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/.svn/text-base/InMemoryUidProvider.java.svn-base @@ -0,0 +1,57 @@ +/**************************************************************** + * 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.mail; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class InMemoryUidProvider implements UidProvider{ + + private final ConcurrentMap map = new ConcurrentHashMap(); + + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + return getLast(mailbox.getMailboxId()).incrementAndGet(); + } + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + return getLast(mailbox.getMailboxId()).get(); + } + + private AtomicLong getLast(Long id) { + AtomicLong uid = map.get(id); + if (uid == null) { + uid = new AtomicLong(0); + AtomicLong u = map.putIfAbsent(id, uid); + if (u != null) { + uid = u; + } + } + return uid; + } + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMailboxMapper.java b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMailboxMapper.java new file mode 100644 index 0000000..1033e60 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMailboxMapper.java @@ -0,0 +1,100 @@ +/**************************************************************** + * 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.mail; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.apache.mailet.MailetContext; + +public class InMemoryMailboxMapper implements MailboxMapper { + + MailetContext mailetContext; + + public InMemoryMailboxMapper(MailetContext mailetContext) + { + this.mailetContext = mailetContext; + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public void delete(Mailbox mailbox) throws MailboxException { + throw new MailboxException ("delete not supported"); + } + + public void deleteAll() throws MailboxException { + throw new MailboxException ("deleteAll not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(org.apache.james.mailbox.model.MailboxPath) + */ + public synchronized Mailbox findMailboxByPath(MailboxPath path) throws MailboxException, MailboxNotFoundException { + return new SimpleMailbox(path, 0); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(org.apache.james.mailbox.model.MailboxPath) + */ + public List> findMailboxWithPathLike(MailboxPath path) throws MailboxException { + throw new MailboxException ("findMailboxWithPathLike not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public void save(Mailbox mailbox) throws MailboxException { + } + + /** + * Do nothing + */ + public void endRequest() { + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(org.apache.james.mailbox.store.mail.model.Mailbox, char) + */ + public boolean hasChildren(Mailbox mailbox, char delimiter) throws MailboxException, + MailboxNotFoundException { + throw new MailboxException ("hasChildren not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() + */ + public List> list() throws MailboxException { + return new ArrayList>(); + } + + public T execute(Transaction transaction) throws MailboxException { + return transaction.run(); + } + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java new file mode 100644 index 0000000..5b9597f --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java @@ -0,0 +1,240 @@ +/**************************************************************** + * 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.mail; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.store.SimpleMessageMetaData; +import org.apache.james.mailbox.store.mail.AbstractMessageMapper; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import org.apache.mailet.MailetContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import mail.server.handler.UserInformation; +import mail.server.handler.UserInformationFactory; +import core.util.Streams; + +public class InMemoryMessageMapper extends AbstractMessageMapper +{ + private static final int INITIAL_SIZE = 8; + Logger log = LoggerFactory.getLogger(InMemoryMessageMapper.class); + MailetContext mailetContext; + + public InMemoryMessageMapper( + MailetContext mailetContext, + MailboxSession session, UidProvider uidProvider, + ModSeqProvider modSeqProvider + ) + { + super(session, uidProvider, modSeqProvider); + this.mailetContext = mailetContext; + } + + private Map> getMembershipByUidForMailbox( + Mailbox mailbox) + { + return new ConcurrentHashMap>(INITIAL_SIZE); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long countMessagesInMailbox(Mailbox mailbox) + throws MailboxException + { + throw new MailboxException("not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long countUnseenMessagesInMailbox(Mailbox mailbox) + throws MailboxException + { + throw new MailboxException("not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + public void delete(Mailbox mailbox, Message message) + throws MailboxException + { + throw new MailboxException("not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) + */ + public Iterator> findInMailbox(Mailbox mailbox, + MessageRange set, FetchType ftype, int max) throws MailboxException + { + throw new MailboxException("not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public List findRecentMessageUidsInMailbox(Mailbox mailbox) + throws MailboxException + { + throw new MailboxException("not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public Long findFirstUnseenMessageUid(Mailbox mailbox) + throws MailboxException + { + throw new MailboxException("not supported"); + } + + public void deleteAll() + { + } + + /** + * Do nothing + */ + public void endRequest() + { + // Do nothing + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, + * long, long, org.apache.james.mailbox.store.mail.model.Message) + */ + protected MessageMetaData copy(Mailbox mailbox, long uid, + long modSeq, Message original) throws MailboxException + { + throw new MailboxException("not supported"); + } + + /** + * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox, + * org.apache.james.mailbox.store.mail.model.Message) + */ + protected MessageMetaData save(Mailbox mailbox, Message message) + throws MailboxException + { +// if (new Random().nextInt() % 3 == 0) +// throw new MailboxException("Artificially simulating save failure"); + + SimpleMessage copy = new SimpleMessage(mailbox, message); + + copy.setUid(message.getUid()); + copy.setModSeq(message.getModSeq()); + getMembershipByUidForMailbox(mailbox).put(message.getUid(), copy); + + log.info("save message begin"); + + try + { + UserInformation user = + UserInformationFactory.getInstance().getUserInformation(mailbox.getUser()); + + if (mailbox.getName().equalsIgnoreCase("INBOX")) + user.handleIn(copy.getFullContent()); + else + if (mailbox.getName().equalsIgnoreCase("SENT")) + user.handleOut(copy.getFullContent()); + } + catch (Throwable e) + { + try + { + UserInformationFactory.getInstance().recordFailure( + mailbox.getUser(), e); + } + catch (Exception ex) + { + // total failure to log the exception + ex.printStackTrace(); + e.printStackTrace(); + log.info("user information factory failure." + ex.toString() + + " originating from " + e.toString()); + } + + throw new MailboxException("Failed to deliver message"); + } + finally + { + log.info("save message end"); + } + + return new SimpleMessageMetaData(message); + } + + /** + * Do nothing + */ + protected void begin() throws MailboxException + { + } + + /** + * Do nothing + */ + protected void commit() throws MailboxException + { + } + + /** + * Do nothing + */ + protected void rollback() throws MailboxException + { + } + + @Override + public Map expungeMarkedForDeletionInMailbox( + final Mailbox mailbox, MessageRange set) + throws MailboxException + { + throw new MailboxException("not supported"); + } +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryModSeqProvider.java b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryModSeqProvider.java new file mode 100644 index 0000000..b61d5e8 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryModSeqProvider.java @@ -0,0 +1,60 @@ +/**************************************************************** + * 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.mail; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.ModSeqProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class InMemoryModSeqProvider implements ModSeqProvider{ +// private final ConcurrentMap map = new ConcurrentHashMap(); + private final AtomicLong uid = new AtomicLong(0); + + @Override + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + return uid.get(); +// return getHighest(mailbox.getMailboxId()).incrementAndGet(); + + } + + @Override + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException { + return uid.get(); +// return getHighest(mailbox.getMailboxId()).get(); + } + /* + private AtomicLong getHighest(Long id) { + AtomicLong uid = map.get(id); + if (uid == null) { + uid = new AtomicLong(0); + AtomicLong u = map.putIfAbsent(id, uid); + if (u != null) { + uid = u; + } + } + return uid; + } + */ +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryUidProvider.java b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryUidProvider.java new file mode 100644 index 0000000..0ccbeea --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryUidProvider.java @@ -0,0 +1,62 @@ +/**************************************************************** + * 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.mail; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.UidProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class InMemoryUidProvider implements UidProvider{ + +// private final ConcurrentMap map = new ConcurrentHashMap(); + private final AtomicLong uid = new AtomicLong(0); + + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + return uid.get(); +// return getLast(mailbox.getMailboxId()).incrementAndGet(); + } + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + return uid.get(); +// return getLast(mailbox.getMailboxId()).get(); + } + + /* + private AtomicLong getLast(Long id) { + AtomicLong uid = map.get(id); + if (uid == null) { + uid = new AtomicLong(0); + AtomicLong u = map.putIfAbsent(id, uid); + if (u != null) { + uid = u; + } + } + return uid; + } + */ + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/all-wcprops new file mode 100644 index 0000000..4c59d13 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 107 +/repos/asf/!svn/ver/1179587/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/user +END +InMemorySubscriptionMapper.java +K 25 +svn:wc:ra_dav:version-url +V 139 +/repos/asf/!svn/ver/1179587/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/user/InMemorySubscriptionMapper.java +END diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/entries b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/entries new file mode 100644 index 0000000..927caf9 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/java/org/apache/james/mailbox/inmemory/user +http://svn.apache.org/repos/asf + + + +2011-10-06T11:59:20.201674Z +1179587 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +InMemorySubscriptionMapper.java +file + + + + +2013-09-02T02:54:40.000000Z +367f451b8b4428f552e3ef24391e17cd +2011-10-06T11:59:20.201674Z +1179587 +felixk + + + + + + + + + + + + + + + + + + + + + +4523 + diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/text-base/InMemorySubscriptionMapper.java.svn-base b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/text-base/InMemorySubscriptionMapper.java.svn-base new file mode 100644 index 0000000..de5ff53 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/.svn/text-base/InMemorySubscriptionMapper.java.svn-base @@ -0,0 +1,111 @@ +/**************************************************************** + * 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.user; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +public class InMemorySubscriptionMapper extends NonTransactionalMapper implements SubscriptionMapper { + + private static final int INITIAL_SIZE = 64; + private final Map> subscriptionsByUser; + + public InMemorySubscriptionMapper() { + subscriptionsByUser = new ConcurrentHashMap>(INITIAL_SIZE); + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(org.apache.james.mailbox.store.user.model.Subscription) + */ + public synchronized void delete(Subscription subscription) { + final String user = subscription.getUser(); + final List subscriptions = subscriptionsByUser.get(user); + if (subscriptions != null) { + subscriptions.remove(subscription); + } + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) { + final List subscriptions = subscriptionsByUser.get(user); + Subscription result = null; + if (subscriptions != null) { + for(Subscription subscription:subscriptions) { + if (subscription.getMailbox().equals(mailbox)) { + result = subscription; + break; + } + } + } + return result; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List findSubscriptionsForUser(String user) { + final List subcriptions = subscriptionsByUser.get(user); + final List results; + if (subcriptions == null) { + results = Collections.EMPTY_LIST; + } else { + // Make a copy to prevent concurrent modifications + results = new ArrayList(subcriptions); + } + return results; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(org.apache.james.mailbox.store.user.model.Subscription) + */ + public synchronized void save(Subscription subscription) { + final String user = subscription.getUser(); + final List subscriptions = subscriptionsByUser.get(user); + if (subscriptions == null) { + final List newSubscriptions = new ArrayList(); + newSubscriptions.add(subscription); + subscriptionsByUser.put(user, newSubscriptions); + } else { + subscriptions.add(subscription); + } + } + + public void deleteAll() { + subscriptionsByUser.clear(); + } + + /** + * Do nothing + */ + public void endRequest() { + // nothing todo + + } + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/InMemorySubscriptionMapper.java b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/InMemorySubscriptionMapper.java new file mode 100644 index 0000000..de5ff53 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/user/InMemorySubscriptionMapper.java @@ -0,0 +1,111 @@ +/**************************************************************** + * 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.user; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +public class InMemorySubscriptionMapper extends NonTransactionalMapper implements SubscriptionMapper { + + private static final int INITIAL_SIZE = 64; + private final Map> subscriptionsByUser; + + public InMemorySubscriptionMapper() { + subscriptionsByUser = new ConcurrentHashMap>(INITIAL_SIZE); + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#delete(org.apache.james.mailbox.store.user.model.Subscription) + */ + public synchronized void delete(Subscription subscription) { + final String user = subscription.getUser(); + final List subscriptions = subscriptionsByUser.get(user); + if (subscriptions != null) { + subscriptions.remove(subscription); + } + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findMailboxSubscriptionForUser(java.lang.String, java.lang.String) + */ + public Subscription findMailboxSubscriptionForUser(String user, String mailbox) { + final List subscriptions = subscriptionsByUser.get(user); + Subscription result = null; + if (subscriptions != null) { + for(Subscription subscription:subscriptions) { + if (subscription.getMailbox().equals(mailbox)) { + result = subscription; + break; + } + } + } + return result; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#findSubscriptionsForUser(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List findSubscriptionsForUser(String user) { + final List subcriptions = subscriptionsByUser.get(user); + final List results; + if (subcriptions == null) { + results = Collections.EMPTY_LIST; + } else { + // Make a copy to prevent concurrent modifications + results = new ArrayList(subcriptions); + } + return results; + } + + /** + * @see org.apache.james.mailbox.store.user.SubscriptionMapper#save(org.apache.james.mailbox.store.user.model.Subscription) + */ + public synchronized void save(Subscription subscription) { + final String user = subscription.getUser(); + final List subscriptions = subscriptionsByUser.get(user); + if (subscriptions == null) { + final List newSubscriptions = new ArrayList(); + newSubscriptions.add(subscription); + subscriptionsByUser.put(user, newSubscriptions); + } else { + subscriptions.add(subscription); + } + } + + public void deleteAll() { + subscriptionsByUser.clear(); + } + + /** + * Do nothing + */ + public void endRequest() { + // nothing todo + + } + +} diff --git a/james/apache-james-mailbox/memory/src/main/java/org/bc b/james/apache-james-mailbox/memory/src/main/java/org/bc new file mode 120000 index 0000000..9b394f5 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/bc @@ -0,0 +1 @@ +../../../../../../../java/ext/bouncycastle/src/org/bc \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/org/json b/james/apache-james-mailbox/memory/src/main/java/org/json new file mode 120000 index 0000000..20b3525 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/json @@ -0,0 +1 @@ +../../../../../../../java/ext/json/src/org/json \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/java/org/timepedia b/james/apache-james-mailbox/memory/src/main/java/org/timepedia new file mode 120000 index 0000000..72d3970 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/java/org/timepedia @@ -0,0 +1 @@ +../../../../../../../java/core/src/org/timepedia \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..bd3b428 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/memory/src/main/resources +END diff --git a/james/apache-james-mailbox/memory/src/main/resources/.svn/entries b/james/apache-james-mailbox/memory/src/main/resources/.svn/entries new file mode 100644 index 0000000..86c6438 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/resources +http://svn.apache.org/repos/asf + + + +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..1bec0b3 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/memory/src/main/resources/META-INF +END diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/entries b/james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/entries new file mode 100644 index 0000000..23c6111 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/all-wcprops new file mode 100644 index 0000000..427e537 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 89 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/memory/src/main/resources/META-INF/spring +END +mailbox-memory-osgi.xml +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/memory/src/main/resources/META-INF/spring/mailbox-memory-osgi.xml +END +mailbox-memory.xml +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/memory/src/main/resources/META-INF/spring/mailbox-memory.xml +END diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/entries b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/entries new file mode 100644 index 0000000..c30c2d0 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/main/resources/META-INF/spring +http://svn.apache.org/repos/asf + + + +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-memory-osgi.xml +file + + + + +2013-09-02T02:54:40.000000Z +ece5d2f736efdefb04e272bcd70e43c3 +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + + + + + + + + +1801 + +mailbox-memory.xml +file + + + + +2013-09-02T02:54:40.000000Z +113568fb1c4985f17a669832e815c50e +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + + + + + + + + +2205 + diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory-osgi.xml.svn-base b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory-osgi.xml.svn-base new file mode 100644 index 0000000..fc6b5d7 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory-osgi.xml.svn-base @@ -0,0 +1,39 @@ + + + + + + + org.apache.james.mailbox.MailboxManager + + + + + + org.apache.james.mailbox.SubscriptionManager + + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory.xml.svn-base b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory.xml.svn-base new file mode 100644 index 0000000..3cadcfc --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/.svn/text-base/mailbox-memory.xml.svn-base @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory-osgi.xml b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory-osgi.xml new file mode 100644 index 0000000..fc6b5d7 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory-osgi.xml @@ -0,0 +1,39 @@ + + + + + + + org.apache.james.mailbox.MailboxManager + + + + + + org.apache.james.mailbox.SubscriptionManager + + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml new file mode 100644 index 0000000..3cadcfc --- /dev/null +++ b/james/apache-james-mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/memory/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..8b3f566 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/memory/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/memory/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/memory/src/reporting-site/.svn/entries b/james/apache-james-mailbox/memory/src/reporting-site/.svn/entries new file mode 100644 index 0000000..032db03 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-03-05T11:38:38.771020Z +1078275 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:40.000000Z +5bde8261948ff1881960902a322aa196 +2011-03-05T11:38:38.771020Z +1078275 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/memory/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/memory/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/memory/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/memory/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/memory/src/reporting-site/site.xml b/james/apache-james-mailbox/memory/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/memory/src/test/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/test/.svn/all-wcprops new file mode 100644 index 0000000..56110ad --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test +END diff --git a/james/apache-james-mailbox/memory/src/test/.svn/entries b/james/apache-james-mailbox/memory/src/test/.svn/entries new file mode 100644 index 0000000..ec580f4 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/test +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/memory/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..12cad7b --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 68 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test/java +END diff --git a/james/apache-james-mailbox/memory/src/test/java/.svn/entries b/james/apache-james-mailbox/memory/src/test/java/.svn/entries new file mode 100644 index 0000000..2bf0120 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/test/java +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/memory/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..fce2d3a --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test/java/org +END diff --git a/james/apache-james-mailbox/memory/src/test/java/org/.svn/entries b/james/apache-james-mailbox/memory/src/test/java/org/.svn/entries new file mode 100644 index 0000000..764902c --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/test/java/org +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..2793312 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..ef0daf1 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..ff1b51d --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 85 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..baf5ec6 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..c9bfa6e --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..3170a18 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +inmemory +dir + diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops new file mode 100644 index 0000000..17d77ca --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 102 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test/java/org/apache/james/mailbox/inmemory +END +InMemoryMailboxManagerTest.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManagerTest.java +END diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/entries b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/entries new file mode 100644 index 0000000..a33191f --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/memory/src/test/java/org/apache/james/mailbox/inmemory +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +InMemoryMailboxManagerTest.java +file + + + + +2013-09-02T02:54:40.000000Z +4c17c1505113cddddcac0d730f717ad4 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3432 + diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxManagerTest.java.svn-base b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxManagerTest.java.svn-base new file mode 100644 index 0000000..b9be470 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/.svn/text-base/InMemoryMailboxManagerTest.java.svn-base @@ -0,0 +1,79 @@ +/**************************************************************** + * 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.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.MockAuthenticator; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * InMemoryMailboxManagerTest that extends the MailboxManagerTest. + */ +public class InMemoryMailboxManagerTest extends AbstractMailboxManagerTest { + + /** + * Setup the mailboxManager. + * + * @throws Exception + */ + @Before + public void setup() throws Exception { + createMailboxManager(); + } + + /** + * Close the system session and entityManagerFactory + * + * @throws MailboxException + * @throws BadCredentialsException + */ + @After + public void tearDown() throws BadCredentialsException, MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() throws MailboxException { + + InMemoryMailboxSessionMapperFactory factory = new InMemoryMailboxSessionMapperFactory(); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager mailboxManager = new StoreMailboxManager(factory, new MockAuthenticator(), aclResolver, groupMembershipResolver); + mailboxManager.init(); + + setMailboxManager(mailboxManager); + + } + +} diff --git a/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManagerTest.java b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManagerTest.java new file mode 100644 index 0000000..b9be470 --- /dev/null +++ b/james/apache-james-mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManagerTest.java @@ -0,0 +1,79 @@ +/**************************************************************** + * 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.AbstractMailboxManagerTest; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.MockAuthenticator; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +/** + * InMemoryMailboxManagerTest that extends the MailboxManagerTest. + */ +public class InMemoryMailboxManagerTest extends AbstractMailboxManagerTest { + + /** + * Setup the mailboxManager. + * + * @throws Exception + */ + @Before + public void setup() throws Exception { + createMailboxManager(); + } + + /** + * Close the system session and entityManagerFactory + * + * @throws MailboxException + * @throws BadCredentialsException + */ + @After + public void tearDown() throws BadCredentialsException, MailboxException { + MailboxSession session = getMailboxManager().createSystemSession("test", LoggerFactory.getLogger("Test")); + session.close(); + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.MailboxManagerTest#createMailboxManager() + */ + @Override + protected void createMailboxManager() throws MailboxException { + + InMemoryMailboxSessionMapperFactory factory = new InMemoryMailboxSessionMapperFactory(); + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + StoreMailboxManager mailboxManager = new StoreMailboxManager(factory, new MockAuthenticator(), aclResolver, groupMembershipResolver); + mailboxManager.init(); + + setMailboxManager(mailboxManager); + + } + +} diff --git a/james/apache-james-mailbox/pom.xml b/james/apache-james-mailbox/pom.xml new file mode 100644 index 0000000..8fd084d --- /dev/null +++ b/james/apache-james-mailbox/pom.xml @@ -0,0 +1,576 @@ + + + + 4.0.0 + + + james-project + org.apache.james + 1.8.2 + + + + apache-james-mailbox + 0.6-SNAPSHOT + pom + + + + + apache.snapshots + Apache Snapshot Repository + http://repository.apache.org/snapshots + + false + + + + + Apache James :: Mailbox + Apache James Mailbox + http://james.apache.org/mailbox + 2010 + + + api + caching + hbase + jcr + jpa + lucene + maildir + memory + store + spring + tool + zoo-seq-provider + + + + scm:svn:http://svn.apache.org/repos/asf/james/mailbox/trunk + scm:svn:https://svn.apache.org/repos/asf/james/mailbox/trunk + http://svn.apache.org/viewcvs.cgi/james/mailbox/trunk?root=Apache-SVN + + + JIRA + http://issues.apache.org/jira/browse/MAILBOX + + + + + mailbox-website + scpexe://people.apache.org/www/james.apache.org/mailbox + + + + + UTF-8 + javax.activation + activation + javax.mail + mail + 2.2.1 + 1.0.2 + 1 + 0.7.2 + 1.4.1 + 1.1.1 + 2.5.1 + 0.92.0 + 1.0.1 + 3.1.2.RELEASE + 2.4 + 2.6 + 1.6 + 1.4 + 1.9 + 1.8.3 + 1.3.170 + 10.9.1.0 + 1.1 + 2.0 + 2.5.2 + 3.6.0 + 2.9.1 + 1.3.04 + 1.1.1 + 1.1 + 1.8.3 + 2.5.0 + 1.7.2 + 4.11 + 1.9.0 + 13.0 + + + + + + com.google.guava + guava + ${guava.version} + + + + org.apache.james + apache-james-mailbox-api + ${project.version} + + + org.apache.james + apache-james-mailbox-api + ${project.version} + test-jar + test + + + org.apache.james + apache-james-mailbox-store + ${project.version} + test-jar + test + + + org.apache.james + apache-james-mailbox-lucene + ${project.version} + + + org.apache.james + apache-james-mailbox-lucene + ${project.version} + test-jar + test + + + org.apache.james + apache-james-mailbox-store + ${project.version} + + + org.apache.james + apache-james-mailbox-jpa + ${project.version} + + + org.apache.james + apache-james-mailbox-jcr + ${project.version} + + + org.apache.james + apache-james-mailbox-memory + ${project.version} + + + org.apache.james + apache-james-mailbox-maildir + ${project.version} + + + org.apache.james + apache-james-mailbox-hbase + ${project.version} + + + org.apache.james + apache-james-mailbox-tool + ${project.version} + + + + org.apache.lucene + lucene-core + ${lucene.version} + + + org.apache.lucene + lucene-analyzers + ${lucene.version} + + + org.apache.lucene + lucene-smartcn + ${lucene.version} + + + + + org.apache.james + apache-mime4j-core + ${apache-mime4j.version} + + + org.apache.james + apache-mime4j-dom + ${apache-mime4j.version} + + + + + javax.mail + mail + ${javax.mail.version} + + + javax.activation + activation + ${activation.version} + + + javax.inject + javax.inject + ${javax.inject.version} + + + org.apache.geronimo.specs + geronimo-activation_1.1_spec + ${geronimo-activation-spec.version} + + + org.apache.geronimo.javamail + geronimo-javamail_1.4_mail + ${geronimo-javamail-mail.version} + + + + + + org.apache.james + apache-mailet-api + ${apache-mailet.version} + + + javax.mail + mail + + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + + + + commons-lang + commons-lang + ${commons-lang.version} + + + commons-pool + commons-pool + ${commons-pool.version} + + + commons-dbcp + commons-dbcp + ${commons-dbcp.version} + + + xercesImpl + xerces + + + + + commons-configuration + commons-configuration + test + ${commons-configuration.version} + + + dom4j + dom4j + + + servletapi + servletapi + + + xerces + xerces + + + commons-digester + commons-digester + + + commons-beanutils-core + commons-beanutils + + + commons-beanutils-bean-collections + commons-beanutils + + + + + commons-beanutils + commons-beanutils-core + ${commons-beanutils-core.version} + + + + + + + junit + junit + ${junit.version} + test + + + org.jmock + jmock + ${jmock.version} + test + + + org.jmock + jmock-junit4 + ${jmock.version} + test + + + com.h2database + h2 + ${h2.version} + test + + + org.apache.derby + derby + ${derby.version} + test + + + commons-io + commons-io + ${commons-io.version} + + + + + + + + org.apache.openjpa + openjpa + ${openjpa.version} + + + org.apache.geronimo.specs + geronimo-jpa_2.0_spec + ${geronimo-jpa-spec.version} + + + org.jasypt + jasypt + ${jasypt.version} + + + + + + + javax.jcr + jcr + ${jcr.version} + + + org.apache.jackrabbit + jackrabbit-jcr-commons + ${jackrabbit.version} + + + org.apache.jackrabbit + jackrabbit-core + ${jackrabbit.version} + test + + + + xerces + xercesImpl + ${xercesImpl.version} + test + + + xml-apis + xml-apis + ${xml-apis.version} + test + + + + + + + + org.apache.geronimo.specs + geronimo-annotation_1.0_spec + ${geronimo-annotation-spec.version} + + + + + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-beans + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-orm + ${spring.version} + + + + + + + org.apache.hbase + hbase + ${hbase.version} + + + org.apache.hbase + hbase + ${hbase.version} + test-jar + test + + + org.apache.hadoop + hadoop-core + ${hadoop.version} + + + org.apache.hadoop + hadoop-test + ${hadoop.version} + test + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + bin + src + + + + + org.apache.felix + maven-bundle-plugin + true + + + + + + + geronimo + + org.apache.geronimo.javamail + geronimo-javamail_1.4_mail + org.apache.geronimo.specs + geronimo-activation_1.1_spec + 1.6 + 1.0.2 + + + + + diff --git a/james/apache-james-mailbox/spring/.svn/all-wcprops b/james/apache-james-mailbox/spring/.svn/all-wcprops new file mode 100644 index 0000000..700edfa --- /dev/null +++ b/james/apache-james-mailbox/spring/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 54 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/spring/pom.xml +END diff --git a/james/apache-james-mailbox/spring/.svn/dir-prop-base b/james/apache-james-mailbox/spring/.svn/dir-prop-base new file mode 100644 index 0000000..93d6d3c --- /dev/null +++ b/james/apache-james-mailbox/spring/.svn/dir-prop-base @@ -0,0 +1,8 @@ +K 10 +svn:ignore +V 14 +.* +target +var + +END diff --git a/james/apache-james-mailbox/spring/.svn/entries b/james/apache-james-mailbox/spring/.svn/entries new file mode 100644 index 0000000..d1f54db --- /dev/null +++ b/james/apache-james-mailbox/spring/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:40.000000Z +e64589ad1c0a899cdcb3ec5e13a30a73 +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +6790 + diff --git a/james/apache-james-mailbox/spring/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/spring/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..5f17057 --- /dev/null +++ b/james/apache-james-mailbox/spring/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,178 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-spring + Apache James :: Mailbox :: Spring + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-james-mailbox-lucene + + + org.apache.james + apache-james-mailbox-memory + + + org.apache.james + apache-james-mailbox-jpa + + + org.apache.james + apache-james-mailbox-maildir + + + + + + + + org.apache.james + apache-james-mailbox-jcr + + + org.apache.james + apache-james-mailbox-tool + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-orm + + + org.apache.derby + derby + + + commons-dbcp + commons-dbcp + + + xercesImpl + xerces + + + + + javax.jcr + jcr + + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + junit + junit + + + org.apache.hbase + hbase + test-jar + test + + + org.jruby + jruby-complete + + + org.slf4j + slf4j-log4j12 + + + commons-logging + commons-logging + + + + + org.apache.hadoop + hadoop-core + + + commons-logging + commons-logging + + + + + org.apache.hadoop + hadoop-test + test + + + diff --git a/james/apache-james-mailbox/spring/pom.xml b/james/apache-james-mailbox/spring/pom.xml new file mode 100644 index 0000000..5f17057 --- /dev/null +++ b/james/apache-james-mailbox/spring/pom.xml @@ -0,0 +1,178 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-spring + Apache James :: Mailbox :: Spring + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-james-mailbox-lucene + + + org.apache.james + apache-james-mailbox-memory + + + org.apache.james + apache-james-mailbox-jpa + + + org.apache.james + apache-james-mailbox-maildir + + + + + + + + org.apache.james + apache-james-mailbox-jcr + + + org.apache.james + apache-james-mailbox-tool + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-orm + + + org.apache.derby + derby + + + commons-dbcp + commons-dbcp + + + xercesImpl + xerces + + + + + javax.jcr + jcr + + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + test + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + junit + junit + + + org.apache.hbase + hbase + test-jar + test + + + org.jruby + jruby-complete + + + org.slf4j + slf4j-log4j12 + + + commons-logging + commons-logging + + + + + org.apache.hadoop + hadoop-core + + + commons-logging + commons-logging + + + + + org.apache.hadoop + hadoop-test + test + + + diff --git a/james/apache-james-mailbox/spring/src/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/.svn/all-wcprops new file mode 100644 index 0000000..9dc67d0 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src +END diff --git a/james/apache-james-mailbox/spring/src/.svn/entries b/james/apache-james-mailbox/spring/src/.svn/entries new file mode 100644 index 0000000..d0f8d53 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/spring/src/main/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/.svn/all-wcprops new file mode 100644 index 0000000..4fe3ad9 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main +END diff --git a/james/apache-james-mailbox/spring/src/main/.svn/entries b/james/apache-james-mailbox/spring/src/main/.svn/entries new file mode 100644 index 0000000..3283da2 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/spring/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..39ff8bc --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 68 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/java +END diff --git a/james/apache-james-mailbox/spring/src/main/java/.svn/entries b/james/apache-james-mailbox/spring/src/main/java/.svn/entries new file mode 100644 index 0000000..42c0e9b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/java +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/spring/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..a516d49 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/java/org +END diff --git a/james/apache-james-mailbox/spring/src/main/java/org/.svn/entries b/james/apache-james-mailbox/spring/src/main/java/org/.svn/entries new file mode 100644 index 0000000..b16203e --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..b6683ee --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..291a90c --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..ccc8190 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 85 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..445d916 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..581260b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..8818233 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/all-wcprops new file mode 100644 index 0000000..c68efdd --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 100 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/java/org/apache/james/mailbox/spring +END +AnonymousAuthenticator.java +K 25 +svn:wc:ra_dav:version-url +V 128 +/repos/asf/!svn/ver/1128289/james/mailbox/trunk/spring/src/main/java/org/apache/james/mailbox/spring/AnonymousAuthenticator.java +END +SpringMailbox.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/java/org/apache/james/mailbox/spring/SpringMailbox.java +END diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/entries b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/entries new file mode 100644 index 0000000..a35f062 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/java/org/apache/james/mailbox/spring +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +AnonymousAuthenticator.java +file + + + + +2013-09-02T02:54:39.000000Z +48a619743d09f6ae03134b8de64d7852 +2011-05-27T13:03:20.794844Z +1128289 +eric + + + + + + + + + + + + + + + + + + + + + +1492 + +SpringMailbox.java +file + + + + +2013-09-02T02:54:39.000000Z +0731b9866470e21c7d4fc92904a56f99 +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + + + + + + + + +1810 + diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/AnonymousAuthenticator.java.svn-base b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/AnonymousAuthenticator.java.svn-base new file mode 100644 index 0000000..7706e72 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/AnonymousAuthenticator.java.svn-base @@ -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.spring; + +import org.apache.james.mailbox.store.Authenticator; + +public class AnonymousAuthenticator implements Authenticator { + + @Override + public boolean isAuthentic(String userid, CharSequence passwd) { + return true; + } + +} diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailbox.java.svn-base b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailbox.java.svn-base new file mode 100644 index 0000000..ebfa59d --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailbox.java.svn-base @@ -0,0 +1,37 @@ +/**************************************************************** + * 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.spring; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class SpringMailbox { + + private ApplicationContext applicationContext; + + public SpringMailbox() { + applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/spring-mailbox.xml", + "META-INF/spring/mailbox-authenticator-anonymous.xml"); + } + + public Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } + +} diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/AnonymousAuthenticator.java b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/AnonymousAuthenticator.java new file mode 100644 index 0000000..7706e72 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/AnonymousAuthenticator.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.spring; + +import org.apache.james.mailbox.store.Authenticator; + +public class AnonymousAuthenticator implements Authenticator { + + @Override + public boolean isAuthentic(String userid, CharSequence passwd) { + return true; + } + +} diff --git a/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/SpringMailbox.java b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/SpringMailbox.java new file mode 100644 index 0000000..ebfa59d --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/java/org/apache/james/mailbox/spring/SpringMailbox.java @@ -0,0 +1,37 @@ +/**************************************************************** + * 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.spring; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class SpringMailbox { + + private ApplicationContext applicationContext; + + public SpringMailbox() { + applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/spring-mailbox.xml", + "META-INF/spring/mailbox-authenticator-anonymous.xml"); + } + + public Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } + +} diff --git a/james/apache-james-mailbox/spring/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..7aa067e --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/resources +END +log4j.properties +K 25 +svn:wc:ra_dav:version-url +V 90 +/repos/asf/!svn/ver/1158687/james/mailbox/trunk/spring/src/main/resources/log4j.properties +END +mailbox-jcr.cnd +K 25 +svn:wc:ra_dav:version-url +V 89 +/repos/asf/!svn/ver/1291197/james/mailbox/trunk/spring/src/main/resources/mailbox-jcr.cnd +END diff --git a/james/apache-james-mailbox/spring/src/main/resources/.svn/entries b/james/apache-james-mailbox/spring/src/main/resources/.svn/entries new file mode 100644 index 0000000..f66a1d5 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/.svn/entries @@ -0,0 +1,99 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/resources +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + +log4j.properties +file + + + + +2013-09-02T02:54:39.000000Z +20bc27843272dcb92124d205cd7957af +2011-08-17T13:11:51.122017Z +1158687 +eric + + + + + + + + + + + + + + + + + + + + + +2039 + +mailbox-jcr.cnd +file + + + + +2013-09-02T02:54:39.000000Z +47c9ba0b1e4f8cf6d76fa05a8d776d04 +2012-02-20T11:14:47.341630Z +1291197 +eric + + + + + + + + + + + + + + + + + + + + + +2677 + diff --git a/james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/log4j.properties.svn-base b/james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/log4j.properties.svn-base new file mode 100644 index 0000000..81c0eb5 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/log4j.properties.svn-base @@ -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. + +log4j.rootLogger=DEBUG + +log4j.appender.CONS=org.apache.log4j.ConsoleAppender +log4j.appender.CONS.layout=org.apache.log4j.PatternLayout +log4j.appender.CONS.layout.ConversionPattern=%-5p %d{HH:mm:ss,SSS} | %c | %m%n + +log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender +log4j.appender.FILE.File=../log/james-server.log +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.ConversionPattern=%-5p %d{HH:mm:ss,SSS} | %c | %m%n + +log4j.appender.MAILBOXMANAGER=org.apache.log4j.DailyRollingFileAppender +log4j.appender.MAILBOXMANAGER.File=../log/mailboxmanager.log +log4j.appender.MAILBOXMANAGER.DatePattern='.'yyyy-MM-dd +log4j.appender.MAILBOXMANAGER.layout=org.apache.log4j.PatternLayout +log4j.appender.MAILBOXMANAGER.layout.ConversionPattern=%-5p %d{HH:mm:ss,SSS} | %c | %m%n + +log4j.logger.org.apache.jackrabbit=ERROR, CONS, FILE + +log4j.logger.org.apache.xbean.spring=WARN, CONS, FILE +log4j.logger.org.apache.activemq=WARN, CONS, FILE + +log4j.logger.org.apache.camel=WARN, CONS, FILE +log4j.logger.org.springframework=WARN, CONS, FILE +log4j.logger.org.apache.james=INFO, CONS, FILE + +log4j.logger.james=WARN, CONS, FILE +log4j.logger=DEBUG, CONS, FILE + +log4j.logger.james.mailboxmanager=INFO, MAILBOXMANAGER + diff --git a/james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base b/james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base new file mode 100644 index 0000000..4e9e88b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/.svn/text-base/mailbox-jcr.cnd.svn-base @@ -0,0 +1,67 @@ +/* + * 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. + */ + + + +[jamesMailbox:user] > mix:created + mixin + - jamesMailbox:user (String) + - jamesMailbox:subscriptionMailboxes (STRING) multiple + + * (nt:unstructured) multiple + + +[jamesMailbox:messageHeader] > mix:created, mix:lockable + mixin + - jamesMailbox:headerFieldName (STRING) mandatory + - jamesMailbox:headerValue(STRING) mandatory + - jamesMailbox:headerLineNumber (LONG) mandatory + +[jamesMailbox:messageProperty] > mix:created + mixin + - jamesMailbox:propertyNamespace (STRING) mandatory + - jamesMailbox:propertyLocalName (STRING) mandatory + - jamesMailbox:propertyValue(STRING) mandatory + - jamesMailbox:propertyOrder (Long) mandatory + +[jamesMailbox:message] > mix:referenceable, mix:created, mix:created + mixin + - jamesMailbox:mailboxUUID (String) mandatory + - jamesMailbox:uid (LONG) mandatory + - jamesMailbox:modSeq (LONG) + - jamesMailbox:size (LONG) mandatory + - jamesMailbox:answered (BOOLEAN) + - jamesMailbox:deleted (BOOLEAN) + - jamesMailbox:draft (BOOLEAN) + - jamesMailbox:flagged (BOOLEAN) + - jamesMailbox:recent (BOOLEAN) + - jamesMailbox:seen (BOOLEAN) + - jamesMailbox:internalDate (DATE) + - jamesMailbox:userFlags (STRING) multiple + - jamesMailbox:messageBodyStartOctet (LONG) mandatory + - jamesMailbox:messageTextualLineCount (LONG) + - jamesMailbox:messageSubType (String) mandatory + + messageHeader (nt:unstructured) multiple + + messageProperty (nt:unstructured) multiple + +[jamesMailbox:mailbox] > mix:referenceable, mix:lockable, mix:created + mixin + - jamesMailbox:mailboxUidValidity (LONG) + - jamesMailbox:mailboxName (STRING) + - jamesMailbox:mailboxLastUid (LONG) + - jamesMailbox:mailboxNamespace (STRING) + - jamesMailbox:mailboxUser (STRING) + + * (nt:unstructured) multiple diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..33d4947 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/resources/META-INF +END diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/entries b/james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/entries new file mode 100644 index 0000000..bd108e6 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + +org +dir + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/all-wcprops new file mode 100644 index 0000000..a177a55 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/main/resources/META-INF/org +END diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/entries b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/entries new file mode 100644 index 0000000..6ba2863 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/resources/META-INF/org +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..4e9c335 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/main/resources/META-INF/org/apache +END diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/entries b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/entries new file mode 100644 index 0000000..07c3d4a --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/resources/META-INF/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..87f2ea9 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 99 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/main/resources/META-INF/org/apache/james +END diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/entries b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/entries new file mode 100644 index 0000000..559c469 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/org/apache/james/.svn/entries @@ -0,0 +1,28 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/resources/META-INF/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/all-wcprops new file mode 100644 index 0000000..3cd7bf8 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 89 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/resources/META-INF/spring +END +mailbox-authenticator-osgi.xml +K 25 +svn:wc:ra_dav:version-url +V 120 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/spring/src/main/resources/META-INF/spring/mailbox-authenticator-osgi.xml +END +mailbox-locker-osgi.xml +K 25 +svn:wc:ra_dav:version-url +V 113 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/spring/src/main/resources/META-INF/spring/mailbox-locker-osgi.xml +END +mailbox-locker.xml +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/spring/src/main/resources/META-INF/spring/mailbox-locker.xml +END +mailbox-authenticator-anonymous.xml +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/resources/META-INF/spring/mailbox-authenticator-anonymous.xml +END +spring-mailbox.xml +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/main/resources/META-INF/spring/spring-mailbox.xml +END diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/entries b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/entries new file mode 100644 index 0000000..c4ed175 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/entries @@ -0,0 +1,198 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/main/resources/META-INF/spring +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-authenticator-osgi.xml +file + + + + +2013-09-02T02:54:39.000000Z +dce15b6f406e4e893040b9a1058f9145 +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + + + + + + + + +1568 + +mailbox-locker-osgi.xml +file + + + + +2013-09-02T02:54:39.000000Z +a303b777b25368b013433e05501c1953 +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + + + + + + + + +1577 + +mailbox-locker.xml +file + + + + +2013-09-02T02:54:39.000000Z +7d7cb89bbd5710cbd3f30f3a4da2c9c2 +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + + + + + + + + +1229 + +mailbox-authenticator-anonymous.xml +file + + + + +2013-09-02T02:54:39.000000Z +bc712aa416b228768d881f4ae71f6241 +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + + + + + + + + +1207 + +spring-mailbox.xml +file + + + + +2013-09-02T02:54:39.000000Z +0cdfc75f55bb936b9a287bc2d7ad2aeb +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + + + + + + + + +2292 + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-anonymous.xml.svn-base b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-anonymous.xml.svn-base new file mode 100644 index 0000000..3be7181 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-anonymous.xml.svn-base @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-osgi.xml.svn-base b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-osgi.xml.svn-base new file mode 100644 index 0000000..445cff3 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-authenticator-osgi.xml.svn-base @@ -0,0 +1,33 @@ + + + + + + + org.apache.james.mailbox.store.Authenticator + + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker-osgi.xml.svn-base b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker-osgi.xml.svn-base new file mode 100644 index 0000000..2ad19d4 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker-osgi.xml.svn-base @@ -0,0 +1,33 @@ + + + + + + + org.apache.james.mailbox.store.AbstractMailboxPathLocker + + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker.xml.svn-base b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker.xml.svn-base new file mode 100644 index 0000000..69c1ce1 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/mailbox-locker.xml.svn-base @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/spring-mailbox.xml.svn-base b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/spring-mailbox.xml.svn-base new file mode 100644 index 0000000..a711191 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/.svn/text-base/spring-mailbox.xml.svn-base @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-anonymous.xml b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-anonymous.xml new file mode 100644 index 0000000..3be7181 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-anonymous.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-osgi.xml b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-osgi.xml new file mode 100644 index 0000000..445cff3 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-authenticator-osgi.xml @@ -0,0 +1,33 @@ + + + + + + + org.apache.james.mailbox.store.Authenticator + + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker-osgi.xml b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker-osgi.xml new file mode 100644 index 0000000..2ad19d4 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker-osgi.xml @@ -0,0 +1,33 @@ + + + + + + + org.apache.james.mailbox.store.AbstractMailboxPathLocker + + + + \ No newline at end of file diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker.xml b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker.xml new file mode 100644 index 0000000..69c1ce1 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/mailbox-locker.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml new file mode 100644 index 0000000..a711191 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/META-INF/spring/spring-mailbox.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/main/resources/log4j.properties b/james/apache-james-mailbox/spring/src/main/resources/log4j.properties new file mode 100644 index 0000000..81c0eb5 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/log4j.properties @@ -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. + +log4j.rootLogger=DEBUG + +log4j.appender.CONS=org.apache.log4j.ConsoleAppender +log4j.appender.CONS.layout=org.apache.log4j.PatternLayout +log4j.appender.CONS.layout.ConversionPattern=%-5p %d{HH:mm:ss,SSS} | %c | %m%n + +log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender +log4j.appender.FILE.File=../log/james-server.log +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.ConversionPattern=%-5p %d{HH:mm:ss,SSS} | %c | %m%n + +log4j.appender.MAILBOXMANAGER=org.apache.log4j.DailyRollingFileAppender +log4j.appender.MAILBOXMANAGER.File=../log/mailboxmanager.log +log4j.appender.MAILBOXMANAGER.DatePattern='.'yyyy-MM-dd +log4j.appender.MAILBOXMANAGER.layout=org.apache.log4j.PatternLayout +log4j.appender.MAILBOXMANAGER.layout.ConversionPattern=%-5p %d{HH:mm:ss,SSS} | %c | %m%n + +log4j.logger.org.apache.jackrabbit=ERROR, CONS, FILE + +log4j.logger.org.apache.xbean.spring=WARN, CONS, FILE +log4j.logger.org.apache.activemq=WARN, CONS, FILE + +log4j.logger.org.apache.camel=WARN, CONS, FILE +log4j.logger.org.springframework=WARN, CONS, FILE +log4j.logger.org.apache.james=INFO, CONS, FILE + +log4j.logger.james=WARN, CONS, FILE +log4j.logger=DEBUG, CONS, FILE + +log4j.logger.james.mailboxmanager=INFO, MAILBOXMANAGER + diff --git a/james/apache-james-mailbox/spring/src/main/resources/mailbox-jcr.cnd b/james/apache-james-mailbox/spring/src/main/resources/mailbox-jcr.cnd new file mode 100644 index 0000000..4e9e88b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/main/resources/mailbox-jcr.cnd @@ -0,0 +1,67 @@ +/* + * 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. + */ + + + +[jamesMailbox:user] > mix:created + mixin + - jamesMailbox:user (String) + - jamesMailbox:subscriptionMailboxes (STRING) multiple + + * (nt:unstructured) multiple + + +[jamesMailbox:messageHeader] > mix:created, mix:lockable + mixin + - jamesMailbox:headerFieldName (STRING) mandatory + - jamesMailbox:headerValue(STRING) mandatory + - jamesMailbox:headerLineNumber (LONG) mandatory + +[jamesMailbox:messageProperty] > mix:created + mixin + - jamesMailbox:propertyNamespace (STRING) mandatory + - jamesMailbox:propertyLocalName (STRING) mandatory + - jamesMailbox:propertyValue(STRING) mandatory + - jamesMailbox:propertyOrder (Long) mandatory + +[jamesMailbox:message] > mix:referenceable, mix:created, mix:created + mixin + - jamesMailbox:mailboxUUID (String) mandatory + - jamesMailbox:uid (LONG) mandatory + - jamesMailbox:modSeq (LONG) + - jamesMailbox:size (LONG) mandatory + - jamesMailbox:answered (BOOLEAN) + - jamesMailbox:deleted (BOOLEAN) + - jamesMailbox:draft (BOOLEAN) + - jamesMailbox:flagged (BOOLEAN) + - jamesMailbox:recent (BOOLEAN) + - jamesMailbox:seen (BOOLEAN) + - jamesMailbox:internalDate (DATE) + - jamesMailbox:userFlags (STRING) multiple + - jamesMailbox:messageBodyStartOctet (LONG) mandatory + - jamesMailbox:messageTextualLineCount (LONG) + - jamesMailbox:messageSubType (String) mandatory + + messageHeader (nt:unstructured) multiple + + messageProperty (nt:unstructured) multiple + +[jamesMailbox:mailbox] > mix:referenceable, mix:lockable, mix:created + mixin + - jamesMailbox:mailboxUidValidity (LONG) + - jamesMailbox:mailboxName (STRING) + - jamesMailbox:mailboxLastUid (LONG) + - jamesMailbox:mailboxNamespace (STRING) + - jamesMailbox:mailboxUser (STRING) + + * (nt:unstructured) multiple diff --git a/james/apache-james-mailbox/spring/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..7eb52b0 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1128289/james/mailbox/trunk/spring/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1128289/james/mailbox/trunk/spring/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/spring/src/reporting-site/.svn/entries b/james/apache-james-mailbox/spring/src/reporting-site/.svn/entries new file mode 100644 index 0000000..fa68f2d --- /dev/null +++ b/james/apache-james-mailbox/spring/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-05-27T13:03:20.794844Z +1128289 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:39.000000Z +5bd8b6970c93cd983424e9cb77f00ead +2011-05-27T13:03:20.794844Z +1128289 +eric + + + + + + + + + + + + + + + + + + + + + +1035 + diff --git a/james/apache-james-mailbox/spring/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/spring/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..0f75b5b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/reporting-site/site.xml b/james/apache-james-mailbox/spring/src/reporting-site/site.xml new file mode 100644 index 0000000..0f75b5b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/test/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/.svn/all-wcprops new file mode 100644 index 0000000..7280eee --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 63 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test +END diff --git a/james/apache-james-mailbox/spring/src/test/.svn/entries b/james/apache-james-mailbox/spring/src/test/.svn/entries new file mode 100644 index 0000000..c415969 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/spring/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..cb74f05 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 68 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test/java +END diff --git a/james/apache-james-mailbox/spring/src/test/java/.svn/entries b/james/apache-james-mailbox/spring/src/test/java/.svn/entries new file mode 100644 index 0000000..39761b5 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/java +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/spring/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..4a839f6 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test/java/org +END diff --git a/james/apache-james-mailbox/spring/src/test/java/org/.svn/entries b/james/apache-james-mailbox/spring/src/test/java/org/.svn/entries new file mode 100644 index 0000000..73ea579 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/java/org +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..6ba1ee9 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..6320205 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..3dae7f2 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 85 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..c60f53f --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..64d8aab --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..6682f99 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +spring +dir + diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/all-wcprops new file mode 100644 index 0000000..9353886 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 100 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test/java/org/apache/james/mailbox/spring +END +SpringMailboxTest.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1476790/james/mailbox/trunk/spring/src/test/java/org/apache/james/mailbox/spring/SpringMailboxTest.java +END diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/entries b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/entries new file mode 100644 index 0000000..31ba150 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/java/org/apache/james/mailbox/spring +http://svn.apache.org/repos/asf + + + +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SpringMailboxTest.java +file + + + + +2013-09-02T02:54:40.000000Z +32ba2ea0fbd5d5019bab9825a5607f47 +2013-04-28T15:57:41.084903Z +1476790 +eric + + + + + + + + + + + + + + + + + + + + + +1965 + diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailboxTest.java.svn-base b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailboxTest.java.svn-base new file mode 100644 index 0000000..f04ea84 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/.svn/text-base/SpringMailboxTest.java.svn-base @@ -0,0 +1,44 @@ +/**************************************************************** + * 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.spring; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.james.mailbox.copier.MailboxCopierImpl; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SpringMailboxTest { + + private static SpringMailbox springMailbox; + + @BeforeClass + public static void beforeClass() { + springMailbox = new SpringMailbox(); + } + + @Test + public void testGetBean() throws IOException { + assertEquals(AnonymousAuthenticator.class.getName(), springMailbox.getBean("authenticator").getClass().getName()); + assertEquals(MailboxCopierImpl.class.getName(), springMailbox.getBean("mailboxcopier").getClass().getName()); + } + +} diff --git a/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/SpringMailboxTest.java b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/SpringMailboxTest.java new file mode 100644 index 0000000..f04ea84 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/java/org/apache/james/mailbox/spring/SpringMailboxTest.java @@ -0,0 +1,44 @@ +/**************************************************************** + * 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.spring; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.james.mailbox.copier.MailboxCopierImpl; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SpringMailboxTest { + + private static SpringMailbox springMailbox; + + @BeforeClass + public static void beforeClass() { + springMailbox = new SpringMailbox(); + } + + @Test + public void testGetBean() throws IOException { + assertEquals(AnonymousAuthenticator.class.getName(), springMailbox.getBean("authenticator").getClass().getName()); + assertEquals(MailboxCopierImpl.class.getName(), springMailbox.getBean("mailboxcopier").getClass().getName()); + } + +} diff --git a/james/apache-james-mailbox/spring/src/test/resources/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/resources/.svn/all-wcprops new file mode 100644 index 0000000..e9433a4 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/test/resources +END +hbase-site.xml +K 25 +svn:wc:ra_dav:version-url +V 88 +/repos/asf/!svn/ver/1347866/james/mailbox/trunk/spring/src/test/resources/hbase-site.xml +END +hdfs-site.xml +K 25 +svn:wc:ra_dav:version-url +V 87 +/repos/asf/!svn/ver/1347866/james/mailbox/trunk/spring/src/test/resources/hdfs-site.xml +END +hadoop-metrics2.properties +K 25 +svn:wc:ra_dav:version-url +V 100 +/repos/asf/!svn/ver/1182284/james/mailbox/trunk/spring/src/test/resources/hadoop-metrics2.properties +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/.svn/entries b/james/apache-james-mailbox/spring/src/test/resources/.svn/entries new file mode 100644 index 0000000..24aea33 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/.svn/entries @@ -0,0 +1,133 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/resources +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +META-INF +dir + +hbase-site.xml +file + + + + +2013-09-02T02:54:40.000000Z +38e6b68bf1e776ee03a7a408ca5dc250 +2012-06-08T02:13:59.296846Z +1347866 +ieugen + + + + + + + + + + + + + + + + + + + + + +1572 + +hdfs-site.xml +file + + + + +2013-09-02T02:54:40.000000Z +117ab56f33382f4065c361e615ba5678 +2012-06-08T02:13:59.296846Z +1347866 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +1072 + +hadoop-metrics2.properties +file + + + + +2013-09-02T02:54:40.000000Z +ac04f9bf14ba3d0b282178e6a6ded696 +2011-10-12T09:36:01.711474Z +1182284 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +302 + diff --git a/james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base b/james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hadoop-metrics2.properties.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base b/james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/.svn/prop-base/hdfs-site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base b/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base new file mode 100644 index 0000000..e5a9785 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hadoop-metrics2.properties.svn-base @@ -0,0 +1,8 @@ +# Configuration of the "dfs" context for null +dfs.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "mapred" context for null +mapred.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "jvm" context for null +jvm.class=org.apache.hadoop.metrics.spi.NullContext diff --git a/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hbase-site.xml.svn-base b/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hbase-site.xml.svn-base new file mode 100644 index 0000000..1857bac --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hbase-site.xml.svn-base @@ -0,0 +1,49 @@ + + + + + + + hbase.rootdir + hdfs://localhost:9000/hbase + + + + hbase.master.port + 60000 + + + + hbase.regionserver.info.port + 6030 + + + + hbase.regionserver.info.bindAddress + 0.0.0.0 + + + diff --git a/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base b/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base new file mode 100644 index 0000000..9284b7c --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/.svn/text-base/hdfs-site.xml.svn-base @@ -0,0 +1,33 @@ + + + + + + + dfs.permissions + true + + If "true", enable permission checking in HDFS. + If "false", permission checking is turned off, + but all other behavior is unchanged. + Switching from one parameter value to the other does not change the mode, + owner or group of files or directories. + + + + + dfs.datanode.data.dir.perm + 755 + Permissions for the directories on on the local filesystem where + the DFS data node store its blocks. The permissions can either be octal or symbolic. + + + + + dfs.support.append + true + This branch of HDFS supports reliable append/sync. + + + + diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/all-wcprops new file mode 100644 index 0000000..32cf702 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/test/resources/META-INF +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/entries b/james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/entries new file mode 100644 index 0000000..1fd1169 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/resources/META-INF +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/all-wcprops new file mode 100644 index 0000000..3e07100 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 86 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/test/resources/META-INF/org +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/entries b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/entries new file mode 100644 index 0000000..6e4ed98 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/resources/META-INF/org +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..f471e9f --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 93 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/test/resources/META-INF/org/apache +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/entries b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/entries new file mode 100644 index 0000000..66390ad --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/resources/META-INF/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..3536f58 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 99 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/test/resources/META-INF/org/apache/james +END +database.properties +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1182252/james/mailbox/trunk/spring/src/test/resources/META-INF/org/apache/james/database.properties +END +spring-mailbox-lucene.xml +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/spring/src/test/resources/META-INF/org/apache/james/spring-mailbox-lucene.xml +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/entries b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/entries new file mode 100644 index 0000000..3647702 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/spring/src/test/resources/META-INF/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +database.properties +file + + + + +2013-09-02T02:54:40.000000Z +0857e3753e94425d868bab884bd3d7d9 +2011-10-12T08:16:00.734248Z +1182252 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1082 + +spring-mailbox-lucene.xml +file + + + + +2013-09-02T02:54:40.000000Z +aa83e4bea5b24cad5c7e35ae026a7580 +2013-03-04T20:31:08.236868Z +1452487 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +1893 + diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/database.properties.svn-base b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/database.properties.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/database.properties.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/spring-mailbox-lucene.xml.svn-base b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/spring-mailbox-lucene.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/prop-base/spring-mailbox-lucene.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/database.properties.svn-base b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/database.properties.svn-base new file mode 100644 index 0000000..8477c6b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/database.properties.svn-base @@ -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. +# + +# See http://james.apache.org/server/3/config.html for usage + +database.driverClassName=org.apache.derby.jdbc.EmbeddedDriver +database.url=jdbc:derby:target/var/store/derby;create=true +database.username=app +database.password=app +vendorAdapter.database=DERBY +openjpa.streaming=false diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/spring-mailbox-lucene.xml.svn-base b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/spring-mailbox-lucene.xml.svn-base new file mode 100644 index 0000000..506b68f --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/.svn/text-base/spring-mailbox-lucene.xml.svn-base @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/database.properties b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/database.properties new file mode 100644 index 0000000..8477c6b --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/database.properties @@ -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. +# + +# See http://james.apache.org/server/3/config.html for usage + +database.driverClassName=org.apache.derby.jdbc.EmbeddedDriver +database.url=jdbc:derby:target/var/store/derby;create=true +database.username=app +database.password=app +vendorAdapter.database=DERBY +openjpa.streaming=false diff --git a/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/spring-mailbox-lucene.xml b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/spring-mailbox-lucene.xml new file mode 100644 index 0000000..506b68f --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/META-INF/org/apache/james/spring-mailbox-lucene.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/spring/src/test/resources/hadoop-metrics2.properties b/james/apache-james-mailbox/spring/src/test/resources/hadoop-metrics2.properties new file mode 100644 index 0000000..e5a9785 --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/hadoop-metrics2.properties @@ -0,0 +1,8 @@ +# Configuration of the "dfs" context for null +dfs.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "mapred" context for null +mapred.class=org.apache.hadoop.metrics.spi.NullContext + +# Configuration of the "jvm" context for null +jvm.class=org.apache.hadoop.metrics.spi.NullContext diff --git a/james/apache-james-mailbox/spring/src/test/resources/hbase-site.xml b/james/apache-james-mailbox/spring/src/test/resources/hbase-site.xml new file mode 100644 index 0000000..1857bac --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/hbase-site.xml @@ -0,0 +1,49 @@ + + + + + + + hbase.rootdir + hdfs://localhost:9000/hbase + + + + hbase.master.port + 60000 + + + + hbase.regionserver.info.port + 6030 + + + + hbase.regionserver.info.bindAddress + 0.0.0.0 + + + diff --git a/james/apache-james-mailbox/spring/src/test/resources/hdfs-site.xml b/james/apache-james-mailbox/spring/src/test/resources/hdfs-site.xml new file mode 100644 index 0000000..9284b7c --- /dev/null +++ b/james/apache-james-mailbox/spring/src/test/resources/hdfs-site.xml @@ -0,0 +1,33 @@ + + + + + + + dfs.permissions + true + + If "true", enable permission checking in HDFS. + If "false", permission checking is turned off, + but all other behavior is unchanged. + Switching from one parameter value to the other does not change the mode, + owner or group of files or directories. + + + + + dfs.datanode.data.dir.perm + 755 + Permissions for the directories on on the local filesystem where + the DFS data node store its blocks. The permissions can either be octal or symbolic. + + + + + dfs.support.append + true + This branch of HDFS supports reliable append/sync. + + + + diff --git a/james/apache-james-mailbox/src/.svn/all-wcprops b/james/apache-james-mailbox/src/.svn/all-wcprops new file mode 100644 index 0000000..3f1cb35 --- /dev/null +++ b/james/apache-james-mailbox/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 51 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src +END diff --git a/james/apache-james-mailbox/src/.svn/entries b/james/apache-james-mailbox/src/.svn/entries new file mode 100644 index 0000000..21ba034 --- /dev/null +++ b/james/apache-james-mailbox/src/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/src +http://svn.apache.org/repos/asf + + + +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..3a6b32a --- /dev/null +++ b/james/apache-james-mailbox/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 66 +/repos/asf/!svn/ver/1200453/james/mailbox/trunk/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 75 +/repos/asf/!svn/ver/1200453/james/mailbox/trunk/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/src/reporting-site/.svn/entries b/james/apache-james-mailbox/src/reporting-site/.svn/entries new file mode 100644 index 0000000..2c4071d --- /dev/null +++ b/james/apache-james-mailbox/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-11-10T18:04:52.424097Z +1200453 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:39.000000Z +4401453c8a2f4618a6a18e70585da69c +2011-11-10T18:04:52.424097Z +1200453 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1239 + diff --git a/james/apache-james-mailbox/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..333d83e --- /dev/null +++ b/james/apache-james-mailbox/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,36 @@ + + + + + + James Server + images/logos/james-server-logo.gif + http://james.apache.org/server/index.html + james-server-logo.gif + + + + + + + + + + diff --git a/james/apache-james-mailbox/src/reporting-site/site.xml b/james/apache-james-mailbox/src/reporting-site/site.xml new file mode 100644 index 0000000..333d83e --- /dev/null +++ b/james/apache-james-mailbox/src/reporting-site/site.xml @@ -0,0 +1,36 @@ + + + + + + James Server + images/logos/james-server-logo.gif + http://james.apache.org/server/index.html + james-server-logo.gif + + + + + + + + + + diff --git a/james/apache-james-mailbox/src/site/.svn/all-wcprops b/james/apache-james-mailbox/src/site/.svn/all-wcprops new file mode 100644 index 0000000..1cb00cc --- /dev/null +++ b/james/apache-james-mailbox/src/site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 56 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 65 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/site.xml +END diff --git a/james/apache-james-mailbox/src/site/.svn/dir-prop-base b/james/apache-james-mailbox/src/site/.svn/dir-prop-base new file mode 100644 index 0000000..139a715 --- /dev/null +++ b/james/apache-james-mailbox/src/site/.svn/dir-prop-base @@ -0,0 +1,6 @@ +K 10 +svn:ignore +V 9 +.project + +END diff --git a/james/apache-james-mailbox/src/site/.svn/entries b/james/apache-james-mailbox/src/site/.svn/entries new file mode 100644 index 0000000..9d09fa2 --- /dev/null +++ b/james/apache-james-mailbox/src/site/.svn/entries @@ -0,0 +1,68 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/src/site +http://svn.apache.org/repos/asf + + + +2012-04-17T08:09:43.782591Z +1326982 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:39.000000Z +f6a351f31f1811d1176061f839fd19c4 +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +2702 + +resources +dir + +xdoc +dir + diff --git a/james/apache-james-mailbox/src/site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/src/site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..0ab1cd4 --- /dev/null +++ b/james/apache-james-mailbox/src/site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,69 @@ + + + + + + org.apache.james + james-skin + ${james-skin.version} + + + + James Server + images/logos/james-server-logo.gif + http://james.apache.org/server/index.html + james-server-logo.gif + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/src/site/resources/.svn/all-wcprops b/james/apache-james-mailbox/src/site/resources/.svn/all-wcprops new file mode 100644 index 0000000..de9a049 --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 66 +/repos/asf/!svn/ver/1127486/james/mailbox/trunk/src/site/resources +END diff --git a/james/apache-james-mailbox/src/site/resources/.svn/entries b/james/apache-james-mailbox/src/site/resources/.svn/entries new file mode 100644 index 0000000..0a8ddd2 --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/src/site/resources +http://svn.apache.org/repos/asf + + + +2011-05-25T12:02:14.365946Z +1127486 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +images +dir + diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/all-wcprops b/james/apache-james-mailbox/src/site/resources/images/.svn/all-wcprops new file mode 100644 index 0000000..3897912 --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/images/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1127486/james/mailbox/trunk/src/site/resources/images +END +asf-logo-reduced.gif +K 25 +svn:wc:ra_dav:version-url +V 94 +/repos/asf/!svn/ver/1027054/james/mailbox/trunk/src/site/resources/images/asf-logo-reduced.gif +END +james-server-logo.gif +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1027054/james/mailbox/trunk/src/site/resources/images/james-server-logo.gif +END +void.gif +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1027054/james/mailbox/trunk/src/site/resources/images/void.gif +END +james-logo.jpg +K 25 +svn:wc:ra_dav:version-url +V 88 +/repos/asf/!svn/ver/1027054/james/mailbox/trunk/src/site/resources/images/james-logo.jpg +END diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/entries b/james/apache-james-mailbox/src/site/resources/images/.svn/entries new file mode 100644 index 0000000..c28a25f --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/images/.svn/entries @@ -0,0 +1,167 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/src/site/resources/images +http://svn.apache.org/repos/asf + + + +2011-05-25T12:02:14.365946Z +1127486 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +asf-logo-reduced.gif +file + + + + +2013-09-02T02:54:39.000000Z +8d227b2637c8f3e1cb2dd30245a4cfbb +2010-10-25T11:09:51.615344Z +1027054 +eric +has-props + + + + + + + + + + + + + + + + + + + + +6636 + +james-server-logo.gif +file + + + + +2013-09-02T02:54:39.000000Z +c4604d8f5a85b2e9a9a2b0fd49c53fa7 +2010-10-25T11:09:51.615344Z +1027054 +eric +has-props + + + + + + + + + + + + + + + + + + + + +6944 + +void.gif +file + + + + +2013-09-02T02:54:39.000000Z +56398e76be6355ad5999b262208a17c9 +2010-10-25T11:09:51.615344Z +1027054 +eric +has-props + + + + + + + + + + + + + + + + + + + + +49 + +james-logo.jpg +file + + + + +2013-09-02T02:54:39.000000Z +019e0d809e051fd8c3b7bef514be5001 +2010-10-25T11:09:51.615344Z +1027054 +eric +has-props + + + + + + + + + + + + + + + + + + + + +5180 + +uml +dir + diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/asf-logo-reduced.gif.svn-base b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/asf-logo-reduced.gif.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/asf-logo-reduced.gif.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-logo.jpg.svn-base b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-logo.jpg.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-logo.jpg.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-server-logo.gif.svn-base b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-server-logo.gif.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/james-server-logo.gif.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/void.gif.svn-base b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/void.gif.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/james/apache-james-mailbox/src/site/resources/images/.svn/prop-base/void.gif.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/text-base/asf-logo-reduced.gif.svn-base b/james/apache-james-mailbox/src/site/resources/images/.svn/text-base/asf-logo-reduced.gif.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..93cc102077a940966a0bf6d77e74ac273a10ef8f GIT binary patch literal 6636 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 ifk3o@FNQYto?ua@#Ta7 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/text-base/james-logo.jpg.svn-base b/james/apache-james-mailbox/src/site/resources/images/.svn/text-base/james-logo.jpg.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..6752e5e8a2072ba67f2ac7533998ce602cf23402 GIT binary patch literal 5180 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 IMMatV7ejgbCIA2c literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/.svn/text-base/james-server-logo.gif.svn-base b/james/apache-james-mailbox/src/site/resources/images/.svn/text-base/james-server-logo.gif.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..f69394f6bbee48b5b336395a573aa484fe1e05dc GIT binary patch literal 6944 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/asf-logo-reduced.gif b/james/apache-james-mailbox/src/site/resources/images/asf-logo-reduced.gif new file mode 100644 index 0000000000000000000000000000000000000000..93cc102077a940966a0bf6d77e74ac273a10ef8f GIT binary patch literal 6636 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 ifk3o@FNQYto?ua@#Ta7 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/james-logo.jpg b/james/apache-james-mailbox/src/site/resources/images/james-logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6752e5e8a2072ba67f2ac7533998ce602cf23402 GIT binary patch literal 5180 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 IMMatV7ejgbCIA2c literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/james-server-logo.gif b/james/apache-james-mailbox/src/site/resources/images/james-server-logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..f69394f6bbee48b5b336395a573aa484fe1e05dc GIT binary patch literal 6944 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 Rm%R`XDRFtRG7*C>{~zWa0+|2+ literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-mailboxsession.png.svn-base b/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-mailboxsession.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..294dd6a0e065334c37fb9bf5b5301ff160c6ee17 GIT binary patch literal 17781 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 N44$rjF6*2UngA1Dh9dv~ literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-messagemanager.png.svn-base b/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-messagemanager.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..0cddb2e4129aadee3b029b14a2a64a1a3a56c707 GIT binary patch literal 45433 zcmagG1ymecx;ESp2o?zL?(UMHf#4S0CAhl<2$0|s+}*8lcL?sTL4yZRaObb)%$b?* z-aGRzibZwPtEk$$YQK-Ygeb^MAS2=FZAN~C~mz%lxjIvD*2Wn%p_YrwDHCM_nY#|VB7m>t?#9}idHq{jbX@p^f_L& zg_xnFu6?n+Gxl!@wO0feeW^L31M2&|rj7-`OEpxTw>)ojvTfw7YH`M3TI9m2CNR ze-Qa_deY#?vj%tll2;ZuQAva}^r(c*2e%K8)`|rmhQCn2BKG8|nNKOnX%`N``(`!xleP?MpEYD!JkfOAt-QZkiG1=p z-CgXx&-A*^eC+m#U3<9Z$rL!e?u!-jJbNgpD6HmBjeJ!tXq$$hh#~jWk(PtZQbR?S z>q6H=WDJU)GgpPzI8JTZd#>S6o=DUj$ZaDk;l2AwuKv?bhtSj|73lgpQ5*C`0lME; z@3@2P|GvS|2YoMm?fqh6b$M1tW5fyP;S;rAI{%+mGKlEn6g)!YB`Abu0kqQrMo>CW=B&Sn@8sUz zcXG&8IvP^ECqo;t8)!DANxI+=LG_)~Tl9i0saW75oMbH(dm~flbmzbjXu9y`8|g15 zvH_@F2K}41dI@4EMB30CTImkU+dCQkf{f3nMF;neY?dD%9EDYpswdKk=S_#UfAHs- z8E7#T?O(jnmCqdtA=PjTTij%TRb?u&6dn~ntr^~y&HB&+zehF5OtodG^0C!PPZbHA zvehV9zrR|?C!JqZ;K`87W%6e^m{Vl5iB=>fD=B;!toTqKi!842(1n2^NozYrRSDR5Q)v*DcSy{#Out* z*r!etTP15_xug$OAkaCTc&0rL_L+{7E?y3kMO;8H|Ge1>lTwd?mkG((yLqYRI4KMA z*RHJGbomRCDKh0fb!FWdD&YRl6^`1>R16^oL7Mz#(Q!-cjSM0w!*Tv_9_GD=DUAcu zdNG3aw~>}EXxeaDW>J3U%c9>>_Twu0En;5&iq$1uQwudT2P+esU}K+kay-;7A)=Sa zTE8hVKn=4`7*aL*!7!bRE9GJWAA|fw0g&9w`>g}gJg_-x%*Xcs`@rmpl8FScIx2454VYr^}^Eso3n;DdEKJbdMVSY3n zAFvcw-3=g(yNdr7HhjaQ5_-Itdsx$JajZGlZo27YIreUDyU_ep zc|hRf+^?#4DsJJRf0;(VHlhR!P(JI6y?iFI)~@#=bI^)C^DVD@g42{%2VOW0@gJx! zTS;Tf4uGqWa|$K{hzzpIlndk#~Yr&r;9}0o!sRFVW0haTD>Hg^?rv%*9v4(93$!rpBQRs z7VqJMl0IfB#a33$x=!P<o1&LNKa?f=cX0#MZ zWLv6Nx^*g+?IjQ1I^XEK=~ce_ZhH zo}kd%$M`(6#VsE6B|?9T6r%tdldIBOGmKN^L~Y{1?3-M2F_%}OsOOZ}YtKqQG|5N> zl`lEvBO>kuy^R_}$w}y61Xy6y-KpOxLRq$MqFdE8^k(4rrW8SirtGH?=P>lL($r%N zIt+i)fqAzl{n55E%5XGbZfsgHCRC_&zq%GBI2(i&csPV=Q#;HxD4%h8Jw$s@;ycIC z6G(yovN#xDsmZSNHsmWQQ_v&&#KHn@1~`ngC5#=zkej(A*P2NAIPBBF*BO3%kgp1) z@EI8yfwI8#eF><>Zf03Yk2+4weX_15x^iN7RomG|s_Fg7)zQeC-$TEWBEJT1$LiGB z$u2`JpDN%n6q5ZEBBlS_{+~P(}1G-{2^nK)_k3j^RFL`*P^I_l|OcWHf5m{ zPM+qX3i8SGgp^Oz(~l?$Gyd+pQHD@pG}AW^qc#ufk$5GRJ7c|F)ma@_hwopzT)p7L z`=-x0?@xU5bsB*WJQPy*8|GF9sLFzE`H5?-7Xy*vAD$?0PE#9FT2qqW79Zw*D(;O+m^b(%nVfP}~2oTXD zPh(YKPoXELWwr7~O5MJSdwI0LXw$pQ1Rq_y9!854MoRf+YNMTSC7K$XcsHv~F9OXr z80!yG!0hJ`HdsApYRri9W^D{_20zvBSAsWs12j>YlRNBxWx*M`SVLnWp&%FLvxEY7 z3wxT-(STC%{<&jrJD(-=d;TkKHLO53nkX=OLzMi248p`%q8Ko=%bqeUQaq8h0-vJ| z1U;{BlqDis2PPyIiQ6ex&NDhLxgLDM*fWgX9Qj|K=sQ*m!LX|GAfNYBguzI$q$3Am z)*B*eQCYNs;&ZiOw}B#_pQ#3g8%S|xroKu%c&}r?A8{<&lX=aSNN!%^zmV`;hqcAX(Gmh^hE2|p3)D}Q3+0-!Sj@hmF z<*i*ea`Y$O9g3PISnP`qW7>N4{HC=XXA(kC=^u_Yxu4yAgqUyMjjB@FQl(F{DNek4 z0o+&)M@&>}?j;A;u_pr8k^qQunEyi)d(yi7*3*Ixxo*PwoAFqD9FKO>Ra&(?6Su-m;+++W7^)RO*kP4E!Bb{T23RCRcUu!MB zBF!E>JkZl^Cq3va=ao+YB5_c(tT{h;^hxHc30l%Ox>qWhwCI4%QSv^t*%WA;%+ z(&HChxZ-AVru?s%2u^flD+I{pW6YRtdCZx|=u)p;XC1mI zi4{#vcA&@Yb*N=M#>0T+b?7mtf_lLwKU*I1@&MWw6+>q4hS1uIeKRF4{VUz`8YpW$eJp^u?(TZY+q0j1?;e10A-m=55%yU*gi z$q`wjNNO;U`Rq!@H9ksd1d7+;P(*=}6te(&EGRBH>W5fV!V~~a&QVgn2Zg)m)Hy4) zw#}^!{`?e73NKPHdQ7tV*m-rsvr0U;^7*lZru|H|(EhTe{q#g?Fa(w3-5<~d)4;rD zP}~oAER;N`FJbn!7`6TCF$Zx8Lho3~`k}IRVZFjUXJ|_XykWF>Yn%288!-%_LaY~Y?OP) zCDw<0*+oBPE`#Abf>nsBdHSpdhc+}*8)*YyUYF+$CAdKUxcRjR5=-8+S-jKtP6aux z+KgzUmIpZJFphU6Tj2M<2d0JnRzZN9drq=&f5&h}rSS0gc2Wugeg|Sp1UU-F0gmcf zcL%(PNGNO{->!PA4IeqYFQGlK^cw+;4Wy!KlV#^F!?xKOHkIJt4->8*(B=<08NKm|+)6_De5N`0Vj5oP;&QTot*A{j;@r?CDi zktqv80^dv|@i{!o0bR!D{lB@!m@O}XC)?RCE@VYv*s_-21G!O~njT?VdF%|Kk z?W%R9(W8WA{gxgP8Wp&=QAw*MP5JUwUPx##{50AK3$yuC-m>NO5FUFSG!t zlWmh9_w>V)pg5A@Zt9gZ$-SyeK1Uyt<7VdqWDG8=O=z*KN#c}O^ls_v9(?(VBIudP z^(RLA_DSv08M$ww7@8z{KFkCrr;&)=` z)Xp$>VUvu(A}saNMN@D8gks-SafGBCX(z7>N=|u$ORDLfFXoU*!0YhOYwi;}TRR)b z=F{9%chR}?P#7t2ToMedwtbnMD1F;v$bXeIlGnd)Vn{yryaM|{F=qv-RYyBF8u|qa z_I_U^f^~GV*um6AK_56M=^Qg#**R3!O026dmU~M6=t0Mwxc<_UUYDwZ9z08aK2b}0 z#vjXchjH`=OTd>3DJw`XxJ%nb-ZFq4g?hbR^GXICSjY0jTJv!-_#Pe~;An^iJT`t$ zRuBogZcOH$*m<7<1<(uY&$@?2`?%czGfP5s+^o+*WejbGr= z=4lwxR63V{cP=b~^lPT3{-D}pOPdoaogMbQx*jSgd?Cmw4UNENtT)OgogWhrbqvJk zGE%N1@HXf`B~q_9|9v`Gt@-NXRW>$}E6q+KU zW-->>3zXm7=|;Y-IFf)sNu_eY0%up@36z+er(8OR{XC*sK_#2Ur^np2A8H&G9br+)jI)Ni&xPE{gN`FP+E|SbVQbxo#0QwwIQVCEcD_ zBlXFAt}Q`SGb4awnlpkPSnlDFy_yLK2t2prYluTuu4gYWnv8E$?u6@MN= zXfgmCs*4bT-}y-rfU}Z6N~pSE&VD(9l%qo`Tx|#j1h#)rB8Z&v%#8rm=6PpATjV|} zEPkx6OeBSr(5L>&u+|rx+QM5l;mTAHD{yoNQ1O^GJxfC1p+GB)=vI>%MQqa0z}}jY za%xJNWX8nGlB8)r+W#&m*mU{5(#~ZjrEbHYj}AL4?XdN@EvvntjwSv4`<_3M>UNLT z>|_wU@63v}$IYmAM?`3HkNF<^`7EcFB$qeyDBj4uV5LMFT>qX8VMI|(+ zs{I?GD*voS?n`=cl`z(RdE0i>A|!CtM&f;tdR2dt;dSJ)_Ff!c&Ap)ZlBuHM+a4uO zE>JqC^C}`4u#MS5;zDw$xGOSB*x6cMagfo!8@+g&j%S#dT_k}t1Xq!uoPbeN-1Rdp zEZCRs@?)c|s0~aJK0R1g?GzdZoX5mG;%h%xR0qhYJef#Kv08x074~xl^*!OZeLf4% z)O!HRdVCfy)?-$GI8r#br!}@jv+zY6rM)ZL_bM`iUGY_*M~e#(Lq#W?0T*!f!@~K^ zPvP_`WHEl@6jtoc*&3{sEK~OCrOdc%*ZhBI7*N@m)L?#K+3iv7?L^lHv9OPry%zG> zABe`myOmt(PRvVPJ+q6f_$5rON@S7^AzJ?KMi07kA%3$|}=Dwy242r7i=;#7sUOJJ%UPwlJty1W7%_4vkkXP_twa{$`R{(sQa_qHZ_v^5{>T>q;X~a~E|_#WN0! z82IM_3YrX7-LnmY0)2E`9JxKWqIAAD9OOuP#zI$`vvv+T zoxXr|DuCmcTh;a#Vtiji?Jed^l&un!Eva@HQ0KptYiXXKX&DI7mY_K*Y?js}gI7|w z+XGmiPo=w$lcyDAyPF;@D+B7N5KvCj6R7RB$S~%DBB2EBF!r*))u~_P43RDvFm7fZ zy;|gBPUOK&e68L*FcA~8<<<+0iIqe^*<`+WEW3dDmBy-D>|#6o^X&2=*-Rlzu?f_< z>YiS(;#&ebw>d&vXA?>RzhyuGM9a%}{w*uC0uPzcn5?^Tm}!}+rdK=*n`Q4+jrL)C z%7y<)B99kqOUsPfHY)5qKrR4o7WnUF^+3^(Qaki(NuJqvV18zJhud2FfkL|~h{p#8 z!@AfPUn0>lbCgP2Y|<28zOJ`AErR7DkUHq}3FBT?$=qAGftTuMN!cK8p?e8Tyc)C3 zc6e*APU>vFQ z6bV1{jqs;}m_7J}Kr9P7xq-aM7Zeyy3e5%M#=ERd;}e8e+=JA;mN!x$Qn@&iHe+7j zPL#kR=y-h&$6&-5SCib56N*!jrEXP&2xor1PqoRn$yQ` zO;NB7=2~cK_j~k8teuNm?d5&)EIZBSg%NI@HobcNe->&MnFa*uE`X@U3J4V4~w+q8-}cI z4i5OQ@GkSJ(dY>!)dyZ$gB7%2)&o>kg|Hd+h8OwW)XS>dgeG^O`iCnlvW`=2x^f8U zXetDrOKALBeWsm81dGnA?Jb7pL*w!aD(~((vRLgJj63t3*zdrgq2vK$H4R< zXY=lhC*y={u;#Zp2@5ec&Pwkl1HoT)a4w~0W0EDLz0jNKXAG)ASmO3SOew=k~36XLkxkr5fAB{_;X;{N^E;jH^`r8OrW&a zqS_z2VhmGk@eV`fO}7b>ITwHeru4cC5G}z?l*FpDlYT!?%RX8!Y@;6D!B2sH$`d}b zUx>@_yz_t1DgP1uT)A*g1l&91Y2}!Ct}G7J2w~kx_53xlwtPQPTF4JfiNKF9v0El z%ynV>Vj+{929(DR_k(DX@Qg>P(`_C*_u+UV)mciol|I>JT!yA}l;BJE{uIaXqG08{ z{R$%d!k=F+bz8(yYqX)eb0LUhWMHZP#Z0zl&NeEG`zy3B5MsofJG#9ooo78={hQ>V?Xc^v%TbiUzZ z=O;(C!&GRT^-?NPhbAnvzN;AOIm8_CF9LDR?>d8mbpvyVDH$^DHRUzsM+y-AoS<+V zF2Kz|x&MNQ2UjPJfyu>4-{UySFh7(I?}i>TqoyMPTr^*;5tDMS{{|>mHfZFMsn;j^4DeBBb0~PJPCu zNu@6W*NhvNd3t^}(fcxpWQazJgJjlNTF=il1X!W>SpC1-We}t+)f3FlMvB3+_C6$u z`Z@|AQn&CR^Cv0$Ap<}q4*&ikhC+|5wg88$gfPh}*1bHkZq(HbvG_Ejgh9kwutt;D zutcLJbfLlJ2YsolOf_vi5B`Y1%&~8*aI>{C2~mHiEnHC(xTh=+qnQUW#efuk%70bq zG`*oYLg;4IFY~(RST*A)rJCY;p$YMr zUwVXySHB3*Fx?0ZZEWs^BIuo~b+yt?EcLZ+Pz#${fi0l_S(VFuvEYz)n2^-y;tcTZ z;igCm_^I!!Q`7y#Pan@7cP{6ix589yZ4&EMpWB5^;6hgi-cy%GD!ll-Nn|ctcbcNZ2H71?5M+=RG_dHAE z16Gw5?4&IM_AIeJoxmL|L~TyJ-G4*;E*+Q}%GD{P@pSK}M?Z{6VgZNiU-Kv=e1CpT zUhxRIuQyF!(6Yra1^iH*;`lkGgl|cdlD@`@ZA4PeH=+%&5$u`H{Kb>j#*s?${P4^? zUj7Ana0|QBSF<`coQ37-T z=KLba(Zm)*MBk&ub;avk@%JgGGa>?OcRAtG@7aP!-Uv^KKF<07hZHDgMgWIz+wC2u zgYRzz6o8wD>r5%7L`jjriBs=1y2)h@ju|*+X1}eqjgbqTJ&!%y*ZVi}U96Xv5a3?V zQKYVqR+y$&L@q7Kn6d}I*b3K!A`V0snhSnUiQc%Y7Es_Gek@fFF5A&<}()~ET;z^=`Y`(o08e^QH=ZdLE# z*w~(0Buc#SKk3zaYC%ASc>($kGglKJBf^7$l1CP!DzV8h=wkV00}4c%6t(G3bzNII zzzRhSY79mTI$<-5iNPQfAI{T`s%XK_`Jq^sW*a|$I-eMG%yIZk&ZEkskQY=s16~~^ z0PE*7Z~x&q7A2R`!+ zddFcPDHs3YpW1nvT2z=HB#R2L#Do6DuRuvTSECFD|4_gWSA874SY{h(OjNBe$R*`i zynPYP8m*zqUJ9o`FNeH;77dDDx^4WW9xOwA`eyt6jxEFdX&!9_Qwv{v%DHlS ziDU#ts8B@zp5&F8dlafQiIO6t$cLSCx1StMx!u3mUsU02m0I=1xuvNwweaFzF`~ z>^gnDB&z3iPvNs+O>{nX!|C}f(LUcR_Wk+?;gO1U#%@PWE{Es&6^9_H;!bo!?Q5}3rk(FtqQFz zzow$i+wSi8!?@Q;(Aw<|phuYLd#*U0V3F@R2<&GuGWtgSlspEn^KKc=RMN^T-0p;l zaV@rw=wwby5%sMitD-rOnP|)J8R<&KtxNW?2M< zr`d6WKoUV5T8crY2Fh@3EQOMO)H!{*=5wSqx2C3C)omzFcR3Z)iVyWKqr3}MHQgXO zedWN7?r&j+1DI%u)U=iwaX*ZRguxN-A_Y5{k4JjfhW!vyuyIZa)UPhHM1I@D>@h3nEb;rf$N2H&1d}drB zI|kx#UkZzbV1PPAo9GG$fOd7kb_LENL~p-Mag19LNR9`hR!*U-6x%?kMy-rFS-T3K zcrxMeRq5fnDt8o;GRu;2Q-t3#y^oyR$zRg;;*PSBu6cB!X52?g+#n0p8mN~2g;)3} zbLjX|PEtTXYrhrbhB+SUi2FlQ6OA`+_KhwG`aMw4P^-4PBH@W861Y#Gh zpjez9@^{SGWA;A|9G)=;!fUK+TV)cASUKG-nBOmEbW}IgrVv&LU`JW;$F-%~&(r|V zdCl{8^yA&uX8Gm^>NWS_5_N}vpaAC!G_63hA&6K}hHHhy^riDz-#@rhYtB@0YLU>A z_FIRq@zQx)WMwRqc{nu`WBdCirJuthjI8CSznJ$D|3xV1Jda+tt$JM4`?&B_+{C>; zP6%MdDI#+%Of{_rui8Be(-x~rJWOrb_;Z+k;3siAEpSEU7AIOyS>pm}6&4Gpg|0(- zo&}@K$nN7}0zV5Ks$U2N;d*wd90n8i)z}azWEuMC&kX93rs5?bn{MqWz;ySBpKV5eP-h0o_yioVZifPhLgP0{{6FU%}0GgQ3QaKVMR#z9m zOxv`hZx^t^5xc?2Y=s6YsLw5Mawvo8F@LP89;1RG4s1r9%Uv8e>RDXms8q8K3 zS{#Qenq^}mxG8gg*6Mg=QA}jmjLd6zedh?a!}*Q7nrBgd-#kRSk0Vvfd$%OI=l?*p z8SMN%fUGh-BUQNzYXeeBFe7saxrmQs@U!?x^{G6fZa8)*p665nkM<&;_H;meWXLiU zX4Bbbp?=Czm;D*yG!t781@)w){CO*T)EBqD@@+5MLU_lWTAR2Xi2voAb-a81>Do?N zA0xH_PDFtQ`6pA`@8!L#6QMT%xWNdD^gjAs1t1jVgCgarc3;Bb{{lZiibmJH-eU*fZ9YHZU^7-Z7RwX-w;Vl4?wM zw5wyCD^(-NiWBAYUB=v^+F*l^2`|Am`=X0?xhsH~Rb2@`sfYi0cXRwBz9@oTi4j_$ z2J>m1}NgT~RQW`oI3! z;%5sQigQw`1BJdh+~3yDx$ofz%=%o*XR{tKU$wl^|A=3TYX{g@rGYOou$4zAy_{@C*pY_TuTa9E5~gL-7=HffS^^lINU3$RY%+*70x z<~|V&J|4j#8#V!grbf!EP(y3+KP9wXf6Z?X^UhSI)xNO)#B0y#){g4W$jaWB2i^Xb z8l{(CVw`JCyqc@(`o zy4&Hg!8h)VXx4F11LNu(PM+gqy;G#>6pI_v?zZ(q`B6R?4u}SJ#M@zzKPor5vy*7G?Xbf^wd2)9`-; z1=?3)l9|OJUjNqq=y*Dv84m|bX#J5DS1WN0)5RNn4Zh;9hq5c9h@R}N2}uR%E#DN+ zO>|!flXZ6HwDF^euYQs)O0U_>5>`t|7>Cc7W6@zhhCKTOYz&Kw*^UVeKj$iKE)>X; z7+-OWumTd0)vCItcVO{ZgBNXrtDT7L$Ug%G3@Cp)FqVri(Eow4T0#c@l_}V3dnA0% z)l3b*BR%)+!*=PIFh%9`(^a3WE?OK@krBgMLFhwuT~`@XF+$Th7O*Wz&#J`sf?4@Y zl~-zlUxB}FO@5&Aj2;SIizc?~`yRf(fR|Fx%I9&Nw%a}+5CrVKS4*ArU0eR?mgay=MOF zM&(7VI@sql!uBu2USj?9Q)_X7s68o@L|W>~jEl#G9X;B9u^kulwH>(l3FGG@e0V;B z`=7tWnedNX;WZdR&nRq)_HQXBSQ~J5{sL+L!}^J$K230}nnL(ID#xKBn)2Hb6W^E9 zLY*nZ<6|Js%%JTOP0G+QgXllO8n5x{_i4Pogk=KFtbM-+AdbLzCWwd)h%|grR=zo{ z859TA%6LJ#RzeR>*K&O5H|i<@#Fi@uQbJ=PSMO$9)a!^YbDQVW%|}MLNOI-zr`L;A zD7eUu3ZYaGFtS8AS|+F$-RXc-b7{|qIWnQYU|Pt_^Tf2){}Ha6(1ixk66M1z($rk) zlZvHDC$?YqMSv}1{A^d;!A3stKw!Z+&@jfy$6CjznnKJXs0~NuZ51|3+-s3VIsrPD zH6i;}8Pz2NA#jE1zU|1}lzmaGS5K3xIE@lgHEB@sL;A&V7Z9%2{SiKH?$wx4afM(D zMDof%qv&FMk^z9)!|LTqX2#v>>yC?2AecAae%ZWsqw{(?IU%wMmy0p<_O_j$Hc=UU zKBcFQg^eNgUheIiRa^PDG2Gf2s8W#!$4$UeDdI~JqOO;iYKVL5Sausa>YGuDXodEi zy1Q6l7SyrmYn7dRWQO=ArGV9$Lrn>(m4X+QGfRnQ zn8;bry4$pnFTOisZi@jB5&?RXrRr@}6>68^BzjI?U_>XL zeIX3z4AK$kxEGgP+f}aLZ+l}xa`SDtjwpXv>>TF9j4(6Et^XH|=tC^4N0R&~JHN8h zFG%dK%utvlE(|b`Pd!eBKI`OpjKDL@nYXTI+Qffid7>^4T~tm6E0_q5L2A z4|mG`NPMz_2Di=~r9`VpIi@034>?5tpX1=LG4gxWvsy*i=Mps1-~) z5(8-MPCuvKsxB)H zK5luw7M$|Q{JiXPKe)wb=RT$f0Jkbjy2uXDM45!>UrBaQ9>M4K9ZlB2egN;)wTx(1 zc@dVD4|C%Hs8;C_C`aZjA#d}0yOkk82HttMU<5YL43?TDKS#}YcuOzC98WzR# zZPmOcxB{F~)|@seq0wit@)EStRa-?_ry6X8GzHj5>`SVlNsUU~<%eL?Y`r;4EY%VV zvej*e$}J10y!51y+$c{)u72+=VUaMrSjZUtE7dW#n&dVUAQ0h_AqTmy+Vtv8j_gGp zu_Ln(GbSmhocv%PC>;&=hEA?-&2Bq- zEZY4S2g^Z-3Wo2Sz$k#_n3WeT)~L5O<2oD^pXI5gQBYae|^ zKzxG+d*O2&WC1h0R!AEuNqpbZrwKl?`#c;F` z9S5d~m@UwpEl#?0B7gv!I24|Q+fSSFy#csXIqFMN$BiN70k!R*GmJrZc96~v^m?6t z8u%7YX$O$8BKEaw)wz1U>)-Zz?GO)6ieV)LRZ9K{jKZbCB#au4BT32?HbsF zuVUR?hn6(vT!y-8qv~G`+Hqq4KHeJ)z zI6C8)J5nkkvQM7@tA|&+8i-bxeR>XMd|fD>@}x|d!rBm3?pN$e)J2n!u?jme+6l+j z_P8Kin!8*m<8#Y=<9yxlx}(u$*LEq}s3PM`4+*eUH`iUA@@A;>i9f|9Oc~Sk!Hb|{ zpq{bl8bh2iqs!1Y26wkqUe&{j=m7rvcB#hsmd(i`=iZ-m8ts)UD-dCqMpG4L^xcEM z_4dsFD}6blj8-@`2Py_oR*?N)T9q!AXxUVBcPNuX2R?I z2gYfE-pi@_qe&enrOO8$#HuC5??i-74al}pKbg;Nx)lk2$vxwzT>s-ELYogcMrgox! zMhwBxL=zR(E0;`=$hz$t;@9T-y)lyl#v*jSzQYVU)$H_7L&))=j%raWxh*Nf%$CgyvYvP(F8AMgYO3#bpXLI&{mFa7f zRoeM;<2u^DGgmID{MtfcbsldvvXT2zf(pL>U zf?WH6Y&mi(HrpNR1}DzZVxc|+vEJ&-b6j81qYC?3*v=m98!J*c(P87Br0M@S>gU$^ zS0cyqh1allC|*@hXj_I_PaFNS?-r1GSy7`qNN}_7R)@-x1&zj;M6I$?KGtQ+p>EnY z&82U=3b%+al_Z-q1o}dUxm?|-F7^+Z&DyJDZ6#%h6A0_5?8v`^B{yf4D{j_J570Gc z{wsaTne~=)=O4D(fcI3^-zik&|K_o6gy@W1!T|jqDdzXFAyIwR#(=s5OG~ft2y!^M z>{oKJPkU?6+gass(n@8K<9zHO3l3YEnY^y8iDqcWVCj{mu{+V`;UL03y5P9Hdn=pD zVJEw?7)%5)-}q^%K7 zYIxIXt{K(c!p$dLwW7z#nPppcHm%Zp8*5u;h3=^gX@6t5xc3bMGQs?@TaNA66n3kIG_mP8#VIzYLNV$zh1IrI9xT^{^z=G_Eu&)|Qu?sz^w$ANt5 zJV*P|(!+q9ObwY)8fNjZ zaZH69XO!8BdBUYoYesvE3>0TzX-_b2=))B17LSgd_N-%5izD*I#zahx;*yd0;T>;! z9J%x55E8P#+cp+GE{O-(22~dn!49U^)qVDqsTo^ELaB$eZICH%$);oh2h_?KO^xh0 zl8S$@SW6#69hWzfAF87KXX^$eGcy`=Yc!t!}!;L^*DEpSO?op7rqHNu9{!{Q}CTC>GEIK_$ zcBM6AbS&W?L}6)VAGOkh0KK|GKhLYe9ITidne0GMPs~b-%N#B1<4Gl zGS#Q_Q3n5j{tiKqx-gG{Pu1{P4)e!)Y6=leKuizl69hwA>|iIb=fsbyqg&PZ+$rol zKl!@TRuV8<+cG9P<~u%?yp7`$W0G9ZYERE^qMM|*|9^eO!;3dd1jwdc{-YAWSZ!S` z$YIQ`^b{}y!ColKa(V}bJ^$nIu2l)%)Y1Q2jI-(&-i2g{9+4m!BJ%G5mU=5{_i19p zn0RYRwTy}>ztg@AF!BbHCE2<^ki$KB{sW(>R8bf)tnW8leFbTsSYU>-$&#U@{7Fs)+ z+Ug+N!`{x^99-&Err_nZyi6?5X%xbNIP3S_qnz_VI2)Wd2(GxVU-K{F)w;nl+p%6o z4op6n`R?T}=mZiR7L$<))W(G8br+x(U4*7Dm?&D&3v0(yzx`6TBxZMdEfR`1%{rNV zPU>!SFjv={(oMPS9F@+QQ%7FDl+JIKl&L+Xqq@7i70$0g?!(OZ4Gv-+7A|G?EnAc4%1>ys|M@SiD$WAZ-{LaNRQ@&((}mL3 zSnpR}&lC08x~ixm?f*o-se}Gk;|k?WwGt#kJ|~g|n1emcVf#u5f7yeBLo0)u;~$ic zNV=~W-CrEcY}QiEGB|d;1uTO;wlKtb>As)hOl8YH471 zlfgKuMXvbq?`9XRo~b`!1yQLL@(s%2vxK~gG{2ZsPyciL$nzU#j{BiInTGd>9vTgN z32gy|20O~X(nsc1K>Dak>@SeT_!NvTjj?UEmK5=^(yvO7EYv2#HGySL$+!ItE;iQu z>Gzs+VO=4>DseT32GOYve>i1)OQyB-u9?cz{47NZBMG=gN9?`v|IPyZFrY}-c_AaF zQ`h6ZECpG#^7B=KDN{6K z^6(Epcr2p zs8`&+ARiU=Kb%9u!oTuLI@T3sFs>@tfq164TLZRui9MHo8>&vI(PL~?*r3c-IpXQiN{^fblL>NVr2YS@Oz=*wy?L3di}iXbK` zEm%NRfNEzbt~MF=Km6=$^Cf3wo%g61)RB(j7=gdU#*~j?;*LEgJN?D{mks;h=46W$ zv$&kgkmnOqlv>nsjeqEyky3q?2SL&`|In4u>bPXo8eALcB|%()S~VsV$rogvp_oYD z+{P+CExGcW|4S?jrO7<3@5GxtQF@0(Wd}mogQS=0STgrlWqOYlHBZIcZ*}SOqQAcs zX=VA^H?5aB!TS@)EV(BCg`l!RV|aZ11&R;RdjY%H^Qq~kP315nPHcHc93Re_v}cLk z>K|rh->bhnbyWQR22QsBF#@gCqdnAnC@;;sAc?{E_8i$l3`5ma1ZcK}-mbW+x0PT} zEmVa=of(9($W7-|W8gezwi@v!e^fG3@k~6OK?y6rUhTlF5o&PsQH4a8JX71)db#CS z=X@}f{9MVdRN)oKS}zras85a0%*+&p%-n3RLu>@L7BH zM?L$NgCa3$qwc=Q+&y)T^7wW|an!Wp`h(`uet+;@9dU*CJtvP zCS37Fl_Ky^$Ho?xw9n&G{t+8b4OUkKI#boOAf2f_Nizfr)-`W0G7)0t z(#1C*IVv&TFQ~w8Z0Vv@<0UBk2Er&eYdZmmW~V(ww4lcXc7?7d|0$M_*8$c{f&2Sn3JE>qUl6A=#dK_uTaAHU=?tTU^<*#| zq{RscV0#@I*6e|15oF!=vTH7{rDLWy;rfEt06Vnq!r>VI_2gshM^8S$CO{k(za|?* zt9bVTjG0Cobf^OaISk#o$J4-C)?6tIswg9W2~MU+pptu!OjopPg2!QUl^)#|PfcFF z9kqXxq26h05Y!EepK2X#g|{9}l9$}SkuA}(9V6;=G-`@th(99{9-uCTMF0k^hLZ}R;~%Yz4MU5ur+XvCpd(H zM5mwwjy6ZClyx;DB&H;JK3PGZ=|W0Ja-q~_BFxWg`?^=FBe_z6lo!sS&!b| zACe)_E3681HPdIrlp8k6(X1}WqrplfWi_a#eP%Jq4#c-JtLZ&N#Ke~CTjUB2=`+6< zC8t6H{QH1a88?^yjlr5ky}bJ2)NS#XKQKxjz$nF_2S?ApP((5tZm8qLp^5><=mW%z z4pp{bPB!Esan}1HRba{0nk&Cs$|mzq>;&sFb^SiyR-vKzN3F6tGe8tTY2*&lVlJO@VNaJwu8K52){4dIBm_nWp1nyuT|@DX^G{F zziFZxRdpEyQeI1!#sq7EYFO^|%D(v&3rY;zokamP+n&{&Rw1Ha|8^-edY1r~GUyEA zQl2>T%_ODD8P7<)K#A{h_o4|GYffA)R-fKe&GZlre=BmC05txd-(8WUeLDXY(k;>9 zdc6=L5!v2xGYDv4u4NaOkLGS3%!Q1MBDLy?OdSSqEJ@1M!*^sQjxvTGB7*(0BAitkC4hTkOJWTHG6!tpxW&|kk+gb1-O7Ayw)fM!d)*D zw7L80>$sf&uQxdo-t$Qpoz+UylGFZt$sU)axWj9@?C|Hu{gmj}w6VbME~qs9-av&C zftm(2{hX!GR04MXg1_={y_rb*U8pX9Z3d7zmhejHI(ao|vHoq+0-%^_D+TvvC2Y|YvX|gT-!^fggPSHMJ?2z6`oTqMpht&_;CbR=#$Y!{u?05sj)*h z?H1{`jQT2O_E2srE*i<>8X!y4n6aSzIA75KzJTh^Me=vT|C-$*0z!f(l(cJU<$=9U zl0*C5kw1=?WEVbk;0|0sV8~zw8O5bBVgA-$l3oEa>ZJi~-ojwa%Co9Kxa$h1&(bNN z{XZfx&WG5Ix^5;dB+=J6*PLe4QT!8-a|)N;`1~H!2xH1FA^94SELTuZf2FtPt}9!T zYzPpeOBanJWJ(pCg(gq_C(%Vu^N~T)rvjz7&flLX{_BTSfDA;3s_-f}clP z91W_DVO2sg*tFTrMT7CH9=b9j!dzy;)E=W*AWTu#WjoNgA{$H9zez73^O6wn_#l5{aS^6J=R;cdDz?*-77T~MZc~cW9Knk=; z0Lf}ud7MO=d4r?!s(!>3tEHl=%Ps=HDR&p zXZO>G|8kAZVGA=R-?%zMeY#J)`_7w zTk#fxN9FDc(I8FN_0;~7DdTuAR?zDx#v&hfX-th@lsyWyLl!c|55#qPU4Pag<*Z1Z zq|{i3aO{53Q;PyT_>d2rW{QOqC#V!H%!((0X}3p_K)Cu@r3=}zEC&t$O%3b4_2;5w z=gFe?IpB)@r;UUe`FY>EXc++GkT3hIRv8uPXg;l8nNth|(OQy;{~xW=1NKWQD#YSm zx|25J7RVJ)A+PtkX43-8bATQYjY~$K!1ps>KlX2EIed5Ok_NKFc)V^Je+Vl){iJOR zBvJ>dzcOq(snC@9y`)+gjf2p z?epKL4{x6?+joqBT zYnL9-2*%wWv+bbc9v6_VIIvY88WTUpT2FSJtN~OB!6tK=#n@9Dbj`^D7H`G^D$I|s zFblxh79?Xj6Nuj?hznl{u&y{*tRL-Wg0R#QacG&yOiHDHsIoeOozh|dpVd_`Q|f(M zQ*pb|e>u_PTLBn2sVNUscyZ8m;svA*t78@LYu!L$8LelX0L~wPgV9C_1vRMxk&KGI zg;f)JG&0Dl71NYV@2P(2dNizXZE5;HWhGK+w;#B)#6_ozhqA^Rur^}_?3tvcc z)Dy~x5SIBVDbY0&g88&YH*gzumvZmfwj1eC^PyA^nDN~RXF08h9RDwAab^zBf1Kt| z1h29*?oS|2=a)ZUG}B{5b^$bNE#Ga`2jiiMvfj4t$i7qIXK}*YLF|&CFKern1$;R_ z4x14O0QO7OAh8#Gvu81xP~0R=G~<{kbcIF|XUJ_SRb|dlUsn36ajL970#;TLC@#vx zSCMRe{jPEN`Q5pg1($YpYG_v6ZXgp z=7;2yXEnyz>?g%U{XE>(JRn&94CKp4PRjl~WLO*OdFESR0Md)w8s_>Rd^OIL*^lGe z@9#*By?!Cc$(TBIn3odSO=}15q-fnVi&?#JsQLq7Nz7NwwH%6*Rlr11?DPNDALkrr zm=-zya=CNt&{=P1F@Wzi9~_NiJ?@t&GfoIheI34#tmg`C=u;7wvGr|#v@5ba!m#VB znh*AtCI0}8sq%KwWM*r;L7%Za@*T9^WRaT{<&1)c=#j1xJmv1M&jGv|W$^&I`3iZb zxB^(iXx3UroVcK=WIfGNH`Jp;~v?yX(%AwH2>&vg?yTd~U%~8G4N${30Vh6! zz`~~^f6Z0rS5AZaNbY~>h-SqF>LIs65gE;8pdq&}KqKC#Nz*I0(FlO11QKTBDy=@sC!EL7| zEu=Kqnu`Xk@@)qSA==fRxbN&ZbR5dnM-CgSG2}-9KxWJJ2avTgg;(`|z{E&-1WJDB z(PlH4K893b_po>q?eJsR&fnQue@(j`*9bS7)ZW39%8Su-BOwC=dKE8@e!p^8?>qDA z%T%a1Bg)cl^9>y~`*b}dfM4QM_@=^~Z(w0s12Tao`sO2nE9a0%S%>ydYce@sm(uVXJn ze)o4`59N;||L*?Bt?=t?dxTY{f!t5K??%@BQ5ZR7hoH=u9+BY^uMqWJK@ULW{2{#1 zI!avsCB^TNO*!-}DDlfVG&qM>w1mvY0gAhCYqa$QvM-U8#h-sZjrz_s#Bs=F@m!oN zm}MFR1Ty%k_y#2T>CTVuAFYrY*Q>gsGKb3Sohgr>X;ju(W<;l`{@*P%jR&DSX>8N4 zMKC}@Hh5ARw;23Sz`q;Q!|JaZwA?r7y@`KW!Eih0|5v~M|17fkSM>EK)Azr^v;XyP zRy)@&JV6M_72*LEZHh`lq`)D*#s$9N+ZYBAh?clZ0AF<>F{G)X4a#RAD2u1z26QCk zb-WyTvp**`oc5o2UG`v63H^BOH66%Ee=K-827YbU?k~rlAFao1K))Wp+y{H#S^9MF z3lWPlzLwDKD+0Yd)WXP|wya*~JvvW4V?g)S#5M12h|><1bA$Fn1Mf>phuiKWGIcuU z%M#;q1AaMpXNGr^#sdUUk`n1^=xBU0*Yt90?FA?p)?QcZzs|UAFR44$E;^nw$XmRS zRg{6G&Q{1oS%U*m@w#o*{^Bt=C++d{o460j6f4sEsY>VhN%?g50kl?V_nm26i9ZT+ z`^5VuV{K$C!=?NoTEzRJKSu;?9jKj3d0>4JF}B`rS$om>^PNm>3~IOm{jIzj9!HVS zx8|>{t}oYqQ+Y>c<*N@z9l0NXrYhr5yM3L*&@5HtZ!X&(ptPO$=(lZPkS9|_mN{a2+z+0dnL~Y6>DCIpkiDyO&}LO zdD{q72>t&khn2r4f8J^6c(S=#LRP7J=YAonGgNcepBcFTWL4zQu%_zL+dF>-i@Ym z+Qa|nPt?J7J8FUZNsA2z%bn*G9NmNcJfV%f5tJ|~J|)6S|aH^0>{ zdJ{j*>>DGdbV8aYizHVBcL(jWD1})mkBX6^goUvxTTaxQl`4}3v#H=vQ#ms3FpNz{ z$~Q_DQv`h=Xj^{J6!UylUpne8pX_m=2O|nY0Z#KUF+1a7>?zW-GIcTo* zB?a19SsQp|c&4^eV>G*YfPEwDL^VaU$K|}@NGVl@DL&Jxq7Y*`nniWK+F$r}@ER6t zeHHEN2$?@!e4WF^Zicb7r=7mr^TM%^%Z(90(cP9p5OJ5`O4NJq^a$Q?zD27#rg=qvz=T zX=Ahywz*iXcG?oeSoU_0RVjdlKFSk%JKnSumRyLrcRtXJe!`$>iQU^RPCO)!ks2JL zETS8o?xr*&y%3SDbE0w1tlf8kQWz`3MSs7fzij6548O+AV0FGIbBbp?Y8&Ddl!2~) zdKo~G3&9?lb{)=_D->U3J*4X(Nr@z>Q37!*=DVDVI|-L2`|Rw~vHOx>odCQ^x~S z(o1&A7V62f!TKmnO(hK_|;cTtY!24lCzn=hcG zn4IJ}m`1L|eI9Nw8Nu5C?N5{;1+B9VAEK=teZ1=WH}RC*cz2}*MJD+(g~k2*>5b#i zHa`rmD!B@y$7a9#vhbd)NKbdxnr|q+&7J)_o_@ zG*@i*5T~g{R3)fLZ%6L^&A33ghdhFc@!^(L;gqHhR`()7awYSu+AvGhxIqqlLB}Ui zrR|Bq?w-Mkq=2HVrmDLs9urG_c|WurM`chx_b~dD@9USo{K>WQU7ZkBy7&5al=8m+ zd7_NfN_)cn*0l8t+m#08j`>U9kQDiSiu9$m*ZKzU^PIJ@_2W(2LP|bYN$pjsTdFPc z4DNQW18<_C9vZGf_7U!l?8uKy-l;|=yK3fx;vo9{xunqp3rJ`!C!^^A8Z;`~-D265 zTnp&lFiCx>%MR9iGGm=I#qpxwM{K+YcJtt14P1F37(W3N6GJg9Y88kY1q52$yIunA zd|dUBhv!cv!n@%#m+S{w!aUgeE1y1L>yz>pj3Y@xrO}|iyO@~N9l#Q}*3W8{x%ev$9NPcre15!|B~m>cpab zYno58bzQ}9a^C%TJGFe$qf>A71ah=PkEH`j>I?(Kv`6xb^^9PN8^nK-Anz8hzqwc1 z`LW1U%q%6@&ooP-e-%Ej)m;K1yVA*T(0P_6t$e;vUhr|eIS1}mj1a>iy>xr&;768! zPW`A^y>B-e8eQshFM)Ej2DJj#P$MI}B5~Y?G^v|OF^_uF97iP5@O$*_dK2Z}{*Q>P zQOX2Ite#mGD}l^MtmSHSEix~6yE2V0COgkLSFtJHxyp&b*^PP^o@87ljX|eHA!5Qf zCpk*Ij>B`LNdRS;`!&d&AoG@MTQvB~-S4t3+VCjY1v2SdZ6r8hb_G6T34g@kFY@SP zke{p5>kMT|7J5mU8O-DgemRA^-6?Kr{7|8L;Ps&1F67_0Z+7W1CuiYpkR+B0+sSQ_ z@OJ#TI>89G&yFhz*3Dh+#UdOcH#1WSJMa+c{E%5?6+|X)p6%;ThNH>$GhELj389X` zzRJ$Qh|kQ#%EF5Z%prhz2%Mr8f@XCBg}Tpi5t}iQ^919FF$JhdxBR&&Z)G`x$n8a? z*QmnBDfdo_jO^^1zcg?0zN5wQVsw`Vhsue2*?l)E&Sl-3=k)h!+5FWn&l}4&&m+2s zil?#xQ7sjclXE+Wr^)JX)hxNYw$AbZIM!BQ?%`*fH6;kZ3|VZv3dL#gk`uq%?!|xpwb9~z`)lR(E_!sC1^tf~ zKzyzK!WK<>#zrulqJZ2MR0-vVF%`>sB2kD%lmRbsL-KhmN1`jENKen=D{DVxm>}Ar zpx*DmJhwW-8HBkE$PRBFzOh909@0mUAUY?@N41_OjsvS{>iIZjx2hpMe@ zW$T=H2+-QLk5}gMWKevf8Cyv1i(Z( zLaSSdF3vhzEh+bDN0NTXT2g+=g+&NHX>o;2P!bJ7ZNw zRPE~y7D9uZlsS5dY#gS|#pocCtaXjxM9iRody{gs0`&!!gepBV!^u#vx^|sV$o}VW z)vfy6GHptk3Rdr~D{Q#Bglne94DY*b%sMlSI`MQ3v*hQilojKS1Ar(l$(oZm>uN-2aMVLiSL}7 z3{67Ld`uIXFq<<~ELU$_G9n{TC0R)&w91GXje~6w8drE8^J}xHXx?!6u3lIIyCy&K-ZgDk-1tsff zK3#b~x2$zVS z-+1i)UM(I2_u+Jcdo?~ma+p#Jae-my@dK?rvs6h~^AYCe2U9T~dv5KKR2k9>rQENh z4yjQ%`^KEfrODzKgTrO82c~Qz0R&oj>yL^m?7WMsvnCQ}97G2Y>j_3;dNveU_uO|l zjn1-j2K22q%{YDzNp|Lj&tHDS7X?n*=ow_&Rm8`#&|^&aV(%u~YHV+&_DMhN&zrL<~s4E z>Duj2>>#;nz5Q+@O$|8OVz#X4`qg6)@)NEro=+0-8}0XMj^*8R+mbU$ierG( zf=||4oMS3$eT3QixJDwO10D;&?mSr05c#+6ha16VX6y%e*_o-D#37 zzxEaf73iWF;D2o< zC$c2M9;u+Jf+)jaA^~PAc8YC0_{kiEb6Qb5`*Tt1@17#*758Ra8OHemFI=2gqZRq9 z?5w~a{x4kaSI$^3K{Z}$(R)&2dSvZb{m!nBs|?lU@@xBzW>~Zr^+Le>c8}*PAXz@lp0}gW0Ju3s-UUD?f6DXI4 zNe|mzMF4@I($JtmI(L2Mz!MR)lU!>KHJUf0_X&Ge2(PADSJ5J-0-vViMZ4jlo7#D;nP_N5n4C}o?w9bAeyt+dq$Z|;+w~D|M~0fB zxd6gLh#r=xT!i00qpc8#eVb|-ysI$f5sR!9_{K!FR97yw1fh-03`rY$B+{8hM+PWQ zx_gL@N#~un?>gKU9Zw6^9=2!kRpd%|vG(eDOlZdL8`10$&&660!)@ShQ{wM#Da?r?4(1TnV9(MWG58|16y@#t=^Tkt&h@bcB;~Lo)W}hkS+lBw@iytw(RV&c-0#6Aj-3NHs7%Uti^FJ8UNy60sB+=lF4=;qt)@3{Xed!2APpgsgFQF_ zrKP1U#xf*=XN@s7vL45ONVW3)UL?Tougl17tB9aNmz511AJ3wTQSJ9kj%qyXqLUii}J#tbv2El(lW zsUb)raLaWe%Mh%+)lIypXxouAdiaF>;`wp<5~(7dnn|@NI&eSUjMBB$hlFn|%#eEN zFV~n6kC!ikG7kvGky!9t_r?KUCAWc=vSRPR4Rss$0(IeI|E+n6Ls?t0ZHPk3w21cgW z=cpE6Gb+cT0JI%0e*%nmPagP<1PP2;W4WjE(k z`dPm%q?(Hpq7ZFqy_TwF%hM3N*`|9@(jKSIn&Y-esPc&jteFje=cx#afi`;0Tj$4- z3*XR=`Kz9j2YmNU=$A*C3x!OMkC<2KB%?Bj(;p{~DA*(2i2A=LVsAVgQL2AZu+=-P*UeRoXg}Tk*z0)OMw>IL_~3GK1qJVG^XxF*Glk_) zFZh{bgoY80s128)p@^j8v%EWz?Xmzfw4WWu!Is4&`&3erO|@QUchzcc_2PcCoqPg3Y9kpYDSmR!SiZHoa$eC73dlu<=1@~}sDp2skZMfnk5|4W1syI|msq64 z0~9Gg-Y@&f{2m#}K5xjJ9k#HkTytLI5xQ+JE}G6p4ur;skS0sAFAIv}3k ziOTnZ*s1GeZzzS52|RfmsoGKtv=$Y3z2h?9kbh08+ow3A$T)naC}Hoo(yI7Yl+1Pb zO-b|S={hHyBb7i$REm_!DSt0b1WyP%jbsEBvW!%pA8Sn6vgO4uWp1ZK17F3mh1cQt zF*s#TDlfu}UfQQ_S9DI(T1puNRdjTz;o?h@(kqmG z-PM!qE%Mo~Q)C%OP7;qg5)vX4g%N&LCjDC}3J$*>9INX0gLBMM@Vt5R2I?O%l+1#N z`+r_cIR{?zwBFr5pW&{(r0HAZ@>c%lcR8irXsUSB>GL9mifxBoTfW!|4X`*(PqRsH z!?s}!9T|C4VATqa)V1H)Z--+i+H_H5NT2r{`C?zQ?**63Hh2|^#+lNyYczwhVXfsH zr_q1We-*0rOD3D-BnQ&-HxDH}TFV(xTE8isi8;H>Dy5JL57?Q32kCMZ}NFj zGT)%yWk~K&^~;b}k0vmX;`51vZ}f^Fmo1TadYP<{Z3t)mfc>*_MA6kD8G` z{ob0&EC*9m)a@rzCl;MN60b)Yk)8XULGXh|u}W~eO(MCHLLhJ^&4<-+`Q}YtL!HR3 zwHxiBiv)Wf zQa;z-Xi2Obrm`yc9c^vtz=cn7*X#U#qgc2!;0P692QA4LeU7?Kd$Bn+=J_)Bg4{_B z`t`JJt?hB&gpPnkwEm-@`r67qv7&2Rjj9c`iD*fy$r10=Jv$1qAKz6vEj;QlhMQ<6 zZs8mXdYOTg!B~3tEaQ-amyj!~+oJuK?&d7Q=iS43Kb_3bbRou_p*ITlq##lE-2HB4 zIrUp&mW53G{uoq9eTg@%Zc&ufOCHaIGiBzpo*XxKcho9Fvlebw>->*S_Rp82=FQ85 zL6)KtLcm#*^#tm55uhn_@p~tdNVQNIM=59zZvva{D41pvQiibOhY}|kB0=Gb&w%*H zD=ePKm<5pvfDoPES74PJ;gz^NtT;YJ{%(*wTMw>I2(dfvrQ!5XfJC*;E~!EYxHDncq~0@l|k^Qim=a&ou4(okrHW^$fQK!m- zX$%KSkuJ&z#O!~A@raP*bClA)Ao?^9rNE}RdV;D-dpO_APeCUSJ6(g zJ?A(p9g!@~k+jDrsvcOewiuIhy?JmfjhCsMqx&%0kP-xpYjMW|r6w93oBo-RH)}~G zY+lJ%oH@#2GE`9bPY<1bv9yjN(;~2Q+kU7TK{{-W6@~ZGsluUjVJeX~6YZE8bZ6x2J@t zv~;SA0EhRIN%r_C2K#6*HEvD=g{d}rZY!jytBza`kEwZmvhT_qZs0I0a`c*A?zc%L zDoKP%tO^K&k8-xb3gu!T7y4h*p6x}KM-kf$-94p}1Dcz0N4p9=t=6z0Bl6TItes!+ z)7!Y`bxAenDc);u`OYiKT%>WUt9Lu&t!QjN@cJ)dgZ?aCGj4oaps3svE%~y>#AF=T zpUHC_%JO!EqOJQi+t-Vl`^8frek-ncVa)(LMV@|FEo@@UsFsj)WMh$>xDHljK=2xz z8S9NPp&}Fwj)^ce{j~ZuoS>hmX0nWwEcK$OL5SL&HMy9}4(1RVKv{3i4bxPES9}Oe zfNeDFDyp(oj0IbtVeIH?;95X`)JqcR*XD8qSeNtv3G+a$h`+Y~4dj8fhI0oHz^DTl ztyFfk!tiD|9IP{7hUEYP#AUT`UNh?bV-zUm?^fH4Z)^-QL)x4OGg^N1Q6EbJ6%&L4gGQZALVB-WQLHDvQo9^bP>Hqs|Pq0OO$y`NPMr z3eg125JAca&*vPl`e~$a%wPVtfvLt_xF1gEw%2#x?af`0csnV+{J{H+IWRcELFF{K zK>o3in*=gN3Py*?S@fCpbf9o$UMY4gbodn0m_D95`r|?&u=;vWXvU?nBzG{y1#gC$ zNSH)|$al(ae$Z)Np_9^ff8jUg{V+gYKBCS2Yw@T@9rEyYVif6>4tFOo4Aa~6nUlnv zO#MQlIgWf>`u>$X736WDU81j^o#7CTH%l0BNJgpNDPv-O7w}o5uHG!a3?rL(pOAPc zbi9J@gmb0g%mM!uPcx3*I$4Pbp~r51ZKu}xFA?JaNnVrRdpL`?7ge6m7`}UYH)#Iy z$2|5*HmI6C&xL}6OnkoZ{vld5Czaw(39H`bKH!}Zo(VKj?1@c`a-UV_=uKOa zWx4q%wU+d7?2Cw#o>zu5xEo=+7xKGyIvP92v(_vz?2gP#@Dt@Z!1IlW)%0$BfJv1T zPEH4K7eISwe@E|O1ZyOE2S*;Gl+(jm9IotDxI&|+iPK!b#fL+lG+9AzbI-X^1-)VP zq|Z95M6TVlWYjy5C9ovu9vc}tqw?vESTu3wno7nACR;{4fGy)b?SRYMGjUCBItf3=HFBhwCn zD0Zi-u18XAX1KDnzmLBz`g*cuaT)R!3^WNL#Z|B$&QuK+e*Gsn^)tO`6%d(_@oOl5 z9n4|KK}+U*|-=xbed^vPXzbU>>FI{EP|MO&KNscpMt=*)PB4R zbyoCjhh&N_3$v2TNB@!?ZBV@?R!Mwd2d#Hsr!?792g9cvR(E;GmtSge0U(ivaDe*yM8JB*8xlP|no~acS>C32azlf%BrF$5ua)sMQ*KmI#N~5$Y_YR~EtR zzlB~pk>;l%`$SqH!C{`66p9yrt)7}V=b)BD`%>=_|CZGOo>;E1tY#B0^dqC+!TIlq zloz)z0I(3a5x85Jb^mCvm09G6cY!zBKwZez5qkWr#9Dcf=b>lwtFMGC;H8N3qq>f}^?wU^#orL+{FbVq^3ckn6fJ3*Vvi%At z3MZ-Jz$->gLFkzAGFlX>()v(>y879IAr%995VJfUV%>63E@#GF{E{l5`%scx=1E+I zl0Eu+Oo4ne>i%gXYifD&(W!4Qqsd1?YMehD*Ng%W2xL=xuDvjosdWw0sQ;zqZ|X~i z3FrL%0`dk4zD0h~hdXZ(YHLzXM``uqQOtFjA}_3+Y7r2wuQF!f^mue+{ln)J#*$vf zSZN`9n?AuOMk`F~pryS{!y4<>^z2$xX66ocRFDs`jnTAuoM?=NiCkT%M^kKliDPwD zE(WNzz{Mn=vPHN$@B8a12fY=FeuNSN`)q8y=iK4Gep=k``iNp>3<9PBt)0iO?M``$ z!S02z@sE^uQ1Sd3vFV2>OSW@p)!nIVzlDvB$wBX=&`Ft9;s$+$P`1-kAQF3unxANk z*XBK&$cvEr0x1`HP$dF?$TZzAsJ@i~wmjJl7~jivcW5cL0IR(~A;BN&RR8i*>{IJ` zxun~QY>f}FtC~DhHw_%j*JYep01Jqk=B$K$Bk1Sur#c4zvr$SEuC4_@9+9pOEOoU0 z@}hwUwFDArWJDuzx!M7fKeyEaI0%>yR$qQMz_CL?0Vt*n_}o*P{{xUv2E?-gDQ?k& zE2_OjnRIzxf_|=lg(F408_D_%l$^{j0`*wKx&6~8wA3ufBUq|;jeR}Pw+7#%S9Qqo z9wP{#YRoC@06_B7Z0STtTNZ6iVuWk$?jFEN{Kg1;QSKw^9M8LVbrrQ|&wBf}DSPmA zX|D1{rfp^nWjJeWg6;_uV2hzMC4V@jWG}4Hx*|U*d~RQY-T%fVt8=MbBxT)g0I4?8 zRYNuT;qBKEiG952$j81c<}6) z?$jnGopEg`L}9}5gx@AiQ?qarsHrz7jQ(DprfUc7o&^t}^`B-bw!ICB^EJR()J7^J z?}r0zlSZ20iGikAJt*)j>`oV73z!G4-`%>p-PtU;ukaVBI6kJ+;{dFr_L1HmK%Fx3pDdHTyuh=%rKlb2~tV;|-#H`ErJvn)>EIa~S06(N-(f{aFO!1`ht+-g&U2~?2B76h@ zYI3>OOA6jhj}@?YIT^-tj6}zi><;JM@n07MlYQY{cu641!MF}2VUjqnr?Ck@6Ft;h zzwt?1p%bIFU$`HXw*!~zov(8f_!20b8@J(2A8l*A3De_REmy9JtznV(`VZ0~=b|JV@YV0jYYtr$%Y%^kse5@$PK4 z5BL#Ti%a7hf~htm96Ti>-byU!A3%cibu2fP18HW%X4ie=n~;BWVY10z@Wbw4_iNO< zau!-c_)D__CY6Z(1Z!ja2wyf7w>kl7?=~ekSI&fl92B5ahh<%|sAeRDqcFbEa95iSe5KQ5p8)kkv`Z{GOo^s~{33 zgkj}z1Gt)l3xOjRSXLe?S)Wnp{-X;R;tBsvb)O!b1kLxP0Y!QPqr$tg@_2**4ix_e zy!PGya-d0;9|)Dr8NHM@&`&>}8Sy35`BD<(wttK=$N!UQygiISHbR_HJ!|-Fw_vsb zq#E|@B*4RXyKL=VU*R-VEZvSbOD)Y=Kib^S{>9HM+dztfMRud>?dmNW4iW4|>}O08 zVS+U@OYx!Sg@YSWl*a`-(a1Z7&XvH(!_e_OE#Jg;0Gml`|9N_yvjT5v`hq*~eAagU znj!9Eb;5RY`)j+Fe^R;!02pG?!tC~qOp6GFn7SSe@%dVNLjQOHOj`;ck_dFZm9W_# z$m()Y!x$)<3RB0yhI}_6!RAx;`9Gk|@XvkzL{7!ANSp|t(L4n-z!YW-(mL&ji$w{g z4+4+4?9v1{6$D_*Ch@?$-Ia2v!qH{Bc@MXETIXD6=i=NyL`no6337#p=S`>#@6Fj+ zJ9Uyc@QQD_IQ0}#>y)0I1nqp4lPr~SGsTIDR_1&7Rq~ivF2``g*NXIa%XMy%$uN;S zDYX97TMKLIt$4K+?%!4IKE&@vhY{SA)&Uhyc&@&y!VTR)-cAP$ZQ))={qXSlz%7>* zWGCnhQgu9)O$?eA%+DWGp@a51$m(4!#ru{^r29J}a9ZA>wsjN^G-C;-(H*}v!0@FA z%5;vaWT@nh|B}j9pn?8vz#<2A1YURC4~ILKz7cQkD*AkR?F^Ac8)7=Uyssbp(pYEn zk(`E|+1A=|f$@^pd|A8BkT(K>^R4)W(Ia2kogiC_zP=Q)FD z!J$oxfP`9a`3gdx?Cde~R;&!Td7AM*B zNFb1{jK4|uOAK%9VtkHb#9==}QqQ51gckAmifx6_ zjL$zH#u?(@K#cvrftW}}0K|--{0|TliD8eYKcSFgyD zXXZ!}@+g1{z(6Jsf*<3U{eJrzC0o6Db`RgWLeE1i!?F zp$d)1sE2EJDEckQN`0KHHZ+uKuMs-D^c76crUcgK}@L|AF z@=1BnF<*G>9koO}!f-PI=!)6=0Dv=d?UU^fL0MbY=^c9nkIQ$b%|G)tsDn@ts?JI(IBr_Fz)VDp(5fdFEKC2oXUV#;}!$6dE8(Ae|{(8vIg^M#AXv9r$v zzbu(fzdiUpAYB2ATWNa`2rwzU@-1&FKYR)N8`MxM1Lg~k=MmiHtmr6AjaL`pXYDYw zH$iVL5Q+PO;s&Vaz_I1>-!_VuI2SieB*+dESY@sOmR%~q;pHGSI|lW8S_6_2`~MQa zdLT{hT>0M532Cw74`2TH+e+!`11s@c%hID~Y{%vh(la%1`@nE=UJmCZ!kD>W3D`A3 zQL4D>lQVz%J;H8OxtGYgBpY{tQjkbWDb&zF7O(`ks89G$x|YR{8MDZPQJDGp?O(x; zA{f77!9mW8NM%d^Y*(GELq#rbL0(rbrWX=Rva9M29bp*;Ma8_15L^*dC<@JtQWkESmdxPG#j3S zYRe`W=`UiW_+Gwacs@w5G7Uz~*#5RWtb~!U@FMF6B+(dQeJ{rfu6$uCE)^jRgSYts zz_b8R=6iZt((9v*o14)d(5Z-1>d_Tya#9m`Pf8uW51Vp8@W%UiB_5;CKD-Sm>63|u z|HNF4M;s=YRTnNNMVgZt4Vwg6j57~-)8HC6zHQV-5Riu3Y~=*aTE_73)>_2^KZ8Sw zq7qAGQdD4(0M|OC4+_&-RlQX~UJ@In{&Pk3w(!Qw@=EU3LCWzI_N~gp2p*b87yJ|h3dXWpPf%gGkhde*d*Ta=akHMG^ zd-*UQQcf}u*4#R|NU)J1otrow=erFsm3~9Y_y0jCd*f{1g-Vag#1JQz`g1vWUsP5- zuGGAAxzPM?UM3d%JK#UOcl|`Tud)k1!(VKrtLGs=h&<*ElHX19WxZy82lzki*I%m~DvK`h$Ov@>>Pz0tsVpIvJ=t?mjgVL`WkGyI> zrtQE3YtpZz-olt4B7M~vji1Wrnj28>iIuRCFK*s^)@CN4HqEEOJ`}sgEp(6nhSMAn zO|i~_CqMaSYG(_N_2sJj=XY_}HiDy7VUZ4r)#WreH-uGqUCTq=K98V_HfEpWX<*d$ z&j5rLWCnS|19_j_Ju!BU_6jP-P2(S56s`VH+OHY&53S+ush<3_>~~yU7V#*MgtEf} z6cZ&9<${n*7_Ul>xE#oT0qwMSYRlP`D2 z>1i+tQrYzm;EY6(jDrVh7@sWj-HHZmbvdVzs-~I2SMY8Er|3}Eo82>iBfOXB5BUM8 zTT-%Dae%l2n2=Oh*h~_+!B4->pT53L6L&c@7T79`DZw@Xonl%n^1L=Ns4*a&7MoC9 z19htq_N77=FgkUjni%Sd4sh+}obZhA4jcZy)okTBbbaMrN=4Tt)BCQHoTBNRC!NH_ zboIrkLGOn2ej1adi5K{Ck{zz~M7`eg+$iMG2Xti7K@Lt%UNoY+TL4`tB^2uk=6gE zBDa~;r-L1UR_<5H~Gn}8iC+yZ8;FY za0`?L_i*d{WfWR@v^<^UU168K><5yLe|VvUj=EQw$6UvB?Wt@0H|yA%J)S%srhUgm z_wIPcXLJx*iDV`F{V(TChQ6l(D zr%{IdgA=&mabHEXgsM6Rz|sP65^Blqj5iAqI4oQe8mw_6cw3_w+SbbgH;O$mfJUMZ zFd`#~pVj~;HknX6hDQNA$Vc&)4Kn2Dx9kbIjcx>BO>L+t9;ea>c;r#w>w)x)R2pV> zbd(a^pBW~c<%^>+__#gW_ZaC$1N|6WAuv6XMKmL$- z>-S}lc#;q&Q-O*7Giv|s?9Q@D@a>f$oqwM;VN_HoFRdW*8hy-VqB!=H_}g_c>u3%J z`V5(A<$XUV!eU98ZzRG__|2w1>yX_!2{NmNvj}(5Mcq;CAllPKs0cq+RjCaU#ACcjCga zO^zkP@^w^?h}K0Fas6l{|ncb=+_jkSEpDSff1J?N;WPZ6T<)Ay|Uo!yNZU^Z2ef39&Hp6(f z3zUP>h$vXo->`w9AsbJU@fWWSdNn_yCO%5yqVY_V-f#7V>=danI%$k~-xKXc=*ISN9#0}xjh%7(M z4JI zUk2!JDOGrJBp6&Ih=vwmyQDt@%(H=OSvMg}_4J#lfp}m;$p5dg^Nyx#-?#WCgy^D$ z=t6X&*XR+w_bwztMCXVSy%W86wqA}B(Q8ESCDDm4A;DHQ!TW8_x#!+H&Ux>Rkw0W8 zW3Ro|{;m1@%sIa++9Ua|?GWO1xgPuHRa7wr){PC)M`X zZFBbCVbvZ2L%i0^Ek{ciW3#v%jf0ac(7&1eJD&_8}r9rkFqF)HHGS%S+7&#>eXRUsLf+??Sr1 zLL{>$5KO(1)(b;3ScaZ8-gt$pm%Xq6tG zmLlZ1|z)my4A5JODd$m>@kx|zDZJ|k$pdk~}2HC4!nPuKV`Chty=)|O(5;ew3x z=V1NQVibcsVUuokH7MWp1rv>h;UT;ZG`eAGYn97_K6th+Qqf%2<94KU8FmT_3UR4Vk1_^i$o!XN1J~t^KI{Q*{3ZVS%)-hX(YeqgvYc zOcH${*99TfCY$C2+%(4F+HiAW2xPO+@bY$5MxT7B1j_s)uLpx=K_2%hM8`Vf`I7=X z7eJl^oP9s_FY@!HXergzL%_UTXnej<=#&MN^ZQpr1vk?GJelc0o*1ti$k$`y)Uj=cP{=oL6(~kzJgS-vvi{ai#)M?V?x8d^#sU{FHheM*Y7cmk1?*4AIY&!*k4Nslg zxs~ft{@3YL=A>9;@N50nk>Av_`8lO0Tup+wHhANWeKhy-cXx{SUa`JJrFKaIUS_xp zioV{x{;|2#AS&yCB(y+=%4rco5Qdtw>0Y_k7$}T+1b}(}$*08otnQzvf~=i}vCSB% zeA}JyD7;q%52M0XU^S?1f)tIB#l9;} zyxAr#dbO%mM(NeBrMb(cL!HwlqpM#|F}d01pX=V97E&Ne3MurN%^+m`<8w9ScN@m) zqZCny-7|h#boF$%55IMe>4Ox}ir_`p$kOGJ9iN?^9enOQKYqz1!+lrG`*3m2LbDTml6X-wEbH7}XG&_0YO&gEf{e*wt}E^8bcuW4i3-_MaKJQHTm$Ut zVl9bWk^HDJdxP8N_Dg9N^{aMcIBadrvjo1t>R~UI4twcLX7#IV<|>0uao-kZnqmX* z5YQTs5ns4m544;b)f|0!V$ZS?Tf$}}sKgEe{f8}6hi7TlkKI12&!;H7y1rH5jWn8Y z35dzeh^BAc@?+(CF{0?*cM#Zi=@NCd&?9!|;)%TCuY-dT?=yo-H>#`kVMBP*kE^LP zsuK8zf8$)^2XHu9C_@A;O|RDPBXaPh4qZSe$DPaTYPT2&9?UVl$cl#B$Sy3H1dmV3 zk6Cca7kaeDYpwr^cdift)}F^%J!alY4Yk{6$%PcC1>1sAZJmDNf-C=^z;=|*C2?o` z7;pcLKWU7=D71Hw*Al>b=5^6=^sev|21{#CEUfGiIIt8p7^~~}xXCCNJu(7rFomC9rq&I(8fFQyqZbyEJ znc9$c_wR(@!R)XmM&b3)q{b4X^=1(D$!BDM`;?b-#(JExf)BQ;+eW8>LeG*&+i}A2 zf*2NvX^C9&5!(4oh-y88SKRH!b3S4#d(Uu_&PocpPG|KwqgOwjZ-#Hq7-Y>SXSTTR zPQSPLkpEH4%Te~sZDS+rwYP}J>Zh#9U%+IG)&A=GXTM!phIy;j!!Ndt#vOfIa@|>Goi~n0-a_y#OAJ2Q;d5^pY0I-2HbPa4mn7h|mf0u^Mz)BqXSmOc%wl zr;(P+mA-faCAMytCIT?DP)HgI*AnLXiV3$Qdzov(IOayOy^}wJ|IT9z^tp>gU&s;b z7GnEKm0{=JF?g2I{p!{oCB8=cv-|#pqU!a=K zb}9C`nwsc*q`>^9ln9?U+Ca-|@|8o)07=5ceVgbM! zYvxdohAjUX??PGfUwl;+1Xw~)5n}}0I{_2{o()JW$|(`OfjXb7ZG+8g4RZrOz9ew+}dl@XN)N^d`iKwkp zU&03Y$4d_Fv>rwV4wN)YyPhdrE#a~fJQr`b_=($sf-0bI&4k9J9o#iFTiyAf^7;J{ zd*1_A(lu?l?f>Pi^e(zhsgKPg8 zoI`89%Z*N_#ybXBS57~_zcL!c2V~+V2c#11beYt$hvWOE*fx$dmkXb^ktT*QzX#Hv zadP@f6d9cko&=?8s39AI#Xm!P*5;II;779)?=E7~PUz)UDJoh2( z&6IVB_Cf(;k|=Fx7=4#`tcflZ?BF9@C>Q*w=|gYY(O2JwaQVti<}MtcEW=N9?5@^F zvKq)rT7QXEB*%8EfW2yqcI5g;1M#hJhIxvJPgAawT!xYPv4AD z`}rLmS?yc8J-W`E>-1+;5j<^?aOwwdpqSP+-`_**8hulk$g#E!Xi((%Z&%??xDiUQ z92l83a)+zaO|+&Lt#|*x7W(#3zV6lv7u-9mXF+S!G$NGu)?}YVPI18hMFVzlctF2dg6S8}3_8r0 zXJS_iKg1ecE^xC9ue!<(PWlFi+63Cp);CCW(&Fh95Bfu*iRar2y`&Xn0m|t$aqxE8 zq!N2nP!@|Al;DiJoKH{SRMHv=oA|oIr8806pO0oYPiLDyRh09udNb^eDF5h)D~}#; z!bU=h0m1m8d@80yqfk4%xWdu9wxxV5agj%A98G+2gBC!A$xXEbe)}KU6@8|^tvK`F z-sW1L?*G;Ol|&sIy3*Mzy>86%(AJcB3@6_6JY++HetTE-u%QWqJYov_dvc|)vE=ml z->LgN%g7$EhiQ?Mme9Ob`ox#Zlp0-r2cC(p@ILCoX6kNiS;gnqX~fMokVY&rP_+SE zoD1rrg1!1%x){Y$!2m^+V=Cj)@9KW%ySw%eJ&#D|x-z_w8w@~z)Z%^Ma^KU;Of9mf z0D2n*lfLd>t1plJWa4DO^#5ovkN(I7azkt^hWtn$W@9_usBfvXvQ|&Nlf-A;OPBi6 zUMfEj{KSzX;kXTwd@gfJvIg1b@h$?_l2Y)%Bi%W3kfMw&aSapFo0+p1(v(V@c?wNe zc)rFAnIt?Hm*N_r;#TrLH#lyz4v{({qUM7(UKsdTiX z7c3Hvj%u4c{aZ!GN2(6Hk5cY}&CJ<~@TRMm!};e{O;V6Wlq?A^Jh`~zrQG-s31W)b zJ201O_dZJy_|Rw&(*;%sw)AwGy&QduImu9R&`O-;zr7q4iWtf<{KiBw@{XJvr5ajf z5tePudfZ)XGSpd0RpQfo<{$F!Z=u&}!-EJD(9LM7N#Agv%WpYR1})BqqoU`o zfE~zS`gX>%MqQzR#}7rP%r>;1Y&b0v9YuRoZ-%SU5y2uR1AiPxw0OQYiaVSd*HdtN zC$LWwF3DAv&qVu0QH6Z!anry}(ZcCf3NVo^+3t8{cC?sP!j_#eKgOCBBh-0ik0xyyxxQ>muht+;m50847N1ErUGUSW@r8W;Erl>kJcF+yu1U@H(269Y zM6>ly_%%NYl7RcP__O*|7ATW^c?QUk3^7NYruJFn3y?HH2pG^eXP94W402R3MUn^hW6E8lKW_Q%wst3z<6b<2Lr*Ak7ENauAtO`7BK7l%%6 z@L|nxtA4Is+9e?1SDjmP%b<51PHU|#B8i39zY}1Lax28wO^)+xbK8A|NXh#H^WEfJ z{(O%r2!Ws+tc!5M!(o#8>rd&Ujdirc7Fd9+p5*RKc2&ay3?_aU(@{VSXm!`5S>$HV z>W5UzMw8}4Dd{~C=hpVz*V4O6zqh(7k@)RdlT99?P+%43g1@;0O+_3Gef!^Hs-dJI zdp;n6VB8FC-_`ENQ45K&tx=iheU%Voj(d!M_m!`YfheOU0#30ggvsT;wGnrFeV-be@I{OP!yQqG#w#1NDA2EpyISGr_NB1U?s76`FMhA(CLyE z7m`~?HBYYMYy;zh3Yx)JG&4yP)BfZ-6@i+C4`S!GG#HC=!3BWUff( zp+uzCs=yYf``&I|&21wf#7S|}@xi7iNcZd02P9O7N|Pao$xw428np9iwc-ygI&QnI*QM!I5ez{Qi(3NwMQEH3su;w-{6YB;mx-C^qe*x z6}WNHjW~KZ)8itAV#%L>$&f=*oS?<{l8yLIIVqUcvI961xz0m0SR!9d`7$@EQDZlh z$W40}=>CqTrLqo#MT={&FSGsLoGLbmrp;p?aHwFH8coLpix1a$VTI3G@OL4m67|7; zD}IgXQz?uyZ~tUc+aLzfCQabx>li@os!qsOQA?+4d`L6ZopHQ9#ixfV2L08zdHggo ze+Ic_mcYQJG3C2xB;Ih|7rC{GCWR`6b?#lJb8&z3MWSq#j(l4%5sJpC2AV5hPQm1E z^D=W{sdkdlq$jcSzGO~k<&A&Lxt zZ?7j`Y6%jm$|BX)$?<;xMtj>Ln6ffBqC$VrKd6=-09YM9=`j}UMLJ853Vrqx{m8we z$Hm$<6IOt_zwB&3BMk9iREM#YYx!y=2QWtBA&*d`a8H5Dk;zqYEFiQNT&JlD?hW5h zEv(hf#J(l<=jlGT@W0EX)*qI9Syy3z34zc;T+S!jh&eA(?)efFIHnF$FTpOXkrj9> zPs@zt9sC<=Uh?qCG<+XV3~{GuDj-39pry3^jvCqv@`8t-8Bz{<#8EKB^QqzIHh>M< zE~Zr2)WUCnwM~#3Jr#BLVJP^63znI9HO?bxxJz#^zUEXGz6OIMj}1bdA3)!X;{7KW zY#B(JE@@{r->7`j%FWYYR{dHAmYM%xv#^NRRB9&N0XhwpA7vT*0H~WvpipVrSYd&x za32v8Uy!UzxzCq>ia2W1Ok}N~qi`SU2qB^Yj~=yzdbGX^{jB-5?8nE-wz;SVyu#GZ)VZNcP5GO~mSZrPzBcdVr^$)XjgssEidCHm9jEy1 z5JqfRU4ilkEE?3~OrADgtZ)8`^pnsT$}-`v);JgiN4^;eI1O(DfS8F=KtPaN*r-D* zdIQM7YBr#cJnZMHO56~;AQF3v4-^|#5M6%J5mTqtSQtnVOjH?y`7|q_<&a7&qd7Im zQ;0z`m?qF3-PHFtc8}RQh)jLgMV@rfq}Tw1vsBbv5rl{Ja~r5{#$(~-{oW9M|8nQu z^I-NH<0tqJ<3}T}KozF?%q~tV%N&?zKFxbs)6qqB4b0Vi436Gmv01|Vhzy3oM|x2o z$=HH#*150AXk?Bvi;CP>{5G> zr=$&``J#CK2{Qkv>N&MhlC}zyTB%&(Wpy2y+IY>wov?bcUV6S@>aZearWi&}BIj1D zmK1Cd%nQyT(dmgX7j*a|6TAyou>#r~TFNL$8s(U!2Du zjR?9=Mzx9BY?VmVJr5dI&|k)|*a3=Odct^Lu-GfdMCZY#MgY2BJWBtn4togEuO?%P zsIkXn?*#SR+1cKkJQkAyj_;^u;{a>nmsprM66BufY&3rthJEo3q3z1{Y{w5`ZT(BC z*wfUcFQ-i!o*U%yOp(45%zsRHN};f>THeh&Og41G@sX75R? zye8uC3c=D!%SEu-%w_>IMpE#vT`-e2_)uc{!)@*unOA2Pg}U`eBJSXHK&-UA>$UrWQA? z`|oHN3PL^B?4|+>o#mgKJ8>43K9Y)}%{naYW>O*Fa+H;P^bV{T-^Zeuk01-D;vc7V zU&Mqv#g=$0CnjPDt=F(6aN{dk+l-1e=x5v@uz1Gn5+(BD0^*3OVbkOPG=)MZB;v-6 z??yE0^o(l}(LK;74SO^z%d&w=qef5UT!-*#%++gtYElr#<-(Gx$e@$GAN+pQuG~!+ z-E`hZGLFSFM`{OTgC1+Nz6i37L%8@$YdO920auD6}>u4wWUY)zeU!xjzG5gU`$OpO)(z0 z`a3zHnk^hTWK{B7G8i~S0qD> zo<%}yq~F-^Qm(fNB{X^;-9lp#Wi}+bLn5rdVG75!fB8;dW<8+bHdYdW4AksZZ=MC= zz5a;3>9>y6CZ#6KS4Up;0ZA4c0)eQo z%0xp=IDQK!wtc~{vycw!6eZX~0D zc1mw+TAhAvzvSAQ=c<860mr5$t;f*w9uclu=RT~XT=W)f0c+2$o4-Fhew-fCZA_7A z(n{&EfB5-F*WtjIF9W*`Pe0E$HSop6^%bv>U5;Yf5ZlD+9P<|Ge7i+UnKbXI-IN!& zr!qCCHMBZD)URE~c#@jn<)|Gi?OIB%iuefL`s z$ge}I&=8!&q*H+_`$_o0YQ(!k|8;0I_Ll^6(nNlnf)>SCts$4QQ}%YL&mU*^r{gbJ z27W$oj(#A|?pZOWv?@#}MDWn@wSUBqr1bKqA_>zj%tQ>$;_^SXHR8NG)Q>gT%GY;O z>s4D?gvGjKy_SOZI!+i+LpQF>=sBQC881zjrdaVw3 z!sOn*RBS9nTDrEOp^y<{Wzi6F&sJdId`6+7yjOqqSWt^-h$#acgAZD`8AfJX+3XHy z<|BQhgc8;(t=BH7&ru$taW_S^!b>JleQ)M*=VQWiELn{a1 zDF`HNXZYR0(16(4)WL*UTvArvfZh=JC}NPLh@i5|-{UqfHN5$Zv$qsTN`tsLx$3pv z@rZC!*50||g*hvZNXR~mTg9k!q|Iq6rX}2|O2y@~8d&2E(-&`ZwOrYnNwppiE6dNP z`tg=FIfCajN8;N+<1uDq`4 zg7)JN>GrI#<9p98Ua*l{AVE~f_pfNS{;@O;ZTk6kk0%f@LJP$IUZ10VChf9ZK!f!q ze*dcO@2UfT{{{66R7jr;?@~US+$Y6bmY-1nekx^RX4WkyaQvD%Erso`m8Bm%9#FVz{ivXmp4_CiBC4?NWP zOKKR|q%xT{RsO@hd8_!+c(6hg&y=as)hYLa8YwpT1Gf05#6r;lemO*gp|41tasqkJ zXIB_rjgCiAEG)Sl-o>dM_a>LS`wZoVE1plIqkCIK&wKWDnk-e*qrY@J_=f~5mX|lQ z6Qzb}T6OeH{i>^k$JJshY%KIo4&CeaNtAEF;*SbHr0JUqQ{eA#E_SDel$uP^2t@b! zeu0DKs~V(UUv+!ip6_VK%seKBeSNH73VAwhaVxJfCB8Qfk3?k_;Nqt6~nj1@=dT z>=XEcBwWRLsMa)1GWgMKwxA3~W>!eO(wfI5vmhZHvJtp%v}?Hgj0klop9T?rf5cel zgqKs?gX*7QGdSC%DXqVC8LLh%ze)}kB$yZQ84#Jde_(A@T0dkyJbE5CrxoaQP|T)0 z878G}M!_~M*FM8nU>9qK3~3>)_}0n?pAghF^*wN(5=>q2N@O3Cc<=`rCck?Zcd7MT za0Y(I-Pd-PH|#U7y2G?sWp3+s`_~urt$MzvM@lC!tzJ)I;ASz0T0Y=eBYOUHn;YH3 zhXFOLIcd%jG^~iBsyD1vz0kZ_)~AODm<%D=@Ns#aTrN=AEM6s?=ITg6hX)jXIIXVD zMu(?RnulT^H^#67^B&l#K(N6S;-cGAvlkTsFCy_Qt$_0A+)IGaS8Vb(5XGMdtuvXH zSAnwQp>F2f`9BN`Vkr>2jl&t_=KA@{AuYmy5n;pP#K!kb24abRy}owMX;p~$F~OBE ztHW8KfaThyA8Mwz%89e$2=zr@HVjVVcycfa!>n{fr+kau?+TX@E2&&~PZ7rv2^9Uq z-gU=c1*kq$&h|*ko8@|@!ubny76oGJCrG`n=pgv>o;t-&dnnjg+}GBJ$qt>Lxmx+J zT{Um#E0iUf3%`jwl%Aqa&m%|gq{CZ`#OC;`5T4)#qY<~8{yKU!_F1~0^BK06-q^e) z=)P*CFpM<6Adgu+aG;DbYrsy^ft3iSzrxg!!Xyi}RQ(kjB0Yn4%lNfMgQHoKf$MqI zTt8As12USdKzTwm^T_5Q6_kr7(Sv@O%QfNxK@L1Wz%xrUIQX;$5-US0=&M>KHWHEi ziNRW2<(oV!*zUZ&^|>ijM-C6;_@IECAdWnnh#r%6kFiY`%$AM#g7>F$tgT8LM&MvL z=(!2Goa9Gl22L$r7!~NF8JV^y`|wtE(h-$J8&|Qjd3A zT3Ij8Cl4HJTpiD|hO}3l0`<$^W+%JyOU}{LgrgC+DsFimi8@jrT`vDbQSv{yT*d+4 zW{*94&+i&0QcZt2NVMK2*G`%w!fokq(5DPUVJzCq8P=&I(^?YC5bANWC9JQzp%8U^k$w(66_#o9h}DoOp3cNJ>67ENs(Gcgmk9T&0q_fmufct}bg z*(xqL!X#eZ4s4?X_#rlHrdvr+Gle%%2__^^-ebW%&v%DfWurdCc_D#LUik0lb+194 zyw0l`9S_+=$j=B_MlTpGPGjM%x<9nkW%Xq)Sm~#~-tW0rK{=Yiep7StuR;~U&uH&U z4g0VbtfT?Yi`0+qDc)<^{ZJf0EWv~5<>e;Zb4nMr{B|+$cC%>gDxs$+TV-&J5ByAOx-bZ_plmQvba0+sVzr1yxzmgL!_!P6RsXnmRX$Pprm7%3ol z2Qem}05n!75XisCmTD45xSlnmv=?=03Ff)fHD0vcKe`1yH7bfK2oCNtI(L?xTG^<( zmFZL7n-~XU@zHIRsj`1baLu>l*}WxgLP^~aZEBV!>h158B>B4^rSC`lKhD?^7`b+r z7ZSxeAh?bf(dPWfg1k+~kUH0|BfVdo@Vu?bdI=Z6frnIQd{*Fzyn5-IB3q-nPy4&s zarOKZV4Y>QZf&^MM9*!$;`Q3d2?QR(TLGY{;kDd4^F}=HHeX zF3M8BmUeg8O*86bM*Rem!Uw9h%@@>bA$@`Pi88J%;?fY=V{5YU_br z6EzJD*gQNcImPl@fE#7OD+b8!)a`z?<*e!M)a@{n=R7|mYT0p5`_Aja69j5(JP3H! zW{H$#*p}7?@BfgRykVTWC>PQHYs0uZ&pG#ZLl4Ur=u{{@TK{P2!H*flbEt@^q_2q6 zxROpoXSJ0H?EN|Y255($WmK;MNk`D^CHE>Zb|TnFzG2;Eg`4O(g!$HrsiEqbLZFqU zXy>Tj-c&!A>V)tK68#=eH=vB2bPtmpIUB2EtkO299=+9(AcE`932++MwsLslq|eqT zyLB?jp!`efsTY%fbtF68v=`Xx%gQ8sXW(VohEJh@E}dcHgxKLSf70_ zXb-v?_CA?O=2140(zH6Yi7M8*phbSLhe$^;fl&Xfbsm_v19Is5_B$+X-XhXcxpz?K zuK+r-xN}AW3~SIqD|$f=-BUkZUv!k%xj$vQojsj+y)+MBoX`-xN_}3s;LmzU6KMj^B^&1XZY9&+04YWZ z0?jlT>|5Xh=69DkxT7xz~cGQ+#%u`_W1@JD*i{YwS$h!T9g%V#)W3%pBR{kVFn z39?!Ah_FJt!N)HhDxwk%HnH?ugbB`FUH_cu0u)@`OGr0-hjH2_LHQQ`->{0yu@X zBDT}OZe9K76+VW(v)%}ATgM{Ir<6HdQ}Ci}5jLh~Zt}RtsU@3~UMyoem{u()?}0q7 zfmDu$OT$ltX?bu?w7*o2DS=Y$Tf3XrEoH`*0cIVbW#JqB)yRsLC;*-d@{c|};bo}J z6&{9z!l-BFKIDZ8Nw`(kc)l490yxe@X%jAMMlCw7WwikxQ)_$IZiD7Qg%3??r9!^}D@fWg%h(N9|4;2=*Tq1?2w88K8%` z$8$O<)pE?7QSZM1qq@cQeOp=>PciHiA&*Xr(qPN?CDG?D2rC;>2oMuB5r(svg4gzJ$)IwqwVesxXAlpKNI(*zUKL>G8Ir<%5G zJ{DxPI=}oJ_=9PW>`KN87b|9UDq%IFqPOKb!WJaFl?PO$z%juy+bUEdp(j8T;Qg^Gs{`Ar+|idlx63g{sC1hcXz`>juY(FR5FNJ zRQdFHF*Y<-OD-bpK}#lAmwfHWE8BYT1GX&gyqe=JPF;aD)uAJ+MZtjw&hi0xk2c$x z3IikYj1GzqzjO0P76#prDUjRo&3^?Ti+vLw#n)Iqw@sh0+}4fx4Vl)0AAX{hJ|=`v zTcWbPZXZx;6}uuxzEXT|ea1U!`qnVPzXuEcI(Loj^?F}XMHJK5d1j~6MBWhQyf>ew z^JMyGjow~a9})}}VUVmOsrb3u8C-uRkpT^a5BBXLz}_e(jhnNEas88Q+6p7H0SX*p z;gN)RMwT+#b722tGuFiy6z_TUImsC}O-f=0AxCH0_MuR*{NjY7%C3^SVW&uNJyJ&> zZ4945vr5qOidxrBr-{L^I`SMtCq3aG@j1|yCXLHYP!?Vma_iEaV3(KIc8P8rcH-dL z$y4XO1Lz4&aCbYYYt5tWpe|q!yJtsg3z7`hw|6UnnVRMZ;b?y4;VETetx{t zDP=Qm?BT)Y@VB$ z(vqSNB{)ZH&3{*hJF)(?LM|F5TG9hucnv+ThFp&Zs43JMLb?Q>zV4gn^;a+;N|XmY zINAr{ne`q=Zll}`)8Hm(I~h8v>5tLJ=LR@csAGMLu;H_>i$$3bhb=(JQHpIcb3+nT zOO9X24-LN+gwCQi_{PGIloMo#_n!!W^tLz2jq!Zbxrjoz*i27`w-0dt!#E6Ub-VA7 zLz9*0_{cUwA;PDovd!I95o~B|PJ)$M@|6nx)HDop=>sB&hrJo8?xxe~DGRD|A5}9= zb{Pmg_JXf(bfLYlDeJxvr2~900N>X^TA(cQZmUn)ozV}0(5XOO!$5&H0uxd_ZTT_N zkT9!vAJvb!W}eb@j1mrlw9V=q5US$Gu+b>}&DMoF_~Og_Vn1Grsli1)T$LdYVaMOo zV0GY<`Zh-YTESmTG5IU=5rmcz|}kP|xlH z9Io;kt5#|)WPJk)*0TS%t`*(IhkfqymX2zc%bl>ZHxt!XVJM29hPn(0otgI3($_+i znnH0-(5Xl#1n3u8GqBD9T$SgF-oN7lh|AEhAl3c~n0z5&a7tyAFej%7-j&HLG;dEI zyafA*@Vz=Yufm@QMe;UYql>{DQaz#adPC#D|AoiSDX<+*I^!|-itam@)6TBhWU)-6 zj`uIs&N#;b0s)CyDPiop6=&z^ES(LEzGP+G2*HI3;wC(12-GRK97Lva z5T|s$6PY+&OdxB9g@X^UW+%_d@RSM|dpB6b5;2OC9iGH}@qV<-VmFFe=G2BuCOl^X@@bPi<_0JDRx>agqm;?=WtFMUX#Q9DGEqf@piQ|UX9GJeS! zjg#;Smjexst{y6!xagbUh+Cba!Ytsxdt)LerF{5gc%%N$VX|Oh5fscPN}Z z&1Rv25VPa#Y)lDRaSa2vCxMQvSO|cGzUv;0^sDkBT*`z)QJ#4SVQ`mNy{YnnuMwu} zyQ>T35WgIDgkQD9>$vezrP(2JTGY%7pg!E@n~c(XnAaum@1QEmS=D=Vy0-4PHO02 z82`jo#qMW0JiOOeCvWkmwN~b<2gJAGGbJZiJ_ z;r$e<+jvpw?Xb@~Vq#PEl2w939;XZ@Bk%`hETP1mvRWcj$py~i&p3iv`{3Jsz{SkQgtrw&s#wpizlD^cW_F+p zvR%&?B^^!@+_bFwQK(Q?H(*4MFCB&X-(5xSvbEp$Ixm3D$w_v6uE_FwJUBaR+LP;f zds;|W7*+d=iW`3pAy^a~@lgRE)VY7VSSU|rR}G*^IA;LPywsg%xpP?$0_0x1n!hY>dtC(uS0I!U-T6UGoZkUm|n#0ey@c&JUXp3S)-(lq0sU zH`vfDonAhMY};#UfkH9Z0R81sgF^endsux9;iDOZHy2T#%=0rUB*-Qmr9&zj^JckN zWuFo`Wj^ZWS&ufg>Sctisj3#XBkIF>x5s*aE_jNB{S^-1DygSZep(ubb+cCa@bECy zPpAWupIUfF8hNtpGy0@*)Zhe0Tzq((VuBT*uCMsMI)aaZ+uhr^5{XzoQ8DT12j`?- z%%9E01(I#Tw=jcilwp5D?PH$n2+m!qQ2cP>uaYKS42sk6Pk_%|3Zj2E8LCrV4hJey z>S@Zo9*5lKE1Y?>V-y@UeL1P^?8QNd2{x=n3v@;UHIp_cokzpb(a&jl=1y;|qRK2=hq z>&vnjniHmmUXSh_tMbA86Yzb~;~gswxLk$A{Lgq){X!-!AP_KJR`Nf`Don|hCR|R2 zRZ9emFnU=m?tc*n`!7`@pc+UnG@UeE>00Bi+%CI%XDkrF`sO9vB3OSWYCIc`h^kn= zzaO5l{>#vsAX9Fz8R!4|fkNs}?6E=v=~klmm^O01Q>6K?M8-%dgTBZ^VTNLbBSb;c zlmQLF)Co*t^Q3d%sp8%LAvkcYaO{nbf~e#P$~)NY7$0-%-9td@u!X}6GM`lS97KxZ zM37B0yjz0@N%TQJQ)rU3Bm2My0mNSTRxu}$C~&?imXqGj9>y3e@Ho%329H!RHu4L9 z5$Qb{%#9=6rlU^vjf^~#dM2|`0=OV5s1vZ7)R{eTo>u@ouIb|0Pbv}~2|JRZsHo_o zVIE8@Vb`iCW@zZ~{iT`Pi~s7OWxl*aF>xmQuK{ULbg28A8l%E(q2|<-f)QIrJ1k{v zDAd4ncbKqytq7zb^D>Q|3>sROV#I(*UY$E+h_FeS0Qgc-Oz$03zYr?)mrv&(rSY)X z#<9p215^&Da0H_3I+rFqO8Qyu|CC^CLm0%WZ|@>=oL^9U%d6={`-&}LRjzXReI%qZ zSUyM7?n*&adjDx)8;n`ivVsH_K^8A~@(%rg7H$DPN0Ir{Yx92?c1LnPZk`yon2wEU zI+VcavZ07H*8>#>9F~-V27@}Lb#fsmkd}mvHIeW0K87?Ip*QNa9uox_EJ({$h!c|t zbM+oz0DY2W<1l+%b6O-pfG8|qvh^ls59&muZY6L;G8<_J!Ps`27TrHd>;aCJ~0_)Na~TA0uJ6 zAdzQEVf!~M&m0=oqJ9P_NYjYQ*Uss{!agupLL8;`fxHuNH`&5kL8awyz)YDSqB$AU zK@vegs@M4^5xv7gwC2^CLl8g}&4CYqu1Ex?P_>tI`+J@EI!ka^ih%4G67`@ieyzYliObAwLs__%*y z80Iuh_$>Ud_05&$9S^;iHgAG}Jgtw7+P;dvo_Z@Y8HNao6?~p>A}mvNHal}|oq{k7 zwz{2^Ss2QW)ZQ@0m)DLwuOA3EuefC$1{Iv1C}Ry5K3us9R#;ARyD`GK7{C@a%oGXJ zcK{dopE@R*M0}lYp1oq8IHuAr(yQ;(QiJUq_0O3t^{n_s-NAT-!lK?_G`EuKYJ9x5 zH_aV)G7F=H2x1w`!U*LE1`9?*jzj-QfU`LP7h@^>YHsm9%+W86F{F!v+iWAmm=pz7 z^@CFDQ_hj+Zxk8AfOy5h9!PnUGRK~umK8XWMS;hSB~N3wX=)2BN67%p+D&@m3$6xC za$Qf-d#E5$tbZ=?VCWsZ&gb0KHm8hv?hy^=iS(bwcJ^$w4%74|tRuu#ztprg7%x=f zYt=1MB+atAw0;&MCfSLYG6Se$ZXa0f@0lWgGj@)~ z1WN-R?O*$s!o|e)78OW~JJ|SGYCvgE8R|CZ&5`3Z#HZp4Cce`1!4(0jKtzGpQWl@f z!4jJ!Im6`VZyF|h{@#$i(AFPi%d^7(xEmpEZn5eL*DN_b;hAq-(%D&5Ned zEnr^^;&GM>UVf=)B6-GhMIaUn6P~&FWx@9v6IR=ur5TN-NK)+E8yq1KjD8@M^RB|E zOXLX*hJ}I?)mxzLVRXm(!TOSeb=fZCe+J?Ysi~+>SeyaE33pk4P5 zEY-X3{oMMdL$a_lEQ`~C!ks|UtMq-R|2_#l%JRHiy*VQIs+zaMGOos||D^P@l2gK5 zD&uDt4j|3rVj+otyc5jlBdqYf{lB*X8|Jv*1(`5a|0&8dJb# ze87*)-gZ*t9dp2H#sSP|aFDmV`L7=QN>qBrzb(g8O+s>_t0h7|_1y$)6&>Kb*R&Xm zi}MiLTr1G&z-}gk^L{dk<(4w~IqAnB5kWyprY_AHlqiHFzK0yfYIR+tA38mJ_10&s zcjXa2Bx~PrCaVcCO1UzKX}Yox=>w#&TfN1F1~ZS8U+~6A6&5l$7GDI_fWG>@ImeLK zG?gctwmD_K{CWTf^@bS+5=+B$&_|V86<;E(!G$Ymwu^vlAV>4DU_mj5J>?;wa4JXi zuFg5m3IRe8L#gpz6cl*jky?FCtOefYTQ#_V|M;p}xO#DWd4Mt+2#`d{cCGXrAU|A` zV1HNU2m(;%?l$`|&^JYCC2agu(bSW{;i_2)l4H9s=yat2>z6*N2<~@(Z@6Z`^T`!? zGew2usY|hwD=ex|pGvh4cwlyOe;S+KONUpPyf{V<+9R}b+A*7X)<`3h!d7qV2!0C7 zpc@bUSj@%qL7x`#dshLxWbaZ3!O!hMT`5nu00F&jF9f_QNxmQ7yO8RYdSG@j{s8^d zXd^QpDGdKp;8wi!*cjKYTJ1`$rg|!JJRj(X4V(jXw6ezr&;MQfF-#V|;T)&wE?jk2 zo6mSP@~qMfbQkq=Z+f~sX^fWvQn}ft*?6{WszgB-%CxF36p^-}1(b|-C zp2v-^>T1=t-JL4&d(uH@QgAr;J~#f95h=1^{#>g#^xKB&ljni_(?yX+O>d~D$Z7}Y zT?qJ4t(6HAwS@Wo10w<%d1Z2U%?M#!J;MF@_t9V43o0W@|GY5JJAD>mNMw+a!!uzw zY8v%oW_6^4Btu|;GWntoJB|DOF+Cs2|2M|v4@zQvGn(;jQJt$Dkwl`U=D|$3ukgRe zq##Yt1d2?#)>6=Rq*t{--d+B@xlAGp8Fi@7>7?c`O-;D9ANvpmrw=fJj><0?`#iZ< zUYe4e{T0#vS+U>CupUip|IQ^5%)fGsgOyM3AkIm@MMSBo^Ha*N2o@6w)>l|c_S$o> zn-S6UX&C2OkdDG5-99S zUW)<#mw}P>O`%gO6KhUF8h7smvgxJBY~nxBA}Hv8qQ&>4yv0Aph6mj84hRor(=`eF z(ivnY<2taOa%e!ok9UOvWNLM<)E|U)DNR+&3smY@ z(sFp@G2n0l(R1m_YJzEcTe1$86w{=o*I(i}%QGc?J!1;v^H!yz=XzI+qF<3etO#ie zUhYu(>e{FokN~}O@UH<$L4@w-+}li^R}S5u+gk?{)pILx&yzs}2!)tWl7kH`JnY59 z*V3-6x0pk)xx!jc`ZCn{OMCgi-b;`~s-XU#MFz8KMy8Of5R8Vw`)RE1vH$!RNq2KA zak>JL0!-qmeI%4mEEo_Z0e!Kh3d9RS1$j4QJzpd*&&$om9V zJtEt?2uAF0fhpfSYB@&->ItpjfFDWv7+ z4fPpjFg6AT#&h4!ND?I_W&UYbBmwtah3f|mo9 zBCY0Lyt{y`%ZJxbK(Rz>^kA<9&3d05|IKT9KqRuZrHDjQ!Q!x9b!gH`NJv1qgOBzX zhKKIxo1{?_6YBWA|FM(+YXg%!PR#)&Fes?t_ixbpbiVX+-^0~jK|w+Osh~oPjn((N z(-ly3Mn;?YJiU9EN`xwE$zuj`1<05K^%_2Lgl4e&RE-BfoT$`sOU4ig*--A@F*~`P ziGuX-)#UQuneH7b9gl!{X$XaQX$(H?>g*KQ$8N&`{Vph&$l_;kL}3T6&*RDI^~XRY z!88K-4mo^Qbudv2MKCKmjx&^CG=%}7jv%0q99M|&%*;SykF)k9rKMKp0-!`Q7y&$l z2WE;lIW-lRkPzBCBqSs=lkk8BkHczvB`N}sgYR)amzZmtSciBB1CMXVu-K&@F;z6avFPx-f4NO^4~ykSTe(@Xi)cTW4I9pTVdTv!n2YG1 za_yGW)+@Q_QoYe?II|mt1l@Z%z3{)66yV-u#3ARO0ViL82I2$aQe zVp>xI-M>!;md*yB8m!b}*Kts=bsdEZBw%Y>HN!hNKJIy3H9&m1ARI_!$7!?fJ!ByD zJGLTT!RsX#Wxk^23aEMGZv$S}Dn!XFVar-bsd5@{5$q&tQlwKhUg1IwF4CjucYRzt z9X?gR7C3wj$^pk-q8SK*8Q`f2e`f`Pmq=<5=n>j{m=#Y|!Jli-f3H@`Tcg*7P2ov~ zSd*=^q@QY3T@OW*DnabkuVXY|C0|8D_{)ig6%YWCo*_5nO%h-cp`vgF4-h8aMf)vU zlmq3ZwAB6eVQ)`{=j{CRRF~rBz+-e;NdhE)OIC%7A9HsFr`c>gyt>fEw;v-Z6wk26 zVc7qct50V>2vYrik5Vkf!83=*qTqKm%CFvyvS{JxIeaJZeS#?+AI?mj1r0$B=)166 z_{2PwkJ98*_AQEa-bsl6hT(oaCH@@XGAM|95y8Jr9B~reE{y!=&mR~R!t~VCFP_hh zGsDF(XyS&7kn$-Ga52%Ygvg>B>(XXMe@8Nc23U<{eKOW8Nj{s9npet(RQjZpxNFgSzT84LUULB!({&9Ha8 zSOf9z@qGFl3aCFGz}rnvD~9H{%o`?fvCXj_c z-FfrKNp@Xi?L55vD5NCWrbZX7W*q4qk6Rl$)FtxAwuRAP$@_V?NFn5CCJ(@j?jUMg+VEo&T0E&t=(fFifCX} z3>C5s9CGLb7V-PYnCoxb5Pg6AOaA^*u z5FIKpg#uDQ@n=)@ufPlFwRM!(Bt@f?)T6mb^mdqm?~{ohHI~~@ey>-y2h;f+t1b}a z1BBz5ykWc{#DHE68!I@fdkp~s6+)ZReg#rBv&H@rzpBikfK*L~vBjPRvp1Co$}hHj zIghA$+7Y~OF~7&a`XsBJLaT?8-6e-M#jcO+)frt z^_B~&pw1(-C2~?!Kq!DSLnRp`<>P0<6_E%gF5^NBxsFc^g=NC%2mfHDw40?lL#%Nj zBPU1r#lghI<$j?`zjdNg4T-c~NeM2}r6f!`qENT>7j?G()FFs1I1%wZ8A2}Gb3Pu; zr&y%qHPgDhg;10nQVH)vOp{(zRP_7z?*SsO*22R5&AzajbhUTE{^mcG5sCjmi)CfY zw2Z=WADwv6SD*qU82tgcho&_>>OICQ%}lMH`neKF)L|w%3Pod%gU{JsnNVVj$q z(n<6;cXu3YY=*|h6Pb+wK>)P57Q3x28VVfonrrNPdVU zLJU({o*LYpE*NqwA=Zlb1mH z5;FM`zsKi?eNSdksgVc}qjZH^9LW@?0!5Khdnmno(rlH^5Q76s{l%)TCEXvRzfJr< z1aU>2z`qh3`i5}H+(5+3t5?D}q=z;RXdZR>&or%OHm$0WK!k&66q2a3$v&pZ>_V%H zrvy?tW{Pic-7Sh30o)mtM1tD&|KFQoa)x%~^|bPsh_z7UBBGJ^9UG8@C^|R31A|=IxUU!l#O>>v zotu6QDN@zKPczRY%Kz!vl#yr#umAFj;ZPx-|0&ZTz|W+ex~gDGl=y}u?Q%!}0<`Hr z+rDDrdGzAZbFfM`w3flC;z@fkOgrMAJrPsUWjZIs*~x40`Vjv{h~77n4n7CZ66hqO z+rr<3ZDIBup=~V%{JY6^)r?b7Z8sF-Dky*c7F3`Rz~tdVW+2gUy7^T_`U^MWnPmGh zHRJ2u-tK1vnf|VsR!0wB&KqfLp83aKNk8x({{oi#ynaBt&d?-a;gjI3QYn8HjCRE+ z;cjK5*!*yaJ-|>5g1{qtFEG`k|16t(;-k6IX3cjAHx~0H`d<*zYe>ho5HA%(W-h*& zr)sr}F?={1?%Y?C zJV)yMW478p5$}1ge}z<%7Xf(x6ugm$h>Zv9-*vJFy9I4m#zXy)P{0MKX(M|BRivoW zS7q5Ww;bV|#jM_UdnAe3YcV?9>O*n-`_7a2;PUsQpyCj)#*QMrEEaAHP_Xz*lf~JK z1%LWRPA8bxQR^rOTSiD)RFK@e^*fD!g4nl7Q-(ytrE>H6e`E>mZ{6 z8-93fR*ICI3i)B~=X;@*7v(R9@ z2K09QDBgPgfk*)BTCFs*z3v4OgS<60-PHVQcNsaqU^$A}G<`Sq1N;xCjyr(=tS7w{ zyJ^CKfHP5FfHP2RlUh+BgC$h7@kUWVE>1(cykA%lG6jQ zhui5G1Pba=P*D*>6|uFw@PU9SRXOVH?7Y6dPCbhGFdS$A0vHJ2HZ1xJPEw&8)2D>b z1l4cTou9%O2c{{zPHGfD$`E_dWpG>Qq!RF*1R}8LLDSRIU0q#D1#%%3h=_<_=ZGto#E-cp$kfK9842$JF0_n$Ogfz>Myq z{>MgE^BJ*(mY_O`?uWA3^6P{_@~6AFR6w&>gaNXJ&sjf^m8eD;w>2SvEWLbe$v$AQH0%&Pi_-^l%sOUcT6+@p9J&aKJwpY ziN(^QpMSYq^SV7&KL}G&Qd+z3=Ky$VKM@xQWL3w=$LD!CQ}pt5$A39URz#M{{8#L_O^jkXmMFto9z~K1AAxG!=aJSh7xHuaFR&_Q?L*KqjJIMKq*i0T=kK~ zGDa2z_#`Shoy^y-En4sJ+wH7ve#6FuH5FiVMbh#rDxVzTRKBQI=$!2D7Zw)}kB!mM z(-Y$3K~`l7C@3g+dA;4=yJ%`I0eeHo z^I<3cqnVjmj%jm8hZn$tO>f|#p`jhVtVDTU=f;QcIv+3C0*Kh->9$NX<+H);#jX8X z@de`R^&#Yg0CU_i6g!8y^rfOc+xjWvhz}E-*W*n<{6k?*z?+gN`Q&p9&{Ccu`AO30 zqs4bNAwp2GjF@%{0pC=fRQ&_~Rnpc{1aTP57T{jT7ZrU@8kzm`hlP=`66o9jlFpDz z%E+LTwO{Y_IazApe>$!y>7k&ar*Hpxb9lPa5*8Zj1OR%&5v+rihSi@(S+56Kk55n7 zYgJFTCvE_ryBA*gAeu6^%ja z;D(1-LhLQwSf$nX8k+roISt)6;M7A&a8ILu2|~DPrGdm2<}YZpYNrga3^`SGb*_>1 zirU)i{3M;9TM-;81O3_9y6zW~*}?!$japhIdOZ&^s&S3^1c(US+U>!3tTEtg_>JhR0q_3wOgEk z`v6R0U%dx_`3`4GzGPT2?PPJ;r6Z~(_ekgpwT~zN43)q1MhTIU`&Zo5W^qI-Yz=_pCpc1+~ z-`nybF5(u{*WUtC&fD|E8*Mu?7ndtg7W}#@*O@Aao?ZJwp^;e$n88mwOYkdljZhOTu=oT zGq6C^tgRVj|Ljd=9ECthcDUuL(W_%qYej%+xkq@|S{RqlZq1=@%L$qA|fW@MZ!35C2+ezsqy+7C<# z^}o>)i4IH<4%ssGN*KkU>;r?O{MoR;{I9;i(bIDuAr(TP)2nwuXa)6b*fjyl*!d{S z1h9gvSDGSlE1btS~WG1wFSA&0(q|h{C~cWee;djR_)KK z_by04=q;x5sZqtdlo6M<10F7Y`aU!q5f> zEmVFNc6M$U*?AmyZjTcZM(jDi7`b=!1MchZAsSAv*VdkmMu!7ML&Kf>`+J}p+(lTs zlYNvKNd&DjnOIp75)$?s!>OYIrpsCa+tuPI(OJh)Ii=(S!YF>W#I~zK%s9^{OYC#B zfq%S)L>cn?_xlKr@Bl`2nV7i8SV?CH0u9edbUiwhhH^bLugN)C^yCp8~@hqH}xK*rXa*mN6M2{6d`3$><7_V!n|Crhrz zzv}DRr7oaDq?MHZtgo*h{3*!2{sJ^)W z2TBURmH(&{{=5KKC+gzhgTFZ;_dX}lz>QjVMG_|E%0KEw1|QkhHyxDmNC+G$@L9&f z5c!A>v-dBG!JH36&IYEMGE=R;}^tLzuu2|vE7=c{rR z&*>lUgZ1TOg2G??aZNBLHf0*S^NaU0!_hNac1^&5PuuQvQhx&g(G!?%K=|3q@*>Re z*)A$B2KNSV7M)<8PEb$RfcS^DNZ{!TP^e8cn%(ZKUh^QoK*QpNu>pr?{`m zx*y;q9a)VTtfUqVGo%B;;4%qv=}=)0#NqP3q~munt;7OI5NvqlN5_l1jowxG@b`2v zIv{o}1wPM{&S3%^Oh^=!ny%JuteW$)Fxx-mvB4++0tbe4%Wnbw;B2u*d`3kIMzh|63#c*QzI{uLlqZuYcF7MP z9)9e8baa08+`V$H(DurwZEMkx5N-FOP2Q$cjA4+uvgaGuL_-5p*T!D=$Moh>1P;9e ztp0Dpxcx7{5rX6hJUQ`mskxCcPn#b5`mjg2Nafi^N=^2;3bP?lxb^FQ{Pjm*Wh+ai z>7P%0UuFSR0_?UfJvD>$zjZ?ZI8#?oS+vlRA@+5>07}A6D+#vF!|&)s$?hvT9(w`>2@bb0+wH=rrYu9;uKr^wkw@=cWm|mv|1S=h6mVS(uq9<|FdQaWtt^Y(^n3A0uiwB5KTFKX^+$zyGOo_ zKB+(pvDt(XjA1Wu67iu8N3+Wv)cTv;Ne;eB zqYS7!0I-ohMh8#|A2&C*u&*SrB5Daa6Mry%Bm~+x0ChKNf&e!9Uijde+#fFQI^JIX z(`>)W$N-%@)ESylz~T#lX*zp*d#jHfynr6AhzD=UWU*LH!Oi^ySS8Z>r!8wQ^dR@Q zr?mk})c=8koD3ge%7FO;T&k5;*Q=Q#rLCs*Q3Txe*K>e5 z1{gS$QWYV-MW;0FLcq0gWCQ}C4=KN6Vx zP5qG48IlG`iqJdreZ6H8PH8+ve)=d@@_y9G$u3bY2;ff;{27wcXjoTpahRaRP2_{4 zQuH?`7(k&p}E!_=DOGs~0 zy15Vf|L2}_&l%(1F|K3m?RHCiyzl$0HP@VT%`ZM6Zm_$%8&r>{J5($}*b8{PCq5Yk zg|k14-(cuG9>75G-2ip&xYUR_+aD{x`rkYGy4q7GSInIzb3|e0eV2b)0ya%#pPE8E z;$grSY7CqykHPS1&dA;;!>@7(8CYNPek6(?k>$?F2ImahXvgvHbpR~6cgBy-{vd(fQ3Mi z!BcB^JaUKw`GJAYi6Nn2MXqb?n!I>T-)?6)47&SUWk`pX&ehp}S(0}25X)dE=%a>{ z+g`ZVW%8LTK3Z$#0MZ!k@duQu+D-y(2GvjK%2uv1$lS(E?BMFWFY<&`eYLZcD}Blp z`A~mYSxcc}(3j8g;oTLs#Ca4kB)g~c?EsY4g0jpr23N4LeQd6A0e{(Lyn7;6OF>eE z!+#YMpNeH0E7o@iFVR@`%{+$Qw=!k-@juEl@GZ5Q7!)m z6wv7`^zjt#%*X7zkT2j55*M^2pdbS$36LkJbsh`wN6qO5On%B)Pz_YZG+T^)Bus0R z1%LH<*uhPuNw(PINsl! zgW1*aVhC^(N(u_*E<52*4OgyIBKRXz9RL47S0^pUm*rEg2;<71;1&D@uu5d zh`w6?NA-ovXW%eheB$T1vnmpQZl)B(fgktY9>@ji8X6ipIzyqOI_@0rbNnqCf{6R80fPwo&~eM><79Yk+-2WG zUGB`zEQ1VkGV=JFPNc#8*s(bOGRS})Sal|68uT`Q;wyb$renllP`Ca;8Nq5k@}%Ju ze@$==dXim6g3b!LkrCM)TeP+^4-Tk}AqNk4S-w6LiF_4Y1U(lhkU866T+x60rwGy0($Zy{|eoNmU^J+bgW;caLMxBHc<4nim;+ z)ipEsvqu#$uV5f#-}XNjJ&G#s373{isq(B;5BMSKppFIi+~@aEuwCJV{=rWOUtln9+Jikr}z0kKR^!$7s6}+Y|U?M0D}ZAllDm0 zUj)Gp<~F^IdH|Ds-&<-!D+0s&pWVonYZZrYt-zW-B$=RjCu$E?BPTjkezDp;>qYP6 z&q9TeOWUHojv34QV`BfFY8Cc4xMhcEi)G6Dg}&l!+i>nMtLRwbJym?~8Om{JBC-WL zwz|vrtn-=pG8E+^`3N)owYtFDcBi9_?|#rCV?O9BUYCNG%RWj!S(u)# ziNcwyu~f3Q-UU3*aX-BFW-!mpa|s~PsWFOhEtb_q0H1lx@^a4j+S_n-3*qqcsZGrz!kr>x7AQGcBw)S`#YZZMWSn>Bbr7XrF<%6@t*!GyLBJFFsCghIsl#&lpx~exu&Hs`&ON@nSGi z>{uAx!5G=X;|ukDnW>(>ciF%K<^%EPjhu+YNrd+5sI2jiAJNiAXZ|oReIwU1J3Bj1 zPluE`114x&gJWYrJ~%ni$g8h+2PGaK_lfS(CNO}4?2u0Tx~=yeJscgeD(I1>wcfNm z-rfodM5JZi{lj!!@azC2IE5vwM$^fzvSY%hh7`fvs>bn8k~PqNvb=*6Uo&~Gn_iZV zKoPjZk!PEk{wMunqi3)AuV}2T(~-b1rLo8gZ!N6NTYS)J^D-=7otnl}lh1Nu5BD5y{=Ypiyl6Le%Vj) zBXIu#=yVwi^`cF;j9NBa=b^&e*5?Yh;Ki(sy4b4l3kWE|2oH{?0+vZo&p$oYUtpv8 z0JghW>S4}=DsqV@-$x`|ayTtCP1OwhuF{qu5_stpYF zttvRy3&e}C*@SkxoSNut-=(nZA&`#HSd`b6AhTtIy6CTCQPTZ9{YbLyyDJuqi2M9C zZ>oOW%r5+aEPIk!z?jy$38CHEHm6}3ym`;_7vEg4ZRC<4;Z2>s2g}MN$L;nJ&nO|$A$#F|5qSxhzeBbpggKI_B?E#B&^RYB=$Y1-YMrltOGwJ zo_xdrdk1_UbqQSoyYR%gZ>3mrxbAC#KB}z^22hQO^?K{+FOhhx7F|hn25|#s90!1m zY6tFdzc-kX58UWesZ$FSYv^@#bYqRe?<>g9YR0DEU1~kq+mC+TB`BA)YMMSniEOC$ z&++5H(T&o?(D#-7$(F=3FiJvj`-+!0Yt7O%X}|y7sLSb=Lh`V>wl<=QEnIQv!S`5u z@n0})XG1V|Bel&e{wz?{SfA6=mq0Cj_w6)egqYjl={Rf-zZjGXL}D#A8<5CAuwD8`}N2S(|pfj7_x+kKMP%l-C zx%pB(LjkM1pu?1QWYf}A)jDclyBK@@z)oe&g(c=seYWmRD(nkC+S)$W%6Xc3DR{*1 znA-lmd4AI!b+RfyD|;JxUYI7Tdncn>TzD3W4kJB>C-F)(PP0m4D@%GG?_~%)vk;9m zzE`7-HrC0blf9oGv+GWaAe)od`U~ON$6`b39JH*xqeo(hw*W(#c)g#Y5F9J|k{JJS z{}N;3KfrBiZJQOZEr1lr#lsUvokFV^9~c4kToI~8Fpkh7n{rV}qp)#>CCjY<6lNLYk0*UDHL>nFG*FJ-z}Se~;kK{d+ar+xWibiC|( zx*+DOk?^rHV8P=DKDVpvYpxFLpLEQaT`r9mQ%;Ga^L2ie_VBoeOd{C{ad=dx8zYU$ zyZRPo9pw}+jcmk`1H<;3Moa7Av!T4t#R^Ve;1-3|kXz*D%G=Xx$J0<-Yi1I)+}hfD zbK?nm#MD$AtvVw;eWex?W8#n*2M!+I`kN2b#Om{FM=ggkk$$0&Y((Wml4Y*Jt`kO) z&zsG**B_<(oNv61ab&y3Sny%cBzx#+jZF~J85#QFgBoMv_;z<8M22rALGE(@dSLlAnU~`|udw&}sN)dtyguCosV0#C zUyk)Y;UK-;mUsGI@Qs!HvtL#BpPz+sN45**zL$O$ruS*?>c+#{-cbStZ}HRA`gQLz z^){b(WB*z07<~Xj;8RW(s=mH{Nl6KAB+cmdDzG9C4-a!vx@fCSQ4Ba!Z*Fb^jb62w z%cnFg*6x>=;k|6A&tXfZ`dWM!A4>(tL^mZZghoXagT$#?{VTVU=(E#Er&f3BrDq>S zyuP?q2DTZP;#rOFB)<&wgML1kIia%OTUGswlAF(JL3DWQbFG6aL6K^RQ|pX*(XY+c z;`--yYDNwI`r>MHW)!-qbF-(Z1WXxGrCMgXW_AN(15W9fQ^-kPHtCZYxm%8A=5vs4 zaOw;mT6H^K1gZyzeJt4OhPJL-;0ql`=%O;ul)=kETc9RoC6 zg80FN$rjTXVtyA;D!I;!va4>ur!q~l1qzCyynMcK?>~Eciwg_bNN_@U2yq@ud06g! zZO*ONBU|EkWd>PQ2(|rH@5oM*OdJY^Z z9FjE0o^6VSoX94>0bRDexmi|Pns0fYAZre^awKAdrxgeUo2MrbOvnul4TeCb>9=EMq*k^k$40zPJIjseaiuU%$w##KeVG;{5{Oj}L>HrU%LOr-QTTxi^OqJBP zUAS!N?kM)==Em(*X=oh?G{Bb1oi=*a`sAc30*4rPYH{&)!L~vLJ5f9S5Xg~9gJvmv zu?eru%?tY1e|xs=>dC|pajq(5Rj}w3_PTF zzJ6X_x6?|(J{SA*Q6;UNMwL2c^KJs4JK&zAftQ+7CROy+Di zi=G*+k)Ec8lpGTZ=cwpzj~NV84t^A@-`CzQO|8w%H+zPUo_*@e7BwV%PUJ!LB0b^@ zH*}kE$;p;gT0oD(&#FYI7R*!Scy>i~eXEs6_l5p@<=O;GRKb&|0Gf^-;W_XBYICg}W8whXL zq9rf)U>YEhRh}WVLCpFC+`jeSMQUHqqSA5D;e$j=ept-^EG;fpp~nU{XH=o-pkm4YGKlgg@Nu<8i<@G zwZv<-rdTy3fuo{AV)$J`2J3x21amoS=*h5N^IstX5KTnWB8hickwx{GBy4p#l$f%; z0}>sh^$RmjiEq?fM(3#WN&-ITp{T1?+RoL;pOgewSr7e36-+M$EX`k@*E z;G}uoO%>T)4;d2eE>$_CIw)*;mCM z-Df`w94p?3GZ_wi#R6GQpy6!87YM+m((V! z;b0oReqV}v;Ac>KyQj~C1o;YWNy2qm4Yww>58Jefi1=A5FHMNLFGro?HRl)t)apWf zys&U!8yZLVisd`;-@Pu#m-5f)h{u_njeMSqvd9)i_QyjhcCA`;n*5`0ZZIxAt$__isTjZj3`j<7Iuz#q@&| z=h(0*>RJfmZ$;*+s!XY;WngYGV^^&pm0P|U9T&=?wPQyeh9u_UY`hyixG7*WKhswi z&Mao*G*KIrG>RGg5s5V7OQ%F4360Ix;zCNonc$paOLPiKjqiMR7v^{DLa((ZeP1`3J5@g=YCRGk1E?}Cz zQ{yoEe5R^8P0&N9H$p9Y8pU=$V>4pGStHZX*vKcof^1On&eKz;Sj}QnH)h@`ouN*$ z7cx)!KXvGJt`gSonRcpKcBqVS-$NpE87!unnNISUSeOH?IQO6rQnr5gO)!xb*lN9G z$9iaUU2zSUiwyKbk{h0raaa%?Zy;r6UR-r;my%`VQd5nD%z2!PYw6UxvD$Tv4ky(J zE;{I%v}F$zhbZiSE5R$lf)cyh8Oxip{Wr`;-*6w(a3I}{61}~@Ga;Ty3`;4dMvYs8z*zycyXqx z9bZM^UDh$z*~(00#K%{K!(L-hl!0D^G&1*+PY;b6kA-Y|EE2KSQuf)4Kr*U;izwe> zsxy16Lp)Aa_seK!zDZ^U3ru!RVc3OrN(+Zfqjz}6)u@}|qXaN-9w6!*=judoIW;-I zayxAU&zeNkm7;4FAZbfD2A-=h3sv8}@yVdfO7_?81-Ye=}oEPE<*SoBMdV;Mwv1y+X>2iA)1>G((fsYdwP_9XO&%25{rw~(F%qr;6$H$fY zH?Xx;1%wRtOfp?<9AqBTp^!puUf|Kk$_B+BszX4-a3;uoaItB9ADGDsds!r5ikD=t zrVoLv>3A?M{Aw+ zRaaIv0zl)YVD<7AnA(BIsphJ;--B`}o0*K8o7=bDFh^O7Dar1~Osv`Q!%B}}9feg! z*7k#bQ3fvz1Yz!->1rL3D^W>p%olz?0^(YVlR%;6*10ws2!X)R_6_gqJ-<=|M}^g- zObvO8B*Z_#-Hxf45$1rclGC#gx;i2gof$R$12!Z(@r~@Nk&xQ;dj^OEPk^C>mXL8F zC2SzMbGP9O>0BDhWTznh*%J~KnU3g=NgXW%IQs8nWQGonejM31uL-xE?QW6eBq}Fa ziSRM2@%CkPVRbl1+5N+fvFW4PFrt!#AuUUtj@Z2KPh@4x;!s2=g(ZfV>1 zm75V$Sr@D6=<8>Miz0eNMMwYm{@qMrqq@i}2@_M?$HxauEnZ#%z74Va0aKU~rgQ?A zWGhbMw#%t8lVx~Sh&gTc(d(m@?pWhcqEq@WqyuP7=DOdG)37jOx!Bz<#d9j_z*b7h zmM8p^Lt!O^ZJgd;bznNWpZn0oNx1>;H|DKJ+)rE~_9*C|OY0rInAiR3w}=aPwBN92 z8aX#JC~7PG-%jHiJ`nkG*lG3_ND2)0?46VHyGQ_d5K18=6F0vN5SX*Gvw^|#(2%T! zx^A%=SfMB?DM1$2A1J3dklR4x?dw$e%{FjRn$lZ+GW2SR;u{GA2?%9N`oYjr@6LoA?9&7MO6lF5`!ty4@$b^2P#PZ z!LI97_oU$>VisCUt-un&Y>0a^(fOG%;`}*_LI|4^lmG1qmyE|lNagQ~f@~%8D&MYb zL2uTo0#NL_!!fOg1jOG-ShwC0*vgqse^(nJ9cI?lMNm7Fr%ZCbE-ftu=n44t%*$IS zSZZo&qUL>O+HZlD$iu@^e_({&B57%9=?A&pW*ONV*L3*7V_@?Hc$8A)lg>##H@zch_Q`E)y9eSN zz^t#ng<*`PMGX9|;Ez*4L1`Ts@sYT{qD0(S>kj2fD=RBQRVC?VV#c(J5f}f}O}^p3 znhOxwc`b$zX$6E zvJ2!hv0PB;GbHj%ONMT6P(OSp-o(bvglP!4^Do@-L>N>p^OwS)MuT*0pUMI<$Qg=^_o~aI$nCbx)Ky`{2$Ti{vM362nmI7 zrc~@CT59G*_ej3L>aTcrSgAD#a#wt9&=&r&6K9GlXL~xKV`lbW^a<6i_Pi)`*C~yx zPteWdLDE?B#0CrPxhrie`GZh!NWnc-ZFj*t?%Ga-21F-5tyngWXNoYrRrmf>((YDp z^w6;3&tm$i5@mrnk(QX|Y|o<$>>uv0St4ci^?%mc&T=#(!ok6T8(E;5W8&{LZzHd& zh(7@Vm+?~$Y42@Fnt2y;HUUq*lJu(~kHk8K;26(=ukHXlSk&|UpHI)_xX$FXAPR>~!998T zZe5C2LOS>**;ip1;u0NiU`0VpvP%pAv=1;?lvn%1%9B`gi~#nlp?{t&slU z4Y7F+D9Y|j9Eowi^%qcLdnghx@{<;2AqtHeSSfjw8 zh6m#@umOX)R=X8dGe;Sa3iY+M8(YjXd*^_lnXmHk_VipZVgn5}eS!O9W!C9ggh{=W ztYW9M+pI{6wAQMk?6WDMeHR(Hyw3$FKrSP9p8Pw0n`wVyA-Af}#1IiO{OqW(+aHfsAy1LTFBa&?_-K0O8K~lz+b_aY{aX^cP zKj2M{!T5|6@(b9H!HL^T%mSad-1?sLSsS{fuUEA-U!o7Q*srHMKHOn!!&nFXbXwV7 zk;dq;9PRE7dL}|3EQa5h;s;&yK*0lygZ!luVKGiCaG`CFWpvXK0udNSRN?b+TC4p1A{@K%bj}1J8 zEzQj?mI-z>^WgbXp)nfxBG@k~PdnYyBXLoO48DO3Jm54CApY}qLclTm!v^d`jD4bw ziHc%#g+YM)C4tNgTbOW%2L=Yt&bXFMo>UwjFJ)K+m&2tdu%BY_4%`UE!s+$j8`a{f zqyRG^g>)TVT56LG0NO1@9uN%K*^t?gCx1A&7-r(25}lr&x{gLuv0zJl%-q}C14r#b z(sg)zyg{c-wfH+Y8MfXI!Ow1}#A3~2&%K-{0}%%G?|j?U7zN#SCV(H#_3N5J#jHh* z;N#ut+_T2-KYo~}&{39~82y{|T?Ppb7}ShAK?s8?-a=X669EwgU`%c{v&? zsxj~C+wiB=6OUtZU7`VxpKZSPp1!_9%ZE&lwWboBurl=8#H!JIK?uxYv3h3a=8`$e z8(-ciwy!9~Z2*{9MRYtcKF-R@nw61JR$M$d1P>T0PA`;FjTemYO&z<_;|`vqmk+m( zSA$FM&@={xs*cD{BX122-kVxKRJ?wCcDntG%06-L9dz=FR52z0Ku5ETvS{AWpAHUp)APE(|GfLglKc|CtU31%I$XMOIo`9zEl`SFtBr zdU_+n!{X?PL@IiE>B-3r)zxu7zj=a#-eyoCTQI~ut1C_I__!BieZxEQ8~peX$fGOR z&V1+_a`dekyStjX&ZvpN$*tvK20!c|J4HTV4L z!2?7BDx3VY`l6l1QpD>ZOh`Flj{Fe^S%$oOjyn8;F~li;h=-4}g1M*vl`qJqAH)F+ zH(+308RuhcD`XvP%n>BE01yF#R=Z>q{(xD0IrW(R>nv*iSs$JP@BSTmv5mP zY#Jk5+HebbHTmc1;QJ;i*<5+}78x2*HLD_e8d|68NtnL>lzJ6AT~B3^W%gGTmcnz-r@0KXa)3lq*%SqqstfA5n!E( z__7&bHDDlQchR;3H>c)@FhdN$0AS4}H!YPolspXm;QlJ~!j~B0dyQOic|LpBe&%C! z)Stz4vo9sHl%fV7yp}z0>WK>yo%RuPznK@E{>bpwL)MxsO%{DUenhL@ z{VyB!o5jhsI(J(a4q^mL0Qdn!!4di9PKC_>zykvWoyfHo^*do$cu5yA6KKiHKPKR* zassGw<7Tuo8@D$K-VqTHo_ysSkuSB_XR9wy#7}%sPSS2v$O1-Q zvSqCg0`!Ut+l*~pB*y(1lKEP>m%+zguwC~irq3pw5*Jb94Cu4O4hF&tI{j(+J=A^T zFyBh+T``J)Cj}87JwG}+8pxRZEJ%9vVWIzqx2`!=_xJaX?e`ZTw&nM?Z6=!bl~pgV zbDzh@rr%Q`=&!0@9~;^T-$`_``mH5qTt!C5H6|T}$!e9Lbp9CvAs9z^@MB-iiSR=! z-|XXbjrp_3S&?y~;U*y9C279LlNglMXNH5&GhtSlGrRF%Nx)IGHddZ}5IDqwqY-mH zHHIpbT=EY7NtZ;Jo6`DPj;J6kjJgwE>bZW7`@@w>_kBDjBk&rpI-RP%?p8fe5v`7R zZZ+D8S+8swLxE%+{I9DJIP-o%REe8_h;a@s9Xu?OabTj&`Foc0xA9-c31j8&R;vMl@jXfD87tZC@;!;WX&XI$aos>F}+ ztMx<7yT?nP?R}r^^V_j`UQGv*Sgt;4N+xE`g}Lzemm4SvxWC6(ey)!iFLDRRxxu_$ z%e9I~N!|3~0t_OU6RPze(C6O=ZIL0pzrFbey_Belj$N~Op)tI-Jr}eb@*H^ z=;dg(+=@QV{Qa?dhkr}-r_$bCxB7;LH2E+4%lw3NPs>Wy)}pNEzbF380*5`m-`sZN z#B%8A=?6N)d=c{SU)|rLJ{}3XdHZ{8@Sb^2&&uCQwBxLM2F$!FCsMyRfX&l|;xlfU(M zVpF(t0OpfU0=RSU+;b9BAQ^b=(uI7QzkGq0#B4X==jBbdZ5!A-AKG+@>+@Sc zgoaFXVh2(qJ)t+0?xV{RwXaXLP__d+A2;4YYKZj`iuQbHs(^f|(gMn8|9YnoNJ)sZ zU3D;}mOH-oW=#BARfYL;|Cn4xwd6-GalOQUP>JQ%Z@@qfsD999Z0de3pUH2Z?ONvD z-;MmDGKV-h^)-Ta47Iwd$?8*o+~8Mzaj5iIoI&&z2!+LLR30a&v#g8?UzCE1pX zPz}+;qiuP9UfXS?85IoUbhNZCm1{sH1#6y(!<)YCWG$i^s!hIyF862vM|W5v``xzO z8$Rp`tLMu4lk=n5z)AOkv0a_<$HWB2je67u7(wmr?VHx+=mIvrU}6f`l$P%A?g9a) z2;Yn*GA4mpNcH0QHgY6OSljDT>9#%E1OU-GXzH4jjEsYS{^3ZG2j86@6^L#xI1&{9iD_lJQ=>zA>z5BM3r>D^3@92a;;g)C2MHQQ)Jcf?Vc!!1n{Y9>eB{ z^oc{cxs+~BDR6f+u3NTm{Z8we+uyHkR{TW;ug=8?I_&)yKM5eg(sYHP%t#|zs%A}9 zJwGKBIQpDYh(5X`C-rmWoG|0Vtz7LLcjq~@>}zSMp9vph>1m835!ZgqRdx|=^v%2} zv~EP|Ljy<)^B~U34Qos35(p_BnL(f~lkX}5DY82hU4izw&WK|VSeXzNg(B2A2>y>o zrHud@6|0MehSk3 zK?hnoq>f5n$a9vwsjsB359ecC+6%RDp+Dojb+Z!wr?sxB2h4@FsTRToBeUcnajGW? z80&fa3TvV)I>}2nUo2| zfQT4}Tjf#eDn+muWJ8MZ1SGrRX*sH_$eu@P{E>h*)w|oAL#SLMOX=RI`+msvfrf~N zD=&HDcdt@DsmM3KPOm=_^XLAsaeiiR2V7a;ml+2^gh}W|sh=t-Z9G_hF#xOUQYvOF zC58gm+3!{zACD(^rk>=KaQ7!U1qrt7v^MBVs3Z_!pGZ?FB7sjwndt#jxqNb;+_SU@ zzgxrW-#9#=K@GX9>*@4V^d-4O7Fi~^8?%bK5G{e~JS7cg^6ZuzkB*Q)36(c*y7_h_ zbh%4@d{qWkaD9lSAfTi6Te}}9{_>P6PwfP}C-3<&wi_2d6r|?{G5L!aJ|Av}4+O(% z^i3*KAObCGFdqpDqK;AoBcah}G_>((lHgCw2{X9R>1!H6wlzH^PG$}XUnsRK12B5} z(7NWhBttF3o-;^0h)-}utBOoz{hMqADqPf5SoHX|fXDGFnA4oe(=f1+Bu!1JqykNw zlk`}~2noeTU;Pw50116=TPeL2pW@!InaptLKowBg{pidA+1=u?2OMn29P4frFZxH& z$%Ja>?LcMn$CZ$lGOC&P^zvee5KAN{9kFnglSBRr8=ebj&VE5mxWPZ8=)u7OKT#i) z45Pz|TH%gIF8uHCQB+YE6EP4O6U+6wVs?CVRGSHxc&jgHLMY_{+@Gv$?gESUnIn zE-TB>zyK^zMdliTReHd(0-|X9G>3~L))JS@TGu=OVC`}R#shveCCE_!Zu(&p{08d* zUl2szgaYbtDj+p*kR<<*ff%w>zCgMMn`O~1(o8$y2slA@xi0sLn>QJ4erkhv*YjN~ z4-SvXRHi9_34XGH2>r2v0Gim{-94AbQAwHYf$(MnC_TI^IJROuh>NH_rO0 zC~9gZ|FDrvG$I1bra{S%U-kPNsD6{e)@li|n_!C%%!A6h=&yK^8$&`SRAD$nly(uK6}#_gbRKdL zegBU#9){Xr(i(iI5-t(m1}&=mLSzcNW?nunAH1MP{>6@?%SAh$+9p|2&?HG?`uEOr z=_z;j_m^7Uv9jdnbN+voYn0gk_i{}u3sq>Mg2jSRrTOH{k$yR~vLejO`&vZ}a4WK= zM8I#OO&gyhmZ;`5?!5o{>Y!l-xIH-{CSeqzZXn%o_?dvu5tW>9{&+v3{S^ zAAX1Xk_a(35W_%E%Ts*L$D}ymrwjQeF{d^7D+!uXmDp7)W~K^gNKr`v1QJ4%8y#UM z$k6N&0G(~HkBv%{55&NQehG@_e}|$UPW>A8d^nMD%B^HxaDkxz<>LXJuRs+FvOseL z8`POSX{C%N19ufU#$wF;9q9%vUE z+uO6Vvw2(`c)-`^zHI#XG%9E!W*4ccigH09{+=F4FA1X{5R?00xi=Ij#K@~c5QN$- z20+5V#^Z^llLo+oDGdjZ9?wD#KrJ4?m9<5N8gnYatks4t;*cN3>_X~Gtn(bo!GRPM zBWO$k6)GmJlE{%GP$FPpXlGPfYW3U`)EgxwbTo4JFg3;>Hg)uU*xAbT07p7MN}&KZ z;JS8Wbv0H&mF>T9y_rt$>|b0@08A~I^)_P@WTNh&0GOuuywT>j@Vz?D8MUyrwS@sp zm^*UeG{_wR?m;J}7FQY1HUn%^8zAb5D@jxh4iDGpl(n_B$;%7EhLiEW|N6#FFY-bV zp<4Gj5U*xDpnw`d$@8Bu zd~W}m{u;pWDhLSGuJG^%W1o_MFE8#TU`ok^ysfIVoUqd{0RP!(GyyJfgj%2q@~=W* zCjjm=&>xO2^SD6i1r-6~`1anV=?;i-0P;v$Q%6))Re@yFD7_?;zcml*Hy!RgG(su2n2(G-q=XRQx!`cjttk*1wb~NhUVS(*;x=|7lo+tXE7Fh zFgf(v+@ z>g1GTgjZZzdgk%UpaM@4Q&_g3x~K@j$DH!->mUXyVMD_u7!r2^xYmH1uy8tjn6(Ar zkbfB-z)#S@zwe#XBmH5LWA)a+pg~1Ng)N!$y35QTykxZXEbPrn^$#CDJYD&k`KVkG zjEs0`kpA>H7XVgO+6n zaO0zZsrTts#-2SPh2XW*YXDqyb#(#AkfnMLddCCUMy3zdFDx%VT&T01TWlzNlOj*s zq-UV4>|c%$_xcdlOcH~d0)tB-4?8wv{kI1;QZJyrZEb9n2?x6^YNDf}vfpZ?7Ipp` zXP7KPCp=l=c7gd3+>)mWGz9Ef)BT^YN6hJE2B7E7IC5lT?Z9)rGCdvSBK_>M2j-t= z*&cbqq^h=|!Rr<2(@#B69)v3~pqP@G`7d#Ro?gV7TK5)+oS@6lM{ouFmIxj?u4e$L zKYwTq5kK>AxH1R^>^wX;f%^zg4T}0JY%vrb(iCcxPNgdd_Hf#rqG{4QJiY#NSO69# zkO2bp(h{-aNdbJtY)fNf*r(yCh<{IfIM}_4^sKBvF;OX21H))R0C?Xa-E26_YLY(& zN-q_aPlIM+*7@*p)ZB!qFR2xt0;T|nnORwh$(=)#--m9}Q&hp0rJ`8S zSF8`oX-XdnA2)g1a}c4wm0q*9-(LG6XL?@UU;65l!21uK3qbH$;Y7>G1v-7;25`Y` za%u9mvO-V~#OCDS=(@s^n!;FeO&577AOHfDBgw9?Pk#wNNZr%SqFWkN+^mn^iDs-i zm92(yR+O}FAQ=E`^%fvK@O9~5{80#v+}-O-N`_6UjF_gdP5VCjrGO zw`wl+Plghwh)8j8g?-y4 zZ8n2#{Q874q07YX0kqZPxp>D=%>4UoniioeXGiJdj zy9@8*AQ7kZ6nL~uk6+>=|65Oxn77#{O@y^bPMNoreM9ky9x+!;tlW7V%XoUwUEwO7 z3-t6cRk?w>0b~z26&}1OUYB7wSv>o=#d71wE%Qbx*#s=a(`|Z&HENV`I%W29?lCFr zJZ#bCS=#;(4hH4=5-=~P!%Ic5@Yy=d!d4jTShRd~C=V$Bp5TNnS?d}nQGI3wC+Ca7 z-vYUD$C%fBxvRuA@N$!$Vj{BpthV_Z*in36&gk^O5PO?1KWzqe9;CmCkj!5|6^7(3 zt@1*|odAsV$;1C1>HEq7+yHqTr{lsgxzesq-Oh2?F#l%rR0w4@Ck4qo&cMIC_c6D| zUke=v^q*Y3I-z&oX@4+G%g=X{Ac|spGyFzDDvhMWfio#wF*C=y-+M5!vxdbdh0pGv z<{=E}-l(7UawF1{fX!wG`p_xMDz1b8ifYR!!YCCmqskYLwHluzXg|0moMj6CxnwO;B3L(^|dc%=|o=(;E5%`9trv+pI{Q_qdeS2iWaIqVlii>aSeMfu0~b2PfGY=%1=P zl{JY3F*;aQ)h>35CT@MAo{GrB(DJOvUf&~NlNjWBxxlAp#UphH^26M4>OcW7-!uGL zhtkYqsY&XND8DR%Neuf+I5*&ymP+y&c_KG6(*aq|ZC6xKAP)~RUP!%75EomVqKEU8 zkkoFfsv!yW0lNgnDsc;)r35B#*e>GQB?1Jg?iB6DjF~A5sT4+no$QwxX>-|CY%b_c z8~`@(-?D&#Stbs!lcFkSush-OGJnBbh}hb>CwQX7t~F!sjPIu(SH``MJRUQyFArhb z^S3zD3Uu`i5J(rfQJqwE2iOs`^$jqR*w!U12z4P2sxnsltE7pQr$f&@o#W-3fxHO) zGDv?KLKm})(C)8`Q4(D_Tf<+$!pf!KjPh$fklZK%`VI?5NA}pCz~=PS7=DPwhd?Cz zq*7GJ*bK94_3;kghC_|doac+1fD`fIiG z8Ed=u`Dy z<w%D{{z zqCy=6F!}PbK}7Dv7Zy^4z@4L`6;-?SeB)VgN;B0_p7ot8xOl_PbN3{w;#tZ;s!JMW zer`;DGh0II8$#HwYv=?Fsyz?U`mCAKh%-uw7ypb<(c`jkQ`@ERjc&VOF^REvJV`qH z;eDR0{~~1EJNM}HXy*ZwCX)OWK>f=G2{YgY0E>qjB1a?$Hvo#EvEDMZ${)C%r%XRV zqa!6^%}5wEVm$r5j}gi?lrzpL5iki{?kxVELjjDU3za-gt&c^xo3VX0i0m^+_+_i_ zo_P}aU0$N$Bne(G!nHq)R-pTR`S9*H-Hp)W$3t3(gtOKMu-9^S>1YZaH2+WZ?Msfx zKNMU!cqhqR`nZ1JVgrq{*F;ps?&K{Uf|~NDC1bBF9oVi}B%XQueXUy~eeLVD!pH32 z6Is5#emHqpY&r6zzQR5}(x%vFU@NXABt@dt;ON&c%CE2lf45{h6{=)F5K}>Uxgs!A zJw*p%4QI|3J{~S43gae{V){ZsK2$vZJo45%?j7gmU?0aLV<^#UcRjgTf$0FRyy9AH zZ}n5VY|JKfi+ypuz#;vP45ww3{&7)0s+5!wvCV7P3uI@_KMM9dUS!xVGq4JyWJfIq+EURaGTju(-U!#4A`0IIjPp!#o&|gXQ(aq`QSE0|g z(*VB%c6)Y@j{2L96=Y(3z+mWhY#)okqy3`sc4qIJArxfp@q8wcO#V@L{SgvS7nqCe zhYE=ozUdTbPCMFhP<+^VwmU@|6_~k0zq3XflGnpyNg1>6!A8Q2;fZ9@sx;>B8~FA^ zrPE5?BFKlhjERnBNR;&!G^gCj;xec54@D>Y=iCz;E)Hv-kk$;UKwO)=sRYM+pNj1K z=7AT2E%((OZ7L|B5f&yhR7#OnP#FF7D|hASwF-4wPEO9Of!C-VPab_7cX!|Im1^^`BR!; zbsS!|WZQ1rsvDxc%9+T{pu^8e-afoTH>4K`xq)>wk>IBoxnOqtZ2u~rTE?bsCy(L{ zAP`C_DnQsJeFQeJfIp=Zf%g=^mV8Cu&iV3seepw@u=j=T%12Y;Qak^(Y|g7_+#hf{ zoVIHzAHvck)>%~%2L^m|-q6RJpo_T}yl6bJ>`y1XOlG-nve{r2I-jKT{q6Ulz&Gcb6dD-6bt04bsxm-5^MJcX#s^dhhq{kN3_WQIFdlYp*%ym}88o zLVjjY9l=3nn~L$40K3o@AfxfT11e;af7_T^0L3`W9RTmlQ_V;lIHpsjh6)cu51)Jj z1W%zN%l}b8J&B=^$IhoFk){0_qRVr=ow;^sLF$|ti-8N9kwvSiLm-@osC~D|$Z@o} zO(|DgURdN8vGN?J%1GC|Zq9FDM&!F51aez1F9PF8N0}IZ-?#OWki?b$Rxh$h-VgPa z&svcR1oCsy+Ud5{*YR<+aZ*L~z?uK>q1*L*T>`%83V{_{7w2p**k_2HOb^ArEi08h zd_6ed_x!2z=JB-vb` z;Nwe`1C5*j>qc9k+Z_-biIE3%QNtM6zp~tjIO{m?@P}C%I4`*6Uatb0P)vh?P)sB0 zd0iRQl(LRJnlQ({Cqjr3r{g}^n9xVzXld_#dhE(XPGx_Z%C`oc*~8ljC4OJ9Otr~h zvWLl+K(+i&W9j9Qt3mZZ7#09XATZi@zK;)^i&rfOCms(6o_Djp+7A;5msYu05TOq> zr1*s>KIfYHn;$c9;zCL}Sq=%8#iEOlbS!P}(D;NjYH456Kg%pvQhsH>_)N_aLzs+v z4*9!uE}GW&tVHi72sum6U}ETNzXkIQ9}yx0XDT)x*s-{lm%<+fa0(0-;@Z|%;Z4bh zQU8eJwBDyL3Z-qO)zcJ^TZ~s`oSWDsjTmkA(9@2J6lZ*BA!(xd$qka#u7H*`Bzquy zdxc{DuyfLuDwpx~OJJzWscY*tmiP2!z))x#CqCjXORoh<-dDv>e?&GzaV%y+itQ6$ z@rMIC-{vWh04#;lWr1GEKa!i6LbmLZO5ukbMP~pHbcyHpp&ecipYtD<3f?^2Tpr#R z{@6Rpl>)j0z5uH<%o9%Zpv%*EEU(me%#4^&5WrhGqIUNYO|yNqsC(&)$jszNa3lUl z<(E7oN0~ZF=F2tJqW7+fQ_jocLX^7A9Bo5Mz74LLXDlAPtvI28sy3coQA z_T|9aqG=$bFaXMUdwUKSvOefDm;0Ta9ngdhp)ds45m1P;CH;mlUy&Rh9&Wl&^Si8% zrr!-2FC0Ib9TY#_0|kyD1G6z;c5}Z<1|6fy4Eh0Fj+~e|*NAy#qkL$HbXLQpr2sF% zjT?J2?Bf0$v+L0cGSx;@zp0U3LI(Yv%;2rrEXP+agF$vqD-o+sk#5-GAcW)quOzDspC2enZ7nX$x8rCiFL>j>q02ZU zW+~;9@U%MB#`k?o%sB)q^|QV}V}@cCqD|U8AxS1$RgglkyoB}HOW~eGO6)(%=qE%Y zePdFkhEB}wl_^Q}mzLUZR+VYJ*i+@BsRyJuKGFGqY*^)T@T}O}IAEl@(IU;^W(^X6 zsuy;7`SMmmT%3j6iu3c{6o4jKW8a3*{TJH7XVa_c9%&U~ePUy@b=`wwFhK8rHffEa0kI(0q+ouzdrCXmsA_BkM9)0P)yG&MB=l?R@ugV+g#JfWWsDO|weGx0pH ze2vw$@{OXQj-#3$(1|9u0>VDOwMog<>~4-O?KX#d*) z7j|~$38Cn3;qR^wQY>)0+0JuybydLmj}F)fAdb`0`ueLK9k6}6HCH#2z-K`}G6NIu zpCtJ!6p&(o4{+AN5xW50)h85WR?9IOSm2G1fB1_|R>S5L{4SeS+#{rL3VTu)Kl}O` zw5>z`g(eBg%y|GrK2`nq!j{$s#3-N0Urc(9igm#PDq=VcoJIYTknjs@Fq6<-k`ZMHVqWC+{==J3u$JeeKnR6;L1AIN!#*K=-mHD6(BIwx`$4en^L0n@ z<}XW2$<23{y_1t#56#UD(Q&7@$%69?!+ONZ*jo{Y1X?ybLE z1`7WYCC`(JxTi#yGZ~m3fezt&oM|L0ZaD<-BOnX|LMdPb+NnHaem~hvi}PT1KS}0# zcieqh)GVYK>E8HVw1DJ9Q(uDrv3LR``gwnnlCkiC7sNcmA7VF;-{?tk_WR6_6vUX;9C(Cxe7R$~1QKbsF?n6dPqlvi z1-cn|(f98Zy7lx-ub@rIVtzkKC^$d^{)tJ6V41%{3c|9?G#$MFOh!ux{)p zJy!uSW2%mwNt!9;q}QM4y7_v>-(c23v*_sfFR=fz4(Qtx5)$;+eik9{W9CE#7_lV* z82b&WJQ}C=2M4EN&yquD&-;%ep&#KK4 zbWf!;je2}t)9KMhj;QBdcG4f!a?{uX9nKVaBW_v`7<7g~Nd0CzAk+GeewG}cdb%Wf z&c?hw!jNKq?PV;fC7dvaiI&Fm59UyFIfmlN^W??QkSCsC8k7^ITIa;UE2tg_1e`Av z(($ygE$kCXOVf*0zoo~JhYf46zhC{+Ct-!`-hE&n4Y+!sonJTM0Nf$aZs#2N3m0#9 zR5=@`-qHJ_1^KO3F}=lob{R@JaL*<3ou5fB_c1u+|Mp$n3=(J}yq9r+7b#*6=WPig zkr@osOwot(Qie}my^TbIe3yn-O^^`e6B1<#lrwFp^bTezuS(S{Q{I+lOCAXQ z{t`QX4hx!6@Ay`FmXmB}M;sp~n+YDj-L+vyW(i^X^#kI#EC$trFLgpcaB5(Y6^>{* zgF?xR69D=jpa}uWr%u;<#HVsr5P;G1B|U_6JaCjo#X2&1qi}ykU*f~rNxzpFCSWDt)6 zK5HMK2l$J4vu=Fl{Du}lXhLi)fX#Uj`I-J61IWE##980k`oAtCN4n;y$ZKwJAUtHr zB213wBEN=~mE2%u=K<;il|r$=Y#t8y^MBo;Xq6u`|A<~86g*kmfMMEdp^}EqH5P17 zPg214`)O!uu?~aVAs%u%&%J9y5(IJeG?d_lSS>?9YPTrL^{b88IC_y|G^IaNJuETD z6G}LNL)o5h#5Y79v2~2zJ^>GSJT3h#-AKivLiT~$YA`KLHZqdHp` z$f+VpukFLvd7TzU@1&vqQhb0GTOA-DM9$MPFaCK)Y(>4E6nf0w?gg;sW3RjiR*f47 zLmh><|BMZIv9XUsMTA9VJ7ys@lvv}Op17XB+7sw1nk`Q29|dIl$%rbCvr1e{1lenOdsSXBkUiy>=dsgPz8NCQ4; zXoxagyv}ZJegq)3OCzaRwD^(M)hAW14d1x7eWcl+&f@PGg8Gh%Rl%;>8X$Id?{=CP z1cBI8);rw!r2b<0Ge;5>k3|~CVr#Gv-DW1Y($N{PfGR zp`zu>VU{ceX>Z<%pIZ3GDAc?QS8it})!<#0UUu-08f5p`(_jErJP%E~_SDoXX_u3$ z!NJcU+ljf$ z_XEWI?S4JFs!NTPQ3_qtq>(NGRR9mrzJmTpNu?@%{u?7VNR>bXkjTH_5RpY!HRQGD z1=c7-X$eRqIR!#28r~ZD^`bZ{%}=2+(CIYtqJ(x?*Vc7=hfK@^9yW+QZCv^oVxB;jh^sjs|azmDU>|m6^OMJ~L>gbI@PmEFOhAjlF6AwB-w2)wm-F zKJ-$iGtG-!j0zl(8Wa>2WMufT7GX%t*bwqP+erCeiSY1~M3~q*EphUB{6CfOClegL zOjRVrc^voLCx!+rTW}CYl@y#v48`&_ene=HcqTTauR{+5%Bxaso<)M-M|Oc(QQ?`i z38$J{8;3TP7ljp7-gjR_EV>E#+Sj;kE#;5{_CIfyukl0%@XT2ld@qg~JK|TGSkY+2 zv*`|B%_rN#Q-8}XH#MU-O4x`mdppmlRcv03?M^Y%o|~9*#oNE47<*NP%TRm#Hs&6f>n%=MAhrQSHHG|5pl@K|A_9eb{lW2eWWo4d{%fwJ(pRx;(9NV99Pc zehWcl=bM@DvTb4vJujaCMxVliSUKCny6Y^immqZl{F)BXMDo7O(0u#lXmFxE1~4I$ z8Ew2>m2GtwrM8~kx82b;)V2(j$qpnm(AHhf^{#gXEWy7z4lX0ni;XbxqBFu)6~&Ad zcP^a;wIc=Nb?mhpbGyjE0M2Ay?EUtNa{ zMNEQ79Gt=|8{T0N%yJvIIt9+gsYm=!G^zkt9oz>WVubJ@Q1;%r0a~YG0mv6Qt9FxZ zx4|18Qx3aux)r!u4ZG3UdL57jY3aEBI_;CTn~7n%)_V`f$+8`0hCA?W5LmuMNVjMZ zmz{^ly$QBQ=28sLqh>X$HC=a(3o}$E?%W^{&a6NXE7SaeQXBu(T=I5n|Ho^1-{p07sVe7`(JO(~v5eus+&-e5_xv>1Nd==hur_du-+2mvaE9B*-9(%=-B z%Ae$^L7x~}v_|Aw!np*E_Y1pn@gc)|BfUWhV(=eugYEphMT8*V|HCU- z@bQ6;6~8I8`DaH_Jb_{f1_$)>*wqVY%r0v86W>^&qs(=o4Xi8O-`ni2d#lqTzD!bU z?cMc6hk&bV*g!7!m3Tn%x+MmD_(0Yzfq^HfJC2z6r2fTrWDECL#IrrTc(eK%PMm7< zpoo^C+uVYqeTj@aAh6P@vE(_WO|l`v-KZyj_alT{(xrWm$Kx-Lir{kvkXHgBo+z@= zYvs2X1~MJy!e=0F8tf4Lmou$JdK^U@YycP>py5!0s2s*P3BkTFE7b&ExwIqafWv%R z%)&#-J5B$6Bc7WzVAbav`;WjR_e-UnLf^*b>eT#n-P*&q4Ki;K*su0k>7p9x}WaPXB_h8Gd`u|rLq zBL*SNY{`#gbKPxJE=W$D;tZq4dsUg%A^1&w&BWqpEUIm-zqJjZ>oI-dWHY83_|9WM z*OS;W&5?p1h-Dl8Q}aW=h*pazRpH#-d^Uirj58_}L`wek(0{H(bid6hQ-I<54AstF z_amNPmEK|A?k%krIad(*6oM!7^%iPHx9ToJ)D;c zUQFpHah%IhN*;JK7b=^;fejgx|GEQIsdMf}Lz;`jf!2*}JkES~+89snQhyF^AE#X( z^Sg$27hKaFh*!;Ps2x_r{;&uY`x>9oPES`?_|UIR^ddAe2MZc_Yfra!e@Mk$pq z>Q&24IOz_zm$^l>_%E^o?Q4_2lY}^1 zC9jt`csS{3y*;g{ReJ;IcQ)J9?%p!7#bQnh2u1J)^cpAf2JT>pF_O`b!vD-Ut0ReX zDHhz(gTpL9qjM@$UsR^mXZjv8Lp~^er@Gi_3*iZzTVn9`EocL@AToL`ik0YP2n* z^d|ui=?$E{87@6Y-6mnv<9)KCF@=S+wbFX?V`n#;E!oY!Z}Y?2 z+7rS{!zGwn^xV@b>g7isfda-D=~30!C!1LOz^T6+!jUqeDd>^6s5Ss{>Y-rzyY zl$oOq9h~n5_J~12A67EnvF7GkGctR+oo(TLtqxFnkiS>3(pt{aruW>FPa9yOb<3{Z z*gDSRfGfQ(oy@yS|60Pswo(iJpPEHU9~S9`I9B=W9=^^W%XfUL%Ool_31M`I&6J~m z{s5u5T-ux9kBShl#tt=xpbIg%j$wcOK}}QFSDz{#?pgPLBklgAzbx0U~w?JL_Zat@9A z!L{eI_|V1%#*v{n#mGB<%2Ex8&eYhTNu!61PsNOR7k~V+84^u;?JDd?v%wU?{nP)U zCg~frqd~V?w*x#^Zl?@EUBKfu6V^W5XA~?cz@C+tkBq&*H~j$xab08(QWJ$7Bqk*# zJ)NhNdLz+c^C?0kZLrD8JOkc?b7Ii9rrXEdd3V(r@A`+;ghNdJccO=$6^EbW@)pd@0+_B7kYruts(PI{LFNdWuR585P4v@bw3qU=iN{OrhmT{ABPYXb zqk@IK>*!^fu>`0&a6ptQyYZ`NGOP{-d(yo1N?93@!k zHN0lz^+n|!qO z?>fO=+m`O|XlQxJC2+Zo;BiAHSg?QJ6@K|xLr;0=`Q~0*m0>-(F}JVx*;!?uw26av zkYws0{IE14|H?JCcZ$1(yG^K1>l34I(FgBLX zK{R!YJ#zAnJ6G#c3ziH~QQ0+T;Oy633-ORurCj1^L0r=3^rR}XmvxyOw1Be2%q;kM z1y;)o0&#+actL;^K+&mmP2$n-U{T=iDC_ZVT^-qa_|=f-S$Z?$Am7AA9CNXP--iv} zxmMwc3`|8M@zm6$P{Q?87Itwt@}?}M_2@1ayDWo~1E?xC_8sUtpw7dRMy7Qs#1$6P zEiW|Jkck)z#jRH^`TRoM=*u8iuE@^UF80*)KN|06B=J5TW92Zlk7}5jpwQEB)-Wy40=UuAI4f?Ruiwv+}H$Cf)6!HE_}aQBo~kf5;f0;;Vz5Q53nLt43!m z`}wGLU3J$HU2&6QUcaN{x`n9dTE2dEGl-B>zE?rWIPvV9@0^9A3m7<`hqu{^7xWr` zX+_=}!Rr{oZx+L}eWRRbCn*MxTUl1kF`@9n`a8cbZQqv;(>~1r;ZiC}W6pFAvJgvX z)8xI`yb^_Y$!L0h(#)eF9mQFAT*d+t`>?E0O7U+l^R6TKBb4Ysn`a}*r8X5l5iIu# z^V7=fm$@g?-YTvXq*vh`3r+`hn`*vGhR-09Vu>#`j!}d{6oH+ul2X>>Zp@x<#_RJE(sL?r1?JU!rgX~%|DJ4}4{OmprYrO*l1Aw1D=Kv9s_F^=G zyi5ZoNPyvJ;DA`2x-@DNK<7v>WI>N%km-_+qNF8{8g7Hw!t@!~)_{Su{r@t)SQtAv z$jg9Od`gr-+$9gFGCEFfRq_mAg!5d+)Rb-y9wKSGW(PN=3K4<^W+Y$g=O;_G1L?rj z5@xNU{Cr6n83rb%01gPG0J;Ghg4I%3+Vo>`_YZ)QbJ}JbN+DkSV(PQz$-e~thWKWn z^abSc4{c!V2?TtM0;5q2h!?fg z)sdBen=dY;EbpV1mIrWd1Cl>*byLs8vyMk3$oTDdaKLZ9R3R-3%XinOU<#Wo2w+d& zAZKp6S^>ke=VxY+z%jm}?lC}?(3ZKazI=Ht#?U;iQ4H}C9hsR?Q&$IGW~_g}0-^^S zTubVbLp@L!M*YNr*dTyWc_?UTzvitHp&?OkbV?vzeBP_IK$J38>?zZ)I$6JV{Kj!w zLhvDYwX~L5&<6d0durICdrl3o9Knz@H8nNx+!?{3A5Fl5LTgwW1+2;}A2IXUN-mp~)4x76w@=?hK@zRc&D*Sq%5zz@`$WWu|~ zj9c(nrRz<(W*c_9Us5mEY3~^C8&X8YXvp&Hr}oOq*J+#bX>WM=`NL_T|2{AreK4jR zh)kX))Oncfp?mONOZMZndm_6NQVY(JY!;*Fula2$LL1L1N=R@0VY!xFO1HZN{PDA)VGg*0&Djm_hi|+Yj$E+QS;u1l zs8X71QyYESW)^g@R(MU;_m1y`1Xw|SpA&v`kdv$yx3*eS(sVE$Ce?ipCcZ*Pk2k-8 zJ+b@N0;y;r$dd4^dM7|s7jP%)A&1T9N-oH)JAoRN6`@l-Mswt8C;%{n_X_Mu@d*jXxp3qJ8a{yE8kdl8SeW8_gany_ zb*P`W;5D$b`__n$ZGt4f{vEPOB;oX$d>alK|E36-J>$GIxj~!nc5U~)3gD3`O zhi@GmbgAQcr8*+g)6)a?(iE7@g`PYdlo=EV@v?Wm-LGMwr!UcNPMgto3V4w@F)@L< z#<{@!ETx>X+jO!G>$cabA?`Dth`HKkLNKD+Y*W4@59`6#e_E2ZELF7u-$@>XSAvx3ONL%vY%&VB$h2xe zL3V#uR(@IQi3VirT$gXq)Maci5F47kBW5F@-2l?I5fi_4s44H#S)hmO48?Oi2_yv( zHyar4ohBKlO|vKULm+g*j*gdLSoF~mi!4;jf*s;ljHjG|1iWlK5w53$T$e)XEL_io zkfP(`fhtZL95y~)MMDF{7g9z7hDn-vc%*GQhuyb=H9!&&7zje?g)B%kgy?zUK}O|D z$H)eeYU^iiegi(dyhsUNPw~y(6o_<9O}$AVka`gpmm6?);17V=ye=vP3*=7QTU#c7 z<2neT_K9g})D$QqP!N#mmR$m4Kwp`s#01qFWFA6nQE_qO^YfY?K2%j!!kYg}9)Pbn zlN{i@#K%7YbDEn+%k5x(sammvvopcOzr1A=%vCDt)N4h12@(1lm5?wAvaSyw;JQJ` zZr}dGn<_U2^%TW)oSmol9Rva8p9Ky=?hGmiVAlsiONi@5M=-VxK7>4TW@cu}x3uYI zr#FVCTovQLu@5x1p`@T7y#ZRKfC3pgyA9rNK*RM5ZV@o{@OS_Lx_mJrhDJUjB7!Y(Hjiu$Q;ZrFN zg(!<@#ARFv213oO`xUs#K=gKS!E0-sc-jrO2lYTUx(-mGH$ipK5RbDgGXCQZY}`7<-~tE`NCkr%NP0trcB0QbD4xOgA1K3vXd8DXBj*!&K7 z6k&9|D;o6`2n3gfASwI6GVVn+P~UQ-j&sbfO?b zvbH`1%S^Wf0?7oKHaG}i%GJU?ZQ)n|$p&*~#j~E$#B5lQGyugeSj3uYP^2t_$LV$!@{o@PZQFh6cd2ujPy>~3#MrTyp%ke}DY zSHL-jK#_y3&JDa_T#Ihlrr)#40e&1jANQ z8NA(PK+e(pu-mKxM(2Y0@gN5VD%a5wkcX4<-*jNh6Z5;gW?*0lc3B=cxNHaiXy2j! z+=c(?=J0^UWCk>l8c)kR25=4I;;8OJ`cq-+=YhM&ZTjPNIt%91Fd%hdM=u_Ff)WtK z$FHxHKC(SMCl=yvs_{7rkW)`cL3Gk9z*|B2l&H&S0MFuYK~LoeA51$73pAv6*rDhY zOjBk9Im1_+h*iet$UyBpC6)OV8wPUG&ULn)pe^8fVd3d1`1B1lQ{4mbp6Vh&TG0NC zo{YzqJjOmMJMD$s7*}Jqk#@e}<{C)f0JG!7V48e!onQzu^B+IJz5e{=%NclmdOu06 zal(5&Y4%?eM%ST$<999(yK`zr%C#K7eI|EUB1(e3}Uqv$s9)nX?J?`gXxW5ZQ4hXZxsl4o6F0<1?;I} zy52~Z+Qq`yKmyyyq1z_klmaO2lGk_Yx$MyI1u*Z&1wE9IMo$Xr4{in*4OCR{GxL;c zG}Ufn9k|Xwk{lg$RSw&uxcSvwqfD8hXSnKiY-e8~4@AI1Tu}gB4hAaf(;FFo0-)1fK)6BtAM{Pvcmam%>nD ztT$Fo#uAIYUEl+Hsl=jfMRL~K*9zmpKVT9r(PaL==pHpJ><~V?nzr8ia1=z*wpy7F zdLKIjf)(?6boJNd6YXKv@P4&pJWo#j0eAX>r{x)2Bvyfq5s0@B>e@*jv! zE@9I1g-pR?<^ejo=QfjCuk%&(cnFBZnmkYN8uvPT*qCJFs8sc*6o7}tKh+PsyR2gI zW1Mi((>)w7&kbcZkcDA>U8pcZU zslgY1(lp|*@N=A`{)KHHp^OFzwp({lM!AhW5w$qUA?bStcFJ|*ng0NH`vMOUXP1|k zXJ^@<`6OxyHhb_{lB0cS4$0f}7uPOpTeo>8*^{UA8!@Am809`hL}V(F(hI^*F`io zItyv*+yA7!79X=H0zVKT;r@KyyIvNK%Kpnuys60Z{O4!o4je=dmb7g)#)Z|2U&({4 zOi-B7nZH?-@{;N3lq`~e^~%MA)|4BGJ*d)ldlnM_=A851vb>bMwX(W8fI`0iE%#-C zoCY&13Bl*N$1#P>LeWUi-0fcUB5N#7|B;p^AKepOSjYvurS>%zCaTu)z^kDc$&TOQ zmsW9}3N_-GDoMLG@>5lCqAyXxqfSspUR_To@d%o-8&FXX48Y|KC;b^IIFWU(dTG=3 zO)}}JxNwU<%#YC~SFKcgCrg^ANmSf}wfFWEpmR!S&YfnNh!sW}WyCvC1|^CPzVv=P z>1x-EGUd6acHfYti3KDZ z_w!4q|G#)#_LpZMYrdHsqf%oFimVmV<dUx==j{fElinWR5p)G}P6BwIE=HD>D4P-k+_R zx2n-9rP*VcqSkxMz4$mblEpAtk|4>KNJ^6l?lQqm3ao7Hx(xax*nCF%sT|=mQ1 z{XXNx#nN#qEKhZPTOWDhCxD90{Q6vF{9WR@H{MIr@)PVYx3xzk(;s)SF4q#@9{*59 z{)7rP7SCaX1)6oH_9RXGpylHKgZ{dr^ddoiuhrmm49wM}x92223Rk}B2(Wp$pGB!LiY8gfeV(bY57--B_l18uC#!H3&mzls}SS@brw5Y67q zTs(;Wd`7fzwVODX!Zo`xXt5~+lPzWXL_RJ3Pwb>T7UuY*s!Ip;KD>HDEls!Xj!2jp zq*??N5-cK7x6*3rGTD(bs~DG$IXKs(P8xj{8rwGCNsTO7SxRhgKMovkPX24R!4<#yn!qY!)HoAO05PqyD0zW zsBHgaYfd7tAH|%n6AqE-XMX2Usl)pzq381_C3k@?vbdE-PHJRP#dKbcOXR}2c=P2^ zk63T8iBWA%>(4@oLx5vK6q6FPJ?em&Fu*PS^&(49!KK1EKidpd4J31>-F*rPRgawr zZ@EiX@Q+k-`Dbb5=(2|o5bNXTA~fPQ!&H-n8{!HOQ*gQUQF}#_6}&Z5ej-R?j`+Fe zw$i8|tW`!L$L|CouZ3hs6h)vk(Utl**)=8m>~eHcgFRy-UI?e*=l79#zPJ8JeL)zs zVf8_l2li$K^6e2(*3xHixV>vf-b>vC(GkDt4YLTT!s?Z~8()_w;5wVO5s$Z|BwJK& zBc0{`a$v9G%m4Hh+oEVzp#Tp(diVLUO3gxn<7V>NJJHzp_SiHkDom<`S22s_wMc+*6%tl}c#lI@W>i2F zd)AtX-AB_7&87kOxSw$4^vbP|`h-p%&1Q6%ianT1^prxwhy2Ut>3zqnI}o_6Z(yz~uc59T^hs8kQu z-Ss7$$!|UEx1(=9-&|C4a<%&Wun*$V28nvbZ$M_)I`#V`F&K|BeG=wBEyXFZ6Nt3R zH2yi%2X?3@kw$Xs&)ECY*iwa8_E7G^>AG!2SsOo(d%(hiUXWb6@mlkr?v;)#D#UyL zQd5Y^5T}@RE5}bjGIhj+M{(k9`l|%mkIrg2j6{V#sP|;^Ldc!pBAggjhpH8f5#-tI zGjN%cq?LS!wJ^5Ckr7q!s@x>i0$!SXebrem#@Q5HJk{RpRpj9y?DYmC< zOW(ZFON+^uu)6QVz$_fdscS;x)t^+HgI~D^U5j8X=00`(gdP9IWr)|SvdT(j1qI!j zSqTXV;JkmS1+$E6k)?n16wA`xu1-B4w*);;mWLk?n49SgT|cGzh!c%hjP|?BGIN^J zafg95T%e4;m8~i`TQG`;aUH19+7a%(=CgkOgQA$_;9J9UI+_z``rpYScptBY!bmcg zqBs@27jVXMwy_ODd?@Ew38@f;9jT@W!lT;l#J3$oQ~9kX;SEdFIK}<%9=G7dlMbeN z-U+ONru(>IfJUIJh*4pb6g%UEnSaA=nxs9p6#~zf*3O$dkZN{_zxLF1I8oy_U-Oex zD)nSGh!nNa&{kdSop^8E0fRmM>w~rEYD$rcp~A4#9wnZ7(tCDF&R^UbV3+L!H|%29 zG+5iahHvwpo9dPI&?Fou{QAsVspA5nJFB(f7no%Rca`wu9#$5*Z9P637%UO*k?eev zR>fQ<1;MV$+p(BQYPt=J_pY@9JMif>Hng<{Z;70BU7 zs-5D4@oso}KT%Qq8oii_<0xUg*1zeVoWat9qVqY6o17_GLZiV>ueYxEF|)plhJn0{ zC6eFf@+b!ci&vumswq)t1$#~+dwX&ukpLWq0WQ~94(7kj@M3=F>GI{g`(Q?di78c1 zYx@y!%!FSSzj(rrzr?bShM3#K%+9j3Z?KD+&3@;i`RFGJa_(PvlK2VzVLu`@Vrf-Y zTOswJfj8`7LJf*RkTDu8k8r7pJXWjY6%M2(&6;GoJhhmUkPzrUY8vU-3Z-B*jw6Xx zse)pLDb8FY?JC6)JwGlziC{q0hi#eG!^ z+0!%i@>4gHAX&{nv(Om_EnSUH*zR+>$%)#DPBr%&IXp=@tVD(&=F>36nBlFUpq{by z3|`Z8uNlt#@5!JwOzf@n)ER!3gw#)voSUNSgE!Tl`Gn5F=+t~(k*#}M9}XpXZjIzX z1cULgEE%DVDwxOfu5q@(j?u;XHKbS0q~@6PVXzIf2mutZh%K$r6I+jBj3Eo{wZE>Y zznYU>LJlAY@ORo+AJ3XqN_<5#E!#w)rm`6CisN&JzaPiGoJVI34*HOl{GBVi&-Hgn+M8nKVZ40J5}RTVyGf3jFHd*x z?)*nbkxxlFXUEY15#aA^j>P%?ONw2niqj($w`!mK60< zlDx*zfKeFRPs9)z@8(3{lB1tG<{YL=$n1|MjVG4y3xI2XKVGR6r@IZb4zZa0C-wph z8p7W%1(c~nmXo|{5!$~fCeenh+`-)}pg-RleQ(^JPaBdsH8R3FBZQijIi;X6_afwH zXr>HqJHx-D=@`=;1WndAkI|mrb(%zyZ|J-nJ%cqA$fTg4f3U_gis-bke0Ct#!z(~s zgY)keMz$p9kc(WSJb@A&>IA2vV;s=NVf>)g_|dw#D8p_G3saMDhmk+!-IoH^h9cwn z#?DTX%#WNnRgv5WtsK2~yv!k)+&_?Hkudx(`GQXIq`4?yJ`I5L0jso2$ueVjH<#-< zH=w+TDmK?TM_lZfK>l^b!Ck=WWj?nJq-5$z09s53KKV5|lN z&~z>zT36FO>(;xJZtP2y*UDZq2(p0w8V0IlUHGiIxXH=P%MUJZ#sn^lxHf0Ql4T>H z5Tt^m03_1Dqus|Z8*6^r`!lhZE#epyELne6D%gHOQPAz$Q0LK=roRN6=~OoE&1(+I z*Up2XC>=WKU$5=8?p#D^nz>KfyHb6}(Z#o(Au}B;g$7I7zJT^)#-)4{l&Bc31NU@x zQKD`S>+#`QKP*q^m|;~gOmAT6%UtPu$EZTysZo3b{GfssV?=~)KocUa9O;R|_-2-f z)kzm=O7fw>wPh^{{5+yXZj%R?6ACF2Os(H$aimC!I9ssvx}GG&KP;RwmSX4%=I-VfY>06KN zW0GDqIK~PT2tX~9gGbU`sr%A)y{Yp6j^RVUlb*ns$WsQrW^`$^Kjw;hw zs9sn~nY$o0K-q|g574xCf0m@RXp#@dYSxbgSnw-qeR+>s7%_zEh>f9QBHQ_*g-R_j=C-w3{x}l=d@s!fHE37qTh%#;r-1vgTHeR?d?* zf;YJF_nm#EWDT2Xu4qM|i`^%uh58Vq^gqbjGvYHlLQ~nvzL?jx#KiNFw>;b>j6~82 z5u$y;Vw^?&$vIU*tUm@-?3}(hw-phQoBsGlW(UhzUc3;MPbe*;Ei$0-3{4Z^2VO~Y zt~|M59wJIQa!T~bnS$J2S9Lg{^jB;;!D{>Hc*Nm+i{yCwbU#1B_a$mC2-roM_BOCE z!;8%b!dXQQT||!5AmqW%WEuU8!0G~z^x`*YNE4BHkl!oup9xLIO-{a2ocOr1leoku zjuP(gosrT5u{oKtIT_Bj{JOCBx36A)ys-lJb=P2>!KQL}#;D7QA`|ga_H7oYR&-Fl z1Llh(&~mOK!urrYH$zjlE|u|2wz&%`EmC2 zy&CWHj#NFkaas?rb2PU@=3=o``VS=y&eamUaWSJ-EY)_l8_#E_ShA4MR;zvvXBe(n z+;fEBSu;sT2#M~%Yj@Bph$ppfZUkSIg9T4vh51RjevtF!kARVUG$}lKK#87)TioOL zAg8k=t)L=OfhMe={ZN~`pRCbe*4z`wyubc ziv{|XB{j0_l|zBXs?Q$#B*4)m^(*UeLbzxs)#T31Hb&VqpJdSkg{1*Zt1X z5kWz+nxsEsR=Nu;eFf|Ui0&+g>w33Na(Zmdi0Bx*KghClqO8vlW~~Q>kG}tGEEN7! z^f?UV}^U zACR5+YRQNXSqPX5fE@N&qqVc}82%Pns8I@1Cq67_K)n}Xrrlm&9lYjTWePzeCASfG zxZtJ$`QJnUrWmG*WEeo*f}UWpud_}Fe^E6CxQtnG*rBOtN*Obl3*UJsbBjm)|A(=+ zfU5G_y2cOPDIuYBmvl)aZTJX;zFIjgmGReeLl z!TxeN828q?Xw$B*2*4{B!L*!rX8`FU`hM{>O+_;*-sV(m@InX`O&5$PfQO#4(H%dR zM$xDRh|`#ll17VA0+8!YemVw^v5y+(PJU|(FAjgxlbuolbLDH0zUAuDlnBWu01M2* z%wcnN2!E!R!SVeiVQsz)U|t=A(uL>#Nkl{+0S<8#?W`$7p4QfNzpHW0*bZd~sxZML zL~DE6R*m$NS&tUt9SrHuXh;@474U$gHiHaBg-Il$d$7UESX*$F^Im1RLu88z>m#sJ z$as=5L&K4&Y4xdvn>Ff@k-U?PSUw<-26c}He0u(x#D)e^g5XSYh?!;n;RJ3s?e8Cr zhW|d>eBU|EVGPl|#OZ6(#^rl6l*;kyDRTroISuHs&sYZt4{2+8ft<4ZUBS1a8ad4~Fq$oY2Nt5(UaF?Zc7AiE*zbAyyO>a1CI7U4Fkl_k&nwtR7X>^u7^`xn&pc(&KP-bndVaEoU88I~I z;u-%RCyT#XrPMtd`%h=p9q~77pyA}yX4OAaNeYNeN{({?7XiWe+046V!)M9D@2P9~ zY~NZGYlJE8lsGogh0rcwa-0qLjee^UhISLz0pqmgh%h z2JtfJv6~x1Kr!TmDpC1>6C|bjp~$a~)@fZ{DCN0Iq(fIP|BI}keO0$Yi5rXFJ0QE6 zn#bGGD@#oI;_cq&+*e6X`LG~gSvxi|8BiIj`z_BJG`g13N8S;8{1k%Nr;ML}nM5C5 zgHTzka5`~g>S?~Yih0JBJDwGa6PI&TbHY=4rtthZ#OJO@>H>f5zmrU#45Q19V`rp6 zunzvW(_>1DVWbtwBqtAmf$CKl)KTCT!Fg`gqEj-+Y7uUqtozbZhO-SS1i^#0-h>rJ zB$FFOOYIvRRtSV3fPcMO0ifELs9sixO)O<>Ur+=BYItNNv+U1B|5c^ zpy7}USg2x+vO+h+M%0=Y31D2yj8IUe;vE ztpDWvCxv~Z`rkRM6KM+A2Go>Zd&6b;jv&Jag@<*aARm;c*(<7{EUlMI&-A@Ye_lvh z4*JWeK%Y$K`S*$9txamA9 zL)FT*{ugA{9TXN<67r_8x_Bn@?1D$+Svpy=?d3OOA zG>)SyCV@E~uHp>%sXxSD)Jul>(B)X43=t6(vd%>GlGP=5r`5Ux|3|LAx?kF2VV{qo@YxSR$r zFepV}QJy>#UWqS(^dCOwcwiW*v%PcIA5Q1vaJ7WQ(LGqDeD{^*X`~XxZm*Ph4 zQ8x`VDh1fY@tlqSQl(h(d6(zps$JK3{EG`+R zE=VhpFrv(9k!DY*|B6FwVK)Q?)G|v9zig`EYYz zKm-S66Mc$(YcGzZmqL7h|4S0f4sRQ&ow;C=7i0M0O)t$g*9?-tQ zbd$WiJd1iTgy^|%H?RoR|B9FH!1fG0UDvzo^UTalAe`7e{z_I=T-gujb?OET2&GXX8rbM0L&HyjU=@#`DA-S0m!_TI7Z)^B z4KMhXw_~yz$R7D|1WE-xx#h2Y87?2izTJc!ANi3I25gJ!gonA9E5jY?fN@=tpi4j?wRvS4qElg7W z%{*!1=SOY{615uS&i6=6&4KO#)7??NKt*zS=L=?(14boaUYD0MQd((qkt@E=hHgM=n31vPGy6@@sMJu+yUzi3kSmn>Svp4xST6r zu#~>h#9SYv>}fiXn$>8AY*~Vp^p~1OfKXUakO(GeEFO&FwCgV~FMES00J@{#!a+w1 zmltpnsPR6o=D+;Hk+{O)_($tP?dGClBGk_x8)zxK|=_cj%Oh{<)9hcXp;IbRg zlfCzbfso69rmCC-Sp$HM4s|Q)U*+ONLxd;HboxX z082n|3|T#pii-9#PE6tb+*eBtgV zE(e&IYfr8P#kD2yu1~uI(Uz~L8mpa=Au(-7>KL1JO9Nlfm=fsmj{_8wQ-%Olq<*e2 z#s~%oST3-^pl%QVQHLH=zgOuxIjNoBkJ7$o8(UoT1o9*nMBGF+O5k@f4i#s6Ul)0Y zWn2TuRpQt`OJUCDT#6L;nky$kM|nAx-JTug6Z~*ff$&>bG;`rc1BTTT zIN|J2s$5hG)#}I&m8cXwb>FKY)OV%$ltAzM2w`(1YkKz_P(&kyoQ_t++V#EfZ?7u) zu~;ZS0CKZphQQ?V@~ZRuU|RB zII$uq+gvp}XK6o?F6{o*wu+o4#^TFtYIm|&*&`t>R5o2GXU!{(s)i4MMshdc+I0(f z<>71{GPOd8{{H@pKe}f zOw2doi3TFBBSM#?nr@k7i5YFgw}g_RV%!|m*eK9^K}?`e$G^EaA{|@z7q0Qfi}xV{ zF{zBnXL7(_4(K#OI)F~2uNt4%Zw6bsTq{|jsFO7ri6hBgroGpz_K$JRz zbDHlh{e=d*g6|{Le{^)TvSJ7{P+K7rS(#Z`iW9nBzzjf=2y6uKI-vqIwdGvYc75Pb zvb?y+f>nw761$9y`rCEAGFoFP*}ajc@x6HOKgX}ER&g4UZuRh#LAXQvPK;v?WpGBi-qDc0%DM?W zb>e>=zY`7_|E-4jJSZv^#n@|ECXi3AyjE=?Zq#(sQKr^Q2oKh&dl+B`17 ze87w$g{x3+?G+jV9t*up_;bLN;PAfn;C_984(XufFfk0HaO@xK>dlCUbkP762LEQI zlKdahXF138Pt5Xngh5g=DY9#uT!aZc6YK;s37$x35NFc3rTnc$h0&@rggp|L}$ zG1PdD_DbQehBE16da*S-q2SKr?Q{S;nPKw_ZFmHsI4igRw8;{6s=g^W_z_ejrj>AO)4k$@bovb2;VZKyTwdV1Rt_`9Ee#*+D^M!1>k*i3wh(NcL`uO-0}I*HK;8R=SN=zf zxHSpvBX%nqjr}qAsX;i=1w?mfZx6h{ih~8?o_6;4+krgsK%EL47%tOCQ2n4);B%K# zAf`ic@Np{|!u&(t_rY5Qq+wy8^BXzcLhAYRU0Hf}D|)-pq4fG6z?;fI042Nu>N5}M zO~4PeJtNhyE((T+3=ZOqv;_PDpiQnX7bPKd9^HdhUX90tvuWot^8`T>S{}*3AD5P`o|ShL{OS_ovNA|R0#>Z;hrIo!a+Xw zfu?}%mOM-|Je)(&;6eYHug7E zbnr<{0m;qSq`3`pyC1sW45kYE-lo$&%LNLsVsnKLB2%$j80qKr4qksW_q{72P8iYzhuK|DsjD{@x-`PJ^)Hr09HCwC*b~nEOgRoG z(9!uALYhxlBw;ryMI{adhT179FX}Pa`QsOv6t1j<^)SwZc0MMkVWf-!ZJNUiRLU^L z+M!>nD|Hh2qF1W^U-77(%ehB9F&;ty!T&C5e*Y;62{g5+RkLzA#s9O01O8`?Vgtfv zP#6O$2B%X?N?h%0Ud|@NjEYWz%@30l+b{m8?pL4^d6aSBE@nRTaT7ul3l<O3D(_fTYZ5xJcc!c0e~+aMT~~zhWjq88l|T9RJUl$)PqmV$MT0 zYwo1^_Fynwq{bP-fOLZI^W~X{i1)8Ap<`o862Cg(z8V5C1dz*s%n?X~Z%8jF8f=Wu zpyn(UwiDJ+jacTq{2z_j6XixuXO`f+sMuk^ZgoeD{Q<=4$%b^3FVhPb-aZEY8}_EB zTBj5>>{3wG)<48mMXTO0`4;vi{p?qvbCs4GYbB-64swI^Q?w9BXz3bWVC?N!w8{T@~@B*a4-N>C%z#LOXT)wvP=V1FUjeX z4gwjV&H?vw#-Otc9cX3dfc`;fLCTlYd5v#hdtqO-rWBg9>lYy~`G$Z_wWYN+O|H;_ zPKW1B!~Fh7$}gZhJi`b`?BD=lYd=%wXoXKmAc?VNz_ zBiOuYRU zC$YtKz&oWkm?}8-)X+20iV!RYP%N^w9lg-ke_9R$PVg8_fCYK2_F(0E11x9@1V#ab zPp^d65wu2{J|C1&t0d9$n1{ZLvnD8l&7%2tTzm1Z)JP7m=y@S>S547>7>X>)~2yyqKvP<&k~s$zzXk75eT!+bmC`6ZoOz+)M^QH`pKFlm5S4s)Xs+XX+~X^pwg> zoW~L{f36uwB7r3x8w*Py1Jrc_SK)ul5Em(dBw|#9C!$&@4QXOlNd*P(vG7R05)RIl zX;sP@f?aQ4=E;9aYw_z`$a}^Ir(7bC+QUTUm>cf3S3>?`FQpiPVQMC|P}#u>VyGbw zCCCGvUv-!Uw5s3=dxMx`wH3AR?XszJ<;cy7P()t$BH~J8B%9WoWB&^}5+ogZxEd z?i`assBzK&H|ycl=^tkgxj%pl-Bj_?+8%5sJ@6{<`Et4goEv~wBy?x*@cg{LIaJiE zf^Lu-_&=d;&;1ll1bDnpwQH_K`O@XB8C{ zEG!0#`(eNNhs<}JwDHme>NAei`}FV@4BylJRkD7KdQ*)6qFQ_lhI><^2T-H_Rjh)I zrnlsEv#(O5089*uKE13qb3N`QJnXnRP`$JUYB{m@1;Ra0W*6o4on^Wl4is|`C*Xwe zz<@)d9#~?O|0(ShD<ZLTHB|;vZVl5#nj+*B*rZDf8I8v=Q;5GivLbvD^c7A!_`Cr>kuXF0 zHn7JZ@wPDwxapK%VGzFY;uxf!0+8WAGtZG zx^&CDlm;^`ZAvF%siax;S^}bg8@&_eX1%M+NNNT{vA`<{br4GYWK*5 z=4)Y|DMfzs=GH?W6gS&9>_leh^Yee+tF{%m4~)kv3cIx)s;h@BenP|%Kt!%(;_qU4 zE~j*8CemWI+~ElvaTmG;5LAC2Ei`3&^e^zRKp>m(#wiUG<9%jBOo|vRFq+9E)1(FCb2{55oTjS2bRa6nQ2f?n(oVR0L!C!z^qw(kQcar9 zf_=+8af*#5jUl5ahF3qi;hVNc`UG)$^wD+Oj{0~$@u!rSZhx1yU(nB9TcHayi9jkk z|3v2z-Z~h2t5K!D*lULU1P;D=F{SDK{g6wQtaqV55-n@GK@r4rAxnD=j6;r{_)Lxg zyZe-TSnk+_8yd)^Y^h(X~Vw@~;rdmk_j- zK#|F_5%DuJzSQQE-Wv};VuJXqFo-C2y0J2z+1)b{x_8PLN{V8FmROa6VN}Q;5c-|% zPB(_W0+7dj`DUs8aw)vR7rK4~Uqm+-{Oqynso2ZZnN8AII$&=s*XSNS_(equX-u}S zkAZl6n4Mt8%A}Z|CvRu;-1zbRY0(4`H&@!H%-$8z0gC9}%^%-G?=k(Vu_s(9otg~~ ze0+%tufKj=dtkUBQ@Q(o!duuw4VjhK(qm~}jB(;%NNmx?fsm(_NPJMEQ+ex*qa<)D zL!00bOs_XRJD2~G#wXn*Mn}8|dFr|ASEeWzROmy=sLIXE1O^wuPd^~1!GcF2<{ZZP zbxVWIpxJ#SgUm$4X2xMU<<0Kl!Ap+zX97b~hRL9Md&`AHWgo1WoQ6b`pg=TKg6~#& z6lMdDGmB3cpUY>2%H})9{I#w(|M4Lu+WFYmyOQI3z=h%Eu?ds=X?;pepe&HW=#*+F z5KB~6qeQ2omqD2e!3~?QlT_WW6Bg9BYg55KLyg)0)w+Clkkom*ZtJm1(%PEZ>IMI2 zjiY5_Dtp%S60%ivoPAkcPplSvwR)G0US;+|X)7La{PoWUhPUG^-_BC=4LR*N%6C@> zR;O>2D+yBhtx4Tq#P?#Mup2c`rJDTg0B za>#Cc5}8b5DtmCRrMOS!E~06;PL+ceig+h6+f;Wgek{c z5+!}%r-Y_RQAhDVG4#wSbgPC?ShoCkyE7P&u; z29q&%zt9x(W_n$3^Denk;h`F-Kqu`|r0Bm_H74YIt0^Mp7@$|wI87rOqg?=#mThx2 z3zXx8-p7O@*JIJu49-ko>h%8hHVmY3KC;4>TxLiND^v&LbRN#Q7WY6>LwB#Gf-#Cb z5y=6;X`tX`68H#*=|b+#jg{is2|!H?#nYEa!3;jhK@i5k5EZbRayP*&Xi#$~irk%n zSX#{ujgnyeGrU+;_Cb@|sVPYIvu?j-Edal_8DePo^V_3mXEQpshooc8!-|bEiIxt4 zh&DSmc6`(MaO0$T+3tS1)aLf+&%(qQ_>eSkp_yLweqGMjNyCf(dpf1joM{??>V?(2 zgR0L8FSv-ZVYVoqry}gq{I--L+8Z&3|tNV&~xt;bd1@mcg(|fNB*fVU~bn25Qp6>q& zelX->7~!&4brSy$U2Do1vP$w&Ld0ExA}D*`sx8&=;whK8b`T3s+(nt3g68 z8T<|H{+9H4{Pl4Rr*s4FKK48qD@VAj^FGjErQx1B)Fxqv!w*Tx&EYXKggq)e!cAaA zhCo8k7&BeK%7m2QDhgk%VR`=+Ur2?BeT9fwknNC966$yykMF!xeBN=;1-m=UA}SF$ z|J&zDG3_pK&s}|te3fk2uSW#Z?=!MwHa^)+#AUI>XqaG0lz7Qqmtf^m1PuQ zey1nK4WdU=|4%LHm^mM8$@JUk2{f=rKpNNti`9VQH!&cXOb&W@nICqx6iDKXtm%vx zzr>JSm%t*IyO;CheJ1)U^U8i_qB%Ae2Qt&!zWz5D6#YO+(@#ktM{ZM(nU8%Efqd={A%q}z`(aSl zxEwBlCwUJt062&vblyBRe7oPPlh&e+`QuiD?N6G(d6mrL>{uFdWazAQ-nxd{29$<` zHN80S(CrU?z#_7uQY>N6<3uFw(Dus3VE6Zl_nk$2JCLHneBO0QHt6i}SY4mCY?w>e z=nQ>R0dA&^dU0x(ntJ0M@(-T3Xo?U%`!%#Ok?68cF~=w|a_ry7(cTdt`hftpg>Myc z_^|wKgUw^xXa@(9toi$X`QC#7@^p|@Bw6qNh6f9R&UOod2=ksz{tP680IFMkl%%rt z{`yb`u4ZH8(4MA?x+bc)j$kv4KN{1n8XF>={5*1aQdv_=vPL_0W&@l4Z5m6uu)4RD zv3|Gm85fGf>57Gt#`?6zz0xIyAIKXf@Y5S-bPUrBTJ>|U<#6CjDm9Ka*p^!Eg(0GQ z`)KhZSH};?5BT)Wetk6}h(66(_iL$#^_Cse8(!D(scw|4g#!nKaq{%TkDWiu)G|Fi z^lNte@t&|52V4-zr__q(P5aB|qDAqQp9E~!>1|U|lB6nU1()%Nrc5b9I_~c=CoL9; zrg%`EI*hGucCT1yWOW$mHRs&G8L>2aoYzS?^x^!X% z`dUWxDI?=sPLTVo<1d_UkLb4FS3yQyS6qZoOIo2{_alL1HC@3{9S?_#S-(*}_lEn6 z#6=ByzFxfi!LvR_E@Z%0)obeTT@iJ%Du8y7^b|Ms?)6!8vLBpdSQ9eVPt1D#6^~vX z%}}?ulLEdV(QcTP=%Xk27Z)J^L7|REO{{{n_u2Wobmh0<#fE`{y2{L}3TACYqgCSOVS?!bm8ExPr-$$i?P(9QKc znRiD9{ECO`X<+#%qsY0ff0Z@btTGs`NI4+%vHg}I5EVgRR$>=?bTlc3>?rAwid;YZ+6{1EA=o3e(%4*yf$zEWw@%F#VNDix0jb3=&t{=MwwEc^Q!>!H|eXWrm;(AVJ3`(d2(e4p3*IGVQS zPUN@kLxE#6y5PV~(TTr&d7sn@IJ%c0)}!~@&ycAJ8=VJT)14RJlb4kRPLPfktH3W9 z4|u0iz>wF)agdat3o;7DF~g4$ay?ORCJVEukTSA)s{7R(H=x<=D9co4g))vZk`zGN zlm|OpmZ7y5FYsyCm;F$mW-?g`K;~vsWVl~3;GF5cT{p8cP7nKpxRt!%Sye8ue?%dR zSyI};;ZdAjGA%@S^%53>F8`bXvr+6i|AeEzdT|wH+x4q_mlI8f@N4hWYgmtir!k*a zhN7Vni1-oVaOc$l_x6b6(dRFLr@MIB(KjmL>A2)1`C|P@`EhNQ9Xhg4%KXlX^jbPR z|G#RmOH-{*Lv#p8MVQ?5wtHb9qFBd$vWY*8r;A_X$D;VD$cOzZC)?PBe^n|LMN{#U zJ<;r`z7w)CJ5X|^KYTkL0l>zt_svS=q_<}ChwbTyi>|}vhochd&IZ$*NyisY7=4WT zvWWw0Qx#qhDT}D0zKI{87dM)t{px9UH>E(AVI=L=YZl`6<;z7SaZ2iyZ+zsG6( z2puktlf{O+xP32WzQeM`&8RPT;6E`r^&RGejGUvWSBObyt!fS`<7HKcA37e-llLG` zUkg5F$5h?HOM)Yg%!Cm(Olm!R5jeRWgeBV(qdGaFramnZxtobrVFH2JARr>mmy2@8 z$9NAg?Uy&(H%pR=#IGeev-#)Dh)BwqZvFIG`X8lZlvhz+owOyt ztaTQ%e56*6_btF)0j(hI`D;xHO#W{?WfnEUX`EOfm7KvXdU`kaaG%$C`-Se}{si1j zw!{04Mq#(h4?yiSgLF5lD7)PzS;QwHF3*ixF!nCPPNoLlVqxtQoXJM_Kpi=|0uF)S z7N8)HcPsPNlqcp1LG~0B4^6~ z7o$0A4$TGCC5hvz-rO0Q2Sp3z+15MfBOnyNjw6Y>w<{$n{6dj>ssyUNyJXeL#MR`Q z7Hye*UM$kPxyUjyIqjnjX=pYV`M=b&XDyN-3z90DMMmdDgk(pI$7^|g^25z9irAP4 zOW^G<-;%QU;iQ1*+f$KH{@JSD&ysQCxq6`+vT~eC(Qsr$ z#NNSDcO(ffWo*cg?Q*3m@&}3dOo@V3nDTvUd9}7xRuQe|W4GduN?|sU@b`Sz^npClL4&%c^Xn-F-DTxK8>EScnf=(XgK8YJY=>58&~X zHo;g^QCUL>hcjAyTqIbIds{Q-=gyr0?P_=W!CHrPZZV20Ve9`Uo3q%|KhOetThBGq z(lsG59MGIrM$-H{kh-OZ&zr`MBtqaS}rN6}9Nowr7F03VQy1YRtrwg!o@whsuJ;_?p5-iJCSxbE{Cv5;B+ikuV1qdCSN{XgE2zvT zdqz`aeXv4Bg6!)q47BjXh9=!5f(q}^qLn9ZW}^>g8*L55FW?)iFOI?SxvS>>gcEy@ z3YiS#xK=hKMij_oU#}9l{6^B2MV^YrTa14Fjpj8_&9mJptWbdr$u@ugm~_Vxe)C7d zx(82{B9GKkS;uQyNaN$PPo)HG+$%J`P>i+z!pTI$^X}Sx1{1m{FgWaf-liSu$KT5% zVae8<`*xab0I5$?ONxRob`ySB&b-Ls_BT% zSBS`Y2q1)OX;SUkO7M^~J8uh)Lyb{!WvwNwpTKU6TKScVaopu;lufd7$Mir143Kq( zB*FM)Yxsc6ftI&Uul^t9t^LD1x8gP}mS&n`349FIaggaF2qa7Yf-gLpnejGMk&W(J z5V`xs>($VSb-!jfMAPIQG9BeMR0ayeoa>}@TlrvFNNq}Oi*~>LTS_Q;0!k3@k?-s_ z9^dV(Gr1f#pZf28#c)3maEMq^cS;yb*Z!7p_2?~^d+rH5BKwk>iH^~AlL+@x7Qo*7 z`d%q9Xc+3xLox;>KH#%~dI>D0KZ{@~=Rd)+-KWge2Me$gs!fqtGrktJ8^JeKa8RV| zwUCh={Pb0*i2{jqVhZAtCc7k}sl{hTe&pfdZ1FTEdK({g+Ql6d)>3%WqYt7n7B50_ zn^KJ7VVVS)?MY64g|jUI*FD;J*_IEyE_YM~O>tXsc^ZoPjMdQ+nJyEZ11428M0XEy!4#$6$d3L)&iON!};>v%cJO7 zY0SMI0%_-uFO%XbDlS_WrP+VfS46>>qGPY|`0Ry+>yg}>yVoFnp@u-vjTi~VN(UP@ zQJ-$KEVa7sTQ4sQ*?$fjiX?aX_4qkJ>!A<IBs2SW&!_ZXaNO&c9g0Abi4}J;KL4+q<~x6 zR!3FvU)*ZlD3*sOPq}Eh6(^sYv|(ym)f!Tw+}M$E!01Y@Gp?M0I2f%zWZS4^X9l@z z-*EsQiJ;q*3sxwoUC-C?Nj)!`|JE6$H-YBomuP1zIz_C{?LS{nmH2dI5u_Zu3i!tk zTud6jxl7<|{8BfG{*wlfM0=u6ri}fQWoW4p_jI-4iGk-=obfv%%^zd(cbx zWeoe6+8NVq9Xx%Ncum_gmv6hq9e0lOK3vT0P%4xs$=pi;f(;4@o+RdpqJWFV@@#a^ zZ?UPWFQx_Xm8k`L=GKmyKewRFb8)F4sQn;LQQ3^#7Jg~}>7<~zVMK?^xsnwa|8iNm z!Al`bi#M7s2bEKY`2-2mbD&l0ld8|W=xR99j37yK!C=(+`a}HW{>Te? z1RwLad0L$Jy7X+s=N9S6y5_sJRxQI{b(Kf5AJYyn8;_4!t-aC4?k_P;R@b8L5}d`w49 zX4mYZd&K5f#-BW%_JFXk;L8oP=nVNBwq8(?QFz3e7;?C&a{ZQezARo_8atmuUo5(L z{q224kj|`7V=YEwEi?hm@avb)?G&H;{hC+;pmahC+YP*2Y1tFJ$_#$QiutS~_@uA* z?8bk|o$~|qqxqnSBc}a#W_V%GxUUIkqDDPsuEg>o*B?uBuBobzZvDBl~+m-oaxmgki; z`B&IWSAPxzdp{l5gxvfuHyuG%V!zst)Ka6%H~SFqO@r!*Ry}xx+45Zpw3AA$|Fp17 z4U%~;uC0`{u27f@@ElY2J>;?`=;qak1I^RYVJ_^hCYjF|Y}87{(mLp$yE;r!f@TiM z`XGBt&d(qJ#NI0X4*QMA?KZ{aadE+M;lBd7LP9uFRhaCZ?P(BR|03Z>Wu@(P)o||u z=CLs?ZEom;?~Ba%$l^EtMB^|k{T>OUz(!&chCF`WQ!&sic|#={RR4_ z7NBhT^a{z{Fa56T?BDy|atmx-{dx@=%~R@**_#)<*&J1(VTg$g-Wb6@EUXrC|DMUh zpg{Ysb?H(C&|giFV+FFyqI{*M8W{`27}i)K){pKQBAhQxa>U|G6DMD72reVItxA-d z%gLX3Edjh>;Id_qrs+N4!4j~#%--`bo_FzlGyL#M0%;5jVp@hT4>5_gRUok@aO)Hw z33xn825~I=TGNlPN8B0kRC&a)Fe3vqn1*nx)5`zcRkPc=6=a|FH@=-7o&IFRB43_@gc?SrI@@Fw-b1%x}ckV zu2eu}26-YA27x%oGPv4UM5q#Kcz@*DSuZ0A#thivU@i$E@Xg@G2?(@|7OM<&;bA7AhC zp4_3-8GPIAGso6+aWO|@$kR8a1TkUegF^|UniQ8k*mp8Q$DxZFG;!MqNUUxRq0T*< zizkd}>CS|&|wc$D!nYWQ8#9qtOqk4`flQJ&w6bvrYr0Bl3F$Hh{;#0RAD$xYmuVO%t0E#Io}W)BSxKz%{7TgD`mm$= z-iSg5n*cJia2{<1)t?ykLBx>#ihFoVKDT^LWg-b4s0KJ8zOhVOVT{~g*gke~QKt!r zkf?EiyYA}>-7dH+l4~A)9T}GQYyE_5de?MT!Nhzx$spSoC@^O5d|Scqw}srQkoVGu zTqob?M(rO|y&VzKd%+=&oxk)14m90lGK{c7zR5KaUTK*j`&nfu8a^WL{pkV!(=@Q+ zz$lb_oeyya589cl{od{e1*|`B26YIu9l`k^eG=(w+nIgd{V8&0N~?RlB>R`o%8ylVlGMT~cqdF}TQ7&xctwg+1z@$JftW@^ z-wk2jXxgVr4U|eBE>WX|Z_+=>w*E;$97OKEaW7j=dsGeGozWrajr3+uS`D2Ww{F4+ zWXw0BE4b|dqzfQ@lHu+Nk+XT=`NZPm%6(+%Y)p@@_Mbxi(J)3UOwMLyro3;U&fZ_? zs>{NoQqOchn&V!n-+PA&@+qEc%pvP%r1Qe~jk~oXYSp^4c`eUiHK~sp-@4oDyiGEK zt936PZ1V#wh>oE0PqH_8cy4|=zh7P(Z5ILfdEB$#Ko0=FmgM*y%#foc*}QPnRBiKZ z3+F*dKhr3aj!Vj&N3k38Wi#)q^KWRevS^cKJoGSsift$Rf!9S=vQtL>Fe(pC+QNh> z$9#$Bhui8{@|DOnAp=3&0~P`c1(B+<$xjpxlj|2|+AQVffJGl+VY*%9Z00)-QMgT~ z=)#YWLT`B`OAF6_pS~1F{?Sm=b1a&7i_tGxo;mDl_>OSdbQ~2BJ6gMTJ&>>qtrwpa zJhw%ddR5@g&Gv-hn4~(1Ss~Kjn}?OfaNr6V>2UL$>8js2L+4rT@A;hPRE;xQNz|6a za$ZdJBluhz_=@UpC2Hfz+x^Pi#%e6<)9mY+ZKnApEtZ);C-J_70D#W=U@(p0uNTal z_z9Utq~Y*_8NZB>4G@P3?Sj&A!oTw8N9Ym3hPYWL@w=Tm`#Y96F=fF(^6$R$k>S9s zczk*Hh>ccu``J5+di#Kri7vx5?giTAPx2c29jeb8@sr;3zqKZz-j5hStZi4-np`!1 zMWr_PM&CHN9KTRHhnKa}2&9BlO#@ad9um|9*%Y|&gjo^spWRqubJf0!P<-qj<&lJy zNkKA04%n_P;T^UN+q)LrFDZ(;KPWkLzCFFM|r|I-tkGgRzL zv>yhYmmN%vNy|o%=#jrCkfB_hpq&A!*Gwz<_IFYj> zqTe_sv!~b(qm~r8GZz`ERoThS+YcsDMA3DzPuA~L^yFMgboaC4#XlBd+WT8@o$R|= z(#(JDd)1?tfAKg+RV!t%Mau8P?@>%Groo{!ytKe(xA;P97-+h=LZIShj?pwIH(-^I zTJ~|-T$x*e0lKyvb7ls0n@S1q?(ecXyzbf=m(4-j?xF7L{-gJ0eH7cnbQRAmP14G_ z;9%9VQOEJgF87VkN?YT1T?aR9k9zdVT7i81zc9P$TwER7I>M!J73ONk(|1bhSkym41;g`slY2Ev97(%w*)( zEpIpq1{ljNP2Ny#?2t9xy*7pdaEg*7o5Dx(;t;lh)@~W zK)ge#$99T+49p4v%PN;og0R8=m7{%+|V{le*m}DN$EYd3=mX@TP zqk*wK`f!X2St+1 z&$s3+PsoYqjg=3?Yq%C9+vOD1z9FFrIb-bWo2)tm(6YOe^u-r z!a=?)bv(O@CwquKGFZ1qh}?@r?p-$t3ZTG2`V{L}K;Vp-qRYCA$AA-kOh z68o}ZGIJnNlt*Q1T#=<7zO8Gwp9$ilKcSNRNl*R*~aJKH-*w-!=A^8KkQKHV^vQ zY>1OpbC0VTpYKKb(oV9rE1e;2;o0Fl?S7KlYd!hRGJ|sdQ#N5TASQ2nBdi8RV6#MFeuYC>07n%pLh#cnhL~vs@*^2 zG_=8>BBW9y8T<2gecs8=^sU2DOxV~|8S_Zo%@^m?q!P zy|z}Rl%JQI&2GOq3T$y2Ce1Tw_mGrp~zcBO-^t+-ExK0o2B#SO$eWh$}5rgh)G$+f{CkwWm-_Ds3{fi4= zVyE0AWbnJUilejRD%0qJ@Sz7M@02M@A7CEeXAh#*LVNOyM)T_Q+#H&W6PL&tj$&$IX0`|N$byndM9ILxeBv*L<> zT=yU9ZqlKfwV6q=uFS@$_f_nVKO?|-9ka74I+~UK!By|IYh$Zc(F3f^WCbt}q9=rB zCLUA@^+;pEKMZ}tMEsT#dUDreyZ0pIlm3|ciJl0lSF5Cz;iHt(LvdQ#6=-1cdOWc1{ zDde%cj8V;owd@}gDR^}{gRvIGxP3iO^(b-v&Q2IdY z!>gwLH`yL5Up^SVk$=~Gzea|&b%ytQ$>;-ie9lnCj)|XRy^~M!J@{gz?I3W`(?K7p z&}QOjr~G(!^ViiRY%3hPLQYpV0-2B`6 z>PT@)8pm%_FOLbH+>4V}L^`E*6AfW&UsYA{_V;E2u{-*ub>pA~Z3MJ;YNq3%w^#vh zDB*2Z%V)kdP~CZ-ull@pTw>RKW$Vj(oPBCD-*{N)BW-fCdAjd&HkPT&$Rt=@!U(Yy znC-Ev>wR2i;WJKlA7#7SZxCw#s^%sQFk_8g5>Bl}c1oH_y`(G>0+{m!ZM6y1F6{N$ z#?+zyjCDC@gmX$>c`r;4P?wYko-fcDGUK;r6{ zpmT*x1S%gIeC))~Yk+G)$0p}HkQfoW>R<2NC$o;W=yC>=XbC8;FLaM`^cBQCs! zAbTDU|J<%ahJfVd%Sy=Z&{}4?)L7R~r!soKCqH3>ls2|H1C2%Ms7oSWRm?<=4J=#-zLnGh*2hEgi`DTjNU!!(++*f zVGr5Kc=@bsue`BOG&=~oqnD1}Llk#AuKMnycuVu9?-5QU&Fe4WcG?KMx+q3I;a}KV z77355P(Pv6tlI#m^i^vEvfq1q0Q}5DGC8nOYoS)QVM0!jOGJz;_!6= zr?gW^Y<=IfV0Jkt7!W$7?NNArW++tfrN^e`!eoV$OIuV=?j+H@W49{U(EUT1Yhkky zLWSlzG8clM68-9*-;+0|KH%L&#_A3J)E5Z@(hh%x9D}&tqQ=L%|qv1dT}b3d;}Z zu$Bw-t%SX^;Qlwl8ekMpgVYn>#KJfE=hv~yxn!Ly#($I9uWJ^3q4|@q2@9m&5dGf& zO`cm1>Iglh_CQO4(t$E6Cwi!O>xIRGC1(-wae0~e!nSj>XLvI=$wWH{INFF=@3Ft_ zifn}fUF5`sO3kcxD6urBpD7>gN27)8R##~IG}(BxX!8W_sZ(g{#rjO(w~Dx!`I+j- zmXj>I_A}Y(-%k<6uqnm+6>Odcy#O><4WrTE(S!|?D&^`L3uQ2~kW6H}6nAV>$ak93 z&{?B%WFWoE@&xZ3Bjt-?Y&8~h=5G+lfXam5>mvZFVZS;=OsjCa%;O*$yUy)c+6Fj6|t zFOwW9WM)~EYX5799eLW!78wVwMIJ-`KZUuaaAB_V5dmD7E2ClY?Pz-Hfvv#z?W6U{ zDwH8<5|_$1tUOx=mZRUD>H3-Yzfb^R_@08yLsH&jxSSaN6aSk=CeU>ytDx5!OPH^X zowCOx>RtPL1tk_-0_5Cp_#Nu6`v&jV<@qS!N6Uw6SnE9OfHMIjtUyYW*l!uAP*{#L zC~!wQj-#3oKfR}rc>GQCi9jFMT+d!zte4x`E}26wkSx%|Y*#2Qd|T(Xh-NOpvKL<% zvP6U|jrQaOW-r6*Hn&}kiYCmFLgjbCce@P^g+fabk7$egdC$DZ-WA2AU3*Hy0Lkov zKIa{wSmYu7g6i}Vw3So#?^^Ok0C)KJj(`&xG-4(o4(oWN+gNomf)`%uQPx@?i*WIX zVXhNxN03uJ_8hYPhOH^*=3&08wG3s)L-mJ)A2WvH6j?hSGnB?y!;lH>logPt?Q99z zv-VuqVpV06d8^CH-hoSYy5oT=jfnH@_Qx{F-h1eQn6T9=@;i=D*)8?3QQ?jvv^7%5 znrPMTyW`yQkv^IwlEC%>Fs9l~O_vj6=-&3B;TvVwL;p(xI0+`MeJ5L;d1sClB=L&! zj#5>MB~|zTJ(lk40Cj%>^JIW!9TXkAe zefFx4%X~4vfHLS2{Kk;^^I{UV&|zV7d9xRZq+*GQEVPCBAz7&bxeg|0wWGnuV7tJ! zPUj%-re<8Dj;4-V^&Nd`qAHUWqBd*$-5rqs;PP}ISY~*kZ{zcH>VXmIK;+HK)X@H= zUKSCimm%jjjBJ@^bGU`UYem=-m7?I&#vjXj|bL_XKEo z1??{<*s0@(R8~$W5SqtmhVBkfnray;%QeG6c5ZXjZ42+1u$nV;`)l~>{ZTF_Nca)X z$3z13a2K0ly@ABi%bA1kJzMk6Oyg#)TR-TBGxO;F<1S+~(2O0+vd>tP`Eke{X1{>Z z((TlIgu;_*-%zJ6#9Jv~bFeL@`Oc%zou^;9&5gj!eEFPF*R8@XL`aU2N%x$#y<~ds zj4JBBh|J3T$aO|>QHHw|r(*J(KtX`)6Jg##t#T{Iny zi{n^6Zxosy=UZ2ahzqpL#_FlGT&7Rc$P;ioK)%PhK^i0WLZKg=2!(a{+x5%_0G4pk z^EBbugx6IM2=Up_t7RlirU!wI2wb#JOhnciT?j8xm6C1brGq_3^SDuuBRs#5e|Pvd zHt*eHVuIh6Cg&EtzCpSzgc->1*HbN33=Hz0D2O@?_e4pHkJNsWc>fAN+}Y3M4SwL5 zM8LnWI6Upa)9TKONdMD`hlac4*Z3?^v8xzls(2d+TB%_t)p9Yybp>Ns;52z*s?%ei z(TO}lMg;t?6gFldK`_eDOpN4(jXIblhJmJ|UK=momZsy6Rv*yDp?&iAQXzh-kqW}Bw=Q0Ih`DqMT zxc4z(k{c6S8=cPeG&dePhu0Zhv*omB*?+~`R2FEmG*`*k*|mDNL%hkJsv~>sg8eZd ztrIW5*Dp#d<08(e`;f`Tl*=n0iDOVrOtv()N|} z`-eoV%Hg+-b_(;$6GZtBIkx=56b5U>J*$Lw6tz7stA0tHNhr2v~ZQs z{g9C!0x8L}tzZQ0sNHvb9bAZglV=pqF1pKV?h88sF>^i1M5Y*q2Kwj;KgLoVNlwnc zS;j86$vQ(D(1m&4sX+>UhFKcYpdjk5FGeAL09>yT3ZnAUpkH6o#h(`l|0z<#mkc{U zK>1KL63OND?Hx?i296NqL!Ip#zFR)S!&OGoa}-r55H!#qDtG%rvbmYrt8}X8-okU38A2ZTC@vIXWPGm}stVsF?)idCX zjJk=6YJ{QerDBDr*t`17Fh&3sN$KL_l{{;>0G6#NcorcLYi<1zJrI-uV(>b%nUzs5 z>*qh6zUCEM>jW`+$)akx`5&sCfZd76n9lqO3$L*pON(VYb{6lFH%Q&%f=f?tw+Z2- z?o9uw?rpA5!nT+F3R{*M{#uy~VF(&I%a;)XOjx!Gn;>yjBs9WYruDqcQ_z?iWeEs- z+_r3!KM?InzXbrX(>Eqr|JM43$@`V0xvik-MXb`kUzx0vX)Y3q>{~m9f_*ge6*-6T z)=f>IR`BM@e3GY~t+JNXr)sCHKnzvM=^NClhk2S6(&yO`%rC5qY#84}I319C{qQ-C zv=7e-M91TFq@YKgxcWpEJPLsT0&umZ+Cl{Lvtx;C(1dnOO~qpY|?yHpp*C4b)L^SMXVxeNG;nDXxrck@TCviA!DtKd+DY zsYlX5Ul5jReX9H^AaLDZp(**dO->wA;g;jql|I;@1spb}BneIu+ylZxbQ(ySCV!X+ zvtDksO|382gLPZAXPQMfh{fi7NT|N=#w;lBZT?0OU=El*R9*>|Hb(RGcRP%HlyOBVN`9dO4;@3nQ+vv6XZ3t z*8X~9i;YtO*LMP!i^hp$W>UCqpRbee&l?UurF(DiMJ!RW7F^Gk15@3%@`3Nsbk^pD zAH+8@GLr3tYXM(#9*6yJ7vAJ+CB1or{&YyQFL&4OyftxM>%H%Ykh>!KJ#i`)VBJE2 z>QSNry8nxI_8+Q8Q=^hnw zU2=LL7yFagossYgCJYZq+-N5Ta6TbVXT$YRV$SB688Fu+hqsHJmHzMo=dKXNbT)7C zgK7BzmrWDuA4m!XvJk@;bDwr}axKYEHDkW*-{M-!gZ|Q!b?t3i;#jM|HG*-b$oXW5 z+sq4IrNTAMzH`t;BP(&OnRN*|pL9yNED2bE3|yDdAaDcELzb&F3T^BoNsogrmT|M$ z6VaI-MWFMuzjS%@NcARo7BoEP{2@snNvl;fGQ=LR0?cFF+mK-U*s3l)ViprnEe@|+ z9=q<2gX~+g*9O{`@3@AM4iYzIi3Ccwv0j$_ZHRnC$I^LFJSf^PZ9{LL_aeqO#HV?UA@7;nmsVE!N(hoc z(*jBk^8mj6Mh7ut;?$)_C!B`asyCVV`QB@~Bv+ApZ*Q8&;}2?QD4f(GOO4T@gs~Ai zg)=p-dq#ceTZ08^wZ|2MEK(XnJQ1rg>9TtBO1JhIE$yAM+p7+Mq*T1GGg3^pq*}#Q zB(E~M%xsk<`NkNnc&&3?TTvm7AI|+$bz0Kzwz|!8C34}&t8sQYrNpp`f3NB%?)AyU zFLz6AOLTEz|94HLu66hSh>Xh6GLo0#u{$X1S6N2{8h~H$5J;GeY5{4GfZvgolgeh2LR&r~ds!MG~K)m?4#j0wcu_C=id;@(5cFP+0w zv{!AlbgGWf;CbV=mDbd6PPzo-X`3TmalsqzRNvQPs*ANPkihjgfT{6QJoaRqv{L08 zS%8jPA(}qDrFpipamet1-ZNC-0Bqxo1=6-L#SHUvG8OL!Q7^b{>}Q13&=cK>1au91 zx>jVW8aDq^k8dy4gW&qMGJnSa-$rppIEjozoJR={6&;yoq>$I`&ctnk)pf#kr+ez? z|B7+A39LhCLTdNSUq#k`XAlh%VQg7efflKHGcTt+&ZR0w(AerSo{~5F z=o>(B;g{3y$WGnl-b4~nFnHcoWze}|X(;CABhZLh;ylz%*{g_FC&c;W+F#M*L> zV@||897i%Xw!6sUSzaL4ZbY^HPg472SEpG-aRhm5*0ngU&%H%ieWC?r=ARlBBgk`~ znUJCX03?+w)~@|uQ199vzl(Rob*=uOwn>=PiW)K4Fn%nl62y!+ItmX}JZBPbN3s7! zn48t89x#2EU!ho9X2C`Bj?y4MLIjEW?a|-B5%`L4F2F_yTIC7@5*~X)CnvN8>A(25 zuR7;INu@Brs7v%~#Ps;VpATAM&Ya0WfZo%KJk62AC+o%gC@Fx>J0vJ}76Ci8N2@Yj-&=Iu+7FTJ+`odnxow9s4e zWVTlS9JH_Ht*iu&eqN(k`(#S|Z0yirfdbk8D+R=NdXQEGSMb{eS>+RY9WJpEL<|XCOe(Pgrs>!YBiU?Zlmg6&|b# z5TeJB_!|o5zD{FSK7NdZC&yNBi*-T!^JSV{C#&BW*+;tIDRl8B9P|LNkU5xu*s#m} zSO{1`|FXYlqXNN+NUk-_`+oURQG1=!9=#w+7rRTFU*MNnST##;jGU4gj@ zNF5V^KIgzk_q;3SYwe!metwxO+lnJB?gixZ3!uw4&E>183plr^tMMX!d>;~nbMf=N za;e4x8=cjYpJdp>a3ef2K)zt&uv13(wgyx$LlnA@AWPr#9SF^j=H?!WB0{d(x(I!$ z%SSch3zFL}3E`4m5Yw%t4wHrc!U_`L+khwIiex&E7ljjC#rUi?)**t$qSp&S#GKQb zT02}ycMAH5WP@9)>+r2%XVLsO zKi&MOvE-!G32#1E8oIY<4=)c)=0(6|}`mMVnf*=P@^^I#DfBPWKjUB#@4z2TVS0F})5 z#jhv|@*FWQK{uk7LKM}0Ldxe*QUnh5mv56f3+8O%$ z(>V`ioVj@Ld$z6Nd(2l7dUBt%D>J^~pXbQ4nBup6$-Kf5u2=)3D7qWl1+gUR;p{dW zBm!9IK1(bN@&OF?T*u<=1E8$veBP#R* zOxo`b3^Mq2;heHnDh;5v3g*8kSI|^>SR3i;xHbHotFXq2y5F6wJ^3no8>;b_Nnrb* zCV^d%j{j|ehi)$t#n!LXk)2;W{-$5xV=1v2E>-S~L2Whq5c1DaABIF+`1v8w3K1pz zs&Gm1=FgIo^|g@!O!;x|Ty^eJ%{2X%10g%ORFeKn`#H-O@)fROjc4=8_+l*Qn5li+ zCs}YD4tGX0zD9twhK43)C{QdmLfd$WaW}{3(w34ZOP0k8k;NMfEV&Wl zU}HNRQxtQViFjbacVCh6)7+38UpQQl_f}aa9zUNP0FSi0Z|g(&9&lUF1qoBh=HkH- zZ6>f@B$%Ru3q=AN`Vh9U6bd-^mEe8*d7z z%`rR#rGLc>M+Ex<_!}N1{)!V@xLTj?2}R5X5wrOGxgeb{T^z>z1A(H#-Q^VG9j#0l z*vNIEXO~=q{?lt{mA!C%9^7gI;)zp_*HPQ#{)-mxK(`|*ILrsxnC*nIa#MOnpZsY2 z(Me0BnvU{MXdD{e;IrRGRfh<4r)rj#G>h#tb4A`rI@oHbUoYCuVL{J7T4_uv4T5s} zceV_CFh=1xA|z=tYW4|LskIm1yz(Exf=rZrG+RunEZH_3VStTN0L6HV_r8~^_tC&wN|! z>G$p$~A0{#PUKo4s6?q!+Kh zCA+`iL$Q_D%aui08H?544m&tq7cBJLTmb}OCRkFY&n#hTY_@|X1-Iee6 z^V3d-Y?S@ye}|sBWVjOUUxpE}|#P3&T!x%dsAt2Y1{6u-!qB+vow7Gl6yF@P^eP zgrrR01B9IdD1`q9G3_|1^}pJdSmDs%Uz0hu2BRtv&xz>f-);Sh9r1H{bY~g@-K6Y) zvISD=9bJ!93kD<2Ee2tWvGEWkpK{I+>A-BHZexbORUZD&^WkIxC`=X-og3Mc9PWeu z9~ilSukhCweWr7J{KX+l27Rk#gv7GkEha&>bgjU|!_Ko~(3JR7JV)kC5!cSS^P=`3 z6kj~=eooV3_%=U8RTks!qRuw6v~2wQ=BjRwH#jbgE1hfF7{IC^2kL_DzH&AXN1!Jd zWl!2?bud`T$-~Fcwdkk%hh+Y7~Rj|68}09{Zl z2(JS%;B}gfXM+t=$M1CC3!eemz;@i2i=!Jqe}Au!$B8Cy9z2G;kuMFPh*|Iz9SF}6c*@CNg&}`c zY2v#}-uf5Yrwd>j%zl4UEYE4N&3f}~hpue#LvM1(ao7jA4eG-t6(#UNsaW8|1Dq4V zS97jWeF8yXK?ICoNjxO`dcrnK3Aart7U{rH?udao+gPKf**=vj{QM`Xlmu5G=`gBK z^IESViuNEmpnxNEPXRA!=@D6W-f1QId?lCw_-W_9HRb6jq3k#EK3V>6$%wUiOk;Nv zN(fvLxE^)sEIGgR@AYaZ0(g7<*!v-%2P(gyP*(RJcothdGZ;lu>`7n! zi*Z_#no);`slGD*u=X%l!GjlgHg(cKMK>^FI3+I<#&F*1hQY>@G`J4y)>w^+@$6x6 zLm<@9p=-$_ipZ}K!*&NJ-X8d43spkX4xuSeUEfHfsfvB0a0>D8kZ z0898Y9jYEj0G${Zyhiy-b*l2;oKS}cCFj)T042 zkx>6|^&rNYy2P=AF^@XD+1$B5UrAPM0c{jr>F=pX=5?sXYSbn&|1u_SO;){wMNeB1 zzl+8EQ)>$Rg5C1_dmz;;f>6Xv`p>zMKl;#R73aa?onA(p({GwR45H9l1+OqAO^=e> zl0M|Lix4JFL*D14;#`Vg;!vs%7bmfy#I99@4$A*USHvNmgT{>4J>*_FmqfXRJKQZL zR)6%bsKWCdVVDFUgB=wXz70H+G%B94moR2Mr`9wJ4OV>@l5*AlEBl$_eDOs9++Ju@V)Nxo-5lZ zVgK2aRA8p}iI2biJWb$kd|UcZ^~7^r71!=*NM!{41PygJB<^!`8xVKys4?nVep&q;VkFSCxgK6|G>Ui>!|FUau$eg>}2O20^>HAx1Gwv zt@i2eOI**Qt){cKG1H#wOO(1U=&Imco9|!dt23`R+%DF?ZT7U)z9xR=+K<+ZS&U!Z z>g-KX)w`>$K6^oy_-m%;YiR_jfWKzZ;@SyWRW5~&)dNVY_p*B{X9VBn5R2EbTd=@`@hbVP ze!JBvn=tl&4s9TM8<6d+tT7K|HZn>ZnSC?1!QfNcLrj&?X})C>xqftri3)zn;}CkH1SrbD z(09(acq{gsN{_-U-G}qbSIDx(h^o8~JLNbU*;+`*_vP_%4lb@oYC3w|JXn^7oF;Th zm*|Q3e9}^U@JBh^9(A^5(P`a6PR`U#9UYHXyMNl;rf=a%)*EOpG?#)B*nrPo~aa(U3)Ak7d)FYB;rCzPl-beJk zez5`Z<^TDRibM-cWZhtz~)AErI>r~G62i?{V2=)CP8H2dNlb(LwHaUq1)Nh_Hdo}v%!xTXl-EEkrBWr z{g5_Pf8NLpmU=@cH++`-ZQ8j4^lNxtY(h_n@r33n5t!jsSMOc#>X{hGUS7`!3|N$ zw61QpxA%jCR&NG-O?0j@eG)Ii6`ECt+%I%Li?7ELmP#izu-P#m^@Lnzk6wJ2ypAck z&&gGdZ!zsJTW~EUbsZId`sKKAa&-Bkjpka%$LPYtQYVWWe{OI6IKVLL>F*+$dB-PS z^F<_jdbVX^8!tTfl5r~S_Yl3X$vEnemb|Fv7FY=lpUzjTW~t{!fvnbMj;r&Qsj z`xxQ9j|3frL%}Cv1V3>tnKjP6I80&-iXNd=y}w4_#N6EU%W~h@4Hj5_xv=m~uQRW> zEDz;7g2iWEUPX;UC_3*py-QzndbavBs*I^9YqWiUUq-G;9GZtEQW2K6Q8gc7F^NIw zbAIB@?U>l!Pe50{vgg1g#_KmbpOlgk`f+C0VC>7*pUmMhi$C8F_2dK@Cj# zJ4>uqwT`kS4|W!F=)IiMqq4@-&%|i0|GJagIeb1cA9WE@pdmqVk>n)Su@Ia--3jic zF7$^9U$K04E?+a;oRuz^ofDJaYNk2{3e3W?2L+i-9=^NjlJ`C>v}Q+YA5%J#pjjNomGE!jow|+Xan&q*AurF zB?le1hjJ-N-diiD^S7C|r~b6Lsr6^q`)(QnM>{sx*XwJ+Gp>{Z+Vh*sE;Yf;5no8; zkTsLSCw3?DiY}e|Mc?dl&VIP^5>x((P|S(>xw#DYHG!f?RxR|GQ$z5~SMVZDy|S5y zV3};arNw;!V=g6$=L#fE^k?ue_?l{$&4=4|_`ua8(ZQO$VoTntokgyC`%TkkU7iju z)d;R;PvX<$iVMfh6RF*kbp1Hak2!>bn;&N`CtNRwM1`i73h2Udzyx#x62bJAQ5d03 z%k@gj)n=-WyVY*-{>>SS&joSn`PCGmK#hL7RAF!IzQZsx(dX(ZE_*EvZRcwBXf_KX zO@g-=jKfKf**}a#?P|>}5~Dw=K6t}p(k0ShnB_6-=G&C6z@@<3taOw;zP4a#WE^kG z!uf2i|Cz$Gut_vMw>cjHt%!=DO<9vv@OBzgcHH@ti87wEHt&vbSAzz6=y= z9JR8odi5ozMS%H04nO~I9x_E@)O4-SH*-s(svTf&bW-pjkSTT==Qx5BuxNq>;wfz$ zz&%`2FRDkc!>5jPTjNqDL%XZ=j`LgrZ_H^$p> zF{{gS#b!Mo)<2pO?(Jvh_hp>pRqBAc)agWa#a<={oH3__m?DjJ_ zn?P7_Z6n(19}FnIJqe4I1Rnq|ILnV?rITP@R9X}WhmY<&w9$_%2Y&N-$y?6K>J?KS)Tnnsqc@@|N(*yr_Z9;7TAXo0_%$=QM-Vi3s7-?ge`CbB7>o-0TKrK!zVQ3n@Oy;M~8 zDM8E7pO_pF$O-x>y0zDZupr}}>r;7MrIv2Lx;*>qShdVi+8EO?MX!~(pJAxBnhYEG zNT8PR#q>N9>%~gBD%vHi%loCsp!ltXKYV_Yhj7&`@@W;)3g0sq2tj# zZlA+}I_|}!i}yZjrzZZi&(6w%8U?=Ee*N8_&@MC!W@|q-+B;5TH2msw=s{g=`r|^G z-h2h6r9}vVlglVLsM)rCbdNcnqyJ=^NzL3PaNd%Uk6aK3=k}d`zAgc4RT-oElyue6 zBs!#ZH`oKkW2f+9+5=15<8)?Fz;nMz*EU~we2|y?b<4Oa{%^cBr-PA-a;xlDMMO}f z1g3sa?NMmykfoGalf^LJ4=)|7i3)MT!7a%&>xvJPOIc%(j9P;i~1Cx~EXS ziQx7yw4sG6bCk->X~M{WoXC8zIA+mDS}^MhB)YurINd+5Y;QYiO0_&Mdpcm`vdvfD zAEEa1fEx4&bjVj2$$$wG&eLHZW!FkeS@?n$9uEhQ@ z6aH3zf}4f8k&3a+n)Xsj8wQnvS`+qt9^>0Kytj$4peTO}3(q3)uTf@z1&)$g)Q zi|K8Yr7W|!*%Y*+tySDG4T|+(O!3TC0c`);^>I$SDvnBw<6zI8uK90oE$aE{Ffq?A zO3Gt!c=DVLpbfoNdQr&D4OR0QmEz#vcBxq~q=rEB0{@t=G@bUBUY*R}TA!usUVt?U zmz$R*{n8gom{Y4!+Q|tyo#!%->HH`B7tZbvXEv8)!G0$(Fn!+f`*o-uE>>~=jX?oh z;vt<1L8Jux1eHSmkL}Zt+!6vi(gRww7(<(hS8ET?VoL0%g{_3{I}6u8Iu>qtx_SR0 zZ30&M*Cw0Kk0SFe$8zb`y0@cDKcpxv&M6dSTa9l7lQGv0Dyf2Hp6|N6-Q>O=L28PC zyg_Bmfq(XRHlv2&R#eW8RvwzHj}+NB=3i*ebwd(px#~I#OkOuEXRABNZ*nVvNxoD1 z!Db1<`l?S$=&0CudFwZGzgb0RJaPrgOTOPSM!Rd|HWo}e?;1H=A?6SM_%+*&d7Ygp z!K$m4a^Ktr`r0a1?lJ?}UwNPly{we~DsnkD2p-nK4Q?;_ z=XdO92P!N)4Ddn(0Z2&fA81mMY%@_pG$@LQ9+2PVgZ|JYFBM?{wyWP=(dWX#f8KR)b8#~Ep$No+ z-+*PQ{5O|A5pFQgUvnP%dA6Z2=rO!{`UQiX0m^^D=wV$dB-P$5ASwX@Ajg=`()wm8uQovdp*#@pFbHQ2B z;>%#-Y>{nyyHo{%qQi8w55FDgH+NKkTG-r$!^9M-a{RexpO~O1RgjRKUX|{5-jL2a zc~42b&WNovj<*k(w0MgSidvvWO{ymg3&HIhEFgDA5X=qG=z6yuwO$i0HrpTUG7?Vu zygWa|fT9iZwGDM8> z1@C*@X86;IL+ItihSbxi&_N;OvnW4Y;2)fy(V5dX^v3DoZdbHjYe*o98! z`G48{lw-1Mm1Wr5eeH(rCJPWgPqI#buNx)J27=O>cB}+iSL9}*R8uTX%#P3z_fWk- z<&!^Va9mC8Zwy9gho#Trav!Mdt*q?wE&b_B4PD^H{5a{+N@WzuJN(p)2haFN^7Q&| z;`LRH7x2toRHh2X397IPp1lMsDiuAT3&XtbsIU07Gp;`kO6y`aur@JhNoPkUQ}2=S z9)G}^cN&x{y}sg$TfDl!OZ36D>_17D?jb<`n$ruqtn&LhySi+&*}b__x>^k?c}hm6 za#-$RKgTk-bm;aI2SFwMjHa-+gv%nK=ruQ8@;nUUn=f%$ufrVz}*U zp?a0q>?H~4EXo*9Lg)t%zL}H9z%EBPcu9K_!zSg&;)*wwEvRW~*8hZDW&gE@bKEV5 zvq*ITudnu~Cy`2+NK&L|si%!R7>VTqr7$!b6txO};c8DZ-2pcJT)5dp>o z{^jus-*YAkNdr%(Z2mus(EqDyZ56T$!UpS-vg3I_zZ^Ea7H;*%KYzbWImkzT zJ%#rAbh{v^@iMw4%+s#;`UuPWoL;93O;2}40v+PJdvIY9I1WVpfd}jE|fKle~?+{Rr^VdhU_VH%&0t&mv&b z`)rX$7bkMfM*E_D(F0bx(B!<@cG+?*R=Pi_voXS$JiqnzA!n5>sH26*L*oVYI$u0e z9GNcG5ewW#BUbj5)=n ziqhaN-9{%T*m$o+;iAXx;Em_z>?J4T5TVLNn6=$VVQ0?Q_e>r`64gFt`MPXJDUseV zcV4a6Q(24KrH1!@u8X+8ZQHdT_;#PJ*^$mBVPX;ln?u;ZerR9g+@BW+@Xq06$m{gl zk1m5waI_ciU-tKKnZ98Uj*nr) z$LHs7cr|4}tmpOHUf$>p%jvYY!&|vQ(F*J6ab@9<&I;S{$&C-S~8LvA#T4%6}ezcFJnqSqe z3j6eeRH|$Dvpv1VUWyJ8LyS*lk_LzQ!!mBeb-Xq$UV5)lx6wY6r)w!$d{2sPYf|Yr zcZ)!#`)pyzYxC&SRkvF_l+&L&ZFO%0;HO8J3$MXw;3!&a3BT; zl%KBiB^g`-jNJ$#2lPFHBMwtsbxhp5mcw0rQj2l^1&`z1KBt1@lw16X1cp4TH4)t4 zh36Apu?v%N?YW)6{9~IV;7HZ>ZvU_+(#&D1oJU+&T$I<3s^aPk<9y`3^^xv{ckiGz zJpB9?*Y=}F+g}>n)Eln?rv}am_^G0qt8tfjM3(LwMTPb%WXRuJbm_O z%759%ZsSt`b1}1O^Id|NP)Hz;BXg=&XP5jpPzL?@zk@O(mer1)`RjMt<1iVSeOSl5 zi&v1hAsgCZ|5f3s8Fq z^&a}bs`{sI1B5;3C0;=5c;aRoUY%)io(|JyxL=>g)^~+42}6;Krq4MaDW`{?!cWwz~L-|Jhk%9cyM4ThG*rQQVQ5P-JCrvS7?g&&|Tx07Lp zQZ1hikBs&6F2oH-4lOPT{AiFohYK5_M{H52pBNc`Qm;#U}{vE(>*wU z8l4@=%!zp(Sn&sOQyl_9P>~Q8Qi>fGp&?DSQG+($zii<|D#D!Cn$vw^M!Dz2v2oAL zEGyK&Rk43OrF1KkbEAXk-0!JyG!71q&8yjGGiwU5SX}Ei*xZ(0US8(r=HSuGOFREn zG6KYxSg$E-isOiHPL{dg`A2i-j;Y5QXWx=$;!p4Oh{Uq*eOLN&`eRxSl+iV~NbB~s z`u|cwgEdjSjXmVjLlyi`S$L+%M3|NoV-UKda3M83@U&#>a2v}I9HazwfHZvaK&h4Z zAK(Q1@d!X0NfdhAq43O57W)60wBX1ISk`G)P~H<6p|v=~1B8A^tI*FT5Gk!e#{hpZ i?C!;X17FalZ_$RSU;NM+N6-ZyfJlhQ3YWam^ZS3gNd?vb literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-subscriptionmanager.png.svn-base b/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-api-subscriptionmanager.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..0ccc707480b24336c2d9b62b26328caad2857030 GIT binary patch literal 5550 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-package.png.svn-base b/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-package.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..470f9fef9f7d1fb9825e293d63c47b288e415538 GIT binary patch literal 18180 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-mailboxmanager.png.svn-base b/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-mailboxmanager.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..dc5569f654d2784af7e0509aa49e9a64300da128 GIT binary patch literal 66754 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

;_u~Veu8-ls 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

V)^Sn2-P#`&6{SH5X=&+}66ta%=?>{eN>ULJ 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 Y0@!dqhC1p~b)xqv$S6yfNSXxxKgjI|8~^|S literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-messagemanager.png.svn-base b/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-messagemanager.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..45fe8d011c73a847f786337efcd08f0062b82b5f GIT binary patch literal 45095 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 l5)Jl~o}r5@{0;mE z;h-QU2r3)K-vhpZG58`T40-|o`PrNw2Rs9BE3WPU0wJJ-|3ZLL(r|z$VI3u;MPL`8 zaS-A08i-UIKp-=G}hb^9}`|3ljl*F`Toq-$8P;W3s|I%R6nj9fT@;qpj zp?Yi7Y&51o?`>mOGr67BlIYZA@aMbOvIdd;loXVNq7Y?ly{QJsq`44{R1XB!5)y5Z z8X?Z&EI8uO^)9k^^oG@%U%s!pcDrj{hFh-0JUovfUv7ZD`a+5Luaw`4U94WZv^d;5 zwTNZrkx4*82}BQpMC3W(;HpIa-og75TMV$_1J9zoeifzSg1=$IetLQWUgAH);ZF>g z!VBlG#97|zEw%8R>GS)Ah@KvvXz_OcQ)u?jm z?rpnEsfgWrx1h^^JGkug(l1~77%+G6@)$K40IU0CBx(E9$nSZb`X#j~XD;O6)>D_F zU*!qD_{tH%+3l#S+Y7zW*U%zU9(ZHuA;Q1l<9=Jj$N#+E+0Gofa(g}i+YZbA;=cRz zGI`Yqihf?Oc-hdFFOz#XY{_i$xSF%EURiv?FkLsZ(S6UXWH;GGb1l(~=VkvXvLT5o z)0PipE<131Zt@=8BJq>ma4tT1n*4gf_xDoVjX_GVrPY!Cd)*bbe4jTEF$JQjSv^=7 z%m@XdBe*r6PrJ+cUS6ChE#LU2W-h+D)dt-jR@2*f+arU#pQcnnr+eum5csK$sd0*C z$Bvxnh7F{(RLlF>+zJY?pb*qw`CBga_5tc^bmgY(p{-%Ss}v)JEt8zHQ`SS=LFWyb{FO68QXw-W@ z9bX-|Y_;opJL^8*ET`)(*Vo%WT+@F+O^*1J^06p1JLS={tdr=yle{#>)O%D!C&wu` zw2wu7n0?!Uw?dNj{K>5pAkfK7zYvcR39pJn#<;&gO4evAbZ(`CB2-1{)!8du(xOKUG@p^^P-Q^QnpwI+S5X2JGSWEZbJ=P zu~OOF_wB=qoRun1eGxL+q1|!%6zvS>q*IZ_MsnheF#w? z+KJVp>~#h~YX5#&W%l6yvm@*HSob({X@gAYX=1bF@ZiHluJyD!x_#1eeOu^mk33&G zvsH`zB?Y8hFh=XTdU)Q6{*q_XgY?rkyI@f;zTwC!O77wMsE(F?FF$hR#+ZzXG!W15 z%{hxTMMkh1zG!YZSvvA)Jy-1L5tL2iT+T6EE>moLOm2AmY2F4iC&}msY(dME5moe9 z?;^9G-h#1Au`Hc-v?XRM_2gp(Mb^3mj$+jL83YLp1$KDSrzBzOhjEj~?D$N95Mcv@ z`JunlS*_H*eRp)6#X{=tb_0puCsSoc$7DCmHHj>Imi>+11V4~f%sxp%(Dl;8A zsh6W#qNm?JsC<9!?w(-n#~<@fc+Vj8EPCVhj-4r{tepO9{S48r!e0}3AUUE!Mhzyqa<)s>;b{@O5quf^zAtPvV(;t2jk zvcx36PYx}OR{=yIG$WoJ;tWFb;HJ|2+`Q-l@B5H^*fX-OE}X27Z|nasQXjoo_r zerrhfc)ec{9ld#EJeFa?=QV#3Kg^436WTwsVas#IC zUk#Q<5wY)fL7<8woe=n|T$f|o`F*kl-o>CEx6%f!FyNL9X3jqqpqc+Tn8}TP^Q7)O z4BVN@YjrX(2fjO;eYDsyzy3Y5kST@?$9X7*%D;?zk|S%?ZWKJ}W?Tm^u=_ZjTx0JH zYmGlEB=X(6-c2NU-0&VDI;)mrVNIl977fuvpt9N7Q|L%;jtUO?vRYS={Us_yV zddZKNfM8W<@^fkQ{?Qrh_Hj06Vwk$-sPvyB!ejr}RX~D5{Q}z0)+F|le*U8`I|*gJ zw9K#i3CVY*w|%2LW(22Alx}v)b|%5Z;=iJmHhmr*9>}SfAAMdrUXGu9bgX?EFK&b4 zdPFzhqQ7_@Z&t<(Z(wsp>2f?#zC5+#oqXZCk@9PWf*n2$Wc&Tb^f$3_pe?DbP$ySj zW(_~dV$wt^9@C5xIl2V}2SeZFNwF2WGI>GP3S+C}b*i}SXUk$={D%}Zb}4D}#G^%~ME8HKV-hCILWbogZKB^ezP>|1*Ecm(>xEnEhTPfLAmBLC{H1`I z)hcb~bCRFe|4|-taE^aMJ zbu>jVV!Lf}RHT;lb@j)dpf5>Rj05r*(T#?{WO@&tOrcEhDRCo%p0JgAIl(Q7_DmQ! zwmB--N~QfC*#5+L5R$19y)kLe&(AZu{6DmL&VACJ&)Q6{2{~?OU+#A3H`8=q4DIgM zr|?>Dr_RTZ_+RLKEI;`PKhWWo<|ZRf%H~4q-AdN1q1$&Ni<^>`ZJ2kaJ(AljuWb5} zP`M|`#WC)JwpA{tV6MvbIXb4xweU3P3lylNL+b6nNh!bd z>u6likeQ{(H|UpC?6?x_UY2a^B&Vb1$ae%B2stbofopGc(Vq~&Tu#TPK(;zP?~$(D zFY(`g+p~D#y7@lB_h2xRk&Y;a{QQuZ>b-MQzF^~vl(y}!O=$iO^hcPYtEk$qWzo{t z+QJD2_Px1W-;I?^z`44w^<3sqok!b*NY`go67KhzK{Z+h5){)=da}XkJpodRD4?_@ zL27Q!pHx2;gL&UO6DBG{0~2${EC=_#|4!*c2<^BqmYx(X1YJyM)!&C&(eDE^1)=&K zTv|VJ;s7t@Z)PL7{}bY8>C}6Co*tJPJeal;TF{$4f#8(42s>}skX$OkS0j-k&M`Z7 zL~30i9K&o9Rs5>#!Y^R@nYzUxo%dn*p6!B;AV&63wcdNrGi`4PYCR(iGSv{(mk z>C;mw$o3}RbmSQ$)2g>Sr{p`F0E*EOaqGgx>7oE|iis^uaFos(d0jnGbHmzP0E1o# z#fq#8Z8yn3hmXiH>A4bqlzTUUOlsy0X+;R-w#y@R^3I<)!Dz{_9?stqvyRuZ7&dMj z^p4w{`j)`6Jr|SAwdA19w@>jTCLamS%Z<~mEtmItiVpc zmrKy~H|$aYoC36-wQ8teubV6Y?5+*@h?h0vO zkd!ZsuZ~246C(*>;x}+=<2-n6$0O$Z|1%TM`uH8S$%Gu!%pSes?ES$08mArhy> z7j+bo=ZW(kDG&87bFwETQ!=#JPmLecJ-EQ%1SX~;ok5XwE-PD0RmIuC-QZwl>3cK? zJmWF}C&^!_c22SH-MRf_K_H)Ka?(sBal)GN^LSYbEIs4k6Kt!u{Y+URj=Snmfa={B z<1j-_4jjB4h`$60OtHBlJc2wNkW}PIq!P_EMx!~+X)#B#aZr9@?Rw`xj^yqvcmvZA?*@;#jYtjTVFSMbvVUz3B-U86^}{!2^N zC+wMBl?h?s9%W8~(Qk8{Kmk`!nN0<}ONS2q!G6!%wj>406|ITMqU17yMCKm_zIC69 z!)gnQw=I{hCBc0+E9){C=G@0M*@5Qq0j5~AQ~UIeq^`$}{cZmW&rR(cdP28n`sdw7 z@(@!G^1@%|HY<>SXE{~|`bU%jSEKT$ikrH0iQUKYQhwMX@txa|^qZJC8gFPg0hOUP z-Y6f(gX`sVZV>1*wNrtKgWuyV&v)^Gwu7ViKV3kgp z+49^OUyQ))9}cjwSR1!baXr^U@wED;lze?LGqrdV$WFkM({$SLj*Sdlj-W|J$E0S! zb*vVOV-fG&gxkR@27a3q8?+FMmOS5vuglx69QZQe@%!X)y-hVS@rOm-iQ9#+`h=C; ze)DbQ)^BpMl>dF0sHdKU9!!k;@oih1+Gm}n-gjV z3aL^D>%MGNXZ^CujJ6AG;I^H^F&WXI7&>rh2`!;UUS2{EUacoPod`BofUw*tFRnka zM4;qvXNAqzzh8$7cN97mBxAzg2p)`-G9Y3sClH_>^eAr{JTz^Ho#4OU#jVbCJ4s0V zfKP$I&;&vSw`J!fH3kAzgm0lR&p>z6QYtL$bv_sN}#aXcBHOVU%3Rz^G;v zzH34C#)cdP3dyEosNpXVSD7CR8D3i6T9%VtQQu@I$?4HIt2trc5&gEN|IJGvRZhSE zGDJ#de(z+}(>b>NW?PF1Wyq{66Ax+yN&|>(hytnNL)yjR)0;O+H9KuRx&lq zvPLo8tlUSZAd3sLV&GuxE|sF!{xC-ItIH6fLd`Lqdb3vk$c*ItZEYI>NTESTJroc4 zmf25#4_;2G&A_AN@hWIb*)B7kayzwboB>MfA?J|hN2i=$lf%^aAEkHDmaAeE0}w-( zzdD9|cS=^23-Rh~a)z2~2zT2g(?ljyAkgIavldB#Lv9s?EI+6xl#Pwe{TX~3 zT5tLX+FX_oBz4@6Enr)2q`b_`$UH;N!y!Nn(ifppENj1bq!~f0P55{9UrSfva)Y-E zHtrn=qWeE z98ALp)--kOj}o!*v|*6G|2VZe2#haf12f*!@QmYqgPA`r-Hx8~{O&_Bwq1!aHxB1q z?jIf?J~wt3uLajuS}Vf&uYz5WbVnWHFX`ItHbWh5@X7&RJcP~JlKAGct(F6oq}@)B zzZ#^(R<9BZyLJB&hPjykm!KMbO5vSKgN<@Kdi^0{xbx`QU?E>19{aGD&qcc4EuVvS zk7ImJ5~hHynRD(%g9*NRqle&o%kh*%QpeCeg7V(|JMik1kJP!?BM+$sf+mA0xSVzO zNtYgh715zpAYkqR@+kQA7JOu10U7nTB-Zh`Igcc|CiF7gt#zSKtfO4`Bq4;)Le2dA zHrsm=Vbe+at*W!wuUgIiaZ#eKUfXc)bc>kd-3|K{DIwqpP*}iGA0boEvirk*|9OX+WAV&O4Se2|$HMG2@)^@K@0u>ay<2t zxPqvp>qHxKKLS56xj&Ffyr*h|dSAa6%J42D%m^aZdjuzuK_o)l3mhHh4P$6!r+N{8 z3jgOcWAv={<~~Z)GU(3VH#Cb{_BG8s=HP89Z&z^z&E5548U80h9f%dFa^} zoxZ|?!r8Z;FCVkNwVKhx-{9nNGE4yE<`8n3-e+5Y{3eoqQIo7oEO(`L$7uQf3-%k4#9}{BWWo6FXWW+pfd(R}(VKD9sVO*t2^9T&Ap71z%+oyuiLqT>Z z`cse_+QC)Hhl`B8-2nX@f&}^tI7X@yn(SmV$Wf_TWUC8$Q@f|tuE}O^UO_U37lC9~ zNq2$Z-O0yPJ6umb;Ps)AVMc9al`0s#dO%d0PDxz!57bKet-b?&HgwGmp!hLRiEu9$ zz(Y$+F^y;Prns*Owk{oh8{rnm9=HnX@q-I#Y_RrXIo1Jy>&cwW=0KM%W^F?aaUSh_ z9ZpdbAQyw)d7n6+a-zx@AChl$-0B0PIHyzjPRPhNy5?pS;D|@u`*Xt5CGaY=VJ>3G@27wsj)RP-#79KK~L}Lqbl(sW+iYVwliE zi(^peMw8{6GJWaWsB#=_3DBdB`%uC+iy*m14u`Al&^kg zI@rYPSw&inNq#ix&uYJqey$kq+KS?~XJpr~M6l{KTcW?@BvjtejrCi0nN6Lad z^Dtm_P(p|&ljz41f%xp>^Kwv5v_RHR`7~+UY|HnO=p3dpyLz7(jt) z^ONvfX5~6U(~*{ml@(;rJgW{Od&!VXsvru~i8e6f;p(ioBEC~ht6FR~699J~ScQSF zl1WR!^jgfpCiu`KHQgFP%#BEjJgPi1zL-!!!06b8zN07tj}`d(dTq%x`&Bfotf(D^ zNgmE(zRcUV=9iiD!1-(M3^00F#;;YhW@;|~Nn$*E)I8u;Cq-sA5nn&*AI*22{@w?g zc&0T^B7Y(zp(*%Y0l(ysfr85i{*df^m~SAU0k!%c)^+XlPq8SUM1u_%%_KZxz)GG1 z^MO)bYxwoel^Es&hf`vt6nH2otnNkRtz|}0c#1}e+L2sx z;H#uxk{B8U0UyswiB0YHHS)80NI-Xm-o-{f7krAE9Yu!8zLbRu8o$oJ6j>=f$j~mb zL9|QSMtX1jrq)o*~*O>ZN;rXA-GJ-a<=uPgPT3RwUHg~`UJ1RzS6P7l?!Is z`R?8TT!yO-Cy0{&07_Yqz5vuJJrtnSAtEKj!=1H)4Rbq_n+u}!0zzQ5#fs~bpJCvp zU!C&oJ-7B5~zo~%+vHOj)Hxq11tz64w}r@^>nix+1To}pJwWxUUR1$TqrVY^sh)7_ELTa^8KXD ztIrtGg9o0I{n@THrmPYd!KeNl`>}B|7sewyLt55m zsh{7veUWLc=gd~=ynzUz#U1G5g><-Y0WZ$hW^=g`PNUnA|H!PE=96lm{fctMN)94- zFz9wzi1mCuCP&p~h4Xax_zkyYao>UJ?iGD31Me zIDL|wVm*58SC!mQS|*sePb+V_0V&K^O0PCwk)_LT{&qP^33y^XxZ z(kZ6k5Vhw{=SOm=&gL$VVl8OK*!R+9tC5n)1$$*!fb7D}u&RGc zQv-=a|65-@+@k(VU{Qo~0D%P@P9&SIp(PKMjErBox*H63Z^rMq`r&J8P8oO`DG43w z6uRGsF+M+i)6Y1xsXg;>T4(gk}?Y6IyiR3Ut6>Rw#%Vh7kWs zfAGG-*vdDQ5!CwiHHbCvwKl)Urh+xt(oO=;j4I`!j48^Y39HiK+0#Of+j)FYSJh5v z@oL4X{N4;tt*O&zSBiDIuadb%n+-X2s#A^l~S5I0q)d%kvm#~En2|H z^HZaxGWI_82IVp+zh^w)(jnpJ<0lv30EZ)})Jbvy2QBt;VO z#O4mfElL*OFjuw|dZ>1P|2!q*UEcz{(Wuw)?>QB~G2#T<`a zaa6YjpIH9_xmYqPO$=xdfW`nU%)1`rDY92j;EmXdK_<$Td^e#m+5A3Xg~R;xwdSA1 z>V4t#xK4k5_6CiXotgj9U$c5EaJsBq>45;)#UVC){~>=)FP~EN5mi(|+}q(d;Ey5h z{gDgvP48m$Usgaq3;VM6*GNr#{k}i4d83!@{J{56Z-N z5v6+5&+e^Z70-sf>)gn|dx?`=!q>c9oLs+bY>}@8;OTmi%VSLP{(iY>@z{0oe&LEl z^S7iM&hQdj{!7LVB~Zs9;j@hVT-I?s#g_s059Hf`f0%ox<+@P}26mznPdL3VLOaB+ z35u3({zdU1*mwp71Q4fUycYFr>Eu7a9LP+;pEK@tpa`J=hrqF;yr$1&*#4Gs{|jy+ zG64HeHsSX5f9CYdkIi;szx*w5!ho#JF)yEjy>5vVY)1C9#DHl;cOyUxHJ1b?G-UIj z8mwP{KAE%aNapUX)m{1C3Ly^w1vTtkzZgmWxo^uFS{bwKtQCQ*(2)JDmpG$S^67-v z6a5`Mm~ygok8HlWzDn34%DjC>Zh3kH9s)7Hc2!6p3Q#=69|zM#Hf)|m?7yM!Y{!>q zJuh}X7)iiE;3h@=@*Re=$dfPkNT!g83X3xT?~N&`eu%6IVxyWdt~1dTf2FIBwPv_u zQG2NNZuqHv&z4$5ZDdQumnVC>*MOn}&;8Z!Fp#6n^=hT5xptr7>3ZLNzFpncMQ*0Y z&U?9@k9uBcg(F&ge0&a=H@$fR0@T!&%ylUqt4E`zB?2++NfWq*upGKO(W0xGLpqxu zjFryykULV#TehJQ+k`2>`D#J1Bqx=htMd)?M7jM-5X#RJou_zQL-mjOZ?EDSotryH z8_fxu`gN&Ax-D&6L;w@@k2Zp$FPQ=J9ZqJP3%25Wd|7g&cy>-hL6l@vIRo4Fuery2 zg{DcYAZs|CRfNSBUO}~J@^^Mz4(&7BKNmD2C;~adVXv)*F!iw^zS&&?YNPL@-mV+lrMk-iI05hzPZtki z7OT}GE%9Hx&n_A>J*&>kt6OI;m(l6U6o($~Qxj-DA9!+=sXtUY$GDAIU2`G>^ zS9dyLp>7%mCVD{z)o4)nX-Go>Zza+g^hcW54Bp5F7-@2-xsb*Bi{gaF%%zrE`=|S* zgPD{5pw`F3mdBH3?$#y;hpp=)-5(#A@a&QybAjNjk0^#tj*3Sv9I^ z+zJuXaCrzT1jw~jg>W6*z}(WCUk?~Ll7SO0&hVhsFZm)8B#C%ggTck>;lx>(a%>+Vj0qj~l)Qrw1T88bEXYvs`G+W$`{WXr6|s zo2TDl*Oj4kj^_u1xI|fFAp15W5Ay99V~CSkX=isF`QdP#?VbpPFA>qk{E{O!ugI-jqa7E*S` zzgaE|_gKSZf?1pD`S6C|8H9O1x1}eEKl6dsO<@Yr%s)5jUA;fm`=wkVZ#W>ja9Cjk zUxO&1-_-2>FzhGhLNr_daKL?o@9KCnrRk!}V_!p~KeJtX=ix42p`IkvvOw)4uL6&< zH56F)W}%pO!5{+#x$i8gC4of7jc|5xX=ykj;sSW$cRIA5CayoWt3ImzGRGI}zoP8P z8kDaWKx<>>_l)I2OS4JOu$Z~?$BA6AI;-NjN~@h5)+oz-LNCXiu@#T`JeK7ZuE?s}J;_XSnNzzlx zy8ln*iOF8>(uqP2YgdJFn+YhJ8BPC$+e-QZ5u(UJHZfh((vduVT?o zn?vZ9(nE$oo9)JW<*qBBoQ|G$#&YFiJG81QrIrPpz_AKy{$ViHOMA!dAfU~N78X*) z`*S<>Y@#yu3Vw@+PpWi6ec*J0v4i)?ws|7SLeKiu3D*5cn)z=+*rzmffTbD)~4>!B&M&F7WzT3^`BlbSdg#Pji6M; zJkRG*70S8eH9DSQi>WOI0L^1h;|xMJcz(xjEJz3IOk?^d0(F9*B>O0K5=BfF8hjaw z;+QaGW7L}|em3P1;xpsc%TQ~ISBR#2--*FF-?~mZBTK-eGC=5}= z-A@__J%_v=`F4qTMgKbnEf4uj)SqbA!i?pw~!5=n?$uDezEyCsrtF1 zWsEKJM}(Z?4H7PZlms~*xBad2m@pHqT@lwNUYsg*T9l_vl{ugon;{+U%&Lv(cEFnZ z!(slCnBTm1J*2zT_|#R;oLJL3`74S~=h<;<*-O@=qk2WFiW~^cxu?)|QrF(2>v#4Q z)?o1x8@~;WVHViMGV)<#jFCvsh_!h8-Hf5Z0P$hJXiJB8L|lQwINhQ!!Ky$>65UZu zvOn%jS;_=e`YqBc(npjHqcQ?utrj>IyZ%M>Rc!~i`@>ec2IEPfbCa5I zsCCVg7gSV#j)FARFV58%Pt>a(SeyE@HnJWy_U)1lo)eKmfXCNFQHY%uQ{ay=kiXEke*m z$sQX;1oofis2w)D1Ub9n;4^g^0BUF>gY2HvPA*Yg<0i;*z!~-b-zNlC1!#e;+beSdwTw~gtv@kGa*<2 z7$tHdhXe=L-){we8YEK?sD7a%ArMc*MxM8GlB6?~=3J5GK<+$;Lqv%&pkl|9qz z{P{_D<3DR-Dc|eQR>-N8E1^J3!Mr%(-At6@s3^cLUKA=Zuzp{J z+WaAIhHEn-tnQW5WsEi1S)orrH!$WGQXG2z`NJ=$?)1GjhbA+W!TE8gce$|diKObUIWi{x!C>PxWtcpCnr8n zSBCev^p8_MOH^KGJz^QnXLnDnFSi@?1?l8u(IGiy4&Ur;@(bBm5GDAhpyB6C#Bp_T zC!VJg4-lpZB4k3+ssrUn@azbp9w;^|(MhxV>U07=f|gfUQxlUx`O)iTvHnxI$sW{CxqUiq!9L?E&pW*^146Sg2rty`3> z_Zw)*9}DnysRh}Vn=OPEH9O_8t7lCqNxQv=zGV~o^E^%rGBD2^Q?rWPc>KR=>+x~% zU6YkXJF7b}kd0t$1knBa6LZGW^*1$ILvk;eJLQyG-83z+TC)vR9C1jor|_r2BRoVL zQnRI_LEA5t(MuyuTpkU5Lp2WBH_^iC?ue!oU6H<230N9pquW3b`z7X!_k?j930$_! z`D5JBn6J20-@c9$p}*xbzQPRKT8vXBr+C61^6Ck925MU|{;+P-8k+Ku0sF zTKVUfvI?Y8zX^O+{5?BZ`vFDQ2eKyJ*CM-n3uwkq|8C>oJ9$BOJ2+lANvo|_w0LRz zbn?~4Of~6$uyG?Oxm6{7^Z{)}p^66q->v{8CYaT^#FV>0%i=V47!S zd5wyJ!W%hg$n?5mEF42yIp|6O1E?c_Q5iA&4g4Y&zseG^`@NxLO31b0)6%E+GfRY_H>Y~rmjUPF7MY#PwQCm^@)XCDIR08pzxwoDh#f&sY!0rQZ%uk_8);QcB`bxCr6E2PQL?D(l;sOV2 z!hjM1X4=G^91cr=mzUGL+;-mhoF44}4~aYuj$%cmf;Vp^hQm@D`(yPK+aD_pBQ(p z#$*Jh<+P~dyKf6hF12eyYYot!|MJe^rL8=3vG#yGcv<;kRry(gipeD4f)>mzEjyk{ z2}2!C`27Ph$s@iI%n}8fpeYEl1$H=hsxC_X;tJhr1s3wV!D&kd0{BjDP~>%_h^Ty{ zgLSqHY{NSi&A!&)hJYfZSTl{Ai+0v@0}yI6JLQREEFA_EO4N8nn;T)qY=XPp@K;F0 zh;|NNy2@hPW0w2Jb{8KL8DO$wh77Jt>VpjJ4R?deTWHg{^@Sen!~DQw_>UE3Ea9Ex zt-sZpp0f@JXb-PinMUM6S2Eo6ribP?d!6q*?wVvOJ}5VfFa(&PU9fnDNBC48m&ZbA8i*wfQ=vahUa;#bm6zk@sdWpi^bN2=FPDe zfY5Hz|3w76TKM&m7|g@{uq8&v`#<5{)8zKadbx);hQSQD=9 zB$`Q6qOt$(e=B-Ib$|W@8hmYmKM&RO&@isOdgX>plusCl*8n&=F$UWq%H1zhanHeO zEkBOSMFCq@s_$&@9gK~1BTBsPai}1RP5t|sr3wr1;aUXV4l+&d*XWNZDw%;lB&`FZ z_jDE`AdJHloCe$s!jA*Ltq$~|_fqH61H&uw`~NI|lD%KcpN`bgj5!60erkvh4u5mW zW_3jL+VQHpO|Bq5S{wv9eJTPX*v<)rx%LvS*h9Enigt!x40jUnI!D?mTdZl&{urd! zu=*6J23!u+#v-I&O%uu@ovl?G z0|DE|GSfaU8%gQT1c&s-!#bQvq!0;y2;I=0k_Sq6c>W67i$q-c^#6wML1{+P429`w za>%{O3PNyD3dH#UACo|s=EC+pei+GVIX z>Q)Q-m`>S!D5K`0JB)h`9#riq(2rn#NG{vFjN)tPJ~UnS*xFK{&m;yjrtxBM2vuSL z1$)6BhK>73fOuZ6gpOWrl>@O0CIH)evNp-s>qOAS85Q}q)|G3G0r)&Q-oBBzR!5E9 z_6FF!MaX&mr3_`tggWJ1SS*wS{o6Df?uc-1k=lef6QD6#G1zyO(Fh!gO5h9EEPu(^ z<}l&TE~k!;rr;0+Ib(jBHv56|(6U+#&fsTvWU=9Y!(EKw%aE>(X5Bo~PK=Y2IUJ)Z zKuw&S-phw%A3Ya##mTX#WaAE9HUU%6EBcb8(skTvFeJ3w!;`+4Y*cVo2KseO#P+d>9H7tPffNhwZyX5;X5-fUHUz%D`*hts=?_Pfo?rin z&hOwL*jsC>a5e@I0hBn~oR5w`#*g)xtQH{g(gFy5zGFDOF(Q3VuKQRB&x&GoC@oip#Aah{;{|x zR!E&HH_t!-uB3t~k(cV_pj|&>%^NM=2chZpvsRcWf)%9MBsiv5u0M~pdtt7VQRP3C zyZsintag>NUIai$I=dR;{cSq4>OWxMX;+^( z)FQL~)gzIGv9ZIc80uQYId(>Z!K(Vp_1U|tcaBRik*xn~(%usM!O4Ihz%Yqa;k;L& zj-SE43<5waBry4Bkg|=Q{4ww&z>D?%)P*ioQ9+d$CJ#{agsuPl$s*)tpR_qQ$(9b3 zdf6k+r(hrSp-m?M3Am`J`xpo+*pam89g`9IZA}%lg(9htj3^Np`L~kRK#wh-`~J_I z%Xe*gteuGE^ivd1&dAT13TqfO_P-Dj;9rl6 z+Oix?XqpY!55Uu;o2~@J3Y2Se+5o~3;P*B6V|*^|6~_OeCpzq?eE*>(pj+_%n7npP zt{fJ0gj*hrhqass<|Z_6m)jpwdHS=%mZA1U*0V9$-X$cj zok~8cBM9dx!i=pbo7>X#+fmAO#1Og!m7p#Y`G2KH$cwLkJ7)`o4>p;BY+yp&-_r44 z;6$+BpOk@pSn+|g3ECciV(JB3xc+(-bg5{n+pbIHtTNHeFUiI4JVOkCd%t=2E!c(v zUqyJ~*YVJr0`%?o_2z2e!N0a?G$Fw|3aYxm7VfJnfwQhIVA7|6m8ML5`VpN-p(Ug2 zb^pTa?rGxQj*8A$Z>zoD*oZhlxn60&WTn-V%24or1!yNbwk(ffbioc-@F!LS{~8&d z{U<|5V>^k11!l7Yk2#U4tm6_bF7~?JgHHo+-bdQyJJ625?o)t|1?;BLG%qMJ#{AVD z)*<~z(vnWS(8N3V$G4wiq(G|bm3t@faf`U6Ae zUA!W>9H5R8t``+4u>FKnC^~vL_w%PLq`-6vut@>jap+{YA$q*;({9u;*wto5*4d&@ z_+ZG!vGM_cCfB54M}&oeO|0+9iKHYM1SoQoumS}mz~lyYhTI0jdHHbxz_x`~%FSet zB8Dvq3jQBM*Sb2`Hp@}okMD~LWKPXAS=!+HmMSLuqHxif{u2O8N~Kf%F}i1A_P109 z-&J6qLatocEF@Kv+F1fK^3s|MHyyJjLN6D0?yJd&iJcSpu-V9kRO#3bS8mqQ@%mSm zQV}0+5Vh;o#;essohoSY!HXey1v@Gkt0BhPCTwLstGS<0lpOyz3m{?(<~_WXPy7b; za08*oWWf%IeTu#5be4GCt z!$3QT_hzT`%+`W~fOJY6dN8AOZU1v7l)t4U(y`lPj^=M0aJ+7RGTop5`*`UAh}?nX zo&L-0ntOi3UWu_KmU(x;^%(6w*u3RWp`b8oDdK?Lem9RXjb25AGb)t27g_QvF~J#C zv@Qp?Ygh{E<|u93(hr3R|*o<$qMK!F0}fof;cnddhu|Ms|@KvWRiXM8I+-Io zd2^{$@nxkk2NgPP8%P&@?_u74mr3S0rP8=R2)qJq{d6vf^SsIi_@xbM-^HtdCW8s8 zAPx;Z)Q`wL3cY+tP86$F64okOn!Glah)Qf_6WQRM;D}`VUp?6a2hjfUM%R|Cs<8KD zKn;5SA6ya|bg$nE#lO?cewm2%Aee750gs=iIy)^EzJh?g3h%;B?LKnOg~a0!>aOt@{1Bc5!z+)XDv@+MTHZ#&LK9*hXae7Xn~MR#8?xUZ@XyObAYU?IuIsczM%}BU{r{R7e#E~ z8sq}6aM85ixKonylJf3^Dc+LwGk23S?Uo6ks40_iCLS5sv1}TYmXOF4M_tAmw1e#< zB4yg+0_6ehntbt?A#B6|9LzY2FzW9>ztFQoh1-;&%VYzaT48*!Ce{ zS#mW7GlmZT`TSQc|2b#aQ?xSqy1x7)?!67wh+r17RC+w~=1c!U#e?XsU_Ig)NtpTh zMZ-TtC0M1Q)N9|zU0PTe_WP8{pXpQWp|<-s+@_B5?!afQ0h*(#_Y@ISXp*gP-) z$r-=PuRuduEEp1JL+M*D3?WvQuCFwHheZ0i>7pd#KW{js zF<>HtmDQT>)W&zBLQRl)5+i{N3WoPX3zR3?4_*x~t6)Z}tPxLBJI&J4>&dKPB&}r2 zSJlkttc)Io_R?3CqUr;ych1W-sDgJ1B@^|`Q8=0>G1vVyk#T4@XIp1l(d-o8(fAg_ z$PxZ?PLU+4y(2q}0B1K2m+6S#vBc`?$caVO^gN-SCW%j^2i1r zf82%lRcBpB1`cz*g#yu-1Mq!{;=BH3a>Z2tDEFZ7<3~Wb?fO%DQm?rGgXBJ)B!U@J278E*74L-VJ~115&}@~?~C+4W#1{vXEP0xZhz zYabm@R8T=uY3T-O=|;M{k&>3~P$Z?hySuvtq(izv>6Y#|d(ij)ec$=NzjMClnhUsw znR({fv-eu}y6=1Kc}?xtiLoj0ypF$Il!Y$$h+tK(tAa0^!sAKz2#yCqY_)53qKe4Q z=VqCEt7r7d8Lr*)_-fEoQAin14X$eoSD^XJUv)Ot8r|3NVsof3P5DF{`LOd#51(2_ zNv0#(WUkkyMRdc;kzhy+Iq>!9R7$zMMykdFsmV@Lfj z74&!#h#uamlLu$)gdrqaMwDVwlk!)PlZ4Ha152*2sL< z980!L@Yd1KrAYVjFsqlDh8Cyw@zyQ}v?)?&eXr#A9JEtX8FV`zbPtz*(M*_JJKq%g zW~hs3$={1f4ArIM1V~7}ol?T0E~pF}ziVEzHhD=^Y5ytDZb{U~OjHS1)c&Cys_rX& zgr*lyAOMIhwGl_hzM&T}t$i>RycBQ2xT_^|T`}ErPCdEDGW7V_x2@|Jvu!`$l&kh9 zE7r5m)J^|ni@0mGx8@v<;On-f+v)@oSat@IPmlu+2isX!p4HcqtlWPtL-YPJbQUsaA-}5j@ z>8vo|gQ=JwZOh6b6@pliBF=Nw+1{XdjoZ)ghdwtwEgTvDR&DybC-3&bRnLJFyEpn0)jnzSqQbD^;>Jxn5@QAr749VBuB`$;miWDU+ z264w3Rs+z&>v;=BZ9o6*vUCL(@fTD~!^fie;AGOMeM9-qyu1f5;L1P+td`ZWd~x5l z(#;9F1v37(Gz%H|p^Wq&mc{CxWhwTiFG5h}jl0JggKL|BI2P%3acQr1CY~}Iyl?Q3 zc>TA*!>9|AVzOptjtgeNf3qGAGoK)IUB|aCnL6&J@Em;p+UEr++FEM2%S1y9=hwH3 z9@kdJfX^MX+mjJ%5Wd$rWQs$94Jmc3z}x&+swp6mq8_R}nazV*i$jnErOIKzW>8^2 z^wljrS+;p=s@n3Nu0vdj@{`*CVh>Ul2jHO*c|p|fJtODU-nnP^at&+SWuIDLBFY@y z-tO&;Yf#xi<~I+S*K7+?C~O3y^Vz1~kT>)`uV?EjS_Fo3VQ#PdEGyT-6?~!Z)qXWT zh6ON^0xUxZ{}q%PGgG-cUE?*gv!o~J?$iTp^gUd0!Jd8qzq;IZ-+dF|a#Q;d|2ct( zdL=Adfa%01i_>o^h2Kj1ZNKSM5&g)iv_nvPTIKysSHB{bt3WzFXI3q#?3cXNeifU7 zvf%BC<9n34Uz_GVwq&Fkrp0I#J+=&-tkHdvbVTdi+Lz7T10XwY#zZnpQ1ptS5E=e@ zY4DsQ_iR9CiTkq|_n6t$TnCl5t|;Rh((c?8P3X=OO&i2~`62sdFPpDh`0uX0ibs}v zfQUuB?9yoavr+B6T9@LLvUc(81ZT@|G$)ePd${?A=B@9=qkK6yX^$MFdw-eGr%Dl|sCPQYXli*-bPDjqpo@bF6&Kp~W(!>@NKGiuRcz1q7U zeF`TQolYl*Z2XZo-_m9g-n&vsSw#&*F2OEFzX)7ce9u^+n}h6gSBbv;7jJ5n_iviU zmYxh^OGi{o}8Hb3JbWCXX6v zez3I|Acod9hkYh~r*b+yfYe%_CQ%tEIDT*~Xt`7W2=V`?{#|tDhUeh z#jP<9i_bE#M4QSyQ>*I(B%N)8A{`^?e8H3{NUPkiYtr#s!@$PPUxbY=V^2nDJVFN5 z(sWq5yB9J0{tzyyDSrqTGJNQ07$QL+*VK}dgO<&Y^_)yzE)-D5xn6SGaSv<*qK^LX zKX$sx>GwZcR|i!%bmNLRzbBxfl5q;Om4N=$H+;guxi6kFmm@1rYB)kSA_L$y%wycG zlvDX$Kl|z3s9X(gv@kI<>b@M-y`9sEH`P6=P7bQA2ODoe8*K=mi4)evE8^c%FS=P; zN{RkCaqRNPJ;!l|_rQL}aHz9POG3%_)Dp&2-(bP+w(-u$C%t>cY4W;vW{OAXd0I*q zmqRm=ExRgYVw%Er9dwm2avC9bbpMF@3dk2X$911WhcmmVq6sr(;SK&f4|AA6 z1(<2QW!L1kM1Ka*owA6V16pHum6_|?o@Mi?ya=Kh@x`+C){ijL3T2&>UG?g=evu7! zvg@2>>{Sl;FM^T)GcTZ>66_`6a3{zZipydlfRh=04yFuWe%p@I*8KY7?Ju%8)}MI} zDJAvDjx!Wh;}JF+_yxZUyr!6wvdY4P79D%n9SdFKggnL*RPHKUxl#&5CwYcwuI2UN zhSVMo+khS&{P}t})76EdD3=-~?k2n63-4+y@aK*kRof6JO-1zv)jAE+46$2d-}T^g z;7ORWgkWhBc$)_Bg2sr2*sjP3(#N_!Z$0b0&C(`-oAFf!lRgPld(OP1X!blQs!5Kt zr-jP9F+A!Q4XdlOmRuJrclHH|*&P?6YrW4Qo;l4#-AA`6S$Q6pLHnr6pPxeZ6{cn} z9Q#$|wd2TpiFcQP+BQbQp0v~3xDEb(vtGhGk1ytXlzURabRylN8wS}QbAWhqX*_;e zaxj=^cJDKW{F;tXEFVz63bb@F!`g=AAsvh@NaWyX!$&HX29BgH!w#6?V}8J#yL zuInjM2FkD}j!T4oouCy6Ve;kF4AE;CxYx3w7w4DI<{*-tnMOnS%$IPG87W=FzIu~; zxqz)+4sS-e0{AE3tHf!xkoIyxcYpBJcBOYFP0`mXDHZBsV(2n4g9#9HN&eYlAQ|uR z`A%u``3?1eh7;lNG=-f#X>x7bhaNT7;(q+SNOlhI-@NlC&An)yBx!x_$I%I%+5iwKKh+`OtjnI?efjzr^^B5R1sfUYulwC zKsa~7tF5;1*BJ@MF*u*hG;h0X33F0)S-G<`??(?6Rhsq>2v!L( z$>+;E1jWx->zwOsIoJd4ACr%G;ty;T?8C7a^ZuPUZQ z6=bi9WSRM{#%>%`z%lj8Y8I39wOanmZd6(JAN@;P4+Wy8_!V9?$=o8>j{T^43cw4)GI!=&PT_SJfd=UkMYUtAjg zY#q;VmMrQN1+l&@pum%2!a?p}^A28J$x5|urC0AwLt{^~!8>Xiw9+_t%hN;c(XDz%KI6V?1H*xhK zRgGAPXVt@P4$?OQX%;cndU>34_y@<7EP>AeacGhz5CktQGyhMf3)AQEuu`o zNY=PYk(ZV0KWQXR-Rf~SglimCktM_b(uYN^O>2tgN0Mq&>(m_0yP|<>0g{H3I?i40 zSq<@FkINkLaTXra0Hfm$7ICt#)BS!Of$k*58vYxiR&zDY86=Z&s`aclNtOh0c#!>4 zD{sBTlnNIiLi*N^51!L+5Q3q|B@Ljen_jqf-M+XSJkM$x`t5k~{nEwPkkVuW&i8m- zZ?0RECvV1-4vieMfMb)JO39lP$gK1IfOR3FrTC2LD*48AtE1{Eg3a~$MRPMZAw%=>_Aj5T#q0&Q zvlmkIkXi>A2=69m*(0~hAmvFfjFKV4tn?_QRLU@U8~?z$79%&hNg;PGy%Ty+Goo;T7RXXJ8|l^m~^6u?7O z=rWC5-XuJO0n_goknF$kv(Xq%4ex&a6zGrZL>Rwc zd6*3`E>9Ymb>@z-R+kPEqL@lTHa(cvL`w>_asr<+?-Yn8A&E5|;t4=(8?-vZcfHw_ zOwd}Nl4M3&K%tA$)9d9K%_J8kSra{9gzphVJF|Z9yeu=FIO^5<`j{+IuAEb?o8{`U_L5*KmFq zujfRT{Aj0YBfN9VQvb%M7Wz@Q;XxpdE6=p*rb^vr$s8<837GVVPt^KH*>G_b zU0D>5iZ~snPJ5c*R%753t4^=Y5mP^;(9t6GO6`uCEdY=efVoTo%N=X|nmm zRnxOj{%2urGKZrHkI!}rx~ER!zTC3VlNrmwd`HYa=!i@XWJ9&$Ci&Ggz8 zx<^;ORL*0pBU9(3pM)>RS8u(MvX>)>_9u4W!WH3pDpZ zkNfB42iC@HihSiRQC)uTsk@_fKyb>BXU|~oQ^q0CPNmc6cL#kabSbE0AO}zD%=JxS z9`&(Nm*@ysP>_?fUz@P%ha%wOe()nfCt=&1t%5xTlVU60{_%@*@Wk9#PgEX2CLUON zII=me#<-*`T74Yq_%fgEqv=*#wrR z!7^x^BGIBOb1rzZ%9F;W(x>+lg&$SgiY{mM0|ouykbyVJ{|QJ!m}u5Cz3oypXp!OB zu-TMgH#e~U$z}9p#3;p(v^(A@tH>6SNY-iP{TPPnwI-@D20`=Z))dNPJJ(E=i z*hXhH!`}2A(2ws02+Vi_k4nIH4$Zp}95t1b^FvtGme`HfWgT0t1-^vKTSVt7wqhUb z!rtN$NEckT4npJ8AkJAg>hPDG8j<|*vA|58ZC_1H#i<+fUd>; zmy6EpiRaiYuKNS4*hkFREG-_jfvum-xqtdS!wr&CuLu$mH`_+a3iLfus-U-5_6j>C z4OSPy@`K4yQw|ElslxFf-uUqxr6u)@jP}!*|JLc( z@Q{SH`bHtN5{_*XqiC+%yq+4xp&`9qvS@G_U>OHvWBfs14Zrc) zKE(RD#HC&C%sVoz$UJ7Xk$`VdYDgPrq5UVU88!Wbx!R84Avy-%%s?1YAW2tFAw}sT z3T_;IUHCip1tvmI!NQL%tQg-bA|beAa5~S%`;3>$j%me6Bq+GzPXRa5ukmZ@ZTqy* zdjm0w$oU`nm@gj$x(ND_I3!Y2ySgElz8pNyw5GboJ^c z`~8;9EH-w4d;(YHS%q}E3#Q5A{~K`i7S6wll)7Z74)j;Pa4b{`S@|I&_%{1h^z^CL zw*`FmHa#L)a57Wu{|vM2SIvz26EjikS9FaoYq`$|AB~_=FE|p&FVwOa6Xn~F!AID_ zG^Nc=$P*YDeaXJo_@q`anK7mk`FWnUg6&TXD~9F>XbjUiaTIT?%v<}mjpK0lZE|s; za?>t&Ug78`&A|vloP&2%W_t4oye2dNAp}#3$;m!~BQpCa&PNev^+pna#=s|D0pSXf z!>{K<{r$49Z*0XxLFkf(hB&pVI1BO`y97&Xnbgiyh17}s6Om*;(pq+wQ9O_%X#)xN zz=FCTR;7?{R4=isEw5GvX?E?-DtvZlK^&v*@?m!hw@epK!Ta?Hg?OM>aLL%CZPYYu zf|;3UGfn8Np4gWa{}1W1DP(0AZM)PUh3_kHB+#bL>YvrH2@m=^sSUB1s+!t469W`Q z%H0qM;uHBJaOH)vrO=!)|2IMvVW$uh5_oW3Thjk7giVcdaJzD!2 zxfl|1*ejj6PfA&2xaS|M5aX#PV#y4PEtS zuEs^H{^a}3-RWk|eJqOA#@yB@@}lkU?Wizm<>ez>N_e=%Np`y5xS9Iu;~-v~LOXo% z#IMxA9#kmwZQV7OYokq9=;j#4S%|{a!GUL2dnU@nhW5&I??AO$Dswft%*mvzV1g(Z zXOwkiXM>!XV_s!3hzeN&)6LE8)k6qg0UZNaLA>Kf!wHt>Lmp$8EtO{%xFyXh_?*Wx z0da=2(r|Pi6X&G`KfjYEu<_Qzqq*vP+s(>ctNyCxGNm)smVuF_^n&!UQLL13-z)56 zgPKf?A2e_4iSdzDYZh?LR_0EhIK3AOQ7ls2MouuvaOhk)4eS<6_8a*mPfaTz?@zGQ;WL_W&4{x4>#0|hpq9e=LbgyQAT1CJ`W(C?}FpQ z0N65eS8*EaXzG`7JLQkh&z1@K6UFG^S^TtIJZ?6K!HT9j!^yaHZEzkUt8)k+{q1%X za_4qCO(SjTa;u&D&cfr=H|sl{`@Tnu<4Vp84_KX~Iof@LM-b0))c9Y5L&1W~b4Rz*Q+b)ZXWu`+m2ja@oEoaYt zo_LCRD=*+LieU%HEQw{~0ei;}2oTWtKh~q`?Ks+ps*nHBHovbvsYJwRt zM_bay9O1yQST5U6(sx%}_y8S76bfx2oY~zebC>Ne@7D9;$^14d7HLp!v;~hZ*0tSE z8-xfiFB2^NHWrfT{PH;>2>hxDjD}BfMSnVcfOh(9-c~W^uTe6^2Y#oDd z$V$T#)kRB;W9*$!cvtLg)d|f#04>Rb?}uq0nIE#Gqn>YcI$IlP*!s5U;jVr2dnsvg z@xpBLbaydEj{)6!2D5YUjWB`?qt2(voV2z#=I0f|SGzInYEjVIF|3Py07S){MoGFu zi>`8~KCe~61!s?hSp^<#K}Gw0F-#1iX~Pdvk^!-(x`oU72AmcD)x_R#shZWdB?>Wp z6~?P|7p|g@MHo$2Jr;zqXXDM;?y7J;xyD#*BFYe}ou1+Tsb28pPg&d@1O`yCS;jc4 z4tnBqDt*(*QXJFQ^2k`YHIG&vt!?J8?p;;96ssh4=E(8N*!s*`Ej3%N0-Y|8&^0MK zG=!ml##@g%uE_YF3e>uM!Ll#&L0JJJxmJHzsE@`t*X=vq z)JEG=ff6QKz3Y!^96^m5IE zZhN?Tjc7^np>Zm*?cT~`@MSejtLw*XI2NyeL913===Jp=KBJ7-g)Th^r^J#LO<22Q zh2e__YPUWIk31z1E3bRzInGk%%kwOL<0oNprQ>lP|GpdBp4F~73rTjd*ludjeyeq} z9Bu}pO4xD9{8Ak(=uxa0yyT$au#FS%Y*hzemoXD3f8&x%1C$_w#VB3b=)E239A zYc{H(>_LZg_Ky6QC4Dl7WSwXo`qz2|VN_R#7$W*sRnWj*q1q8K$C~}CoSQ>X5L!)y zuNoS#EyPpDIM+W7ITEw0!%2uvHqv18mIfW{H9vD{gBY=FLoFuS_{+p=U>`#<7O$~% zrxU{t6(S_?F54#NByg+hCAP{{%4VYwg8RWcUNXHBTQe%`ti%HcaX!(PNfGtg^Dqy& zUq4mfy|b@jnY-OV`dZV%c`!(_%BT)COK}h-$fqfe}{( ze6nwVQqADK@`J;DD;zWc^$C*RZ2qXvWQuuRJb102Bs;0lKTJa5g*h|rm5<*>tU&1RInbm23*@CZB0c(+srHE_jt;#-e zbkr|H1A^aK4LJ8#h=&g#-=RFu_Vn4p&slse+7i;m40116W|N^VDq$$e7Hd_3hgKQ5 zMVU@do^>}xZeT8*X}%e;$9;mtv$EHhmge`7`sRpcc7ZDJxMTz4g^t^ggBa=C1pK?6 zoR&J5?V6Ou%iZwXOV@+JmxK7tW8k0B&OHNSk+BhDC3gU&-IC!aum%N@@2x(n4C(M} zK26Guqu{EdKxbzUjp&Z7HdB>zn@EnTDoIoBmr!8j$fy$&v?{sG@acJO^Xolr)Pf-8 zip1UV>tt+X=UG+x3?Xxj(+5y#kzCMR|oWWQ?Iy)8059K*S$q@bi`dCE6dg4cieGd%)!PvjP zAtq_Kco-6rc*RY&vW;)0udi1y&*~CxOs3~(!&Qb8-Bu;1C)+D=LIA+8^(5*8Ug)UV zpI+~WXkVV*Txl=XpIlv?wYcZ)aU^l^P;;#j5KcHq{HVS$kK0uAm>e4H)J;Iz5S14g zRci~c_Y}NJO0YbU$&Aq5d3GcfIf5y9$=6E@*}OJWTRwv?P!JL;N53LH{bV$ zxwin5Zu-=HkeHdJK|Tfm4bOEH!7ak9zyZjNzGyezr*aWxSafo-&bv0#1mODyw~ z>4kDT9Fj+Gvx$gfZ0o-UL6;Y2f>N@&cAV1fOY_Qe10APLrJuZTZvxVPx<7{GA}E+= zH8(dqEOP1NxQaI(C%ELboG)nIcplc^dtA3E&1xT%M5FHJepOx8%4SS2zK!%v0&#kz zJpw>MojExfD&s;AW@T2yZ2rOMW6owF^{uNxzjpEn1UB__^Na}YguIjf4U$O`r>UOj z#N}>-&-kBJY);V3mhULQtXDV}wWR5iIiw8`%5v`+uv0`oI}&{FX(ivEPFI(_VpFA4 z)F^O(m{i{V^RcP#a@Ww=$qDlC7FUYOf+!x#M9jva7&popBsX~$4$9Mw5Bvn`_-u`v z^?LMa{grC_skxUjt&)kfwp`Di$s22-Krhz_UU^123VXBWp5fQVrn+&Bs6;{E(7JlwBYn>IA5OA` z^p1j@WR;&zgs&Brzc#abPLj+GQ6h=n#~S@U$16~nnfjiMf-4Ga9Er=<|6l?W8l%Vm z60lk-Y8ud6eNAbtqxr8>K+7uSRL!3Rk37veBMJSyvK~;B6SIc0nHYfK(dOsGp5t|i zffMCqPMtgDqP8$H83C4& zOwZ_*p8G3LJE7-3fCV1MnX8blXX~5XBk+Dj=$*Eb!-}*wUBh0anUc3rmT-cLdM!}F za3C~Nz*$yUh;mQOGNkZ;5(8PivMA~^7@}8x!C9_#7CBrLro_qviu;bx^4xd!XUq2X zmBUs;gV7K^UD?Q|<|O4o{FcLyoO7KlN^<{6l_drqA@>ga#wV7*r+6MLNa4|)_N+I zLLTB8=h3sFJ?RnDC?YEAW5Ua8HL_uk)BRRn1lw%9vTcXKqFq7;vXZ}2SJR+woF$5g zBp?wc zijcO}zVq{YdnIA^X0g38rQ*up56f#rRLi5>2vinfL{lU`;0Ot=?H*&Z)o}4A$69Ba z*L&+edzu`ZUi^zb#<({a16rJ5bV>^f2GjPu6Wq=D{PJy;-8XsJ@B}KHQ4MC*Qt*vW zmYa^GIg`<4N+E>f>}(D65Ps?&6z?orvK$tZz|5*4QfcQx4H~s=5$q;L(weJsmN#HS z;#4DuEnZe83`A!-l0)|?2~-i<_dAZEW**3oJQ<52>`p_pSc`QM4d19bo7DTxkIvb} zi6I8a4PW<$dvdqXMzEwfJ2cF+W>D9y>r1x`JCPLLUdRgh45+gm&F;07Z9GkQ$;E`< z2d)2AImnl5y@9+&?Q8OXYNpc2?gwMu{vMFV_w;~wN^;cJAi{FH58{d5>D&;gP8s?W z79gedzxei)l;PUEd@7+D^H5WM*+*BpgR9=b< ztvd?Q;4xn4_@CH@DiX#W80V+u z->LZYFOw8GsJ@g16e_Lt+AE~y+tX~x5}Tdu_{V@R7AqRsv$P4U9j()%WLaEE8aPUEClQfOjGp)PI!A~wU14Jb0{0G`B#+!nY3U>yPL9B6z0z5x?_u9b z8l-Og$TfZyWpY7WAnsEfRpG{F6=rrnm$~lX>M{Ey%payW5hmxUo}bG-R}U(K5llb^ z>t0n$@6C;9Iq;ZHHJk7Eeh+^Rl#UEOte*%HgD;SoA4#kB1~8daVD1z@vh#g^P{|`J zQ7B!96hzQK7-Bua$@6MM6+1145PQ)0&sj827h58fscC_1Y$IueBL?Co|VR zXv91kGYqV1AT%94m9TFMx-t zh}0XC_LzAjMN9lSp7wvHmRAx!bM7!1eKjz9t|3c{u}B=6tVbIBPK>%0AD(?ArVwH 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 HEzkc4^&is6 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-subscriptionmanager.png.svn-base b/james/apache-james-mailbox/src/site/resources/images/uml/.svn/text-base/org-apache-james-mailbox-store-subscriptionmanager.png.svn-base new file mode 100644 index 0000000000000000000000000000000000000000..5133658e934bd9985ac2bbaa64459d0f494a92dc GIT binary patch literal 11997 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-mailboxmanager.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-mailboxmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..b6095f3520bca54f2bdfd68bd38f570500c364fb GIT binary patch literal 16030 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 Rm%R`XDRFtRG7*C>{~zWa0+|2+ literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-mailboxsession.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-mailboxsession.png new file mode 100644 index 0000000000000000000000000000000000000000..294dd6a0e065334c37fb9bf5b5301ff160c6ee17 GIT binary patch literal 17781 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 N44$rjF6*2UngA1Dh9dv~ literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-messagemanager.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-messagemanager.png new file mode 100644 index 0000000000000000000000000000000000000000..0cddb2e4129aadee3b029b14a2a64a1a3a56c707 GIT binary patch literal 45433 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-subscriptionmanager.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-api-subscriptionmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..0ccc707480b24336c2d9b62b26328caad2857030 GIT binary patch literal 5550 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-package.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-package.png new file mode 100644 index 0000000000000000000000000000000000000000..470f9fef9f7d1fb9825e293d63c47b288e415538 GIT binary patch literal 18180 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-mailboxmanager.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-mailboxmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..dc5569f654d2784af7e0509aa49e9a64300da128 GIT binary patch literal 66754 zcmb5W1z1$y`z?;5q6i|QQUVH6QUcN`4&B|1fOK~bph!u#w9?(3N=kRfNDLiPgUrCp zJ%fI}aex1N|Ia-uC=xyN(z$rxMa9kSXlVdQXmy9EbMJ8tn1~s zuLFDh(jR;QeqD1?krcx!9eT0_e7I#KD+$6vV}8E0hLxO3 z0_?nWBr=(U>r-0mX8nS296kAOaxc>>lR(tSi<@hqNE zqLlPjg?*$tv0pn~MErFrT7LUcU3h8NRHOgu68ZHV6~3i6x!1`Zchsy8{1$EQ?AYVodFzL|wVz+Hk(=yOagyM@?q9crZdu&_kwe|L?7 z)$E$FE3oPnoM8*->1j#VHspp-9ptxa+eLrI%-QJT0gBHJ4t0Soa5W%*RQW1^XogYe z;8E~1Tc8^hCd6V`_GxSToX{akB$xm)wv6~D-&Vdu(c}X^&bT}_Iqx`tpuxjJhjyE& zcu{32-}%W2(1%91-_>Xz|~;`b!<2$6mfl-H_7Hix3n*N3OG1W$KFZ40&dG={ysj`b9RQ*V2* zRX22TBx%R@D;kV=h190@M8ND6gpql^XNsJlPM=M9itWta+}UW`Z#G}~z<5rtAm8}f zMFeR>wF+-Jv@H;a)hwxfXa0+SzcyVHGr4EuI;)+$oB`WQv*84EWyoZ$`g1n3q$;zP z{-R0gb8RlYl=}l*uduM*mXy2S0`3Gwu++SDOC^pxcvBz|eSX-^6TI2ZL~NFb>R0rI zt5PEl6@3uhZK!_9EebUQzARyTuaXSy!BV~T{bm>ZNM~1^_`Q)gW^PJbbeWSMr^ajC zJXSwgNHVA5kF@mpNwSDUU*=(Z&8d%Cy22a>6FQwX?t;=;gPb@&G$h;t1~LQ(OTez! z;Cv}Qqxo_*H+{bz-os7c}S;hsksJ=uwxgninhk_44Rf?q+UolA`})j`3X zVxA2A3Qck#*)cKcE8%5t5pX3lQAh)le2kZNkOl@^+NtkKSTfOvGYAE z;R!ZJ%WFI*z|$gcHh7z!x$w;| zLxX7cWafzeV$o}DOy)f+Q9#^#p-E&^bE*lHqeOaUyyfK=6H=q)T`dbP9oB3!wBO3J z3VZHf+dI9wb~OvJz{pb6Uw(t#N0~W&m#|TtwdJk%?#z6V^A4pk=Zi=#dsiIV7d&N2 zbV4o(`!;&w9=Y26zZBZ)3dg!l=4#fyrNOu;VdU%f+Tjtt=xd(a>Z49Y=+o;>W3>1; z!d2UIr(81xtErn^@{+&S@-HMf zKy2k+O}X&awE58gs*%S${%x5qyHyzf9zj z)%0!Nc85x~c{9Vq??Nwf2rgIJ_?)5hBCUuuk>Dmf_hCtqkyC6Pi*>PsGRgGD_Mi_w z)R{s!OJ%ltgQX<&&f&bJNDc)||~Yy&BbkGFX&PFEgTmVTP%B<^+EG`+-oW>H#UkOXHb&{;JkW$hn* z*61Q4sc3@xu>MGxac0kYWNxzauBV(1g)MoVry~yQt#p1SbD^)WVgrZHTHv7+ju z+GYg&ka&syrrnS!n^&VSY$5EeWxt({t*GiMW&lOLISqP&<#$4XRa+37u_}ztYeRlL zYeg#`qEY%#w9oKf&_ToHOTx?kNi_TA9(u#q0J*iogciIw|0M zy^iO}+?%5QIounN3~aX-I$|muOX@?f5?Z#7of)!kOy=CQJ6+VN+So}7op3s!>=%N; zh(ERjJvUD3?n$ug2d?9sG-g)Ar##mm1pi#=2H68;(bXt*r33G^a$|hJqf$$HEPvn@ z3p@1XM_B`(AtUGDVB0-`w@FR0{5K6IbqeDacI|n>IHRGQU)+NydQrp)ic52QdNz{c z2;nlKQQf?ZrUxjsk%q?9#%7+20-+Q_xyuSGbI~k^I`0=;!PuCHx~15$SUzqS@-!030>Y)?z8|DVw2MoUM0MS$qBU zAV(!rqLBWab!jW__s`$x(ghS@u%GKDc(51(>AF(7%Ew-x?DTWbpcZm(iYTpSN?b=9 z&aFZ74Iy+jD&pr>(T|y=$>L|f(7JSpj}<-6%Hs0B=gwO8PO~X1*L|XzTPi*#g~T={ zIk<`t+1dg+aM5rx9DJk>Z#ajf_it=1z0XH{YtdKrDvr#6->N#AnIa!CU(h7dP^7K! zd4lzJqa%RkTItu>;|eQo=FpZN>AK|>;pzPl+EWXz6gzoMWg0&z-`Cln8RXW z#Rkn{0d=Jh2uoP0L=-Rb8q>D(;K-8oO9Qs@jhd#;4?ct~Mg55dlKS^r8@=`Bzbi@Q zyz3VV3Eu3R-#-1_DfYV9QdHfzI`_fo&dt+IpFmR*H`|Hq^&%~OxCX2LZ_o9W_tE=8 zfimO?+HJJ!Q@;4@I=FRsTE(xgWsP3ixk7F3H*LGl9&i#CGWO43$ZS5vzjr zg?&($e34TDE}CjB!|%QDtsHkFfcth5!Z+vLpHj@}cEIwZxL0+6HLZLLogsW~=9`KP zJ<#_!rZ{l#8TLgT&kO&KMxXc!F6=f75EAZ%EO|VIPpA%vQ}aiW-$wK{3z{mHTiMC! zhgG

HMYl!4ux+7>?n#+~RKhdCr@Qe~UtQFYBIEQaOm`H}BR6g?ze$SZv!w6Qp$ zyEq~1w}``!SdA2!>a(ba3#yBFt3$5xg-v~3m7mlzLt!j);$jAooAKPNRkpH)wx2sp z$kHZF4rY%7u+LsBdIdAg)bO~XGCKD!)nDh^u#6%L?1cx5pTvAvl*e<+;w=LP0C7N- z4h3-aS3eyd6cqJN>qa|t{Yc-xIh>0#e%M;W^JO_5cn%W4b4ZI+Mu+sQmZf^$T*mTq zjY+I3_R){#-fN&8#GYYQq{W(P@NqaHUOnVBJjBDQ78+*AZ_AY~Bi6D>0=>~a9Bpf) zULpDs^p;*I9_y#4Ul%9V$W%6OlOww;$|!8Yy|-gt;P@5`QqWJa+(bjNG!w%b{aIJGA#jrH^vA!NbfnCKAt z`qdTCrXnJS$=lI!JIW?KUfNBGuCiNrIl_#w>*IcPs7;~N|&g7FCuW-ra;%u zZ*xXP5!z!8_6t{pLcN9;-N3rd$hpkM>jpWj=^k4ZLEIJJ&u_1qp0r7|I!5xA+C>JJ zq;UsF7iM+_ryMHm5X2B+hq>vLzJTgkt9RhxD}{cB>p*p7+>5pg_nvaHa;vt|hRRqa z>mRob@&ND#3yYr(l5v65+!x}395h&5-3pR|#|9?HxJ;3)r>s2DCT+VFQ@pW%t*>A{ z=WC9r(;eHSHa3u_a@_E3R)Rs(el!~kn`e7a8p=>SY3)asY*#k4l7yH|o$RN4+>4Dz zyP(Lgv#&H1qG9I`8w3Mn^wnVu;vtFG~btAZ2XEFB8bHF`Y?^YgI&DGWlM% z@3z$KHk>60fvV9bRQaK1@C+0-B!gI1b5E9AZeg2fUfbq+Q>v%V5W@z|fT)<7SwvB+ zxQDR%?NfCXGqB@VXhH6&u>~tn@#5+(_(#CUz}Z5=d39-Bn5^tVNOXFaNkPnDkblJE z58IfpaVwmR8f++i=qyI9W2&kiqWT^x78JX%?&DD&zG8r`3-+|C%M!ZB~wPeBTb zMg+`uP|Y6a3|}rM3ZDfLh@9tSHD$CR=Oz}XbA^w}iv$;amUDXcYm+?LXq)}7^As?M zizh8hr_C6Iv7Eh-Q7*_%Z_QRor%VV;xb9imNZN^-Pan&~ zZ>ZiR_8pT1fg;RR(#}}y4$mB)>rZfW9^DWWP|mk@K2%Lm?TXlJ$5SLK<~Fp`tJ6<2 z6gWFold%e*ME{^CEwIy^?3BsDZtDqvRAi|TT!NGDRJk3P2lOsAmfc-Tc! z1fh=JVtOGB7GUCfIP}uLosR2j7;^JM&?{x$%aNhzJ;l~jN#COq5ya|>FA~`KH9#dN zUQCs93YZ}Eox|pLXH)vCvhE^OtUpyCK0;h7mk@Cc$5^tmiSPQ%ZovH#x`s?mViJgj z7Ln7$?{`@^#F;?h#Ia%QlYPdE942SYC}|l`qr{t=Uxr% z)*8*#*@HC#_fD8uWlPw+d7Mjz%W4KKxjckvK01DS7W;U;aJv0>%-H>|2yI^+O*Sb1 zE7$nBbjgyUf4!CD?`?>jXeP96bH+s$TP#;ffx;OrZo$@r+dp( zN{ZN-G+)KkS5hL~^GqBR@^xaX+UAXQ`b&Lqc-}VtGEqNIuzpeER9Ug!jTt7QLD>g8 zKZ9B291%u%b7pB+0?0h~u>=jX{KFN}jpbisZ($s3*ZQ_JRSyZ6_;BMAM^z`qHN%Qp zcJ$Ap_FNy~swG*OH3 z3x6^ckyA9i#{0oAv1`)tqT_)gyM}zSO<&zR<$}d?ImS^V<6qU7$v5*N4527UDdMud zsjwL(xMyP4F*ch|Tr3`xq(&3oHBJ>eq9#; z-hsg;Y-K?IJ;P6uO-3)z9U3iE-8;DC|(x!CISLtO>UI$sE z86(gcQPnL0!oAj=pL@gaT%j%_+e3HRgk0TP*h+3)O((j{i!G7>e`KHA?-&_nWpwt} z96!E>36|~zuSYrgYhnR2i8VJ5cR`A!`3&N;J>4@7(N9JLNYm*e=w(3XQcnkA%=C*8 z$b!*sbpohwcJ})8PM%7;01!Vu>!_4!l4Z{DVNQ^a&}&vxi!=0<|Bf<<7$Y^N0_Kb8 z3T;hfY^n`-z#+dNA9JG%*_jU?nFQv^I@<7UTlhoRy3J+S>t@S+b3D@K-34Vak2hH! zYUgzlhk{O>D;zx(NTYX9_^F_lHvzk&>6$4zCO$to#)`@=OQmLGl?AP&ihdRLm0#Ko zR=`)@)uVQh_(>OwazyoL$P)nZ{rNxvqcL~QkJEFhbj3$vG|D)HKoFhDGFygurXfAI zxymAd6y{wTbE18gWB6q4TC z41|!gqL7++Oj^6`6fKv$bF99x53g#Qg1dS3@d)V`dt_Dleo8cA=T1CPSMeqCvkZce7?J7y%~-=dGvz>T|S&WNhe`YQk01TJ^TYi0C#)QS#H zXH9<^qYOi{m_Y5lF}~d=+@GfgMAgOxL{J+vxJz?Ke-%uCGF5 z%EZBB-S78~Mr54A&2xzHL-*FYG1FWRx;@wmMGY{nLg!xmItvh7?iiZuENz|=OC?HH zh(=z_eMn+I)s2h~c$L$67?QqV&%xJw$Da_Hb29XkFw%bA{ZspEoM>(;5e?M6oK%uB zuhBP<(ct%(U@LTitv;K_YjAErHxDcD)>B#}p688Ya-_WHHFj-JHX{f0Zm!X#;TGd|=h|iR z3t0PXNV;A{tv|}3MJ_N6-^!|99#RqEUqLPDY#2jyt0sW9dN|i*@Jy_Tx_~W`y{B!Q zoB-&Ry}t2;@5jT{mu8JTzxYqg1%dCrDRA>6-U(9X4SoiQFktiJevJTl zMgT_G?26sy4qz+zJ#sa|wCD#~ufi*aF~e+sLny!rU47j0iue9Vy1AP%xqI=L8yY|{ zl30U3es7-_`1UCTDw^|gdeA*jcQ|V*!-rJ%hvetJ;WCNN!C|)P5sI3@)$Z<3ggjLq zd_@h_{IG)wXOBz&U=ko!i>BDD10u>XmG@{yIv@pX-w z8oHUNEh{z4WVOo853R|xhTE;;Nuafr^Qf1(5Z7mBW+7B718F7IgQf^>-8_C5^frNX zr$Y*#&ztkp?tTtiZi>43DvkwL8V-gDs$2!q&XUMy2CG14OS4YWf`NGX3vR~IbihC3 z*_&ex4AdY__m3*af$vP&YZr=ucu4%Bvo$p;;v7BhRKnYz2QY>-#tk#s3pp3%c)0GOuFR8+r!H9>+6uXY@{S-I za;5#&SD-oVk0wbV(8FCAZk2XY@KK)e!+ZNQ^JIIUGv&{33yj7x0cf|kf?dOA55SH+ zM6{XS&!g5I&2}IG)73R%R7v@G)s&|kE%>ytNGeF~vbiV1n-vmw`|iA|`1N`Qe_Hp0BJtssXV zz|r@~lr=|r34(+y6~Jc(hk9~Qt*(jZx-eJ%v(tR9xddrYYph_G!iIDzQ9aQlqf0DT zawf~rorl}U)2Y3@nW}Fd3M5LU64v)-w;As4_VjZX=0;49>1GtH`h=_-4U@57($8JY70A%JhWxUXu41I5zR%06xM|a92m;v!Cu_V*jI^+{izW zmO&XuS-3s*88r}HJ0q%-OQe`?%HVviZhddHZlFj!FBhw0q@3GLD!39dD9$i=icbWa zRyO9FQ=cT|24czYYuw%IWUND_A_k{fGFEd{mFU_ho%cRH0|w0Q?~}_QXzQ;dAxH^v z%JA*4dM3TEh1&;0q`@vasO`%1i1VNt#q%x)x|d6NCvzE(2kt!Vo0M|`FsF_MqSFY7 z(nE^RA4FF~o923nXSRO=ty#}xCw%UIwdO&SDp3CFIqkG|*0Z_zZM^>s-_~*8c}U6- z%KG!^E)or^I6#4Q8|ckExTTA;PQ$GL=u4i(ExyKgvJ7c~a(05V?0` zUiBF7~YHRLc zXHpaL)wQ2)a!bsH%uGG^5Y-w*?>#g7^0s_x^~rh7cD0ZCC)0#%e0M{_qLYoW`5od)Q5%U=O<3vb6yKc z47>!b9qffrjI)0KJ`|XTNLv%=ZTjdpStFTxsVZne`|c#?I#4SC4RC~|7)ErNXB2#N z%)x*atyaryn#Pv}nuEEGAJq9OVJ)riSfAf0w5qd>fe>42$7{!D;!hi_k2Olj=9 zIU#zE&E{V@!74l?IIw3*??&!-_sK}hj@xx)`S++g6+$tu+S=C#4H$viye^1;Du1=W{9 zFLW(M=NsG$@)=-l0W2+#>v>CaYX5`IFopi;Hi7R^zyJU##V7P5c43Ux!zww51|PUE zKMlkeUV#E52fpavLK|&aw#0oPxMMsA`jVPP>k~Y_Uzp6sZ1#zp=)Ja0?Mw>8)>gEp zS1|@#a}Dr*T7~UdS&C-n^7n_U)2a#Ssm*iS#$M`7>9z=Qr2RqI{jdpmU2f^MvgwhR z4$ZDd>Jax_O=-k7w}sL$-({~6ICuG^nUN!t&zW4XZv}{T+sEWF2QurHlwS%JDXU|X z=2!yPX9E^j-q!m>zH0UZMuM{j0_A*E=ex)0FE{gQWMB%z)}cV9g&Gv@9iBk)v$OcjyP_D8FT@QPJxzRbIv zg7Qxk6LVC{ThJ#%K7MKACghw{+mReQCys~Hfg%{PxbH@RMX?KnpdrU z;@GX*w3F92%WE$cvox?N{w(bUkV@Tnzj=YvQWh3R2u)%*7nT(tKguzD0FDTI1*$NX zlW2n|ETk&KsW8YZw1!#(`?;bQwu(oBXH3dd#m2~;0{8n<1PaL7I1P7e352n z&|{`E4t)Yb!tv7)p`iWCCXuGLu678$$EUQRSYCR2`oihwac0-9a*2(mVEyk{VQOk$ zHp$Tb2hqIrN*?W#(0+^1$4^EJo_$J_eLi%1A=^-1stEt3x>M zSKk;Lk5<82r0^7=V%(C?toeq~R&OOzgL6{WKB8>DE#szd+_d#9*?R}XR@j?vgMcWm+|$r=owPeX_G z0hDv^GEcI@3%N##UW5pKph+}GvQ zmmK1#&-FWq*zyR=4E<1~zjB|1(BW%i|BJ`Q+qP>fIaNN?)L<2|qspGopIa;9eo=;= z%DwQRKB9GcM)xy!VJ(^LZRwC>7LcSG2J&s59^pxku8SG!m+8zs&7#v@co*kQ??Fs% zfA?V@V@EDZ=jhboxl$R_=i$RsVTaT^*&)GNXO5-$fwK7#{vz5ZW#}JsVan-u*eHxi zT2hr1l!?!^@T02g=#`elSPl4hoY$@DqI@`hTCwh02~W7~M`PGPa=CuUpMs?9KxvI; zAjx*Cc*`qjJ)8n4#wm3vwV~d8KczY_$Bq;LTzpv1YV{CUKS1HxwB9ueEJ?tS@onhi z355Q>qT)l|Vl%9(7byT`{rF`dQO$eMW|tTBPWe05%Aaf4Q+(U~L)p|;q|cuDV=_^s zfGu4nK;D1>W+wCQZ6J66;lnKV-~`}-6gz!!UIPdLV54qrHC6H%-**(^tFGN%J`7eH)JA@rGxZWPUzNdx;$_jDFLu4rxam?dLM zrv1n=9_81G=1UHIOpQrH%=YM!$&4bX`rlb%12i@^Nb4h^{OQxH>W3q$8%OB?2Ran; zSlog|8tA623h)g_$2e`0w{%W^zOT-EuE;^j8K9C?R{kUdWR^g(9&?!j#*81C-zX-v z1r;%!s+C0e%xC4Dplc-riFf!2T^{fe1X-+9&s@;xUr19CMzY-P$lAI`)58lCSS z`~#S5(q^68LcpY&5Mt>)E)erj#Ly)QnSH6R1e9NjXrvM`OytpZ0VEI}m%{0MF?ONu zPlX+3Ol!{X5U4qtEYQhf8J*715~sz3Tcq8CFMEt`KHxa7sZ-WrRO0_qvd{aNsd#yL z_nMh}p?>7WL7?aKL$~88s>7!)fWUL2b)J&uXw6-)AlIwj@^eDKRh?RM<1Ntg?}T_wA1f@9o{UCE z-M8lqL-JBEvaS6}ThkZ&=FOMb*O7HMR;a>wyGC_JO>@Lmyw*e+1A_C)*1kd%fLNr+ zJ~jB#mhO$l;4j*oXDU{BQq2?QtP73bDCV>=&v}Ob-}v=vN{zh#JGoy2;wfe;Cye_4 zBpIel^auA`f9;v2`)=Kq1i2019<*%OX5Iz;pK&RRB6KT=Bdt1ok1Ls+&MT28^m#nxrBT!SXP~)_qNC+j*%q4i%u6e_w$90U=DW=W)YA zIk;&!v3Nlo#F~yl#d@t*sJMgusur-3hK87*>Eqb>(j5acOtT7F|34Ho+jZHZm8}qf zzttSqw$af^tDZ7zB{uGF*_IfKS^Puk<~z-#rjGz%v;p8c;-8Qkvrzej^-esTl!lcE>%fTIH8>MJ`z)?Pcx3bH$2rj|Uq!_CXUNH1{#D zW-hU{?H{ssN>(E=5C~PW#Nj)J15VrA9_28&jAL`#d{r&0E_Ya`T=K`)fexGl6I_$vi=jp9=W*Pcc z=dIZoG(E@R=AiI&Jhf4u(z>A~=1E(lo|a-y5xK;9*!{k(yIyh93ZIM8eceEkGc#L! zJMAZtH-VBl46HD@Z$v<1Hk)=yqo<<6j+=F>qD@pdK$Of3N=p}0ees%s8RPuuK$MBGQk z$eU$GYk(V9mQ>X_%=}rz<`)6mt7F2eY%j_saY;`ZnZfV!a2fL$u{K4svJ|^l9p=pX77rCYM^DI0n!YWH7gr_Xnca#@B^z?jNFlS=T^ZZw~E^W&6 z{)o9VjASxHQe-+0aNT!S%pL+#b8KpD-k`+6&zMGKG9BE_UWAc#SZ-WqCLVHHmko2R zZ`_K11q~O%jT^Y91&p63KY8lF|u<4fjl-Y_OVEGi1wDY2sY{==^niC+(2bx6w<3FbsO_! z-{oICQt1x|GS<6Dgnc)LpBWrVrPapZN?wCA^-a&`I1UAPzO@BC@OIcjv2 z9x$!goDXw{L?6*U#Skl?G}XF)A5pKbyXfcaCrw1C%#gR4=vV!(ubx8cHWVMnzf&dW z`TFN3@15QIi0L>W8T=VQe@XR!40_3Rc6A^!z{*^fO!A=nL<;MrKKSCHhBqML)GfG?Gi{@gy@%L zr1}})bO=0maaa)SIRF$UX8jnJZ5E9A0btEL%x-PW9({ZJ7Q4=LnlExf4# zF{t3+qjd~6;{e3>8cNY)_b^7kX9Rp^I>4KuNQWqddQt!^xI#)E6?}TieJIy(ojY#$M!htUQ2X!{6HlJ#hCPuD zFpCTDp^tyC^qtnHd*ZNb1|Tjx$+>GJ1h2xHtHM9vbI2_wZ)Qs{#Ob<#>X@OGti&94N8p4au1`L@;yB(d^gSbJgDk_m5K{)2s@w@D zAuv1JFbm*A81fYE@a#;Q2%Rz)OM!-x!;el5N%|eOM6&^!s<>ZEwuO7Z=53Q;wb9&g zmn`_}Y7aHeC)n@}F~0dn926Qfn3wOI?pbwkMGV)~;$A?WDgL&fe8P^wra8e*Vw^df zMUoh!OV0#byhcLYe^R&-y9s0SQ%&{EH50T&+!&63%MX5uDXhVTV}xlyY{Ps04kKaa zU5HZiu`lhOGVruXVA|Z2YT|Cbh5jWrxKIYjo*5?EZehXoN{iymq3kX^j)!B~B81TZ z1M4wsEC&b%GVYOGo^+2y2+w00?i1Oo+Ky$u-0=ihqrcT~OL+3@?RT{W_<%8kRgRU@ zKg?6-o4kb84#m?gYiC2pBxNe8HPV<&`CFrPTU?mHBua1eFXinDW%}KF^$$ZkONZ$q zmO1_ZTSl>K1u)x892k3X^AmUJ1qBf`<|><=CoOb$C(w01uAd`c+Df3075Z(gM0G<} zJ!xoG=;%l0pMx;Rm6wdU3AAQAa&H$``G+Qc`lO;3m-#T=gytN-$Xs5#X^s+Io+;-D zun0zDc2#!o29qvb?-63ov^M-3D_qsgQV%ETV{mR3>8E|CNzak3MBI14Brj9F=hWIQ zd#220Col^(z>8Lz&k?RaR3-l?xZTjq`IH6d*tjdxNDk>hbH)xC>00xKAPG3mKaQ{dl*BLEO{ zrRuxJY^Vl|NKF_v8nbs>a<*PeW;PoDbSJPb>={NWQgu=UD8j8UDOBtIJ5JPu%8V&G zYY1=r7aX?E7-5){g^x=O^jxAZ1S!^51&jC`^IK!Oz%J-&S&FDx&cD;;;G_)G%^iUk zcbjZXn?eNji(RdJ>m=n>sD4gfsH7{7j!64veFA1|4)$DMlySue$huHvJBMxV5W;aj zf@J6#iCb>jKHs-m{Hr&E;d=iK_H0_#FvbDZ)@n5;`gxePQ|w(P*axuw)R>Iq%iRYY z9)J}DP|9;(;KJe@9~95{V8>r{01p|lPSW&zsg)+avqjl`7}X#DM@nPJ2qt+F9dh6crcPT&z>ipnS^(YKX zkmstU7qS8)P$wxjQDYNqY5eadg?O^i;zxVroHc6>#`gguX@REWd}O=^dAYV?2;fcV z%BE80pm-k!7)<+hk5`NipJ<@xe# z(?({K9g_!o3kl{PN6e(1STR9ejX~Y3u9~co!5rl?3 zakkDaZXP8Eb~?7t)^24O$om0f95Ah0mv^8sP~q3ebE!4-dVx~{guZE?m6mH1&}_~A zy(gLXWP;`%@apgV#&r>oox`^o^(R_m86Tp1ANQb{&)`)sn z!8UH+-usZWA_=>=B#T)`czo|xz9p-?msjbck$JlQ49mS+Pb|1+z5c25S!NYUQVfz< zB5|5{)x?W)^OGAbZ#B{hTY35CQ~gSto7FJ$W-)kI81v~TA>ag^)ibs!Dg+CyS(MrI zgg>e|!kZh>?4KK!t~CoDm_2^nHmWO8FnZ1IvYE5(V^Rk3`bMGyzqH-5&YQ;XTnNQ` z%9aL8^(U8xhqL5v`bfck)aKlst4xXOPfDcUw!bDw6#2ho4Lx-6fek?E@I!23&ygon)d)=2c1?B|;RL zN!SPY-#ewz-z&)pCAd z3Xkn-K1~fhO%3Lsw8CjQl%Di2~FQ2U%3(Q>^d0Q~)8Xg0Cro1#`En2AgoE|cD3cwQ% zi2dl;dzn^YTlRPsKorFol3`~t(T{3Tb$mJT2!BR^yj&eRDY4-@T@S0ACD`b)M@dj2b|eTb`4S5J;@U;%{OM9n?wAoYOvqpOCsTW)mM4r$`a>^Z<>0#^KT5X?LaodfU}JYIz{& zg&yJaG@i1Bqx8kPg>qyE&w_Kmm#gnac%ATBg<@&RJ+qUKr?cvYIo`JbLD9zU4PVYt ztJW#(5#X~+e4J32K?yiCPZdT=NG$8DV+0|B-Hv+vO2>VbL$_x3v@PB^IhfzSv>~JI zf74#?Ab+i*IC0PNm+Nfj`|X3fk5rCBl27pf0|Da;h*NFiyNa^LBaMkM`|-!&m0g^R zmVOg{7?0M2iAdr>T^%pTw`kLpA=4Yi{q%e`683{rhzIXx4^8h4CEW9?d9pWOTa_!l z()TXi+oE8LIH{Xi2L`xN#OT<}%nH_TSC`~BytQ2}g1O#Nrf(ay8;KoVxV1yl=x#xm z%5VA^TgOU@r>z^Fd*JJl;X7R_G_3Er*K*LfOh4^|TF9ApZQ03fYcAVb2Na;y5e@l= z@nCbH(28$+Z*}o4bNl=P>!83}R#PEtnz{WDa8W*1jC8#IY>3O{@=lBE4qg0zcxk5O z?w1x_OYkBS;VZGWzK>J#Dkap%x}QOt@Hv&c&8_#zuCY-`XU5wn> zV!CA~yFM)d;KgHmqL9C8r|YnD|Fs7_OkicBCc$*pspc+{VbgX6-Z3c!JS}e`*;JxJ zjq$yMDdFKo=-#Mh9Ff_L{)bUIK8&@4bbI`^0<0_&S>Htu@`rQ?UY#R&bsDv7ZCq=W zjw`|jL*31~v{sPfL{U#G_<)Yy);hr_s0uO5CgRLdn{FgRU^6R?9X%cM8eIC)e z7!Iqvefq3y)y0d`l38ER(WS~p3hFkyczEL9M(lO8_5GsC?V_C}qqQVO1Tm|jaDyNy zF@inpFdKCFvL7&LR$QwS{hG>=3c%A{cDmg3p41x8ZNM=R+aK0Q5N#&)X-asK_@ZXx zi|Pe)f1@#|Gho}tvWi62t!QGOldIr=sI>3A$lkW(X@n}(R46~0_&lCi<=EDB^J}eY z+`ymA!_uk8ViTndHm&b9@5d>G)4fnCYa5!6@yy^Ix-kX<6?^e%R+HHt(TCdbxj~Bps%JIu&VrWvR!KFvG2o6(t zwRg0|he4;2S&?@68wr8AskxWa@bwzfvV;W5c5dA0y-l|fa!A%f?&$H*4Bn|!t+&Bv zu(;JWP+9Mif$K_es!O;4e=X#Jk#c`0=2W#!s0dHn&~BaEB79Ao(Gj{b&W&%eRd4;l}^h;^rnbGyaEyLI0C z#%`$IGG9w<%QMST60t{LYwT0T!Nz6!i zI_g_OVwRTx6^CKOD9nX(pk4~_v;K}%w`f7H-OpJ+tjfmRNO{)SSx}dm-Ga;BF*7pD zN(A{coX02KWvPB=u4AQ2{_$K7OSi&C4W{}8);#@J>2LM8BJPUH!${B^y<7}Fe>}3{ zm?Z#j_%I5kB&)%T7k%Ymz`8So&DEy8E%+pNmig$4w4agwKY<&%yQgOXQlE)Jf@NkW zsHZn*u82N_ZpEL41Sr-k_1Sc~y1tsv1&D3{GYDG&xbGdVOWu-IQ9z{q=zu8|{V3x8 z@A;fZw-94=90~>jhJZkYLh0(f(8>KJ=pK8{90}$>^T6_}$vMbC&d|3VugB{yRUs3| zU44HCrr3H6?;kWDPHhOodvilwZgj9di^W(4go|0;G8V^~wSe_Yd#7eu2?DQZ^0ziy zNbu4Iw8m=KDbc{|pvm@kZ%u}X+g6SKLE{$N!7&fGl+(SFCY3K8Q2t+MvwS91ZL_e{ zlgip_DU$>%&!n<1LUqJ;yct>r1ReTM!Gvp z9sXu_oB(BDbyEN-S>JV?;*O=$!5<+C@GAgg6<;$0_cz7^4@k`^z4zASfBkg z{p>J$q+1ABsm7jP-MmfU)tb^g1&T#ONGuWd?$I4u?FQFu5{F2aVdkHWSK4uTNmzt1 zX=IN@k*|x3F<%xv?{e57?uoJ3@pRqQP~PNia&sM`@ zw7fYFdSvoBIQZm>8^@ZT{NL6J{c9@z!`Som0WFq8&S4{0|AAwCaq z(YG792Y+4fia}Eu;uzOE36CmjzW5?5_gaaoq|I@CMTJr4-~>}%OFo>tk!aUPXsdQJ}AckgM%pNcd>wxAv(T= ztcpDK<-gd>>xe96@&C8ojjtm3vK7u;K7)eF3?OlYR5xwDUxyF zrl_i_P6CJZA&ogx&buiks%eeBr$NtG>Ia{+eZBBpH6i6miJ0HhCf!ed)5oF26%fbo zaxxfQMwFy*+lMiQ8kIdfa}VA7OlHv71K`0#>2nVh>3xyol2n;gZ89Q0r{ishBjfFM zby|-97+DH@+Q5GnXZ8XsLfPowQG3jhM^&FI|25Amq_9)KiwH=w7{xEaU=`&vW-{Tv z|6JV}@}Y*7geVFSk|$XK-Q)kqIIlx#p-o3fc+-n-uPXG1CGCuwg!a#K=yFqU;Wa-wb39)+7pL^dXthgQ55XJu;IycHAxx~tmM(7Px zR&NO=p4DvFly6cN{I!468bImb}DG?W34aNVZhmdy<`7~Q+S#G3d}qPL=c$}-(7opxsC1&Sby@z#%}yx(z0(HG$a3ljN4rB`6x2w%#$ zJkhy3>qRFomOPqhWp*U(D{s)+HvgtSo7FtbvrLNW zt+BPjAs0gd3b10oD6tskf3NfAnJzCPA=<9@@hvK#i~M|7zRcgR1%3EP$~hwt$u zlJZ<)ylb6Pj6qYIFDmk-lhK%FS^^-pGeA!WnEGB4MgtJ78yRPS9RI-gUaTRRRH?TA z4U>F^7RH)?9s;7~;2s-rE|XHj$fWow6Azq{0-bICC^BpOR5@YbLJ z7+YY(S0{e;mpTWB7#i*+5tH#k0AO?5!C+?UL*MdAKwFK=R^(2RG^6srMIA-yXtKqSu`J3kH#b9`hS zMxVvyY$q)4;>aCx2)kO0WT3%{c_!jqb%e_dx=IyJh$vU(-rV>AfP&-ftld&VWmHf+ zo8?9Z+VyB(YT9l*fqRscA3ndpaKGu8x^a1x^vLp7O4}uqDX8`?3d8loT4UxRteo~x znymxintUm#R&5)$UM<-H1y^R`kfd)t^8f7KCtuTAZ3RoW&d+0_VMfJGL=F)Aq2DmB zuS@0c#3sP1913sDN;^~!bS7@+-?rC9Wd0QQOfypA*;5(6Cr64$oefT3{Xr)C9`ifV zeFBN@K05J2mLxFimKfkz|2Zz2!RaGLx9so`;7>nJ$$bGMs2K*jxs+2i@`=M`PUGWM zNI%vS;}*32*K9a90Wds*%A)`S@B>V5eC(d(5CMc@Aw8#e`WZpdX^0BV%kz=-W`nuIZm@)8zEqYG{PyF;i_WPgeL=85f+L*dkk z=*njRM7#cJFYBW37*m?_VU=loY*rkt-!Yse zK2`7ZMg^}tI)Eu$gXzvdx?$|R{s8E@5-9bWCyFSdn3IrCBmLXUMZ>ggRBJaZtIHB$ zK5E`4gQqaDUr~hnWZp!a3{^X$sn^}wS9(uT;toQu(#jct-*76{@#{rjX`<`)Ph6fJ zb~sK4oGt02jd^UHj6J7ex&H>rr-i}Q!9y^7TZCVoB<%y!Kxq(YMDg}9BwS<%l`-CR zLq=n1Ju=)R6#s;V&~V^OdoA3+0yRsZ%gWp^O1~9QxS(z;(O<;X@x~8qRMLT^U;GAc zJR`@O6f9#Jr%;HzZd54p96#px_x^i4wIW~dU92KbXgdvGE%oS#}*cL zPb`*bPS|Huz>v(ZGI+bDYb6vft1eD5A8LS*Ot_4w@>))Gd)JcFY1Zzo4E|t|HAyf=e_lt z*|SfW0)DZl3mRXR&Te`1nTDCp9z|r4NP1Fb8=q&w1~x|L=hBHQpn9Q5y3$Tt;&T!PY z(wwhec9}}Zz3f4$eA3gFxteH}&f0HID#{u?G5%4sLo9E+iWFDc2w@*U5CTX7@#`aXCsnUy8-);A<%&83+Of^@Y zLae=1mYQ~SB!GQY`0Rix#r>0}WZl`jv#65K;I`kvGrhD~isW+wW1EVY6?ZpXDFQ5X z-n`jY%Jvw(V$Koc;{(yPbO!I+tPe4L7F?7c7t-YB8!!ZBZeR;VncF&X%|WZ@{KuAT z_=xSsD?u;nH<|vU3<7sv=QZ!qGuG!#PoCppU5%(na9#2bXghl+*ELzY6*f+bQ{lY9V2`!Zo~X$uw|v+TZN?;{BMVX|IrvvdL|o0uNZsktO18 zhyPxP%V!mZftP(9{r8-ht&JG^d@EKDD|Ss6f-IW4*;zo!!MnBI#z2?wR3h$pa;@B) z#|CymICZgqpi(X|c{CHSq_RA8lEiRJZ&uqonU9Nq_|t*X-d&0%M0=C1u!}<*9NYMv zRcK63jVj6hz1y`P9sWWIzl*+-rF*hLJ4~$RO7X1w-mgbQ(GvHf?#C$nHLMX|>}q=8 zIsM^6v_^PoF@b_XkNt`p?;-#F<8jYVN;;o@Su5`Ex>ML%@oM3bF2=b?LD2kmU$G6?9njv*@1i;!U^r zB}?v2U6*M%M0&V5{>V){YOM#WBm0aVwL~jGFS8|>x?Q&|e_1ur_GN5J&ejLb`bDElfxJA)l0n;}fjN zH01qMQq^$`SX^YdRnQ(}vhq&+uO|vE#CbRz&_>)s9hXcCT}oF_$bM+_X1I!uj;^#> zp`Pr!bMmabH$!b~B&`LNSKmG*Hi>Neq&_(3x69vPjO%r}sBY7iREj)18S=+;k zZ*1##pA)g^cy6giw|Gmkmv=wBjK3$+@*)on{CQ)I(tGRKoN%hS19`Cad0#u;yM`bi z#P5gQpb~^-?%yu)>pkUe{_k&|40NSy@<@i^Ed^%D{#hA| zE)9?1I6c?A&=MxOSrX@an5{Kod)k99#k5(FE_h-8XdR(qz zcH?X1!h;_|E-I6?FazU4&^lEs!>d>Ycm1_6k#*FI4)@6)2U)~=$%RRmp%<4Jf;XY3 zR3F5UR={)<`4z@g?uk7#%<|^$tZdiFlM@f}+*~4eKp}wF{g}F|#ixB_J(nTe4in>- zyN6*t=i4Lo$9882nIM{YcimCK3n#D6LC;da@M^WG$#9a8PpS(oe01Vr_@df$VT;=_ z^gv)|*mK=yeBAsm9GfYG{kT<7prYwte}Seu>ksOS<9Ug|dY89^H7XQUXT4;Euqhci z5A`3X2k^jb{3zO){fiDf5Uaf?m7F5*EJiR76_ejNqvV-S*zP(=cWCi|@hqHXLN*BWR6e0q}6gI&KXKk@^qs?iHVY@>hyhJ2C z+GFKrK9t($DrnNwd2dA_hT8J8xaQMa?5241CYhZocR2 zj=?2ZOOTcRG2)?Dg!9;^B~(^*TC;UR)9$4QgP8>k`l(bVf>8P^Q&_uxmk1oAgI7uG zLPvgAXJ@a6t8J+gSc4CRY}FnrA`W#nxQ4oXGv3>wf3IYjhs={*ft`6h*AO&O<5-RF zuBT}du{EmS{-RVt_XSmU(jqh z+s&!`*$e{g^st2orhmx~K{?d>Ol?+|QRDI4D=!wVzy3S@d)N_|j6Kq1%C-x2PMb14 zja@&!z@D#+H*+DrI1!xV0!v0#{>7%VHp0~jiiz%tD^PG zS$73+7>>=&RorxycYD%leUELf+cLge{ebLWtasGAtX)jxCtRHoy3DUx)ViO6w@lRy zhZIZeFn@i49yEgc6vXv5Ay~;BUm=Qj=#|07%#!xWDqp7(QJSo!YErJ}%o1y`lz}%& zH*a-KNz6;fqRm`^;$%)FlL$IzJ|_D^0w#+(bs{V4k8g0<8{m-*Va6e2s|Pwl$ZBuY`ZFo~3l zChg<=#P8Luy_lmVHtZjSkLROU%zlg6fL>qGeq#{y16q;$QEC0bKg>&6H+P%W#nX<%6`pQQ`}Xbd=R0 z&uU|{@)yYdXGH!=%ULxN>Ugt9Y3cz9G8bhq_-G)!9cfHByNtNcbF~<6Pf&Gb;p^M_ zV(#+1uSLUgd#_6vi>}oJ(yik4nqJVfGj|UsnEuz}0u|dfLMGOw2Ogz2wnd6$LlgwVw+Cp#^k=@6->q=y4~B9rtMQz*YI7h==+nQlE-{5v&+nkp#i zr^Q13Ji=wHrbpJ_s6&J#NIJGmoO-adR%hbcbQEaz@0D2$#nMmLklQ6i?jmn##pI17 z+vrS;_=GybFZshfJUEqZxhgpDKkLb_-(3`&eKI?KR5uZ$30B28WpDILlXb4}VN!Cd zp!suE#jw&C1qQwqRsM%(Y~4LH#rj`-KV2*q#wB75CkGI79yGBmZSaC<)r28sD1Atw z<-C9m!NLK7&J)K|ts~O6imgCOs>^QW0E|rd(dqb~Ff0z*+bxa(d~Il~lc3 zSU_shTq5*J{P}p39jo6RxUUsqMzLRW3XUw$`c}4RO39hSnR#0}q-?2I{Uij@XKk7A zDd~#TmAg#uCiBc5{Yuk!&CI~YH0ms%niotbxiVe-=!?pH9X;_lE}?FlZtSP$E6aQO z=rE)q;+CveEhpd{MI(HLvt}xIRUZE6v10nv1TG^+I`U)ONrubkJ;*{X=gTIVURstT zJOxU>T#EJ{xp(<$KH26{2Q=D4Bq+k+YGr|a04wZ4GDI`YA>zKYKejdC!vb(iM&ijO(*nnad||}sbH>lf zNn(<7P+_i}E=NC?B6-Ho6n(XOpx#|fTl~}eBohIOu0phKs>K{d%bM?_PvWWobpCy&Cu<>=_9v?Ji-Bs4(-qm(7N$T%&xWAywzMxk|}RXF3P2KVdBb9243 zj{Au&E=QKO9WAW+^ee_CQ@4vbd#M!b3DzF1he@m3`hAku&b0v1P};Ql4qfzP-o}1; z{ZjtbDs)zcCgKm*lOUuHgA1Uej{NhF1gpj63kq(R`^EWqP~+7qUa`RUVZ9yV-H)ml zZY{@hcr8-deO6g3FN@6y;0=4DCogikiX(9U_^ar4cw3d&vmnejxD=4Z$8%5NcNBCq zww(}Otwy+CMxUHTL?-fF%_|(0PLrnFJ*d6qT;^AmH#io?GiK2fSa^hapQODzhR%td zte^1(BX!>)^4fuT2oFF^0}c2(DxAQC1Fi*A2yvy5aV$67=e zKFNE#jI0=D7Z+}BdUkr}_mFXg{ddGwlON$ts+Wjvm(M8^HW)aMTvp;WKq z1ITA%xcH3r7G%470IrWkkChP@&F5!W>MYgv^bDE6o5dZ`qvS+O%jxIAUQ%KDp+efI zmZas^ny}UFA|eNCMN1#PzI;5szvEZ~{?uCvEY5NUHPJ{5!TtY&zetB~_P=7KRjoT8 z@9cbxKdPYc!z0N;7fBAo^!*wU`qYOBiPa|~EO zcxoZ1TDeg^)--adnLr(Bn zI-<2+#P0iRBMU5Ud!=+-r-SnvR~Z%;YHJO}AfFp9N}TrRNV_jdjz8E)R0D7-&RU?c zd-`L!4N;w|$65BYN6}W9ed5x=;76js=PnbJwz813+a+bt;3mi0fwO<29$h4u@T}<$ zL3N3)Cy{UvH?-$|^~u8JBKODw{#NcHUJ;&@ey$Xst8L(~hkorRVLId`=cyAqnHEe& zo7kce?O9{ez3ju2pnhieCL4Oy5|}<1BN67^A4^=5eb~-t$E3u;sG>3_nJ@2Av`Y~Q zm1E5*y9-SxhZOYYMomclfr75#-6gf)GcaYszP3O+RgI}9&9aouN~a$t(BTvgR0i;d7%HS~KZcx$wmt6*f|={oa;joI!;I$+b9}k=T)eA1NP()vQs$2<0~O`{M+QS{sBsl%B`?m;rX=c)zavUKI!7eiXJrzK;Otk?KvC8psg4 zg_iQ|YJLHTeQ%jnqpdY%KxJCK5CMfAq-iRKG0CJk`YFwc#u{T~%FD%mYm$qQ%O~o> zeb@I|UbiYO)~B;S8ANJ|(5z>TQorN+qAO-kNv#3-DpfcH$CeG(7iGItKf#o`X1E_R zXFzA7~E>ft`UKTqK8gi5mMs!104mtiVIX#0$`fU_B zkTT2z_5($eY7h^a1{Ob*NT^c>i#k)vk4UYjjFy3N>&)EvIvcD;D@l-Kq?u=PeBXmt z$e7u0Y`+R`Y8Jaa}7qOw`yU?LA*i){w(UjVZ6vWsJD(($U6W2@eN>4FF5xK+!tRkH^`HVkS5nB%uf z!88VGvy!&sQUsnRcH6s!b#G4$zA0Ef-6akv`YQCGQn+CR16JZq#uT0Ngu|@-?N~v? z&v+5c932+O6Owg@^etMe2H9`iDd7kUC ziE!HSsISN6u9 zOI4h2x=N4qPcP>jcbfcqYTecj`GEhwZ%RS~0RARd)H(-s)0Ep*tkUnXsVYs`+@(h% z^ixMAdYb?8D=J=ec8CyMO&e^kyquI}=c1#Sr;iZQKw{r>aIR6Rd2zqp2Lo`Q!er86 z5l_!Nr{9A66Dhl{McMn>0MGZ{x=MgFu+ zfjVw3?2f2K!J`TP%&)7Z`)>M5P5Ilnpzh93b)@Bke5H}wNR5Bz{Ln|V;0v=V`mx(!QvnMWQ564atRXp@x zui-CsQuDf33#fB*9Kbeip#&ZMER{UySb zU(7F7cTq3m@0W*N)Hk3MpO>u}9URBT6Q^9Ha%dx?`PZHN|BTV7~2vNV2>*;Jl zF(10%`^F?tX*JvAm4uLN9VVMMq#c!u>z%fISZNA(gw{*Q6{zf<;mL*JfeOlmn+`|V zVw^B2Yf#;B8cJ#+SZOSRw>m^ADbF2|!|`^ZU>rL$Cb`p!xLn&@Fhcr=l~)=1$0I2x zvQbdcg>Wi~KOLnU8kL?8S!Dwcm(cp|0-b+M)?!g!Xj(qPh%AuNqpRNP%xu_IC~hWa zFI%AO;fiX*@0QKwZ)DWisw8}u>Hu2UZo$Q5ys?EPzP_ieYLw!mBRe~Lr_3M*DVS?$ zf_H$>3{@Ydz-lKM2GH6OXN`C!N23E{g+cUnx%S%XA%bOo#@2pY#+9StXk{N+Vi~Ek zp(JHa?kaz{`^&%jlEQ+lz9nm!BNI58li1xuPBHvNzOnKdu35Ofd& zrW$IrsSV{15B4gS-V{~^k*a96-O~qZ^0TZ)S`H9vk4=j7p^S9&B5@~TxwnU)#u(uh z(4tkjRL4egoS*)p-t;7Iuyk0VWOZzGp%TM2!E+qn?Ocgv(8R|YZA~ML zO|z^XP(7xs15Y>V4mA?V{bI{dsXP#6ZeT?8S#*Hcwr&CIr??rY5NW%{nBKHt1~*x3 zh6U*v&9}0#VIE-Fna6|{=NuchwC9z?(-^VJ(#1tB3G5+Hz0i&p-zGPG!#SQ`o|ewj zK#+s(2K17oIP#k^(!fXTLek!QC!)EwCfACM3 z;Y<_!?ExHSEKlEBO|%I8dQn0 zGIql50mEa{6F|qAiI~X7F0NpB{{a-n_!(uiU0f!*8N!0IEF23I{ooF2s=8%KR1Q<+ zhX)rSV-P5e8SL7)IkZ3Ju%$D9=$v@I*;QTdycY(mHJWtmaoC!dPum0HX}gmt8x^t- zG%P`0a1fNZ;qLdG?%KcPDIz!V^9;#W%>>AFI3$TQjHxws1DhPTEYEid{qEXm7htG3 z2wJN|;$CBA2A8S4Xfb4Tz8|TLjNi-+Z8XhK=~vG*>~~?U65X?T@o48`%?92F3*yfz zIXk`=`YiMv{rW_Lc;QIZIH6T>5#D^~6zw@*h3ZT3K@H9FRD9fA3%0Iz_|leZz*?tN z&O$`Yy~&`^mgn^!cw;A1)tJt-h3^UW<*7L8w?AFp^hrEh1J5b&t9RL{13z5}-8S=A zQY4hIdqA8~(sS09&cTl{w&LGd44RU8YR3dBJsbL$Lek8g%84Gq!S5q>QiZw`9%_b< z2t3Ki`J&2CPY5nAGw=4n6Q|e;;VMj%b}4EId;8(Nu0C2UF=Y=eWyJgPX)*)6b6y8r zo>nqq!}M-cEQOJ2fy88t)OwLa=SAqxvHhBUTsFzcO^}!v^JB~o4*CtG)^>8ICK-&I z=3uXyRjveIQe*oNdv(5pzUgRA$v1CyzW)-?rJJ?;7oV<Jz#aZ_U%W zcyk}81|l`h3Ypnc@84S&39XtQTr)gG)By6ydd>-s%mRhwKNVL|lK#k#-JH_|eNOb-hH>kT%OP<7nwv07Ng5?~A#8`8V!jE{ED?ot-5i`=GTwKZAWzb%fir1{} z#eHoc7H*L@O37wsL<7Y`y%>ABuc-0;OHKxM4*q01y{mNo^rKxnc_WU`wK1J8u>%(b zzn&Zi+V<1an9*8$a>EYSKWcZSA|Z>6jVa|`$x0N@HYKhbP;}Zw`qY-ny^jA*Yf8^K zo>@MW&I2U?I}~WqCKZ^kQXVqTa|u4%lG9)LS(Y#H0^H}P+v=>lVGML%SAECsPM#6n{s_Nk8(lG zUaSzR*{+LySn`ZO_DIyI8Qbk!qf>!0q7R95>xo-Hb-wv>Ei_MbGDXytn9l&)@ixK- z=UZ#1udkIVr3%FLr%uMQqt6-oeG2Y5vgC+NeOD6sON+*7l=St*Fmm^#ggt%)M!Q^Z zDC5O6McT(DyYzTMy@~3U!&fr@VuNQC|4_l28ftfs*slp-lue8%;~Y-dhl@NFg`^E^ z<4FCFFqB?3Q8K)b2G8X1v{rIMze>tt?kw_CJ`^eJTF@XAM6$6^B6*pn{j|6^}!WC^%v%e_Rv)2s4@ka6UOM zh}#`-!3l31Tqz6#6((YvBgcE&1W1C_;M+&=@;Wg&^F!Y7J)2F}!zeAaqP zR%s;7vgMgL5ynkl;l!>t)cUgRVY}(FFBAMk5G*ZM&Pw;~#S<^MT^+)>YaCCbw=`tx z2ELxVh{7EUaDd?HsVPA`CcOR_s8_u}UyvgqqJ$^w8trGm&=fh^|3{J*PL&)d$61tT z`pb)ijkQjVs`7brH=NpyD`;B9js9o}UHlXXd`!dgq4n1~;|D}xnEPJ9a+ra;5Mhm( znY-gzn}OT$nubfY-VUHRL$x3YeyrqDc4O@yU5C+_+=~)RaM?qhn529~Tbx(@_u2xw zgQFww*Lzidr0}Frg0)tVu%tzagkz*D!sV)}^r7EUwdwWM&S>@@1@l}E9(UaTnFz}@ zbw8O23*IV|;R6;TH?HBQhSoj$tu3raQS6V*$_oUWmC>)CLxS-ACD8gf&)RpX>c9D% zuA7}iB=TIWQ*8`Nj?x5kz$PgeD-AG!n9hWkOGZSL#PVMb<+r(PsdrjPbl1OhTUb~E ziuI6PQ@|(mh!HibZiSuEftVl3c~oM0Jqr(M4vv-pARuAMR}|5{F8fBPkNN}6G!j2( z-bk~~3;gl*7mYl5=RA?!=zUjKC#7}^KEK11Z!4LJLKD05B8X7=XrY*)I0ik$0lP6T zjxhp{41~?q>F)zSk%6Bts4VhY(K=B1S1RRPUv0WL18%f8T7clkZz&k*Z;3jnkMc&M z-k}wB{BPD|!zvcsj?J!hba=Q7B+OF}%5BQE6M+Rfeg-UNX%TjZs8I zL}xc5I>Om^jUU&_4`7~uKce7Yzx*kD0L}d#zAW~;?OT)9{$1c|8@e;=KF>Y!q-n%Q8S_A^_kn5P(L3Dig!{Dx5jy zeBETvc{mv^>*QEd^Sq(aGl-SGk+fd8+FKqjXw)5CZYcCzU7*(O9F$MjyMnjM$j9gx z@jF!Xvfme{#=T`|Y2JgoN@(x0@w8NWC)*ZJPClDoqyLxiL*hX=)+vDGmrY5Jn$riP zPfxHm%&W?MCbqIEJk?+#N(E;sAG4WYk&%%XClSBCjyrGnK49OND3{MdQx=XCMAcsd_nZiT9kHJD$!i<*0+EKq10^jmS!;BD&JJ1|5tSy8 z0@P{kz|jE6tNdqxGF&7PRGZLGB1wgVd?n&dnNkhT8|JMOv6kE<52!uDf#&beR2rM+H^e2)J|QyJ%OeY*8CxN6a4CS+?5*5)0j= zRt(&iXtG6{xg>iv9OT^!>|fyZSp2of|Kc|U_4d ze{(>EwBtn*yU@2I`!239LJnBNkI`%XMcwRc9-}ke67o_2X#|OMJjC zoZL07EmRh6{%X@hUC(*Nucm4lWgwYUfk+ljZVhnCn z_U}8Mf1GKi3P!-e^~hB@_EDbwgWsSdqLg?j%Dv1&uQ=_Y!1egdRQ!e#gSOWp2kRLK zIlTudgV&gXa?f}_O{uxGccE5Y0oc&9V`CFj*qsx47W^nCksp#W!X|f6SA&e0v zy7t64QR~cWat;sWv37S|ce*&UyVeWtj{G*ojGvvcIR`A@K3s`AuP_s<8u?X*mhsq3 zn8NSV8%IwMAI=DB*VBtb*oAEiaZtpVtK3I)b>S#Adrvbm!d^$_ZVZ`oqz zPyhDFR9$cQ8g#p-?}()?!qOtQOosil38p`gzICFp(kIF>R80Qg9h#YX!LzMywOp+DbcpK@p)Q94h^mnF&ITfkk+Pgd#5>?wW+>y;}K(US3_u*n>w;=Pi!ix=Wbv0u5 zqI&7&kGVgGJk_LJA*Qo8azg(wR`jB$lLR`*?{q=iU9A>5UzP#~Zj3rFEeMOicuz(V z(~n^(B6KbBe>&ok@0I*QGz;z<9d3S1Fb5v+`Rx9Cl zv^WK;J!#vTW;Cxis5!Dyk>ebGN`CctA4v>5#t?3w&RqwLujZ1mSTGuc(YJRK`%Nsl zTAE^{BVnGk%VUh^t@^adp7;#!N4fXkPSDr{w%JV-&i&Z9Zx6yWmK_t3Eu0xRmI+s) z33W5!u+;apXvDf+ZGtg%1A_KE=aU*&6Hg`17XpqhCZ2AcZA09}2wnT-u~_WaIWr5g znUoY?eksm)_pG_Z5IIp|YoYnaJ>zvmu+E`-cfRRysr(gJG#L>!1_JyJIA?#nJsiuh&vt$+-x7h|-2-z!T z28NkS%Q3ZIVWm`@Pda>P$%M$V zqX9m-0Gn%lJerg+i?BWO+8Fs;nE(FL`UZG<=I60n7w!HEfxdrnFf*_7rO46}hGxc> zhWOV6jgXE{7`?rJo2`Yuenf4p)Ah|SxW7Fl7kg7Qq6_i;NeQ;F`&_aSCVXdIPT zD0`%IZGZ#KBcHQ@ANBo-N84|m--bQvw_Wn>k;nhdBFUJ>;f{xh@)P@aMj+l}ExjzX z7g)m~Sn7r2*s3=mb+GcZki;t(FfA^vs?8s93DVn=TNJvKr%584bhZHN`Iw3Pn~hxJ zRLGZxGNu*vu&omn{C^so-)8>@Gi50)XLXLapT2b#a4H2k|`J21Z}=u)}c{l?zE+N|F|88+ZnN+dIqJA3bit}Ec;>e)8RPe5wV%W zcqjg-&;Etd!6{skr+IMYb`(a(z5RXQ_SJX4YT*4;3FBfU#~PNmC2Fq^`L^8KCY-JZ zl9968rW|Y=H}iq zh9FO{xq5-rT1S&FE0;@mw=pIsnzBoz6WstsA^UA9bo>rUo|8%wXU(;kE?+w2_~6D))Mtgj{~gx`jt}$$Pt38@@p(#8@ZUQ)+Y=4Q|QO=Lvl3D@ubLn zlIt3}#TZ_F#Uk8LRLUtUCPDU5hBPNMbfy$|$xrY(>>*0MslMLRd!6P#q4IzRzOXkW z>aAf_c|tl?b*FDEP?~@1@b#|42*v*($RYo;4*&C7heu(&(c!hY{?_4D0&H4SJbi00 zEi>B_*YkCr>{$X8eHdCt_;*T_|5|I#7C`L6u#21h%dtgnfkncn`0qRzlJH~lMe?!C zZ8KPZS4hZD>@&MA+$p{05wen1e=g7G#RX~d6EY4*AR|t}vF``2)GfGC)neLlsrzhe z>RNTbI_Y-as&!n7&*wZo`FENdawIQ^P59qv?$mLv9WfI{*|0X82ZMHx!jV-T!YcWH zu-h>_m1+pey1j``CVDr<$I&6Jz)bOQ(~DWw`Ydq8lCETmcO=TR9G@yG){opu67GCo zwU)<0*~uU;RwlX_wwvihgLzXOqUmood{y2EX}|^Nj9@ze?=G4{-KsfT23ra)UnE}a zUZ8THM)zFKb|)q{o%?-l-UBTS@?!~6Hryr(BUr|^x;u+O@qC#7Mq6un!)a@+K0sS< zXog3c{idxmkSgG`byp*2jdTU7?8F4^Mpn}{ZpW-BP_aj%RLwc^;gbpEsp_1?$cWYg zp;st}WO>kWT3|b?txR5ct)nt!la24>0Ef^*u;(=n>YP?{5MZ6X=yg4Ew>cUBbq%)_ z>=WoUn#g>M7j&odb1J0>m4WiAlT7}?80x}rf!!^F?BIVA*lpG5?=v+6N39EMXo7IM z4H3w(j#Ae-*8Y7nLHg~k>QA;65Onb{lqXJzi-Q5XQ;#OpiCW?UzaHdd_v^Uc5-Cbh zF0iwN--I5#b6uWnX)dOA2Zh}SqWX7VF+{}%kSFz&b5(y+4Ex60C~apazs55(P(*cz zuD{+OuWTkUt5#LGvDVlMoKgtANC}Sw)j{VFM!p(ukH*z_BmvjO5ckP?nAz|PakJr9 z73ZzVHKY$N?>u=QS`}tchQ4SHPp0}`!-h#eHn7oZJ^d>E1LAk-$oDE)lKtsygIjwp ze|e8p91R)B%xu-bhjW78%^64jI;q+3_{XJXr6&;Qy_WV$NpubT&F?CIbgW1#>mPkt zhI&7Fe-g^AT`Tn+)nODDlwrK1`I4^;9+#yXAOe%nz%a@!N8V&624~1g;4>79-2Q%J zeX18%{5H-r1!C8i-mFjLZ@B!JWHEIm#5Dz19{8!Ju;l!s^P-H|JEOd~a!bYu8dPpr zql?$@%fBsudehdRRFQ)C+ZtRWC4SvjL{E&riWh-$$CZLSM*y|jF1<}~)yLJYS?ams zzw7AV9@1*Knd$3$qpW9Ve`5k(;dlH)uR@%*;oS|a6&7Y?N&Oz*;2bdx`z9%26jjtc zf{#P%l%UwtL(N_T=YeoUNjCGb`vvY|5&y5KJN+Sl`W>h&unBU6?|)#`^NpWGnp!|g z_wdmy%|>GSo!w55Fd4fG|FA7>jo2WN8JOmNnGOm zdBj$(o$yVWz2YpGCgv_w!!*G}r}MR|{oHzv5954B#DhizeKL>y8TDmp+~d{c zGx#B1#=u+6CYl0_!$50axLdzh2$H@p4+{5I(=d$3d=t~fwc*x-#Xo*LR025KIeL|l zvSpxgxuCKIb2~T(4trb;chGDB-qPFS&3-3$koJwuNi9KS=2?|pQ2n@0=R9)1jyIk` zQc8_gU-wpx5rYYc6MqpzflTnJJ^Ov0KBYje zQ<`jV&)r;qAu(%UPM89^X=5_+!c+-Qj!ahVn^<14;h=$$udIW6yAthbPz-x4!L;`s z1c$UaGiA-y5nMyr4=F7W<%%cq#O!5dTMB=ta$GoQd3&lp`=3*JEG9d(8HswqqQi2w z$z3DKR2R{LMf7K&;vD3>p!-L&29zw8A_Ja;OMOQ2`_g}_+HjTohT+z?{4aWYgf5&D zAJ!CZ{7t6ON(*rsi23Hr<&)J_5)Di?%YwZ?9vP%fMpUq(A?r5UiTCVBsof~-@;S#p z)cU`Np~#g!6jqg+Iub9v9V`@P$2s|$QnAdT%o_gy`Ux830b0VM&ccBsK_G_ZbFx-W ztBr^O6(2u8ylMEuHOmE>9EoE7XLc=&w|Vvf#hSi$ZOqsgZ@z2eoB%dl(dO90hnN1M zEDX;-Pzct>SQ4t&zXQqYBjfUEh_j(|deb9E=@Bd=>TZRMgvH~VD%RlLLh-wO5V_0j z80ik2<^q5*AVwI_7B0$+7(d{J?D+jhD8W0R^fj9r4wA1c74qQ}UOmgmp#=QCnN;cb zl)+S6R2D)Xxw6+Pl|~1v&ngt%z9s|5t)2&v$b+{N+R>zXvfL)3!EFWiReb+<+sS6l z)pYz3ta|SZFXm!oplV@+>Cd4r)AZbN%b}YQj)_%>rpS;U zHS8&y@#(%3elQA8x!?)PV%&@cTC3~izW~QY6N+XXai^TD&M5HG_>GcIIiLfJw5T$8 zBFPn7%ojlu82wa(T9M#Y#a)63QZwg@zwoL|jWQxsPz8YBZYbDwv>kqyp#LD?|B|cI zn-3ogxVBrXYpA6xe~1XfwCiZ}VzD>2ev(w~6&HAOrp$XsKpo09jkFLD9klg|0h7%m zt2jkcqbdsR6$P$-?(4>umelsQ7OxB>R$q78TKhIpWWRbU)wzPb`GxD$=jFC9qft*I z@~ZV%zc^Jnf&aS9hXqCW2?B0b&+n2-z_tv1#?m*$4S_yPiie!L}jYBn5}u zz$8vCIkHw;)gC?{7othnowrk1_kn5ctpp7#C8dM_D99(uqxN0^vH5!)R#vL&f^Jpa z_|WtE++qF(8z*(?pE{JGb(l7bK=y(A-&J~8^gAp>SAQGl?8&`ujB|rWx;htX zw^0=^xR{iSgA7av@-&_fd}UUyjQ*C(rFAd))>%SsxIfK`s2(+F?nBjAx<*Pioc}2` z=RO1>FRgO>uqioW+xwp~3KS#iCJB4_rV34+JQQW?Y>ak3Y|6;xLIib<$znoSk;H>D zEdO7hA4xfx%MtojN9to{FgGjXK3!n?Ynu!wyEfvrJ!DliTOIhzBbO6dn*0UARs75o z=UG+6Ul~Ldr~NO&F}cP_SRjK`BlYhoqP8};m>WJoH)l)1wX;-JJ**w_j!v9x3~Mq$3W`^uMc1| zk$QU9izR+W@SoNh_!omMvbOi*`$xWBMx7_Fn22l>U%!bEpHTNnM~nh%Ux7OUsS3xH zqV8T3`)jgD|{^#bYAuAUNX z_NQXl_R3|hyl^@b+dy5eFr72Nr-5YMR$G=H9eO8nf7NofqAx|Yw3eY1`YEPFC1dr@ z0C4dTD{ItfIec4DkrP!6Fcu)jCvv4&9&EX^5OWlJ9F;z%(ouekNqsjjGwjpJvx>|> zv|RpHKn6a?#^)KA|L!U<8d;aD>_CDLbvX$WHv*4N#8wQ-&Xc*!Y|PQgx4-YqOfrNh zb=(^JC4z6$V#`VQAzyY3(7=U?SI!P1-{sXaHk^mCL4HnhF7;%-4^kmyhMT%RCzGC? zN*tht)>X-e+&gm6297wdm@k76zuSG&+!OpQQrS+8G;`skXF?!;Trg(!}CYTwV<|syn2NYy1h3;X8*u(yQ`=eLp6f zX~+3|zeZ#gm8gk9Yh@Wf&k^tttjqqS;3Nx0r!`=pcHgwfD51-3#2!s+Cp3z(-8|St z=t*}3R@$?R{h8(1eqx_VXAC^94I`p`=7>5CccWjR|9YTrDf;=jLjAVh;uaL54;*|H z)nmuU5hX&#opi6tgr3vEzDKh_x~pe_r7t+GlcQtYwf#sPm?XZwlDLCZ4I+eBJcIRz z+kLkZqI_gwxiJ}rX73j=SI=so))@)8hRvAjfRHFuEOP2K&o{2XQ-suC;?a!9D;P1s z%VVfOpnA!plhEennBFF}d>jZ0BIA&+Im4PK=&%k{NS(U4!`Wxcl*R{ zKFehD_jPgQtvxIyjAGvh{EoY5u7;G)9Z2q%yruf7`DEkj@yAj_E298@izM5Q@ZD2j z$s?_dvDz8^(jg*>>M)_`S|odfB`5YmT>Oqub&>)L-decD;vE}X;Zj#MzC^|m>CY9b z1PdHwox47R0<7`*%maah`(ykIgJp-7Op9oFq}gsGxWzoD1fQb$`ls`DH?s}oEHs?O z9eH*N%=b_fFfMiH_b-%BIWl_O61i*M6HaqpbROkXz_P|>HOMsdF6}HK83hpzNyB&N ze(`@GVguTr!ki2D&-jVxvd!#prGf51QIGJ)G*yT6fUno*D^EN^X7hg57#^D{`r|47 z>Pv0>WKuRQ`|+B<0n*zSWRCU^0Ri7Z%y;M^ZZyvSp|j7M!+a$y`3S=G>T@S9q*L62qo&_Py|QXV?_x&-57dq;@e9b%^G} zr|H1H@|zm2LPJ!`Ra`w+=#>i3J!lZ+HF}en`!!Yb+NC@En1E?!`9x|t*Slr~UK26Z z5{_jwT{glwb$N&U=A~&hb5N@At<={dJ}7AcmQa>%8kW95!kIB$@N65SzIn@Juw#R~ zGF$dt&U=a+`4kdwAgzb_yE()8U7RL8UaNw)N{t$y7)g`4UUktWOC(&pblom1y;#^F zOFWs)ci%BHIP4x*9Iy%BiNSra6c%pnF2>)Kh>pk6i3{3WOcKWh)y~`8fH@IpBS{jm z=y?LUS~<~nmrcH zU;?LpT9{N`c|P98rmCTfCcikA{VcGNffCiZ+}Eko(es0E#U?ZQ-M-xeV$R-uGsarr zxl5;_*<^yGyFG>jXR!0Z;`Vn)iQ?qvqd_CsuHPgCTHn7GZU5weYBfYNFFsi6;=KHk zs;qkuKNxVmItD|(R!+aDDOM>%MIt@6=kR9u-Nt?Y$j+DK-uG9>!1S-s?;Z>^8=>ea zNsAWun;MZ@8#Wek{3*vm4wKTdQ&jV8H0rXQzwgMb7D4Ob~s(P(8<(pr1sbG~qt?6rx$smg6{@KFz2icAaHP?B>{UM5e zOIW|J$%e|ZELKwl&EsKjV3u8`ZJ`rA!llZjAPVOPrt>!2E)3E)$;&S7(|Rw%o%_by zEZ^fG4lW;zOVJh@t6&lIznRyUCXCu_kt{mAY-wIUtvqVo{X&8Zs!aRwhsM>AgwnE8=#5y3!`Fp-519P z(W5qti0R=A0(}y3Y~H<=TsPFI;f+j|n^g4$rgYpIbFF2lMS)zCNo9C`u_}(765{5N zN3nKKO7NsZj1;rJfq(kW)%5u#mim4Zbsgy-x~}HwUTv9yXBAWbhvEGnO_^{oM#@cg z1;Ljz!aP3H_TwB{k-@U~gpkQ-^xeJ<(=Xpz6qiX#t+d;PsX^`?=W^z7x@#@aY+$G? zrMrpx&dJ%od+WOI!snd{&Lf3@P3$w*f97((sC)MLTt>duc}2Qwm~LP;}3|l{yPl7)Jw!wEuq24 zNGUlL3XDP#1xJf37iR(tLHA4k``6J2j{pV2& zpv$Vgo99Mh4Xt9}K|rw1DvBL`x|H%Ay29L~P~r4&uP}L0oZhuZsfThUL(c(P>udbP zuHzb%6Kf#ax)+PUVpdv1gF^b?zPjC5s(3YP5{H5ZS)owUIF-No6XlWlLt_Hjp5n)3 zzx!3QOl_(2aF%b}23AjNcx|_Qk0(VX@O5RqO7JMyKD5)Bu||RRd3*J9_Iy>JgKVVQ zPCQaKc)u3z+QiQ`0^jHK-N|%^++2vt?{l~)@m-nUOiPW$Q?vj5bP^YR2Fp|cu931Y zRHBEBsiFw8St_(jO4K0Kxg-xyt}pr^(j4xCjnv)Fv2JbnTj6XMTXfFJ_Z!a0Z!b)E zg^Tha=!^3ih8Bl7T!iIIH-luI7j+K4_^~$HU-qfT9w$;T<0iV!j?3pMF3hkT25<{i zXlvSMo~B?L`H#M5PUf%`GggQljs^ONE09U4Ey%0&nEo7Y@y zV^LRcZd6sH(X2@X!+mLIvzfZie(7T;nfjZTr%(J? z2V&!(e~sZhxkoG43k7%x?k_!lCvMU^Ca$a`b)GohNdf;cI{n_0liNM-ye5Z_^vU>k z9`XLU`T)kCmv^Kk0dV;hEGnDXxMd&Gqqf=f8oxCHMHj`lDl3CNT$k6W?6>j}7jhkk z9Fg#i-3I3KEsOAY%s1#;{%AlSKWn+s@cqH0XGUofUl!mZM6R;Ny#VHdW+m*cHXsf{ zbM5?dERq&!c&(G%z!;bgE?|g_a*UQyHOGs}ekB)djj<*JEi!ElN+d`ZLyN(|nin>U z%Qg>wUuIw)pL>E7sW5j&$My^;b*=0V$&*`o@{tM`B=-U)IPezACVC3Ax6YSo5X4n@ z?_q#jDWU;46#_*+&tx_SF>LorZ>?_*wOpwF0FS-E^9^{R^V;r!7wRGkG+22>^YI_~Ul z*{AlBVU^9GTf}|>M>?|j@IRTim1DOoPp=AyC&zH8>Mws*xh%ke&7N%kH)NasV9 zP!6x5zoOoHtL!Y#!sjZmS8AQuzZxPJdb%C<09IWgZdzGDnY<=a1EV06u$-8`e*In4 zAeyEQ@c$T@yIi()N-QWh(GWGVLz+RRNw7Qr)li~R5=Q0 ztJW%U1KK}^d%<4=HwqK7WG|XZ&3ezJ=D0h^7>%bEQVJ6)dW~7%>yV3i=5Mn6*)1h0 zT<1gZ&{wDq;HLLKm00>CZ*@XLtp@lg$Vfy>M?eNR0?y}ku~y!U467E?{e0eCFUh_9 z31prQoMgYT}Pt7xU488s%H4(+}+z^U)uwSg1QURl?~}hKE<2G!x0O zwrmIlI>E^HrLjJq-N`7w73@a@eT9$ar zv_Cg7X=m$!v4JIldMM=}Cxt+QTEcTgoTx)6%$7j1;X^;N8_>ZoWX29uL##axGY_6j zFNEosO76eYD`zIy7ANh_BPH(;ilS2wQATRcUljD_w)?D7jS(H2rZ|6n#M1`uMMpg^ zNwNr}ukgV>2n#4G1cqs*iE9~&8!oFepVBb64#4eY!Sz-TbEe1~BG)3)t?#Ay*wa!S zA}YhnE?CW2!>#q(3nv0$tEE0Gk9Y2;`|vwHbtMOMR(v;u@SYFBR$Nt%)5>SXZlENP zDD<_0_uAaYj|jsJ;WIw(sUTPHHQ6Nqv7r%=0*YJ!-BYvmghTOstXSfCLc<0ZScxr3 zB_d4ldwWIaz?~vJDl9bS+K+xgT~!M(;iM}t`{?htgR7{)tYvz6{nva_(k>C?y%IrL z3}*jJo{A~VmFn1?(hmc|G-H~=M_`@Z91+Tu6s9Kp6(dHRH8u0u3XV-!HxP-7k&>C4 zE+Z8el9`W(v2ay(I?yqqrdlDiZ^^3$=Ut|fP5JD53Vg{?THm%!R41}*Sxh9%O5Sx; zMa=~w5nk7lnqmUoKhSlmY>st%xVW`1tn%ze9#6#slgtuZ_HOgd_LOEfF?ecj-ciq- z7iHF21gZ?AYh_6nnEb#TzLmE!nUDK=>8x>5Z|AC2w-hRr|7)K`?ux-nh~BO4Oy}tW z{=oBz?tpHE?dkFA>GsZ(=ZL>tT{Qv|<6VS`_${%)0OQM=a(tF$dt0r`@5rLaCAlU= zf)I%c5k?=J-?wQ-i5L0;p8U7Kxh?)73XioFQ#}E1N4gZ?KXdWp$`5MZYQ$37HReVDHGI)|*XIu+ZpsV1UieiFo5>lgL3n z)uGHtINt5C&apduSsE4A(&zz$`*FN9{B-i;NX6sn>3cX7rYBX~QyWlZxx#@~zQjxj z-0U^iGDN9@%IP(lp{k4x8b5+fkiWAlI zakf`)s>_qpG9lM37``BU`dY>}kQ!=4eYJmAMzHYs4>C4=){f3rHOa zql=vf1Z2o+3(gO|^wvTs81?;F|8z9R5eB@2X;6CSfy8Q{MICGB>`(oV%_5S^T-=8y zl1Gp*sY_W8pUu`{4rg_=G|FnTVVaik1{*n>8yoMrWM}dfOx3~sZC~c{WvX_Q(ttda3c@mv$VcwOqZv8)``q+#ipF`TE-R@f)cU{cJFnZh^k4!wn`SRXl(ORP4iaFPcx|0 zGCCeh2PRw5rZ%FCeTjVX&^*!Eo(tmOAuKDq^DePTHhV>6`|Nw}tnMlk%DyR^+SoJ} zj;sna!b5Lqo8velhe?W{tl)*eyJdY>HPOWiv0UnyQV#9$0H#vkWB;Lp;ZKYB4JED7 zJUNwtGZn6d+NEp~=$U*Z3w4tBE>cx{htUcpM-waBZ(@zzSCsvx1L7^ii*&Hsq7OBV z%L3iP;idGz{PF4e_{!99Q?NXr&OGn;Js-o<-g%l{#ji=OYO3*7QVL@}SvD6HGNChg zQrd!rZe4G7mxjJ563i}eP{7PEG+PxDF_HWJc+|b){Y$n(efB*GTg9Og0;Yb!q`xGc zl=`Cpef$!a3&TSWZ3ms^TxqygScca!Hr}D70qDy3Gr}ZByXGb#@laWq+$C@f+bgmz zKz`rQOa#6(#*Mb`TS*T%K4J0gKJL2p()dYp@;J?1JjWh|){K8Xzs5piD4Kp#qkSl6 zUv4bNXq`Ne#+q3){(+6@VMVo0bog>Qe&vhZG=^!^n)<$%=k?W#hoO;?JtAMzuSa;E zZdqnB@TLU)`$IQ#@=Kn5rTPB&y|CfQuj$?tTjscqA#e2VQFd$-;!4J& zET|`>HrV%|O%3e{>Qf3?dY5gs+SFJ}i$PaN_m-SDEqlT8-RE|AbU8{%IfE`@(s2Zc z6IdQjMC+$xGDL3u8!tCcQNr}h~TsS1;Rt>Du#=A-Msy;(z5JdLAv|f^Y-p`kb`ZX1P(T3 zc^su+MI0+xnUosH(bk7q^o&_Zs*pzX!NT!-O?;2=JZ^rbJ=$N+w{7v>pZ*B8$T_Q| zIyTu;Q#V<(oox3;j!=eDC}Oglfoal4i6^!!RMKU;sPLfD{!q%)6Z-UmrGGfrM9WS` zD@5AoBKV5y&a_-zc;F;!* zySB$6BD1i(s7%uLYJ91&8vO_g0yHpKwzUclD8D?f?g761B@138Dn+So= zdTvey=7Mub4EyK@&PTEt<|m)4^96D%)S^J^X~a`YmR2j5^*z6W)P+!yb7r{JdDq;; zawZIN%U|lm5CYQdZcqHJo&{SscW=&SwmhHuawLwJ&T~??@OrP@gG;i%bw#9FQZR8( z1eny`K&nogCbA9)YBje&w--i+6OgeEew7+9Ut?|KDmGy$NzOmT3oO*=YeY|pt543I zVzy8M<5@A_xzf&>4J&u%UrC$T!$uhvm(~ko z@r<3(tB6>Nahh0;wk5=@T&Q|5)KnD&dk#1`W;%>2`kRIB&L7nr-`@=DvrLL2?b;`f zaV)>&>6flH`C^U76%!Kk>#MzhXshLQ{n%`e(;iZd*3SepL3k1YM<`%u^!Xk&`0MyC zyFG-jJoR1Qt&U`{>5TB)_m?R;3%y`kYKOBOa$XS7n;UHEy}CBlFCCf*9}0o4Qia@z zG<`jG=Dkl3wa^qK@xi`Il|@3(inDCSjHga+P|*ihT!dO-P*gVX5VqzX*v0R(!i`r% z?3;71-4ul81~b4Lq^(>yUf%P_X-3s+rji5&4MBfjDe!6@tE|lQdBqvdt}d{Tu5D>E z)*oNB7&@1L85g$1h`?zP3~!e9WiPgcl5OynC?3xn{g7f~Y@0-lt&_sBDt(OZl)9RO$Mmm&5_K3AsEZh@eJ88fzw|p1^vi3s~r-Ya>EH$^T=5+`@0{kbxkJ$aLuxbWVp{FJD29Qb+%i3zV1BUN83{oy-}pBR zbJX$`r*5_QsB>9H`5>$^lBuqkfiUxZ8iMgf2!52RA++*j4gPASK2H>z+8a`&3B15_ zCd*hv4sX)8069}Yv!e6suexyyi_RUY+kn0;Ukeno3E&lcnIalcg75IAGKst9)G9Mt zp+kkt5k>HuB%g9q*uG0H->*tGF~{>AbLiy7#II-5%u30`&D=}&R|UTL7mS2fVv&(@ zx9BsWVV2Ag4>&KH(myq-9UaA;2`ff!lM&`P%!zoL#)aSORJzb_ECh=TiMkZmX~Xr79hUhl386I5X6MFjejh{cppw5(O{ zp_j_Ei<<31tX^y!ZbzRTu~cG&CzkT6NEMZ#)Dg8Gsg4xeIyO!WqrYcaC+<&t%JsNvjt;c9rZDf#dhpGS z^Y)>N%0>*w$ig@2x7#%iLZ@e_)HW5I-GTYmmYmL1b}m8Dal(tUhu9hWN(UPK_~7oN z>JjsYjehvhzuwEJn9+3=AcCppD)^v^85KCefYsZCb`77l+lL^@5gn%_OH%7Br++)P zY>4YA^`r&n$@wIQn6x3VrxiIchNHcxQw4xI2zqqJ^8fuqHW9s>=$~J_S$X$+4~QSf zIDhu0cY}wEMP_lfbSyR__uN2R%?+RXKHPDo-sWM~_34uC$?bJb&#AgD-=3;^q_LX2 zl{O#_Q%r;utv~$AW#%Nm-A)6aX@kxRik2)ab6r{E&Xd~9yaPrQW~Xe5+EY(Hw{rv5NRrO(UOKs2YAtGAFSA7a8a2LGOp zPn-=&gjQ5q;uEp@U5d1Rb9uVbr6OOoPPN1IYa@CMvHJ$~d#NB(_Y&xK$zqbu{aoG> zf}U+LLt~wbj~eo!G;k@%?N3?&u7OC@2rUzhukU|{Gtn}vI3lwgX7#}U{E zRoQdqj7zhdj{Ynqxv@w}k35DLzUd_QTvi&3{F<_uSTX?>br+sG2(0Usu{ zok6^f^Aa?M!_Zo{a6M35&Mx>op2*gY{Q3Ns-FU8!snP`JP9Vgv7{U16OX`P0d-#_l zzDqh*bYRCQ7hf3y^gwFRc@V!SscSbqA>vSK>q2oZz1)0ruo!VW!!Vm*WiW>!SV2+T9@#i?JXR86;YDq{(Uik6|bX)T%C{kz<2kubQ2iU(wS(K)rgF|u+z!kSP5<4sx$dvo%XJcu{NgMY-?p%X;2il z19O<@b}einowSir7Ica(D=;j=pbxs2q5Z0kw(auZ$AnLZ@iRMkvN_(LtAmnRX}nL= zDcGkGXge!Ndfz*^%~mDGXK;Ui$)8=*T&Z$_nh)X!5w&RKU7<-Q=sFe<;He+nPt}aH z&FzQ@EwYux7oCb(W>0~yuhci88-qUYtV}=VR=Cu(Us7^^HKb`Ug!t1z-6~HrKi7WB zy=8|cD>Mj9MEu+A&CWBw&`=I%4_7@wpA@*dyQzc$shMinl`@@{%b|&*vgXs>*m#|X zoq>4JCL0u=W}C}U-R$z%dV00I+{`oqhoSJ&6c1C+`BEv3y7BG8;^sWiyC@ceWqYVj zGlH2=-oRSs%Q0;FN)wpQALbeG`0U*Js`WreYP!lg@>!GOu;t->`Z=WBcHjkPRaM=QHiF|Sz~Oo2z1SK$+WJlk5?JG`Zcpc=3lnEqP

;_u~Veu8-ls zrmJ@2{f<f)3e19Kl2G&j#Zw87|W&tSZqPSq}>e& z#9w&@e$-3RYj+EtoeiPy?d(7#?w_aLp*H)@($qD-@ILB;VbV-6%fcSBpjJrONk(!v zYbe;p6tP=%u5@-vuy13QOxX5{LoE)kvyt-5t4f|Oo7^5ab={SKNM9>|yDuCI;*B~}xJUwl)hMil}) z*QrtHtT!+(F+s@LaRrMEXIk~?6~qtlFp#!a{(SQ0kA&B~etrft$o{jyca$V~FXeKW zu&tGTuw$HlQ(+%bg^VKI_hoErJtaU2)&`4?oQ$a$gw^Z5a4Rp-U6te&zhzcVYcwan zRxrN(F8iSiMPRHhWWSNO@x`W2b-Keu(pIU-k8SB`qg=`@i^0@n(*5FGLx*2#Z+-%B z9RZ8g>Uq84<9RzXXZDnOcRT=R}6WDs;Wyc%n$pJF!*N5%!#RKwwU* zd+o7SHIjzS``9t0^KiL!#KYm{20ZfsYta`3jOSE$MOZl^$l2?uL_V{1=sc}o0yrOPcO3M;x;qBB2H;4i#RTgl}}MQGgSbp}@YkgB){M>FqBbgo>q zL}!<~cfl@}MC#|vVO2p@Q@=rVh7h#YqS5>}js6;EtM-OVgizscP!$__5r>glF{Cro zwS68|Zb}RdLl_824FzNSYgh+_n!{rC!G z5c3#R-wsf>rL0O`mpf?`zf_bzK~!l?@*C2Rb?%8%*w5rGN$hn*Ez00clr0;ot;Ia* z!=3%?fso;<}0qa_aPi; zG&k_Cx*o2o0Q&lA?aGt!(c{{7duYZMzU`DE21mxLn&2ha(&2`k^!WRl^b%|?{(3bcihP)5dbd?`0z;FJoiq`R&cp_*xejHU+@1srh^(ub$2~J zFBvGii3Qf{^M&$8wXcIA9>$^POx+`Zo>m{Aa#gbW()pra8%5ljh!$h5DCK^>6bx{C zB3b3BNG!RhG0mMv5ZNo3<354A=&Q*(rBY}RAwp?Q&lJHMkUYj)Y#Q1QGy-2gUkhkn2VV)=}w){&WmyhL%b z#$F5#W3GEQHR1Tyat2kB>A~W#p%fBvqx#j|0w4AE7#~R<6Gnx4!w|O>hz1Ed3n?mklDi{!KY>-c&< z>i4llwqMCuRr2ugxs*R$_t3T6*KIvHl|R*Qx7j~@^ci@Jh`e6TR+ zfZ8*xlWU}#{B|1023=N)2wPtFRm>I%Mrp9N`FQKLh{Nj8ofD|W1Td%aB+9%qIv1Lp zAi>5qUZR%$hw@cm1e?IKp2N!Kx~=Dxdbh{Zzyh;XVp7O%vVhNsaHs$)XNFDpENSO+ z&IaZ-sZu39iN{CS!I5M>^9HBv_1yL@amv6N1K}+A6c=!Um&eP`1J(`Y;%3<4G zcdX96Ie-1hz%axFlI^0t8(Eb(1a(DoR3M`PGCZ*K+0>Q5aNR- zsB1fg($%jTV+2WZ7ap>Z?eCX63)kl)qJGA&W{3=_J8`2du$(PpsRKRY`E9P*b# z?;sSdqvle^x#Qc3z|YhO`ise8bv-)4Oa!ET(OxOnN!#zBfq*LcMVi}b*Zn=W-Ca=; zG&mq^6uwfJ@3rO^U3??yMcH%a?)lE(pJl02zUo& zAsI+K4v8)a%?7_#L16g@^rbWb_N39+8))y2*TA|WVE*$L255l+9)Al>Ks^4XK06%$ z{1$la(Kqp+#OBWnq^y|s`_p}Y)Tb@xqY^C0XkXp*XQe9_qU#0Q$rPXk@NxW)4&4@O ziOOt8j;&di*--p;YT6#iRvr~Z<(+9oKKGZ`&4VF2)3p$sVIwtRns;xJ5^VaNhzcVF z*RtkAp^aQAeTrH8u&KBaS!x#;E^w?n_-;=CMn0XFRn0u|T&>2#OXppxYI8Z_6;hWl zTjl}9N6v{?g}T6&H+`97wossU75W*&k2n&EVry3$pofm zt7yz7trem^USSglnCLaF$`E>_(}?%>3Te|~26n`iRb_~~CBKdD?x;EY=}#~}seN%g z9QWQ^H1FW@=zM;ONQ0{A)B9VVer?u3s_t=O##tJiSO{00Je#mhW-nsbkeW?+gUn`4 zg9wbBao)dhbXDpz@U2b1vjG3e_x^*axN?7CYDfj4AQcn0LDIX@;ISPXy(zJV$Q;Wv z*z#h)~1dE>s) zcT|Kd!exyhuN-5*_n>>{e}UKUd;f%2t7li+WK(Tgd#y#eOUyS3bSajX&)2=NMqL*n z2l4koULO+byU6Y=^?+^^+e5;hcj2mOPYYMrqE-aZ+_ujG^^hMl2S$zoHCi;9RQ7ao z$A{s`ch7OYxd(XMH^}{AWc>4^Yd>p#v9jU51likV-Eg+kFfL%SY{65Vnn}LHKJAqW zK~-^i{!x*B)dEBuQdhqG&V~bfOuExZ;WtGy>RJB!aBWSOgfdpOtAfvGIE*hMIWikH zQMzXBs=|Or^Jao0{Q8Ka6Sd|wu+X*~eZ|ZKomwZ){|&fUNxuQNEa*IGFJT2YpgKuq3kGo;?0>Lgj?jKnSf?e#YEp|u;2_(>3V}jMH)8=xJl((Ye zyfH;Z*LFJ*PY2qxg7c{NxJb0BXvyjmFv-a*EfKQm?Ojp28blsF49|_IuIr z6}oEhaNP=B^smw~YMZEHYmq4|VKGQitzlix>)&|%@yjJZ4m5s%zIdofm_Yxp{_e3NNepl`m#=@2TJ9W2%cp z05hARY*+Cfm$Rg2_B-(F$bA`J5bEMeglWokz!|P>-;oV(o^ePgO80?DdUk#!3On4M zNcbV65ANkiXCsP2W^qTLZTB+KSBn}|)+ZlOp<(gARVX7aph8h({;1HfIosqvZnBzX zCrF$nzjRsjxV~j;+ibgbX|#D-Vk^D>W&E-|RL6nzTk5wp)HSYzk3~z%D$>bO(jvIE`Oyabr>S2C?uU(yi>*#(M`mI9h4-hD=T|AZr9}jLD{p*QJnSK z>yJ;&7+OaO44&*j>!Rd;X-~j>96XHKY(U1fRxLsJI33;JaMuoo3Gv$$=Gi!k2g!&n z9~s^oeGd;@?#d%EuneH~P(wW2Nq@Wx_OYyA$|9p{6o2bjz)V1V&;-2+RQaU=G*oNA z#sC&F(lSj`TS@RNVDVO?V6HC`7#BDNQq7F20DA$-a*OYB{26MAKqA+f;l}FI3(yD$ z9{(rSibhlk4$CXDlVlNie*na2P~iohu}%$_DS@~z1?A$sE+u9;(aToVS3e}k0X9wV z=BO>nLtDmM*8FVv?r2H-7SQU{*q2~>&cQk^!RF3sDTaa|w;-@(BSx^A2>sGfs7M)0 z1a`MiI2nj)JX?7En2?CDPzu{08TLE55K<@DzZRenvo=FUSlj8O#0kG z{;w#&m%FS$YpjFF<-@k$zpK!*N#U?8&pBJNwEyfS@Uqp5rw~w@8RqE16E=&(k0<&^ zNLMp+rh+nq);YdEhXn|re_{M*i}(yl;$^RzL~0iPvr@$nWt2}F(8icPt=QBO6%&IM zX%y4afFkIKWF#L#ugfWYy(1>EOhQsOZF$dvh@MSZJkv532~Ck=fLfZJ|3Qu!`P1;& zU1{*pOt(rqFDV;M&0dM6vR}Mjk-#lu*-CIU5=C9ryV{UN+!Fq?ybV6i~s3;-V9c6~Lylpaz`U`f6Re)F_f7D7S z-nYH(wQSen|Nnsaxpf(0u1hOzvnYB=UJ(pe^p{_xrSAnJ&d~mgvrHCAL?gTwlyKK) zJsQ8*_y1Q!!47c(WVt_6LZ^r)ct`bwTy@)fmWzuQlOEyJ6tX3aB&=mp0wsLgIY z7vADM*Ut6NCWFoS*@7X*{{{OvH*d$YC0}5Fj{I{ue>bnIT^1 zh;lY=9z1-Z6z=kKS@Mf&;J=E?@`6j$%1E}inMy9?7$%ZkoO?F73`kgm`Y17?lwNM( zR$@MvO2;9Q$Ep~0yjJX;yUwIMLXua$Ve;`r0glV|lSBGb#gHx&H!$iTi<_>ZL(?{O zo*JMMr-*3m1g@ zG#0P478K^g3^Mh&OarQf5M3$$q8P#3gECA&N!p8qM?}J(m}g)NRDnXR*e8znoiZD!7uf9p zW}*|P(p8)vwQGZ`cS{udLCCpd_V_zz%PhB8p?3sJ0h<9`D4vQ%DROxdVmNG^Y>?) zJW9j+3yJWh#-$H1XclQdiia7-FoPZB2LmPU@0I1l+l4kTrIFY)Wcg02rW|60?HS() zB^d{a$sIrFYav#&?&+Vg;LUhGESiY7%~&^7C+y`oWla??iB9`eSi=#c8xed57CE`! zWpKUtYP6a<)t9nc=|m}rRUw@k;Xw=URvniy+?(`vU{>B*U-B5!0m6=fH)dkis3Rkp zh}1IBqk`G^8XfIJ-RcA>n7V7ln@=>{5|ygDZz~fQ|1u&}h;c6{!D9_Buz_i<$RfuU zOkar|&_eePg1|ttdh@wz{wmQ&nB+apTXN&7a4Uq}!r-xrC5{mYPyC_MXO6UY3bs#! zlYt6U&E;WwFQ>K;nDGHVLzveN`?4tS{(%vw-L?Pm=<_b8reHrmFp@dr&{?PMHM%W+ zv}nRl!>XFtp^#SJ?eBr3)3uTVEQ+`M&+p>vG^&nIlPZirEPI{~qe=c;JKvP26VW=z z>;T||wkK7hs^@g$Z4k^++=$2S&-M${UzscrZv$R6%=W5*qE0O5DBcj*n%q7h0uDR` zpwLGCZ1&!~{Fg=XpGnnKu+$&^)N7J7|0Ovm|FgJ=ihj{pDq2`H#uD&$Vr(6bTT)|d ztUG6L>L29v7TD@QJ_YBM;n7fDh8OL`wVC4?*Gz^835p8D`<3yEy;Ad6*pJkB&A=^G z9Di==oFVOPRlXP4eqTZ=rR2SS2=b|?Vg{wTgTc9hw~y}t)z6P*o;Y0(R%}6;y;+p5 z)A`8OIx=Got02JNe-Hrt{XB&NG#rYb{sIC80E#Lwa-FP+DB4h_N9I$~ez**;wVR5y zi3<`^GgIe{aNs(DGk0JGGT1eiZb~Pe8k1exia?=Q)^e7q=U~6+2jlQxgo&0Li4fXb z_uA6tKN|nxDv=~927NB_1>Qh9O*hGSKag1#i^gqq7D-_k|?FoWX5q7Lz zKNrjv`#h)4COHzBxr5@t&l)SPCXvJe3e0Ovue7l>_XYn39DLsxuS8?7q)$yk$I?Gb z=?_#@=;KwNKKdC~GM?wwDMP9@(@cX?f+Rsw1Gx&wY;+M-tsx&>m}kbrtn5gRl(Iv; zqzVPmC#Hb9Ca?Tv{||MbFrgg_u&??rn?&V_Y9;XV{j90>D?^acptOmj!B`et`gKSj zpvo0u`nN<=JY7s9yXE0i$d2&#g4LLE|O8^J-H7ckZY_ z?-*qIU6Vh=;Np2Z2xr?!dg=#Zgj#~Zf0Y~0^nC@XizIMhW6t4qKFF$))(Pc@Cn1XC zcnD)TSDP*3X9^(uiUx;PPbpKZFRRL`m1T)ql*^OBmNFBrK~u~v|6IEtd^HeT5{_i2 z)0;)8a>pzske7#Py&lB;4g0`;Dh?DLY?3>|s8&yFsmL`5?7~(;xR+)Qu>I8Z#-VbU zm8u4ZXzAenZ~J748Fb<BX^ol$ z<4UrKMJHE${YNIi{e~Y$8t`sElWe%a@G18C&6y&@Pn7hl4|F^LNhm3)7FiUDYu^U%& zBvO7jK5{TVj|qIb%}`u&Tej1|E)yDl3@G3s>vnY^Yqpw>eNwd2g)ALHwHkPNj`X`IxP;2_LWs4U~ny}o!1(KC>rLVSC3HT__YJB}G=M(C z)IPeE1J+;Vo9!?I0)O0;x*dK+z&ezh#;%YP#bmY7vVd(u`}FEi8mC$Fr|fcgyW~@a z<@S;d2~d---h_^AF=dh@__d?Cl)n~eYOO$AmTc0a=AetE-9{EvVNhpUUnx$e_RxQ@$>)yUI{?vwS1L_A+hUn;*Zklrf< z(t8a0q~mU80P$X=o;&%hbXk71K zK2Ii*lmqq;Oq$4}SjMF%as_I(^XxOz-+%?AS0Y6uRBQV?DtEBjjCA$E!LY}B-2L1V zXwD{Xoq*`et9hT%;7P$r7ZcOL#^e%fPTZNiA|MMWy{AC}`tT@5*DgC|+HP2$x@IO~ z?~YonH_-U75G?O2h58n_kv5Y88&|s@@R9MdLhwM5z_;{7Zy13{V4%&~fsz@54A5s= zr|SC3QcczP9J2vpOQJ$Rpkd$zSm&uYY5%tR4%FW7#2+e{{*8@ykbg$s%7Y*NAB29fD7vYbnQ< zhK`zQ165V(KqXjDOIBEw1<%Q4!WM=SHx7(Y(9g%-af>rB)E588u^g`LNd8L=Q4-;= znw9JaYQ=KC;mguowmQO4VOd@h`IRYzl`M^_itU2*! z@(Ke1^D^=yrd2U;sq%uY&l3I^L2&CavRxmH9L0%>OAi~3e95a)6{QW^4xSqj&XRB0 zp%mpO$Gs3E-uB8&3-s>%W<5*SsbIh*hy5#>0MH&K2$L_MY=S%jABKbsHSBk$Wu3iq zj8F5dFGQtZd@M6vk$(lV$>gH9`iZIsSV1D-b_9^{jRpV20XaVRnQ*$RNThjT1DUGW z0ljmNc7*s50s$c)LyM?Vno$P42B^@`Dr4skX|G>yn9X-NjgITjYP`b+(UdQc|6)D= zh%Nk`W%#=<;ndsjD0Z@DJUh?9urq6uQW_F+(A7SG4Piug?sM&VA-1acQ*A{WfaUym8 z%J*_5oZBU*5U4CGs0ZJV2XPPVHZc_WLKc`ED0O`xotkNZFH4=swI6f0XW)Fhk=StH zLV~iS0=^B_@Y9?n14zjndpiYp(IJ7nJFPC3yZ@YL;EHRBtJ(Sn3JR{P$?J6bNG0Cz z*={zH5PjlcrwCHJDz44)GN;<7Rpt?#0JR>(#8%Ok5TRBnnoYu z;-=;W&^z`!#Nhk2TDMna1{fxv!L&^1{%SLrJ~$wpM%PtI)ttbVf!4B1GrtQkr*2~r zXbKZIwz9*m$QMPwu<)lZ__qjyz?&7mh$9dO;qAD;xLNVE*%`lk;|{bPK%G8F6I-1% zbpUH{e<0;kWW}3!wKuVV-kRutGqOGQ=0sQG(MnWxsygxGCTHA?Y2xDyV~WLezM)qe znQMNwp%-43vbpb4CvJ(!zvcTnvzv?=Y7jtfpjqr%I$cZb8epq}8W`q1A^(sJnyd~@ zdv_^FuYMfds4Vw5i1mb8mDVcNw-;kyU0gNhKbDr@3$MqON{5(Bh&F_+In6owMS%ubLsPRr!MMX|E?jH)!8y$Qx&0Jyx;r&&=`vlYn zz%~8q`esV~^S|mV#YqPiuj=zwdF%6~_6qNS^;iT8gzL=Kikz-lj!)<47ypPXAbvl7 zxScXv;eL4Rrz+ESzL`pEb-MvJJ#3FVfW&{8VwYo+(peh78@GOt23AgE9`$1(exs;E zS|_9&7&>#5>HC@mU=s&(r z4^oqZ)CEAAUT`@=x*t&#VIddg*_-B08rfnundjj;)Rr7()k!kAB@#&-O>T00>^~QE zxC}HSpFI&K<`9O;Xeqr&{VQbucS`SH^%2UHmdHidi%X|47nOfT?b$#vo9v`|OYEBq zD~}=1lMB0z$0jSy8z5-@6{~Gu_LOzxt23CpCl4wZejLc~3Hh~vYd)H^+~@$bTkZpw zt3lV8D;E6A<*el)=&!4vrxX9g^14Ah-VSMPlbet4>7<8k6nhItDcR5=ud%L<(+1)> zEMNIB0~T>e^ep%_>W@IpZsTwN#O1%2m1@%it`iweU_Ia)2+kGF*To3DxJ|DCLV0Fb zz&Z^5%LkYFM;Bonot3PlQ=R5O)u+%iFs`DEJn!)2oL?lRnnF6D^%{VvJcK`~JWx-Z zx2D+t-&CGM>jl9{aaJJb$93xWz6z%w)OPk4oWxmD)r1;9HJ)w9<5zG0VmZ*%c@C4K zak}x{1q{hd~`wv@j&PC2H7-xqsG!5 zubNA#2vp-Rp;_6cDCw;#ZcVBN`lyFh4+MVPFXvH8GZwVkUUW?#CepH}aFi!P{80FV zbPXQAkq&Ko3<&L^X(L!wTt85N{$#M(3tIhQ_@XQ_Bd=!#A ztQ^l(E^4O8i{g_a7T3w%2&rhxLz)|=fpxkQYAd<0!i{1QCY9faCQ0;hws^j{4%DJI z#!f{!og!Wv6LaBb{4u`_Mc`XtjqnJl{cil`8!m8jkl$H=<=+v7qCVQ2S{8ps2eX;3 ze3CX=N z3q;{r$UFm$Yfz?wKy`nCf`pB`@#kcGPOeE+v6A{g_8BPBhX?VPs)eD^_os=k5IDfI zFq$K_|B+AFnlmKKeJ*75s#2NPIs98?BGS(470RE$pB3;`o#rgd(e4Fyg!(T{8%r

V)^Sn2-P#`&6{SH5X=&+}66ta%=?>{eN>ULJ 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 Y0@!dqhC1p~b)xqv$S6yfNSXxxKgjI|8~^|S literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-messagemanager.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-messagemanager.png new file mode 100644 index 0000000000000000000000000000000000000000..45fe8d011c73a847f786337efcd08f0062b82b5f GIT binary patch literal 45095 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 l5)Jl~o}r5@{0;mE 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%0AD(?ArVwH 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 HEzkc4^&is6 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-subscriptionmanager.png b/james/apache-james-mailbox/src/site/resources/images/uml/org-apache-james-mailbox-store-subscriptionmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..5133658e934bd9985ac2bbaa64459d0f494a92dc GIT binary patch literal 11997 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 literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/resources/images/void.gif b/james/apache-james-mailbox/src/site/resources/images/void.gif new file mode 100644 index 0000000000000000000000000000000000000000..75b945d2553848b8b6f41fe5e24599c0687b8472 GIT binary patch literal 49 zcmZ?wbhEHbWMp7unE0RJ|Ns9C3=9Vj8~~DvKUo+V7?>DzfNY>Fh|Ltj$Y2csQN9XW literal 0 HcmV?d00001 diff --git a/james/apache-james-mailbox/src/site/site.xml b/james/apache-james-mailbox/src/site/site.xml new file mode 100644 index 0000000..0ab1cd4 --- /dev/null +++ b/james/apache-james-mailbox/src/site/site.xml @@ -0,0 +1,69 @@ + + + + + + org.apache.james + james-skin + ${james-skin.version} + + + + James Server + images/logos/james-server-logo.gif + http://james.apache.org/server/index.html + james-server-logo.gif + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/all-wcprops b/james/apache-james-mailbox/src/site/xdoc/.svn/all-wcprops new file mode 100644 index 0000000..da69c97 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/all-wcprops @@ -0,0 +1,77 @@ +K 25 +svn:wc:ra_dav:version-url +V 61 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc +END +mailbox-memory.xml +K 25 +svn:wc:ra_dav:version-url +V 80 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/mailbox-memory.xml +END +mailbox-api.xml +K 25 +svn:wc:ra_dav:version-url +V 77 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/mailbox-api.xml +END +mailbox-jpa.xml +K 25 +svn:wc:ra_dav:version-url +V 77 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/mailbox-jpa.xml +END +mailbox-guice.xml +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/mailbox-guice.xml +END +mailbox-store.xml +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1165827/james/mailbox/trunk/src/site/xdoc/mailbox-store.xml +END +mailbox-tool.xml +K 25 +svn:wc:ra_dav:version-url +V 78 +/repos/asf/!svn/ver/1165827/james/mailbox/trunk/src/site/xdoc/mailbox-tool.xml +END +index.xml +K 25 +svn:wc:ra_dav:version-url +V 71 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/index.xml +END +mailbox-jcr.xml +K 25 +svn:wc:ra_dav:version-url +V 77 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/mailbox-jcr.xml +END +source-code.xml +K 25 +svn:wc:ra_dav:version-url +V 77 +/repos/asf/!svn/ver/1147424/james/mailbox/trunk/src/site/xdoc/source-code.xml +END +mailbox-maildir.xml +K 25 +svn:wc:ra_dav:version-url +V 81 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/mailbox-maildir.xml +END +mailbox-hbase.xml +K 25 +svn:wc:ra_dav:version-url +V 79 +/repos/asf/!svn/ver/1326982/james/mailbox/trunk/src/site/xdoc/mailbox-hbase.xml +END +mailbox-spring.xml +K 25 +svn:wc:ra_dav:version-url +V 80 +/repos/asf/!svn/ver/1180347/james/mailbox/trunk/src/site/xdoc/mailbox-spring.xml +END diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/entries b/james/apache-james-mailbox/src/site/xdoc/.svn/entries new file mode 100644 index 0000000..1d8fbc3 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/entries @@ -0,0 +1,436 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/src/site/xdoc +http://svn.apache.org/repos/asf + + + +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox-memory.xml +file + + + + +2013-09-02T02:54:38.000000Z +5d805bfb222b818e209fa7254e708474 +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +1371 + +mailbox-api.xml +file + + + + +2013-09-02T02:54:38.000000Z +3ac4977cc5307c1c5484d110c6f262d2 +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +3438 + +mailbox-jpa.xml +file + + + + +2013-09-02T02:54:38.000000Z +d9da004a70bee61ac304c8e8c54d1e91 +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +2243 + +mailbox-guice.xml +file + + + + +2013-09-02T02:54:38.000000Z +803b75d6d762ac920b699dcb8765fb1b +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +987 + +mailbox-store.xml +file + + + + +2013-09-02T02:54:38.000000Z +ed1ddd9c5073fb97f244f1c027916d90 +2011-09-06T20:27:49.923224Z +1165827 +eric + + + + + + + + + + + + + + + + + + + + + +2971 + +mailbox-tool.xml +file + + + + +2013-09-02T02:54:38.000000Z +6d196dcba389db1fd56e78ae4061f20e +2011-09-06T20:27:49.923224Z +1165827 +eric + + + + + + + + + + + + + + + + + + + + + +1075 + +index.xml +file + + + + +2013-09-02T02:54:38.000000Z +6e1fbcb3699af4a8d675e0330bd644ce +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +3412 + +mailbox-jcr.xml +file + + + + +2013-09-02T02:54:38.000000Z +c50a9372b858cc45c97d0619914ee4e4 +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +1445 + +source-code.xml +file + + + + +2013-09-02T02:54:38.000000Z +3e531b7f98f45d731c906689be16d5c6 +2011-07-16T12:55:32.527934Z +1147424 +eric + + + + + + + + + + + + + + + + + + + + + +2696 + +mailbox-maildir.xml +file + + + + +2013-09-02T02:54:38.000000Z +b7e29e5858da253b14d41e5bc05b6adf +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +1365 + +mailbox-hbase.xml +file + + + + +2013-09-02T02:54:38.000000Z +40b3c0c5e7e9fc125a4a76d4eb92caab +2012-04-17T08:09:43.782591Z +1326982 +eric + + + + + + + + + + + + + + + + + + + + + +7151 + +mailbox-spring.xml +file + + + + +2013-09-02T02:54:38.000000Z +441cdb21d7ccca1137c24576730602bc +2011-10-08T10:05:21.497039Z +1180347 +ieugen + + + + + + + + + + + + + + + + + + + + + +2662 + diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/index.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/index.xml.svn-base new file mode 100644 index 0000000..89337af --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/index.xml.svn-base @@ -0,0 +1,72 @@ + + + + + + + Apache James Mailbox Introduction + Apache James Mailbox + + + + +
+

The Apache James Mailbox is a library providing a flexible Mailbox storage accessible by mail + protocols (IMAP4, POP3, SMTP,...) and other protocols. +

+

It can also be embedded into your application to implement for example + the Mailbox Pattern. +

+

The prime usage of this library is clearly to serve as mailbox persistence + for IMAP protocol. The API and implementations rely on + RFC 2060 IMAP4rev1 + definitions and requirements but can perfectly be used in any other (non mail server) context. +

+

The mailbox library supports different persistence + mechanisms: MailDir, SQL Database via Apache OpenJPA, JCR (Java Content Repository based on Apache Jackrabbit) + and Apache HBase. +

+

You can download current 0.4 release. + The API as the schemas for the different implementations are susceptible to evolve. +

+
+ +
+

Apache James Server 3 uses this library to implement its user mailboxes.

+

Apache James Mailbox is tested and fully compatible with other Apache James libraries + such as the Apache James IMAP + (see this link + for the Mailbox/IMAP integration tests source code)

+
+ +
+

Go to the source code page to get more information on how to + develop on Apache James Mailbox.

+ +
+ +
+

Apache James Mailbox uses SLF4J as logging facade. For specific bindings one needs to add + a particular SLF4J binding.

+
+ + + + diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-api.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-api.xml.svn-base new file mode 100644 index 0000000..f80f326 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-api.xml.svn-base @@ -0,0 +1,84 @@ + + + + + + Mailbox API + + + + +
+

This module contains the interfaces and exceptions to be used by the + mailbox clients. It represents the "external view" of the mailbox + library client.

+

The services the mailbox library provides can be summarized as "store/retrieve/search/delete mails" + and update the mails metadata (via uid and flags)

+

The main entrance interfaces are represented by 3 managers, each responsible + for a specific area: the mailbox manager, the message manager and the + subscription manager.

+

For example, Apache James Server injects the managers in the POP3, SMTP, IMAP4 servers + and in the Mailet container.

+

Each implementation (Memory, + Maildir, + JPA, + JCR), + HBase) is responsible + to implement the management interfaces. All "common/util" implementations + reside in the Mailbox Store module.

+
+ +
+ + +

The Mailbox Manager is responsible for session creation, operations on mailbox (create, delete, search) + and to return a Message Manager. It also allows to copy and list messages.

+ +
+ + +

The Message Manager is responsible to create, delete message and its associated metadata (the uid and the flags).

+ +
+ + +

The Subscription Manager is responsible for the (un)subscription between a user and a mailbox.

+ +
+ +
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-guice.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-guice.xml.svn-base new file mode 100644 index 0000000..6abdf12 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-guice.xml.svn-base @@ -0,0 +1,33 @@ + + + + + + Mailbox Guice + + + + +
+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-hbase.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-hbase.xml.svn-base new file mode 100644 index 0000000..17e8271 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-hbase.xml.svn-base @@ -0,0 +1,132 @@ + + + + + + Mailbox HBase + + + + +
+ This module provides a mailbox implementation for peristing mailboxes (messages, and subscriptions) in a HBase cluster. +
+ +
+

+ This should provide an overview of the design and implementation of Mailbox HBase. + +

+ +

The current implementations stores Messages, Mailboxes and Subscriptions in their own tables.

There are: +
    +
  • JAMES_MAILBOXES - for storing mailboxes.
  • +
  • JAMES_MESSAGES - for storing messages.
  • +
  • JAMES_SUBSCRIPTIONS - for storing user subscriptions.
  • +
+
+ + +

Mailboxes are identified using a unique + UUID +

+
+ + +

The IMAP RFC states that mailboxes should keep message UIDs unique and in ascending order. Mailbox HBase uses + incrementColumnValue + int the HBaseUidProvider implementation to achieve this. +

+
+ + HBase uses keys to access values. The current design uses the following row key structure: +
    +
  • JAMES_MAILBOXES: row key is mailbox UUID
  • +
  • JAMES_MESSAGES: row key is compound by concatenating mailbox UID and message UID (in reverseorder). + This way we have messages groupd by mailbox and in descending order (most recent first). +
  • +
  • JAMES_SUBSCRIPTION: row key is user name.
  • +
+
+ +

Message bodyes (more importantly big attachements) sent to many users are stored many times. There is no space sharing yet.

+

Message data and mssage meta-data (flags and properties) are stored in different column families + so the column family optimization options can apply. Keep in mind that message data does not change, while meta-data does change. +

+
+ +
+ +
+

In order for the mailbox implementation to work you have to provide it with a link to your HBase cluster. Putting + hbase-site.xml on the class path should be enough. Mailbox HBase will pick it up an read all the configuration parameters from it. +

+
+ +
+

This is a onverview of the most important classes in the implementation.

+ +

+ HBaseMailboxManager extends the + StoreMailboxManager class. + It has a simple implementation that just overrides the + doCreateMailbox method to return a HBaseMailbox implementation and + createMessageManger method to return a HBaseMessageManager implementation. + Other then that it relies on the default StoreMailboxManager implementation. +

+
+ + +

+ HBaseMessageManager extends StoreMailboxManager and provides an implementation for getPermanentFlags method. +

+
+ + +

Message bodies can have varying sizes. Some have attachements of up to 25Mb, some even greater. + There are practical limits to the size of a HBase column (see + http://hbase.apache.org/book.html#supported.datatypes). + To adress this issue, the implementation splits the message into smaller chunks and saves each chunk into a separate column. + The columns have increasing integer names starting with 1 and there can be at most Long.MAX_VALUE chunks. +

+

+ The magic happens in + ChunkInputStream and + ChunkOutputStream that extend + InputStream and OutputStream from java.io package. +
+ Data is retrieved using HBase Get operation and stored into an internal byte array. + Data is stored using HBase Put operation and chunks are split into + chunkSize configurable sized chunks. + Things could be more efficient if HBase had streaming support. +

+
+ +

Extends AbstractMessage and represents a message in the message store. + What is important to remember is that the current implementation retrieves just the message meta-data from HBase + and uses ChunkInputStream to load the message body only when needed. +

+
+
+ + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jcr.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jcr.xml.svn-base new file mode 100644 index 0000000..e2e4f12 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jcr.xml.svn-base @@ -0,0 +1,43 @@ + + + + + + Mailbox JCR + + + + +
+

This implementation stores user mailboxes using JCR (Java Content Repository) technology.

+

The default JCR provider used is + Apache Jackrabbit. +

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jpa.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jpa.xml.svn-base new file mode 100644 index 0000000..3b8f1f5 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-jpa.xml.svn-base @@ -0,0 +1,59 @@ + + + + + + Mailbox JPA + + + + +
+

This mailbox implementation stores messages in a Java Persistence API store + (think data bases, but not limited to that). +

+

+ Default JPA provider is + Apache OpenJPA, + but you can roll your own by extending JPAMailboxManager and JPAMessageManager in the same way + as OpenJPAMailboxManager and OpenJPAMessageManager do. +

+
+ +
+

The JPA implementation uses Java Persistenca API Annotations to map Java objects to data base tables. + The current implementation also supports features such as storing messages in an encrypted database and provide + on the fly encryption/decryption. +

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-maildir.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-maildir.xml.svn-base new file mode 100644 index 0000000..fd1ac47 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-maildir.xml.svn-base @@ -0,0 +1,40 @@ + + + + + + Mailbox Maildir + + + + +
+

This implementation stores messages using the popular Maildir format.

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-memory.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-memory.xml.svn-base new file mode 100644 index 0000000..ce55ec0 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-memory.xml.svn-base @@ -0,0 +1,41 @@ + + + + + + Mailbox Memory + + + + +
+

Provides an in-memory mail store.

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-spring.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-spring.xml.svn-base new file mode 100644 index 0000000..f6a2a3b --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-spring.xml.svn-base @@ -0,0 +1,62 @@ + + + + + + Mailbox Spring + + + + +
+

The Mailbox Spring module is designed to test the loading of mailbox implementations.

+
+ +
+

The module is small. It contains just two classes:

+
    +
  • SpringMailbox - this loads the spring-mailbox.xml
  • +
  • AnonymousAuthenticator - provides a dummy org.apache.james.mailbox.store.Authenticator
  • +
+

SpringMailbox class is used to load the spring application context from spring-mailbox.xml + and provides a method to get beans declared in that context. +

+ +

spring-mailbox.xml contains references to Spring configuration files, one for each mailbox implementation and for other components. + You might find the following files:

+
    +
  • spring-mailbox-memory.xml
  • +
  • spring-mailbox-maildir.xml
  • +
  • spring-mailbox-hbase.xml
  • +
  • spring-mailbox-jcr.xml
  • +
  • spring-mailbox-jpa.xml
  • +
  • spring-mailbox-authenticator.xml
  • +
  • spring-mailbox-locker.xml
  • +
  • spring-mailbox-lucene.xml
  • +
+ +

Each mailbox configuration file is used to declare beans that will instantiate all components + (MailboxSessionMapperFactory, MailboxManager, UidProvider, etc) needed to start that mailbox implementation.

+ +
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-store.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-store.xml.svn-base new file mode 100644 index 0000000..3c84016 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-store.xml.svn-base @@ -0,0 +1,78 @@ + + + + + + Mailbox Store + + + + +
+

The Mailbox Store is responsible for all "common/util" implementations that can be + used by the various Mailbox implementations.

+
+ +
+

The main classes are the Mailbox and the Message. + A Message contains a list of Property and a list of Header. + A Subscription has a mailbox and a user attribute.

+ +
+ +
+ + +

All public and protected methods that can be used by a Mailbox Manager implementations.

+

You need to instanciate the StoreMailboxManager with a mailboxSessionMapperFactory, + an authenticator, a uidProvider and a mailboxPathlocker.

+ +
+ + +

All public and protected methods that can be used by a Message Manager implementations.

+

You need to instanciate the StoreMessageManager with a messageSessionMapperFactory, + a uidProvider, a mailboxEventDispatcher and a mailbox.

+ +
+ + +

All public and protected methods that can be used by a Subscription Manager implementations.

+

You need to instanciate the StoreSubscriptionManager with a subscriptionSessionMapperFactory.

+ +
+ +
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-tool.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-tool.xml.svn-base new file mode 100644 index 0000000..5000657 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/mailbox-tool.xml.svn-base @@ -0,0 +1,36 @@ + + + + + + Mailbox Tool + + + + +
+
+ +
+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/source-code.xml.svn-base b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/source-code.xml.svn-base new file mode 100644 index 0000000..459670e --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/.svn/text-base/source-code.xml.svn-base @@ -0,0 +1,64 @@ + + + + + + Source Code + + + + +
+

The available modules are the Mailbox API, + Mailbox Store(the base and utility classes) + and the different implementations we propose (Memory, + Maildir, + JPA, + JCR and + HBase). +

+

A module for tooling is also available. +

+

+
+ +
+ + +

The source can be checked out anonymously from SVN with the following command.

+

$ svn checkout http://svn.apache.org/repos/asf/james/mailbox/trunk james-mailbox

+
+ + +

Everyone can access the Subversion repository via HTTP, but Committers must checkout the Subversion repository via HTTPS.

+

$ svn checkout https://svn.apache.org/repos/asf/james/mailbox/trunk james-mailbox

+
+ +
+ +
+

You can read a tutorial on how to build Apache James Server on http://james.apache.org/server/3/dev-build.html.

+

The SVN URLs and the project names must be adapted, but the requirements and process are the same and you can inspire + from the build the Apache James Mailbox libraries.

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/index.xml b/james/apache-james-mailbox/src/site/xdoc/index.xml new file mode 100644 index 0000000..89337af --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/index.xml @@ -0,0 +1,72 @@ + + + + + + + Apache James Mailbox Introduction + Apache James Mailbox + + + + +
+

The Apache James Mailbox is a library providing a flexible Mailbox storage accessible by mail + protocols (IMAP4, POP3, SMTP,...) and other protocols. +

+

It can also be embedded into your application to implement for example + the Mailbox Pattern. +

+

The prime usage of this library is clearly to serve as mailbox persistence + for IMAP protocol. The API and implementations rely on + RFC 2060 IMAP4rev1 + definitions and requirements but can perfectly be used in any other (non mail server) context. +

+

The mailbox library supports different persistence + mechanisms: MailDir, SQL Database via Apache OpenJPA, JCR (Java Content Repository based on Apache Jackrabbit) + and Apache HBase. +

+

You can download current 0.4 release. + The API as the schemas for the different implementations are susceptible to evolve. +

+
+ +
+

Apache James Server 3 uses this library to implement its user mailboxes.

+

Apache James Mailbox is tested and fully compatible with other Apache James libraries + such as the Apache James IMAP + (see this link + for the Mailbox/IMAP integration tests source code)

+
+ +
+

Go to the source code page to get more information on how to + develop on Apache James Mailbox.

+ +
+ +
+

Apache James Mailbox uses SLF4J as logging facade. For specific bindings one needs to add + a particular SLF4J binding.

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-api.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-api.xml new file mode 100644 index 0000000..f80f326 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-api.xml @@ -0,0 +1,84 @@ + + + + + + Mailbox API + + + + +
+

This module contains the interfaces and exceptions to be used by the + mailbox clients. It represents the "external view" of the mailbox + library client.

+

The services the mailbox library provides can be summarized as "store/retrieve/search/delete mails" + and update the mails metadata (via uid and flags)

+

The main entrance interfaces are represented by 3 managers, each responsible + for a specific area: the mailbox manager, the message manager and the + subscription manager.

+

For example, Apache James Server injects the managers in the POP3, SMTP, IMAP4 servers + and in the Mailet container.

+

Each implementation (Memory, + Maildir, + JPA, + JCR), + HBase) is responsible + to implement the management interfaces. All "common/util" implementations + reside in the Mailbox Store module.

+
+ +
+ + +

The Mailbox Manager is responsible for session creation, operations on mailbox (create, delete, search) + and to return a Message Manager. It also allows to copy and list messages.

+ +
+ + +

The Message Manager is responsible to create, delete message and its associated metadata (the uid and the flags).

+ +
+ + +

The Subscription Manager is responsible for the (un)subscription between a user and a mailbox.

+ +
+ +
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-guice.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-guice.xml new file mode 100644 index 0000000..6abdf12 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-guice.xml @@ -0,0 +1,33 @@ + + + + + + Mailbox Guice + + + + +
+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-hbase.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-hbase.xml new file mode 100644 index 0000000..17e8271 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-hbase.xml @@ -0,0 +1,132 @@ + + + + + + Mailbox HBase + + + + +
+ This module provides a mailbox implementation for peristing mailboxes (messages, and subscriptions) in a HBase cluster. +
+ +
+

+ This should provide an overview of the design and implementation of Mailbox HBase. + +

+ +

The current implementations stores Messages, Mailboxes and Subscriptions in their own tables.

There are: +
    +
  • JAMES_MAILBOXES - for storing mailboxes.
  • +
  • JAMES_MESSAGES - for storing messages.
  • +
  • JAMES_SUBSCRIPTIONS - for storing user subscriptions.
  • +
+
+ + +

Mailboxes are identified using a unique + UUID +

+
+ + +

The IMAP RFC states that mailboxes should keep message UIDs unique and in ascending order. Mailbox HBase uses + incrementColumnValue + int the HBaseUidProvider implementation to achieve this. +

+
+ + HBase uses keys to access values. The current design uses the following row key structure: +
    +
  • JAMES_MAILBOXES: row key is mailbox UUID
  • +
  • JAMES_MESSAGES: row key is compound by concatenating mailbox UID and message UID (in reverseorder). + This way we have messages groupd by mailbox and in descending order (most recent first). +
  • +
  • JAMES_SUBSCRIPTION: row key is user name.
  • +
+
+ +

Message bodyes (more importantly big attachements) sent to many users are stored many times. There is no space sharing yet.

+

Message data and mssage meta-data (flags and properties) are stored in different column families + so the column family optimization options can apply. Keep in mind that message data does not change, while meta-data does change. +

+
+ +
+ +
+

In order for the mailbox implementation to work you have to provide it with a link to your HBase cluster. Putting + hbase-site.xml on the class path should be enough. Mailbox HBase will pick it up an read all the configuration parameters from it. +

+
+ +
+

This is a onverview of the most important classes in the implementation.

+ +

+ HBaseMailboxManager extends the + StoreMailboxManager class. + It has a simple implementation that just overrides the + doCreateMailbox method to return a HBaseMailbox implementation and + createMessageManger method to return a HBaseMessageManager implementation. + Other then that it relies on the default StoreMailboxManager implementation. +

+
+ + +

+ HBaseMessageManager extends StoreMailboxManager and provides an implementation for getPermanentFlags method. +

+
+ + +

Message bodies can have varying sizes. Some have attachements of up to 25Mb, some even greater. + There are practical limits to the size of a HBase column (see + http://hbase.apache.org/book.html#supported.datatypes). + To adress this issue, the implementation splits the message into smaller chunks and saves each chunk into a separate column. + The columns have increasing integer names starting with 1 and there can be at most Long.MAX_VALUE chunks. +

+

+ The magic happens in + ChunkInputStream and + ChunkOutputStream that extend + InputStream and OutputStream from java.io package. +
+ Data is retrieved using HBase Get operation and stored into an internal byte array. + Data is stored using HBase Put operation and chunks are split into + chunkSize configurable sized chunks. + Things could be more efficient if HBase had streaming support. +

+
+ +

Extends AbstractMessage and represents a message in the message store. + What is important to remember is that the current implementation retrieves just the message meta-data from HBase + and uses ChunkInputStream to load the message body only when needed. +

+
+
+ + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-jcr.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-jcr.xml new file mode 100644 index 0000000..e2e4f12 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-jcr.xml @@ -0,0 +1,43 @@ + + + + + + Mailbox JCR + + + + +
+

This implementation stores user mailboxes using JCR (Java Content Repository) technology.

+

The default JCR provider used is + Apache Jackrabbit. +

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-jpa.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-jpa.xml new file mode 100644 index 0000000..3b8f1f5 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-jpa.xml @@ -0,0 +1,59 @@ + + + + + + Mailbox JPA + + + + +
+

This mailbox implementation stores messages in a Java Persistence API store + (think data bases, but not limited to that). +

+

+ Default JPA provider is + Apache OpenJPA, + but you can roll your own by extending JPAMailboxManager and JPAMessageManager in the same way + as OpenJPAMailboxManager and OpenJPAMessageManager do. +

+
+ +
+

The JPA implementation uses Java Persistenca API Annotations to map Java objects to data base tables. + The current implementation also supports features such as storing messages in an encrypted database and provide + on the fly encryption/decryption. +

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-maildir.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-maildir.xml new file mode 100644 index 0000000..fd1ac47 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-maildir.xml @@ -0,0 +1,40 @@ + + + + + + Mailbox Maildir + + + + +
+

This implementation stores messages using the popular Maildir format.

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-memory.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-memory.xml new file mode 100644 index 0000000..ce55ec0 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-memory.xml @@ -0,0 +1,41 @@ + + + + + + Mailbox Memory + + + + +
+

Provides an in-memory mail store.

+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-spring.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-spring.xml new file mode 100644 index 0000000..f6a2a3b --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-spring.xml @@ -0,0 +1,62 @@ + + + + + + Mailbox Spring + + + + +
+

The Mailbox Spring module is designed to test the loading of mailbox implementations.

+
+ +
+

The module is small. It contains just two classes:

+
    +
  • SpringMailbox - this loads the spring-mailbox.xml
  • +
  • AnonymousAuthenticator - provides a dummy org.apache.james.mailbox.store.Authenticator
  • +
+

SpringMailbox class is used to load the spring application context from spring-mailbox.xml + and provides a method to get beans declared in that context. +

+ +

spring-mailbox.xml contains references to Spring configuration files, one for each mailbox implementation and for other components. + You might find the following files:

+
    +
  • spring-mailbox-memory.xml
  • +
  • spring-mailbox-maildir.xml
  • +
  • spring-mailbox-hbase.xml
  • +
  • spring-mailbox-jcr.xml
  • +
  • spring-mailbox-jpa.xml
  • +
  • spring-mailbox-authenticator.xml
  • +
  • spring-mailbox-locker.xml
  • +
  • spring-mailbox-lucene.xml
  • +
+ +

Each mailbox configuration file is used to declare beans that will instantiate all components + (MailboxSessionMapperFactory, MailboxManager, UidProvider, etc) needed to start that mailbox implementation.

+ +
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-store.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-store.xml new file mode 100644 index 0000000..3c84016 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-store.xml @@ -0,0 +1,78 @@ + + + + + + Mailbox Store + + + + +
+

The Mailbox Store is responsible for all "common/util" implementations that can be + used by the various Mailbox implementations.

+
+ +
+

The main classes are the Mailbox and the Message. + A Message contains a list of Property and a list of Header. + A Subscription has a mailbox and a user attribute.

+ +
+ +
+ + +

All public and protected methods that can be used by a Mailbox Manager implementations.

+

You need to instanciate the StoreMailboxManager with a mailboxSessionMapperFactory, + an authenticator, a uidProvider and a mailboxPathlocker.

+ +
+ + +

All public and protected methods that can be used by a Message Manager implementations.

+

You need to instanciate the StoreMessageManager with a messageSessionMapperFactory, + a uidProvider, a mailboxEventDispatcher and a mailbox.

+ +
+ + +

All public and protected methods that can be used by a Subscription Manager implementations.

+

You need to instanciate the StoreSubscriptionManager with a subscriptionSessionMapperFactory.

+ +
+ +
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/mailbox-tool.xml b/james/apache-james-mailbox/src/site/xdoc/mailbox-tool.xml new file mode 100644 index 0000000..5000657 --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/mailbox-tool.xml @@ -0,0 +1,36 @@ + + + + + + Mailbox Tool + + + + +
+
+ +
+
+ + + +
diff --git a/james/apache-james-mailbox/src/site/xdoc/source-code.xml b/james/apache-james-mailbox/src/site/xdoc/source-code.xml new file mode 100644 index 0000000..459670e --- /dev/null +++ b/james/apache-james-mailbox/src/site/xdoc/source-code.xml @@ -0,0 +1,64 @@ + + + + + + Source Code + + + + +
+

The available modules are the Mailbox API, + Mailbox Store(the base and utility classes) + and the different implementations we propose (Memory, + Maildir, + JPA, + JCR and + HBase). +

+

A module for tooling is also available. +

+

+
+ +
+ + +

The source can be checked out anonymously from SVN with the following command.

+

$ svn checkout http://svn.apache.org/repos/asf/james/mailbox/trunk james-mailbox

+
+ + +

Everyone can access the Subversion repository via HTTP, but Committers must checkout the Subversion repository via HTTPS.

+

$ svn checkout https://svn.apache.org/repos/asf/james/mailbox/trunk james-mailbox

+
+ +
+ +
+

You can read a tutorial on how to build Apache James Server on http://james.apache.org/server/3/dev-build.html.

+

The SVN URLs and the project names must be adapted, but the requirements and process are the same and you can inspire + from the build the Apache James Mailbox libraries.

+
+ + + +
diff --git a/james/apache-james-mailbox/store/.svn/all-wcprops b/james/apache-james-mailbox/store/.svn/all-wcprops new file mode 100644 index 0000000..2c9e7ef --- /dev/null +++ b/james/apache-james-mailbox/store/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 53 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 61 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/store/pom.xml +END diff --git a/james/apache-james-mailbox/store/.svn/dir-prop-base b/james/apache-james-mailbox/store/.svn/dir-prop-base new file mode 100644 index 0000000..4d86cdf --- /dev/null +++ b/james/apache-james-mailbox/store/.svn/dir-prop-base @@ -0,0 +1,9 @@ +K 10 +svn:ignore +V 28 +target +.* +*.log +coverage.ec + +END diff --git a/james/apache-james-mailbox/store/.svn/entries b/james/apache-james-mailbox/store/.svn/entries new file mode 100644 index 0000000..f9dfbba --- /dev/null +++ b/james/apache-james-mailbox/store/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:39.000000Z +d65e343761dda09662771328ceed121b +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +3380 + diff --git a/james/apache-james-mailbox/store/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/store/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..132237c --- /dev/null +++ b/james/apache-james-mailbox/store/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,90 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-store + Apache James :: Mailbox :: Store Framework + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-mime4j-core + + + org.apache.james + apache-mime4j-dom + + + org.slf4j + slf4j-api + + + commons-io + commons-io + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + commons-lang + commons-lang + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + org.jmock + jmock + test + + + org.jmock + jmock-junit4 + test + + + org.apache.james + apache-james-mailbox-api + test-jar + test + + + diff --git a/james/apache-james-mailbox/store/pom.xml b/james/apache-james-mailbox/store/pom.xml new file mode 100644 index 0000000..132237c --- /dev/null +++ b/james/apache-james-mailbox/store/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-store + Apache James :: Mailbox :: Store Framework + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-mime4j-core + + + org.apache.james + apache-mime4j-dom + + + org.slf4j + slf4j-api + + + commons-io + commons-io + + + ${javax.mail.groupId} + ${javax.mail.artifactId} + + + commons-lang + commons-lang + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + org.jmock + jmock + test + + + org.jmock + jmock-junit4 + test + + + org.apache.james + apache-james-mailbox-api + test-jar + test + + + diff --git a/james/apache-james-mailbox/store/src/.svn/all-wcprops b/james/apache-james-mailbox/store/src/.svn/all-wcprops new file mode 100644 index 0000000..f4d2e2e --- /dev/null +++ b/james/apache-james-mailbox/store/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 57 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src +END diff --git a/james/apache-james-mailbox/store/src/.svn/entries b/james/apache-james-mailbox/store/src/.svn/entries new file mode 100644 index 0000000..c6c7086 --- /dev/null +++ b/james/apache-james-mailbox/store/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/store/src/main/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/.svn/all-wcprops new file mode 100644 index 0000000..5ba7501 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main +END diff --git a/james/apache-james-mailbox/store/src/main/.svn/entries b/james/apache-james-mailbox/store/src/main/.svn/entries new file mode 100644 index 0000000..65aa438 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/store/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..5f1c081 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 67 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java +END diff --git a/james/apache-james-mailbox/store/src/main/java/.svn/entries b/james/apache-james-mailbox/store/src/main/java/.svn/entries new file mode 100644 index 0000000..02326d7 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/store/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..8ae8e21 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 71 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java/org +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/.svn/entries new file mode 100644 index 0000000..a4627d6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..4638341 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 78 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..5a498d8 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..f308a95 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 84 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..6d741f6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..233da57 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 92 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..d730d0b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +store +dir + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops new file mode 100644 index 0000000..99de238 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops @@ -0,0 +1,143 @@ +K 25 +svn:wc:ra_dav:version-url +V 98 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store +END +ResultUtils.java +K 25 +svn:wc:ra_dav:version-url +V 115 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/ResultUtils.java +END +SimpleMessageMetaData.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/SimpleMessageMetaData.java +END +StoreMailboxManager.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java +END +MailboxEventDispatcher.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java +END +MessageResultImpl.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java +END +SimpleMailboxMetaData.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxMetaData.java +END +AbstractMailboxSessionIdGenerator.java +K 25 +svn:wc:ra_dav:version-url +V 137 +/repos/asf/!svn/ver/1179502/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxSessionIdGenerator.java +END +MimeDescriptorImpl.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/MimeDescriptorImpl.java +END +SimpleMailboxSession.java +K 25 +svn:wc:ra_dav:version-url +V 124 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxSession.java +END +AbstractDelegatingMailboxListener.java +K 25 +svn:wc:ra_dav:version-url +V 137 +/repos/asf/!svn/ver/1476172/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java +END +StoreMessageResultIterator.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1442850/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java +END +Authenticator.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/Authenticator.java +END +RandomMailboxSessionIdGenerator.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1179217/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/RandomMailboxSessionIdGenerator.java +END +StoreMailboxPath.java +K 25 +svn:wc:ra_dav:version-url +V 120 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxPath.java +END +JVMMailboxPathLocker.java +K 25 +svn:wc:ra_dav:version-url +V 124 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/JVMMailboxPathLocker.java +END +LazyMimeDescriptor.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java +END +HashMapDelegatingMailboxListener.java +K 25 +svn:wc:ra_dav:version-url +V 136 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java +END +ResultHeader.java +K 25 +svn:wc:ra_dav:version-url +V 116 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/ResultHeader.java +END +StoreSubscriptionManager.java +K 25 +svn:wc:ra_dav:version-url +V 128 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java +END +StoreMessageManager.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java +END +AbstractMailboxPathLocker.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1452787/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxPathLocker.java +END +MailboxMetaData.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/MailboxMetaData.java +END +MailboxSessionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1476176/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/MailboxSessionMapperFactory.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/entries new file mode 100644 index 0000000..9cd5b8b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/entries @@ -0,0 +1,828 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SimpleMessageMetaData.java +file + + + + +2013-09-02T02:54:39.000000Z +759e8d77b4bb7e7fc7785f1a212250dd +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3185 + +ResultUtils.java +file + + + + +2013-09-02T02:54:39.000000Z +32e0afec93465be0aa1946ea97fd6844 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +10845 + +mail +dir + +StoreMailboxManager.java +file + + + + +2013-09-02T02:54:39.000000Z +5300b67e9b00192b11d8b486c3871065 +2013-03-05T13:19:13.802094Z +1452787 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +23942 + +MailboxEventDispatcher.java +file + + + + +2013-09-02T02:54:39.000000Z +bbb4e8e87d0cb1c2ed720f3366a77a3d +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +9825 + +transaction +dir + +MessageResultImpl.java +file + + + + +2013-09-02T02:54:39.000000Z +c9e5548989b7e0bf16e30b2c976b9a64 +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +13140 + +SimpleMailboxMetaData.java +file + + + + +2013-09-02T02:54:39.000000Z +9b6bc7d39d3cf40aad15495de19b9be4 +2013-03-05T14:22:04.421500Z +1452807 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +4142 + +AbstractMailboxSessionIdGenerator.java +file + + + + +2013-09-02T02:54:39.000000Z +240c096bea42475979ae66386115769b +2011-10-06T05:11:50.648286Z +1179502 +norman +has-props + + + + + + + + + + + + + + + + + + + + +1896 + +MimeDescriptorImpl.java +file + + + + +2013-09-02T02:54:39.000000Z +4577a56e1c8219027551312fed5bde02 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +13188 + +quota +dir + +streaming +dir + +SimpleMailboxSession.java +file + + + + +2013-09-02T02:54:39.000000Z +9dd8193475df3e30d0032e0979bfd660 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +5923 + +AbstractDelegatingMailboxListener.java +file + + + + +2013-09-02T02:54:39.000000Z +6520fa0c8db47a3174a2d85af1998cc7 +2013-04-26T12:44:30.285062Z +1476172 +eric + + + + + + + + + + + + + + + + + + + + + +7921 + +StoreMessageResultIterator.java +file + + + + +2013-09-02T02:54:39.000000Z +d41b90d82f0af50f9cd3293dd9e3f76f +2013-02-06T06:45:09.424765Z +1442850 +eric + + + + + + + + + + + + + + + + + + + + + +9721 + +Authenticator.java +file + + + + +2013-09-02T02:54:39.000000Z +ab951ad4ef47455183019a0af10e6ebb +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1562 + +RandomMailboxSessionIdGenerator.java +file + + + + +2013-09-02T02:54:39.000000Z +1027b71666d9b0548fa044c8811e1e11 +2011-10-05T13:32:45.491147Z +1179217 +norman +has-props + + + + + + + + + + + + + + + + + + + + +1708 + +StoreMailboxPath.java +file + + + + +2013-09-02T02:54:39.000000Z +7176c50b2299843ca7ec511ad5c4e6db +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1857 + +JVMMailboxPathLocker.java +file + + + + +2013-09-02T02:54:39.000000Z +2de9b706da79dfab23c8e51448ae864d +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + + + + + + + + +3030 + +search +dir + +LazyMimeDescriptor.java +file + + + + +2013-09-02T02:54:39.000000Z +8fc67d0678974c0b7d7204d3e67abf7c +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +5099 + +HashMapDelegatingMailboxListener.java +file + + + + +2013-09-02T02:54:39.000000Z +7ffd634f5980859003014c323fd0c053 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2127 + +ResultHeader.java +file + + + + +2013-09-02T02:54:39.000000Z +28c04026568c395546c564612093b7b0 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +2696 + +AbstractMailboxPathLocker.java +file + + + + +2013-09-02T02:54:39.000000Z +7cabe527980662aa7738b6f12ea54279 +2013-03-05T13:19:13.802094Z +1452787 +ieugen + + + + + + + + + + + + + + + + + + + + + +2575 + +MailboxMetaData.java +file + + + + +2013-09-02T02:54:39.000000Z +3be4c73377c6d8aad480ae478b56e2d2 +2012-02-09T12:13:02.344953Z +1242288 +eric +has-props + + + + + + + + + + + + + + + + + + + + +4498 + +MailboxSessionMapperFactory.java +file + + + + +2013-09-02T02:54:39.000000Z +ec8ef25567ca58d2a98196e2e132b546 +2013-04-26T13:01:43.322108Z +1476176 +eric + + + + + + + + + + + + + + + + + + + + + +6024 + +StoreMessageManager.java +file + + + + +2013-09-02T02:54:39.000000Z +8133f352f6549776b64ed3038d81af38 +2012-12-08T06:46:05.827269Z +1418609 +eric + + + + + + + + + + + + + + + + + + + + + +36563 + +StoreSubscriptionManager.java +file + + + + +2013-09-02T02:54:39.000000Z +32baf9205caa900db27dbca51419c75e +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +5837 + +user +dir + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/AbstractMailboxSessionIdGenerator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/AbstractMailboxSessionIdGenerator.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/AbstractMailboxSessionIdGenerator.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/LazyMimeDescriptor.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/LazyMimeDescriptor.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/LazyMimeDescriptor.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxEventDispatcher.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxEventDispatcher.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxEventDispatcher.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxMetaData.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxMetaData.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MailboxMetaData.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImpl.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImpl.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImpl.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/RandomMailboxSessionIdGenerator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/RandomMailboxSessionIdGenerator.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/RandomMailboxSessionIdGenerator.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/SimpleMailboxMetaData.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/SimpleMailboxMetaData.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/SimpleMailboxMetaData.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMailboxManager.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMailboxManager.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMailboxManager.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractDelegatingMailboxListener.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractDelegatingMailboxListener.java.svn-base new file mode 100644 index 0000000..e33673c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractDelegatingMailboxListener.java.svn-base @@ -0,0 +1,185 @@ +/**************************************************************** + * 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 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/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxPathLocker.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxPathLocker.java.svn-base new file mode 100644 index 0000000..aa7dea9 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxPathLocker.java.svn-base @@ -0,0 +1,63 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + + +public abstract class AbstractMailboxPathLocker implements MailboxPathLocker{ + + @Override + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution) throws MailboxException { + return executeWithLock(session, path, execution, true); + } + + @Override + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution, boolean writeLock) throws MailboxException { + try { + lock(session, path, writeLock); + return execution.execute(); + } finally { + unlock(session, path, writeLock); + } + } + + + /** + * Perform lock + * + * @param session + * @param path + * @throws MailboxException + */ + protected abstract void lock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException; + + /** + * Release lock + * + * @param session + * @param path + * @throws MailboxException + */ + protected abstract void unlock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxSessionIdGenerator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxSessionIdGenerator.java.svn-base new file mode 100644 index 0000000..a7b4cee --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/AbstractMailboxSessionIdGenerator.java.svn-base @@ -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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSessionIdGenerator; + +public abstract class AbstractMailboxSessionIdGenerator implements MailboxSessionIdGenerator { + + @Override + public long nextId() { + long id; + while ((id = generateNextId()) == MailboxSession.SYSTEM_SESSION_ID) { + // loop till the id is not 0L + // + // See also MAILBOX-149 + } + return id; + } + + + /** + * Generate the next id to use + * + * + * @return id + */ + protected abstract long generateNextId(); + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/Authenticator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/Authenticator.java.svn-base new file mode 100644 index 0000000..71e6eb5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/Authenticator.java.svn-base @@ -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; + +/** + * Authenticates user credentials. + */ +public interface Authenticator { + + /** + * Is the given user authentic? + * + * @param userid not null + * @param passwd not null + * @return true when the user is authentic, + * false otherwise + */ + boolean isAuthentic(String userid, CharSequence passwd); +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/HashMapDelegatingMailboxListener.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/HashMapDelegatingMailboxListener.java.svn-base new file mode 100644 index 0000000..ba08d76 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/HashMapDelegatingMailboxListener.java.svn-base @@ -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; + +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 + protected Map> getListeners() { + return listeners; + } + + @Override + protected List getGlobalListeners() { + return globalListeners; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/JVMMailboxPathLocker.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/JVMMailboxPathLocker.java.svn-base new file mode 100644 index 0000000..beed7f1 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/JVMMailboxPathLocker.java.svn-base @@ -0,0 +1,74 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * {@link MailboxPathLocker} implementation which helps to synchronize the access the + * same MailboxPath. This is done using one {@link ReentrantReadWriteLock} + * per {@link MailboxPath} so its only usable in a single JVM. + */ +public final class JVMMailboxPathLocker extends AbstractMailboxPathLocker { + + private final ConcurrentHashMap paths = new ConcurrentHashMap(); + + + @Override + protected void lock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException { + ReadWriteLock lock = paths.get(path); + if (lock == null) { + lock = new ReentrantReadWriteLock(); + ReadWriteLock storedLock = paths.putIfAbsent(path, lock); + if (storedLock != null) { + lock = storedLock; + } + } + getLock(lock, writeLock).lock(); + } + + + @Override + protected void unlock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException { + ReadWriteLock lock = paths.get(path); + + if (lock != null) { + getLock(lock, writeLock).unlock(); + } + } + + private Lock getLock(ReadWriteLock lock, boolean writeLock) { + Lock l; + if (writeLock) { + l = lock.writeLock(); + } else { + l = lock.readLock(); + } + return l; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/LazyMimeDescriptor.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/LazyMimeDescriptor.java.svn-base new file mode 100644 index 0000000..d72c9ee --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/LazyMimeDescriptor.java.svn-base @@ -0,0 +1,171 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; + +/** + * A {@link MimeDescriptor} implementation which tries to optimize the way the data + * is loading by using it in a lazy fashion whenever possible. + * + * + */ +public class LazyMimeDescriptor implements MimeDescriptor{ + + private final Message message; + private final MessageResult result; + private PropertyBuilder pbuilder; + + public LazyMimeDescriptor(final MessageResult result, final Message message) { + this.message = message; + this.result = result; + } + + + @Override + public Iterator
headers() throws MailboxException { + return result.getHeaders().headers(); + } + + @Override + public InputStream getInputStream() throws IOException { + try { + return result.getHeaders().getInputStream(); + } catch (MailboxException e) { + throw new IOException("Unable to retrieve content", e); + } + } + + @Override + public long size() throws MailboxException { + return result.getHeaders().size(); + } + + @Override + public String getMimeType() { + return message.getMediaType(); + } + + @Override + public String getMimeSubType() { + return message.getSubType(); + } + + @Override + public String getContentID() { + return getPropertyBuilder().getContentID(); + } + + @Override + public String getContentDescription() { + return getPropertyBuilder().getContentDescription(); + } + + @Override + public String getContentLocation() { + return getPropertyBuilder().getContentLocation(); + } + + @Override + public String getContentMD5() { + return getPropertyBuilder().getContentMD5(); + } + + @Override + public String getTransferContentEncoding() { + return getPropertyBuilder().getContentTransferEncoding(); + } + + @Override + public List getLanguages() { + return getPropertyBuilder().getContentLanguage(); + } + + @Override + public String getDisposition() { + return getPropertyBuilder().getContentDispositionType(); + } + + @Override + public Map getDispositionParams() { + return getPropertyBuilder().getContentDispositionParameters(); + + } + + @Override + public long getLines() { + Long count = message.getTextualLineCount(); + if (count == null) { + return -1; + } else { + return count; + } + } + + @Override + public long getBodyOctets() { + return message.getBodyOctets(); + } + + @SuppressWarnings("unchecked") + @Override + public Iterator parts() { + return Collections.EMPTY_LIST.iterator(); + } + + /** + * Return null + */ + @Override + public MimeDescriptor embeddedMessage() { + return null; + } + + @Override + public Map contentTypeParameters() { + return getPropertyBuilder().getContentTypeParameters(); + } + + /** + * Return a {@link PropertyBuilder} which is created in a lazy fashion if it not exist yet. + * This is done as it may be expensive to retrieve the properties of the message. + * + * @return pbuilder + */ + private PropertyBuilder getPropertyBuilder() { + if (pbuilder == null) { + pbuilder = new PropertyBuilder(message.getProperties()); + } + return pbuilder; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcher.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcher.java.svn-base new file mode 100644 index 0000000..bf0d7a5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcher.java.svn-base @@ -0,0 +1,296 @@ +/**************************************************************** + * 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 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.mail.model.Mailbox; + +/** + * Helper class to dispatch {@link org.apache.james.mailbox.MailboxListener.Event}'s to registerend MailboxListener + */ +public class MailboxEventDispatcher { + + + private final MailboxListener listener; + + public MailboxEventDispatcher(MailboxListener listener) { + this.listener = listener; + } + + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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); + } + + + + /** + * 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; + } + } + + 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); + } + + /** + * 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); + } + + 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/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxMetaData.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxMetaData.java.svn-base new file mode 100644 index 0000000..a04d2af --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxMetaData.java.svn-base @@ -0,0 +1,156 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.List; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.model.MailboxACL; + +/** + * Describes the current state of a mailbox. + */ +public class MailboxMetaData implements MessageManager.MetaData { + + private final long recentCount; + private final List recent; + private final Flags premanentFlags; + private final long uidValidity; + private final long nextUid; + private final long messageCount; + private final long unseenCount; + private final Long firstUnseen; + private final boolean writeable; + private final long highestModSeq; + private final boolean modSeqPermanent; + private final MailboxACL acl; + + public MailboxMetaData(final List recent, final Flags premanentFlags, final long uidValidity, final long nextUid, final long highestModSeq, final long messageCount, final long unseenCount, final Long firstUnseen, final boolean writeable, final boolean modSeqPermanent, MailboxACL acl) { + super(); + if (recent == null) { + this.recent = new ArrayList(); + } else { + this.recent = recent; + + } + this.highestModSeq = highestModSeq; + recentCount = this.recent.size(); + + this.premanentFlags = premanentFlags; + this.uidValidity = uidValidity; + this.nextUid = nextUid; + this.messageCount = messageCount; + this.unseenCount = unseenCount; + this.firstUnseen = firstUnseen; + this.writeable = writeable; + this.modSeqPermanent = modSeqPermanent; + this.acl = acl; + } + + /** + * @see MailboxMetaData#countRecent() + */ + public long countRecent() { + return recentCount; + } + + /** + * @see MailboxMetaData#getPermanentFlags() + */ + public Flags getPermanentFlags() { + return premanentFlags; + } + + /** + * @see MailboxMetaData#getRecent() + */ + public List getRecent() { + return recent; + } + + /** + * @see MailboxMetaData#getUidValidity() + */ + public long getUidValidity() { + return uidValidity; + } + + /** + * @see MailboxMetaData#getUidNext() + */ + public long getUidNext() { + return nextUid; + } + + /** + * @see MailboxMetaData#getMessageCount() + */ + public long getMessageCount() { + return messageCount; + } + + /** + * @see MailboxMetaData#getUnseenCount() + */ + public long getUnseenCount() { + return unseenCount; + } + + /** + * @see MailboxMetaData#getFirstUnseen() + */ + public Long getFirstUnseen() { + return firstUnseen; + } + + /** + * @see MailboxMetaData#isWriteable() + */ + public boolean isWriteable() { + return writeable; + } + + /** + * @see MailboxMetaData#getHighestModSeq() + */ + public long getHighestModSeq() { + return highestModSeq; + } + + /** + * @see MailboxMetaData#isModSeqPermanent() + */ + public boolean isModSeqPermanent() { + return modSeqPermanent; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MessageManager.MetaData#getACL() + */ + @Override + public MailboxACL getACL() { + return acl; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxSessionMapperFactory.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxSessionMapperFactory.java.svn-base new file mode 100644 index 0000000..31757ef --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MailboxSessionMapperFactory.java.svn-base @@ -0,0 +1,142 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.RequestAware; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MailboxMapperFactory; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapperFactory; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapperFactory; + +/** + * Maintain mapper instances by {@link MailboxSession}. So only one mapper instance is used + * in a {@link MailboxSession} + */ +public abstract class MailboxSessionMapperFactory implements RequestAware, MailboxMapperFactory, MessageMapperFactory, SubscriptionMapperFactory{ + + protected final static String MESSAGEMAPPER ="MESSAGEMAPPER"; + protected final static String MAILBOXMAPPER ="MAILBOXMAPPER"; + protected final static String SUBSCRIPTIONMAPPER ="SUBSCRIPTIONMAPPER"; + + + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapperFactory#getMessageMapper(MailboxSession) + */ + @SuppressWarnings("unchecked") + public MessageMapper getMessageMapper(MailboxSession session) throws MailboxException { + MessageMapper mapper = (MessageMapper) session.getAttributes().get(MESSAGEMAPPER); + if (mapper == null) { + mapper = createMessageMapper(session); + session.getAttributes().put(MESSAGEMAPPER, mapper); + } + return mapper; + } + + /** + * Create a {@link MessageMapper} instance which will get reused during the whole {@link MailboxSession} + * + * @param session + * @return messageMapper + * @throws MailboxException + */ + public abstract MessageMapper createMessageMapper(MailboxSession session) throws MailboxException; + + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapperFactory#getMailboxMapper(MailboxSession) + */ + @SuppressWarnings("unchecked") + public MailboxMapper getMailboxMapper(MailboxSession session) throws MailboxException { + MailboxMapper mapper = (MailboxMapper) session.getAttributes().get(MAILBOXMAPPER); + if (mapper == null) { + mapper = createMailboxMapper(session); + session.getAttributes().put(MAILBOXMAPPER, mapper); + } + return mapper; + } + + /** + * Create a {@link MailboxMapper} instance which will get reused during the whole {@link MailboxSession} + * + * @param session + * @return mailboxMapper + * @throws MailboxException + */ + public abstract MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException; + + /** + * Create a {@link SubscriptionMapper} instance or return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + public SubscriptionMapper getSubscriptionMapper(MailboxSession session) throws SubscriptionException { + SubscriptionMapper mapper = (SubscriptionMapper) session.getAttributes().get(SUBSCRIPTIONMAPPER); + if (mapper == null) { + mapper = createSubscriptionMapper(session); + session.getAttributes().put(SUBSCRIPTIONMAPPER, mapper); + } + return mapper; + } + + /** + * Create a {@link SubscriptionMapper} instance which will get reused during the whole {@link MailboxSession} + * @param session + * @return subscriptionMapper + * @throws SubscriptionException + */ + public abstract SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException; + + /** + * Call endRequest on {@link Mapper} instances + * + * @param session + */ + @SuppressWarnings("unchecked") + public void endProcessingRequest(MailboxSession session) { + if (session == null) return; + MessageMapper messageMapper = (MessageMapper) session.getAttributes().get(MESSAGEMAPPER); + MailboxMapper mailboxMapper = (MailboxMapper) session.getAttributes().get(MAILBOXMAPPER); + SubscriptionMapper subscriptionMapper = (SubscriptionMapper) session.getAttributes().get(SUBSCRIPTIONMAPPER); + if (messageMapper != null) + messageMapper.endRequest(); + if (mailboxMapper != null) + mailboxMapper.endRequest(); + if (subscriptionMapper != null) + subscriptionMapper.endRequest(); + } + + /** + * Do nothing + * + */ + public void startProcessingRequest(MailboxSession session) { + // Do nothing + + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImpl.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImpl.java.svn-base new file mode 100644 index 0000000..0ed73c4 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImpl.java.svn-base @@ -0,0 +1,422 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.Headers; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.streaming.InputStreamContent; +import org.apache.james.mailbox.store.streaming.InputStreamContent.Type; +import org.apache.james.mime4j.MimeException; + +/** + * Bean based implementation. + */ +public class MessageResultImpl implements MessageResult { + + private final Map partsByPath = new HashMap(); + + private MimeDescriptor mimeDescriptor; + + private final Message message; + + private HeadersImpl headers; + private Content fullContent; + private Content bodyContent; + + + public MessageResultImpl(Message message) throws IOException { + this.message = message; + this.headers = new HeadersImpl(message); + + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getUid() + */ + public long getUid() { + return message.getUid(); + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getInternalDate() + */ + public Date getInternalDate() { + return message.getInternalDate(); + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getFlags() + */ + public Flags getFlags() { + return message.createFlags(); + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getSize() + */ + public long getSize() { + return message.getFullContentOctets(); + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(MessageResult that) { + if (getUid() > 0 && that.getUid() > 0) { + // TODO: this seems inefficient + return Long.valueOf(getUid()).compareTo(Long.valueOf(that.getUid())); + } else { + // TODO: throwing an undocumented untyped runtime seems wrong + // TODO: if uids must be greater than zero then this should be + // enforced + // TODO: on the way in + // TODO: probably an IllegalArgumentException would be better + throw new RuntimeException("can't compare"); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return 37 * 17 + (int)getUid(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MessageResultImpl) { + MessageResultImpl that = (MessageResultImpl)obj; + return this.headers.equals(that.headers) && this.message.equals(that.message); +// return this.message.equals(that.message); + } + return false; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getFullContent() + */ + public final Content getFullContent() throws IOException { + if (fullContent == null) { + fullContent = new InputStreamContent(message, Type.Full); + } + return fullContent; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getBody() + */ + public final Content getBody() throws IOException { + if (bodyContent == null) { + bodyContent = new InputStreamContent(message, Type.Body); + } + return bodyContent; + } + + + /** + * Renders suitably for logging. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + String retValue = "MessageResultImpl ( " + "uid = " + getUid() + TAB + "flags = " + getFlags() + TAB + "size = " + getSize() + TAB + "internalDate = " + getInternalDate()+ ")"; + + return retValue; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#getBody(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Content getBody(MimePath path) throws MailboxException { + final Content result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getBody(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#getMimeBody(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Content getMimeBody(MimePath path) throws MailboxException { + final Content result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getMimeBody(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#getFullContent(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Content getFullContent(MimePath path) throws MailboxException { + final Content result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getFull(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#iterateHeaders(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Iterator
iterateHeaders(MimePath path) throws MailboxException { + final Iterator
result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getHeaders(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#iterateMimeHeaders(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Iterator
iterateMimeHeaders(MimePath path) throws MailboxException { + final Iterator
result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getMimeHeaders(); + } + return result; + } + + public void setBodyContent(MimePath path, Content content) { + final PartContent partContent = getPartContent(path); + partContent.setBody(content); + } + + public void setMimeBodyContent(MimePath path, Content content) { + final PartContent partContent = getPartContent(path); + partContent.setMimeBody(content); + } + + public void setFullContent(MimePath path, Content content) { + final PartContent partContent = getPartContent(path); + partContent.setFull(content); + } + + public void setHeaders(MimePath path, Iterator
headers) { + final PartContent partContent = getPartContent(path); + partContent.setHeaders(headers); + } + + public void setMimeHeaders(MimePath path, Iterator
headers) { + final PartContent partContent = getPartContent(path); + partContent.setMimeHeaders(headers); + } + + private PartContent getPartContent(MimePath path) { + PartContent result = (PartContent) partsByPath.get(path); + if (result == null) { + result = new PartContent(); + partsByPath.put(path, result); + } + return result; + } + + private static final class PartContent { + private Content body; + + private Content mimeBody; + + private Content full; + + private Iterator
headers; + + private Iterator
mimeHeaders; + + private int content; + + public Content getBody() { + return body; + } + + public void setBody(Content body) { + content = content | FetchGroup.BODY_CONTENT; + this.body = body; + } + + public Content getMimeBody() { + return mimeBody; + } + + public void setMimeBody(Content mimeBody) { + content = content | FetchGroup.MIME_CONTENT; + this.mimeBody = mimeBody; + } + + public Content getFull() { + return full; + } + + public void setFull(Content full) { + content = content | FetchGroup.FULL_CONTENT; + this.full = full; + } + + public Iterator
getHeaders() { + return headers; + } + + public void setHeaders(Iterator
headers) { + content = content | FetchGroup.HEADERS; + this.headers = headers; + } + + public Iterator
getMimeHeaders() { + return mimeHeaders; + } + + public void setMimeHeaders(Iterator
mimeHeaders) { + content = content | FetchGroup.MIME_HEADERS; + this.mimeHeaders = mimeHeaders; + } + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getMimeDescriptor() + */ + public MimeDescriptor getMimeDescriptor() throws MailboxException { + + // check if we need to create the MimeDescriptor which is done in a lazy fashion because + // it can be relative expensive on big messages and slow mailbox implementations + if (mimeDescriptor == null) { + try { + if (MimeDescriptorImpl.isComposite(message.getMediaType())) { + mimeDescriptor = MimeDescriptorImpl.build(getFullContent().getInputStream()); + } else { + mimeDescriptor = new LazyMimeDescriptor(this, message); + } + } catch (IOException e) { + throw new MailboxException("Unable to create the MimeDescriptor", e); + } catch (MimeException e) { + throw new MailboxException("Unable to create the MimeDescriptor", e); + } + } + return mimeDescriptor; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getModSeq() + */ + public long getModSeq() { + return message.getModSeq(); + } + + @Override + public Headers getHeaders() throws MailboxException { + if (headers == null) { + headers = new HeadersImpl(message); + } + return headers; + } + + private final class HeadersImpl implements Headers { + + private Message msg; + private List
headers; + + public HeadersImpl(Message msg) { + this.msg = msg; + } + + @Override + public int hashCode() { + return 39 * 19 + message.hashCode(); + } + + @Override + public boolean equals (Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof HeadersImpl) { + return msg.equals(((HeadersImpl)obj).msg); + } + return false; + } + + @Override + public InputStream getInputStream() throws IOException { + return msg.getHeaderContent(); + } + + @Override + public long size() { + return msg.getFullContentOctets() - msg.getBodyOctets(); + } + + @Override + public Iterator
headers() throws MailboxException { + if (headers == null) { + try { + headers = ResultUtils.createHeaders(message); + } catch (IOException e) { + throw new MailboxException("Unable to parse headers", e); + } + } + return headers.iterator(); + } + + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MimeDescriptorImpl.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MimeDescriptorImpl.java.svn-base new file mode 100644 index 0000000..232e5cb --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/MimeDescriptorImpl.java.svn-base @@ -0,0 +1,357 @@ +/**************************************************************** + * 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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.store.streaming.CountingInputStream; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder; +import org.apache.james.mime4j.message.MaximalBodyDescriptor; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + +public class MimeDescriptorImpl implements MimeDescriptor { + + private final static Charset US_ASCII = Charset.forName("US-ASCII"); + + + /** + * Is this a composite media type (as per RFC2045)? + * + * TODO: Move to Mime4j + * @param mediaType possibly null + * @return true when the type is composite, + * false otherwise + */ + public static boolean isComposite(String mediaType) { + return "message".equalsIgnoreCase(mediaType) || "multipart".equalsIgnoreCase(mediaType); + } + + public static MimeDescriptorImpl build(final InputStream stream) throws IOException, MimeException { + // Disable line length limit + // See https://issues.apache.org/jira/browse/IMAP-132 + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + // + final MimeTokenStream parser = new MimeTokenStream(config, new DefaultBodyDescriptorBuilder()); + + parser.parse(stream); + + // TODO: Shouldn't this get set before we call the parse ? + parser.setRecursionMode(RecursionMode.M_NO_RECURSE); + return createDescriptor(parser); + } + + private static MimeDescriptorImpl createDescriptor( + final MimeTokenStream parser) throws IOException, MimeException { + EntityState next = parser.next(); + final Collection headers = new ArrayList(); + while (next != EntityState.T_BODY + && next != EntityState.T_END_OF_STREAM + && next != EntityState.T_START_MULTIPART) { + if (next == EntityState.T_FIELD) { + headers.add(new ResultHeader(parser.getField().getName(), parser + .getField().getBody().trim())); + } + next = parser.next(); + } + + final MimeDescriptorImpl mimeDescriptorImpl; + switch (next) { + case T_BODY: + mimeDescriptorImpl = simplePartDescriptor(parser, headers); + break; + case T_START_MULTIPART: + mimeDescriptorImpl = compositePartDescriptor(parser, headers); + break; + case T_END_OF_STREAM: + throw new MimeException("Premature end of stream"); + default: + throw new MimeException("Unexpected parse state"); + } + return mimeDescriptorImpl; + } + + private static MimeDescriptorImpl compositePartDescriptor( + final MimeTokenStream parser, final Collection headers) + throws IOException, MimeException { + MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser + .getBodyDescriptor(); + MimeDescriptorImpl mimeDescriptor = createDescriptor(0, 0, descriptor, + null, headers); + EntityState next = parser.next(); + while (next != EntityState.T_END_MULTIPART + && next != EntityState.T_END_OF_STREAM) { + if (next == EntityState.T_START_BODYPART) { + mimeDescriptor.addPart(createDescriptor(parser)); + } + next = parser.next(); + } + return mimeDescriptor; + } + + private static MimeDescriptorImpl simplePartDescriptor( + final MimeTokenStream parser, final Collection headers) + throws IOException, MimeException { + MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser + .getBodyDescriptor(); + final MimeDescriptorImpl mimeDescriptorImpl; + if ("message".equalsIgnoreCase(descriptor.getMediaType()) + && "rfc822".equalsIgnoreCase(descriptor.getSubType())) { + final CountingInputStream messageStream = new CountingInputStream( + parser.getDecodedInputStream()); + MimeDescriptorImpl embeddedMessageDescriptor = build(messageStream); + final int octetCount = messageStream.getOctetCount(); + final int lineCount = messageStream.getLineCount(); + + mimeDescriptorImpl = createDescriptor(octetCount, lineCount, + descriptor, embeddedMessageDescriptor, headers); + } else { + final InputStream body = parser.getInputStream(); + long bodyOctets = 0; + long lines = 0; + for (int n = body.read(); n >= 0; n = body.read()) { + if (n == '\r') { + lines++; + } + bodyOctets++; + } + + mimeDescriptorImpl = createDescriptor(bodyOctets, lines, + descriptor, null, headers); + } + return mimeDescriptorImpl; + } + + private static MimeDescriptorImpl createDescriptor(long bodyOctets, + long lines, MaximalBodyDescriptor descriptor, + MimeDescriptor embeddedMessage, final Collection headers) { + final String contentDescription = descriptor.getContentDescription(); + final String contentId = descriptor.getContentId(); + + final String subType = descriptor.getSubType(); + final String type = descriptor.getMediaType(); + final String transferEncoding = descriptor.getTransferEncoding(); + final Map contentTypeParameters = new TreeMap(descriptor.getContentTypeParameters()); + final String codeset = descriptor.getCharset(); + if (codeset == null) { + if ("TEXT".equals(type)) { + contentTypeParameters.put("charset", "us-ascii"); + } + } else { + contentTypeParameters.put("charset", codeset); + } + final String boundary = descriptor.getBoundary(); + if (boundary != null) { + contentTypeParameters.put("boundary", boundary); + } + + final List languages = descriptor.getContentLanguage(); + final String disposition = descriptor.getContentDispositionType(); + final Map dispositionParams = descriptor + .getContentDispositionParameters(); + final Collection parts = new ArrayList(); + final String location = descriptor.getContentLocation(); + final String md5 = descriptor.getContentMD5Raw(); + final MimeDescriptorImpl mimeDescriptorImpl = new MimeDescriptorImpl( + bodyOctets, contentDescription, contentId, lines, subType, + type, transferEncoding, headers, contentTypeParameters, + languages, disposition, dispositionParams, embeddedMessage, + parts, location, md5); + return mimeDescriptorImpl; + } + + private final long bodyOctets; + + private final String contentDescription; + + private final String contentId; + + private final long lines; + + private final String subType; + + private final String type; + + private final String transferEncoding; + + private final List languages; + + private final Collection headers; + + private final Map contentTypeParameters; + + private final String disposition; + + private final Map dispositionParams; + + private final MimeDescriptor embeddedMessage; + + private final Collection parts; + + private final String location; + + private final String md5; + + + public MimeDescriptorImpl(final long bodyOctets, + final String contentDescription, final String contentId, + final long lines, final String subType, final String type, + final String transferEncoding, final Collection headers, + final Map contentTypeParameters, final List languages, + String disposition, Map dispositionParams, + final MimeDescriptor embeddedMessage, final Collection parts, + final String location, final String md5) { + super(); + this.type = type; + this.bodyOctets = bodyOctets; + this.contentDescription = contentDescription; + this.contentId = contentId; + this.lines = lines; + this.subType = subType; + this.transferEncoding = transferEncoding; + this.headers = headers; + this.contentTypeParameters = contentTypeParameters; + this.embeddedMessage = embeddedMessage; + this.parts = parts; + this.languages = languages; + this.disposition = disposition; + this.dispositionParams = dispositionParams; + this.location = location; + this.md5 = md5; + } + + public Map contentTypeParameters() { + return contentTypeParameters; + } + + public MimeDescriptor embeddedMessage() { + return embeddedMessage; + } + + public long getBodyOctets() { + return bodyOctets; + } + + public String getContentDescription() { + return contentDescription; + } + + public String getContentID() { + return contentId; + } + + public long getLines() { + return lines; + } + + public String getMimeSubType() { + return subType; + } + + public String getMimeType() { + return type; + } + + public String getTransferContentEncoding() { + return transferEncoding; + } + + public Iterator headers() { + return headers.iterator(); + } + + public Iterator parts() { + return parts.iterator(); + } + + private void addPart(MimeDescriptor descriptor) { + parts.add(descriptor); + } + + public List getLanguages() { + return languages; + } + + public String getDisposition() { + return disposition; + } + + public Map getDispositionParams() { + return dispositionParams; + } + + public String getContentLocation() { + return location; + } + + public String getContentMD5() { + return md5; + } + + @Override + public InputStream getInputStream() throws IOException { + StringBuilder sb = new StringBuilder(); + Iterator hIt = headers.iterator(); + while(hIt.hasNext()) { + MessageResult.Header header = hIt.next(); + try { + sb.append(header.getName()).append(": " ).append(header.getValue()).append("\r\n"); + } catch (MailboxException e) { + throw new IOException("Unable to read headers", e); + } + } + sb.append("\r\n"); + return new ByteArrayInputStream(sb.toString().getBytes(US_ASCII)); + } + + @Override + public long size() throws MailboxException { + long result = 0; + for (final Iterator it = headers.iterator(); it.hasNext();) { + final MessageResult.Header header = it.next(); + if (header != null) { + result += header.size(); + result += 2; + } + } + + // Add for CLRF + result +=2; + return result; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/RandomMailboxSessionIdGenerator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/RandomMailboxSessionIdGenerator.java.svn-base new file mode 100644 index 0000000..8f51230 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/RandomMailboxSessionIdGenerator.java.svn-base @@ -0,0 +1,40 @@ +/**************************************************************** + * 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 java.util.Random; + +import org.apache.james.mailbox.MailboxSessionIdGenerator; + + +/** + * {@link MailboxSessionIdGenerator} which use a {@link Random} to generate the next Id to use + * + * + */ +public class RandomMailboxSessionIdGenerator extends AbstractMailboxSessionIdGenerator { + private final static Random RANDOM = new Random(); + + @Override + protected long generateNextId() { + return RANDOM.nextLong(); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultHeader.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultHeader.java.svn-base new file mode 100644 index 0000000..904c9aa --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultHeader.java.svn-base @@ -0,0 +1,78 @@ +/**************************************************************** + * 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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult; + +public final class ResultHeader implements MessageResult.Header { + private final String name; + + private final String value; + + private final long size; + private final static Charset US_ASCII = Charset.forName("US-ASCII"); + + public ResultHeader(String name, String value) { + this.name = name; + this.value = value; + size = name.length() + value.length() + 2; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult.Header#getName() + */ + public String getName() throws MailboxException { + return name; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult.Header#getValue() + */ + public String getValue() throws MailboxException { + return value; + } + + /** + * @see org.apache.james.mailbox.model.Content#size() + */ + public long size() { + return size; + } + + public String toString() { + return "[HEADER " + name + ": " + value + "]"; + } + + /** + * @see org.apache.james.mailbox.model.InputStreamContent#getInputStream() + */ + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream((name + ": " + value).getBytes(US_ASCII)); + } +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultUtils.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultUtils.java.svn-base new file mode 100644 index 0000000..33db193 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/ResultUtils.java.svn-base @@ -0,0 +1,264 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.MessageResult.MimePath; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.streaming.PartContentBuilder; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.parser.AbstractContentHandler; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.stream.Field; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; + +/** + * + */ +public class ResultUtils { + + public static final byte[] BYTES_NEW_LINE = { 0x0D, 0x0A }; + + public static final byte[] BYTES_HEADER_FIELD_VALUE_SEP = { 0x3A, 0x20 }; + + static final Charset US_ASCII = Charset.forName("US-ASCII"); + + public static List createHeaders(final Message document) throws IOException { + final List results = new ArrayList(); + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + final MimeStreamParser parser = new MimeStreamParser(config); + parser.setContentHandler(new AbstractContentHandler() { + @Override + public void endHeader() { + parser.stop(); + } + @Override + public void field(Field field) throws MimeException { + String fieldValue; + if (field instanceof RawField) { + // check if we can access the body in the raw form so no unfolding was done under the hood + ByteSequence raw = field.getRaw(); + int len = raw.length(); + int off = ((RawField) field).getDelimiterIdx() + 1; + if (len > off + 1 && (raw.byteAt(off) & 0xff) == 0x20) off++; + + fieldValue = ContentUtil.decode(raw, off, len - off); + } else { + fieldValue = field.getBody(); + } + if (fieldValue.endsWith("\r\f")) { + fieldValue = fieldValue.substring(0,fieldValue.length() - 2); + } + if (fieldValue.startsWith(" ")) { + fieldValue = fieldValue.substring(1); + } + + final ResultHeader resultHeader = new ResultHeader(field.getName(), fieldValue); + results.add(resultHeader); + } + }); + try { + parser.parse(document.getHeaderContent()); + } catch (MimeException e) { + throw new IOException("Unable to parse headers of message " + document, e); + } + return results; + } + + + + + /** + * Return the {@link MessageResult} for the given {@link Message} and {@link FetchGroup} + * + * @param message + * @param fetchGroup + * @return result + * @throws MailboxException + */ + public static MessageResult loadMessageResult(final Message message, final FetchGroup fetchGroup) throws MailboxException { + try { + + MessageResultImpl messageResult = new MessageResultImpl(message); + if (fetchGroup != null) { + int content = fetchGroup.content(); + + if ((content & FetchGroup.HEADERS) > 0) { + content -= FetchGroup.HEADERS; + } + if ((content & FetchGroup.BODY_CONTENT) > 0) { + content -= FetchGroup.BODY_CONTENT; + } + if ((content & FetchGroup.FULL_CONTENT) > 0) { + content -= FetchGroup.FULL_CONTENT; + } + if ((content & FetchGroup.MIME_DESCRIPTOR) > 0) { + content -= FetchGroup.MIME_DESCRIPTOR; + } + if (content != 0) { + throw new UnsupportedOperationException("Unsupported result: " + content); + } + + addPartContent(fetchGroup, message, messageResult); + } + return messageResult; + + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } catch (MimeException e) { + throw new MailboxException("Unable to parse message", e); + } + + } + + private static void addPartContent(final FetchGroup fetchGroup, + Message message, MessageResultImpl messageResult) + throws MailboxException, IOException, + MimeException { + Collection partContent = fetchGroup.getPartContentDescriptors(); + if (partContent != null) { + for (FetchGroup.PartContentDescriptor descriptor: partContent) { + addPartContent(descriptor, message, messageResult); + } + } + } + + private static void addPartContent( + FetchGroup.PartContentDescriptor descriptor, Message message, + MessageResultImpl messageResult) throws + MailboxException, IOException, MimeException { + final MimePath mimePath = descriptor.path(); + final int content = descriptor.content(); + if ((content & MessageResult.FetchGroup.FULL_CONTENT) > 0) { + addFullContent(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.BODY_CONTENT) > 0) { + addBodyContent(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.MIME_CONTENT) > 0) { + addMimeBodyContent(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.HEADERS) > 0) { + addHeaders(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.MIME_HEADERS) > 0) { + addMimeHeaders(message, messageResult, mimePath); + } + } + + private static PartContentBuilder build(int[] path, final Message message) + throws IOException, MimeException { + final InputStream stream = message.getFullContent(); + PartContentBuilder result = new PartContentBuilder(); + result.parse(stream); + try { + for (int i = 0; i < path.length; i++) { + final int next = path[i]; + result.to(next); + } + } catch (PartContentBuilder.PartNotFoundException e) { + // Missing parts should return zero sized content + // See http://markmail.org/message/2jconrj7scvdi5dj + result.markEmpty(); + } + return result; + } + + + + private static final int[] path(MimePath mimePath) { + final int[] result; + if (mimePath == null) { + result = null; + } else { + result = mimePath.getPositions(); + } + return result; + } + + private static void addHeaders(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws IOException, MimeException { + final int[] path = path(mimePath); + if (path != null) { + + final PartContentBuilder builder = build(path, message); + final List headers = builder.getMessageHeaders(); + messageResult.setHeaders(mimePath, headers.iterator()); + } + } + + private static void addMimeHeaders(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws IOException, MimeException { + final int[] path = path(mimePath); + if (path != null) { + final PartContentBuilder builder = build(path, message); + final List headers = builder.getMimeHeaders(); + messageResult.setMimeHeaders(mimePath, headers.iterator()); + } + } + + private static void addBodyContent(Message message, + MessageResultImpl messageResult, MimePath mimePath) throws IOException, MimeException { + final int[] path = path(mimePath); + if (path != null) { + final PartContentBuilder builder = build(path, message); + final Content content = builder.getMessageBodyContent(); + messageResult.setBodyContent(mimePath, content); + } + } + + private static void addMimeBodyContent(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws IOException, MimeException { + final int[] path = path(mimePath); + final PartContentBuilder builder = build(path, message); + final Content content = builder.getMimeBodyContent(); + messageResult.setMimeBodyContent(mimePath, content); + } + + private static void addFullContent(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws MailboxException, IOException, + MimeException { + final int[] path = path(mimePath); + if (path != null) { + final PartContentBuilder builder = build(path, message); + final Content content = builder.getFullContent(); + messageResult.setFullContent(mimePath, content); + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMetaData.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMetaData.java.svn-base new file mode 100644 index 0000000..30ac760 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMetaData.java.svn-base @@ -0,0 +1,125 @@ +/**************************************************************** + * 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.StandardMailboxMetaDataComparator; +import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.model.MailboxPath; + +public class SimpleMailboxMetaData implements MailboxMetaData, Comparable { + + public static MailboxMetaData createNoSelect(MailboxPath path, char delimiter) { + return new SimpleMailboxMetaData(path, delimiter, Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.NOSELECT); + } + + private final MailboxPath path; + + private final char delimiter; + + private final Children inferiors; + + private final Selectability selectability; + + public SimpleMailboxMetaData(MailboxPath path, char delimiter) { + this(path, delimiter, Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.NONE); + } + + public SimpleMailboxMetaData(final MailboxPath path, final char delimiter, final Children inferiors, final Selectability selectability) { + super(); + this.path = path; + this.delimiter = delimiter; + this.inferiors = inferiors; + this.selectability = selectability; + } + + /** + * Is this mailbox \Noinferiors as per RFC3501. + * + * @return true if marked, false otherwise + */ + public final Children inferiors() { + return inferiors; + } + + /** + * Gets the RFC3501 Selectability flag. + */ + public final Selectability getSelectability() { + return selectability; + } + + /** + * @see org.apache.james.mailbox.model.MailboxMetaData#getHierarchyDelimiter() + */ + public char getHierarchyDelimiter() { + return delimiter; + } + + /** + * @see org.apache.james.mailbox.model.MailboxMetaData#getPath() + */ + public MailboxPath getPath() { + return path; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "ListResult: " + path; + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((path == null) ? 0 : path.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SimpleMailboxMetaData other = (SimpleMailboxMetaData) obj; + if (path == null) { + if (other.path != null) + return false; + } else if (!path.equals(other.path)) + return false; + return true; + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(MailboxMetaData o) { + return StandardMailboxMetaDataComparator.order(this, o); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxSession.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxSession.java.svn-base new file mode 100644 index 0000000..9200eb1 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxSession.java.svn-base @@ -0,0 +1,203 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.model.MailboxConstants; +import org.slf4j.Logger; + +/** + * Describes a mailbox session. + */ +public class SimpleMailboxSession implements MailboxSession, MailboxSession.User { + + private final Collection sharedSpaces; + + private final String otherUsersSpace; + + private final String personalSpace; + + private final long sessionId; + + private final Logger log; + + private final String userName; + + private final String password; + + private boolean open = true; + + private final List localePreferences; + + private final Map attributes; + + private final char pathSeparator; + + private final SessionType type; + + + public SimpleMailboxSession(final long sessionId, final String userName, final String password, + final Logger log, final List localePreferences, char pathSeparator, SessionType type) { + this(sessionId, userName, password, log, localePreferences, new ArrayList(), null, pathSeparator, type); + } + + public SimpleMailboxSession(final long sessionId, final String userName, final String password, + final Logger log, final List localePreferences, List sharedSpaces, String otherUsersSpace, char pathSeparator, SessionType type) { + this.sessionId = sessionId; + this.log = log; + this.userName = userName; + this.password = password; + this.otherUsersSpace = otherUsersSpace; + this.sharedSpaces = sharedSpaces; + this.type = type; + if (otherUsersSpace == null && (sharedSpaces == null || sharedSpaces.isEmpty())) { + this.personalSpace = ""; + } else { + this.personalSpace = MailboxConstants.USER_NAMESPACE; + } + + this.localePreferences = localePreferences; + this.attributes = new HashMap(); + this.pathSeparator = pathSeparator; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getLog() + */ + public Logger getLog() { + return log; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#close() + */ + public void close() { + open = false; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getSessionId() + */ + public long getSessionId() { + return sessionId; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#isOpen() + */ + public boolean isOpen() { + return open; + } + + /** + * Renders suitably for logging. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + String retValue = "MailboxSession ( " + "sessionId = " + + this.sessionId + TAB + "open = " + this.open + TAB + " )"; + + return retValue; + } + + /** + * Gets the user executing this session. + * @return not null + */ + public User getUser() { + return this; + } + + /** + * Gets the name of the user executing this session. + * + * @return not null + */ + public String getUserName() { + return userName; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getOtherUsersSpace() + */ + public String getOtherUsersSpace() { + return otherUsersSpace; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getPersonalSpace() + */ + public String getPersonalSpace() { + return personalSpace; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getSharedSpaces() + */ + public Collection getSharedSpaces() { + return sharedSpaces; + } + + /** + * @see org.apache.james.mailbox.MailboxSession.User#getLocalePreferences() + */ + public List getLocalePreferences() { + return localePreferences; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getAttributes() + */ + public Map getAttributes() { + return attributes; + } + + /** + * @see org.apache.james.mailbox.MailboxSession.User#getPassword() + */ + public String getPassword() { + return password; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getPathDelimiter() + */ + public char getPathDelimiter() { + return pathSeparator; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getType() + */ + public SessionType getType() { + return type; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMessageMetaData.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMessageMetaData.java.svn-base new file mode 100644 index 0000000..a744c05 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMessageMetaData.java.svn-base @@ -0,0 +1,100 @@ +/**************************************************************** + * 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 java.util.Date; + +import javax.mail.Flags; + +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.store.mail.model.Message; + + +public class SimpleMessageMetaData implements MessageMetaData{ + private long uid; + private Flags flags; + private long size; + private Date internalDate; + private long modSeq; + + public SimpleMessageMetaData(long uid, long modSeq, Flags flags, long size, Date internalDate) { + this.uid = uid; + this.flags = flags; + this.size = size; + this.modSeq = modSeq; + this.internalDate = internalDate; + } + + public SimpleMessageMetaData(Message message) { + this(message.getUid(), message.getModSeq(), message.createFlags(), message.getFullContentOctets(), message.getInternalDate()); + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getFlags() + */ + public Flags getFlags() { + return flags; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getSize() + */ + public long getSize() { + return size; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getInternalDate() + */ + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getUid() + */ + public long getUid() { + return uid; + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof SimpleMessageMetaData) { + return uid == ((SimpleMessageMetaData) obj).getUid(); + } + return false; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getModSeq() + */ + public long getModSeq() { + return modSeq; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxManager.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxManager.java.svn-base new file mode 100644 index 0000000..3cf22ae --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxManager.java.svn-base @@ -0,0 +1,593 @@ +/**************************************************************** + * 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.MailboxListener; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSession.SessionType; +import org.apache.james.mailbox.MailboxSessionIdGenerator; +import org.apache.james.mailbox.RequestAware; +import org.apache.james.mailbox.StandardMailboxMetaDataComparator; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.model.MailboxMetaData.Selectability; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MailboxQuery; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.store.mail.MailboxMapper; +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.search.ListeningMessageSearchIndex; +import org.apache.james.mailbox.store.search.MessageSearchIndex; +import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Random; + +/** + * This base class of an {@link MailboxManager} implementation provides a high-level api for writing your own + * {@link MailboxManager} implementation. If you plan to write your own {@link MailboxManager} its most times so easiest + * to extend just this class or use it directly. + *

+ * If you need a more low-level api just implement {@link MailboxManager} directly + * + * @param + */ +public class StoreMailboxManager implements MailboxManager { + + public static final char SQL_WILDCARD_CHAR = '%'; + public static final int DEFAULT_FETCH_BATCH_SIZE = 200; + + private MailboxEventDispatcher dispatcher; + private AbstractDelegatingMailboxListener delegatingListener = null; + private final MailboxSessionMapperFactory mailboxSessionMapperFactory; + + private final Authenticator authenticator; + + private final MailboxACLResolver aclResolver; + + private final GroupMembershipResolver groupMembershipResolver; + + private final static Random RANDOM = new Random(); + + private int copyBatchSize = 0; + + private int moveBatchSize = 0; + + private MailboxPathLocker locker; + + private MessageSearchIndex index; + + private MailboxSessionIdGenerator idGenerator; + + private int fetchBatchSize = DEFAULT_FETCH_BATCH_SIZE; + + + public StoreMailboxManager(MailboxSessionMapperFactory mailboxSessionMapperFactory, final Authenticator authenticator, final MailboxPathLocker locker, final MailboxACLResolver aclResolver, final GroupMembershipResolver groupMembershipResolver) { + this.authenticator = authenticator; + this.locker = locker; + this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; + this.aclResolver = aclResolver; + this.groupMembershipResolver = groupMembershipResolver; + } + + public StoreMailboxManager(MailboxSessionMapperFactory mailboxSessionMapperFactory, final Authenticator authenticator, final MailboxACLResolver aclResolver, final GroupMembershipResolver groupMembershipResolver) { + this(mailboxSessionMapperFactory, authenticator, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + } + + public void setMailboxSessionIdGenerator(MailboxSessionIdGenerator idGenerator) { + this.idGenerator = idGenerator; + } + + public void setCopyBatchSize(int copyBatchSize) { + this.copyBatchSize = copyBatchSize; + } + + public void setMoveBatchSize(int moveBatchSize) { + this.moveBatchSize = moveBatchSize; + } + + public void setFetchBatchSize(int fetchBatchSize) { + this.fetchBatchSize = fetchBatchSize; + } + + + /** + * Init the {@link MailboxManager} + * + * @throws MailboxException + */ + @SuppressWarnings("rawtypes") + public void init() throws MailboxException { + // The dispatcher need to have the delegating listener added + dispatcher = new MailboxEventDispatcher(getDelegationListener()); + + if (index == null) { + index = new SimpleMessageSearchIndex(mailboxSessionMapperFactory); + } + if (index instanceof ListeningMessageSearchIndex) { + addGlobalListener((ListeningMessageSearchIndex) index, null); + } + + if (idGenerator == null) { + idGenerator = new RandomMailboxSessionIdGenerator(); + } + } + + /** + * Return the {@link AbstractDelegatingMailboxListener} which is used by this {@link MailboxManager} + * + * @return delegatingListener + */ + public AbstractDelegatingMailboxListener getDelegationListener() { + if (delegatingListener == null) { + delegatingListener = new HashMapDelegatingMailboxListener(); + } + return delegatingListener; + } + + + /** + * Return the {@link MessageSearchIndex} used by this {@link MailboxManager} + * + * @return index + */ + public MessageSearchIndex getMessageSearchIndex() { + return index; + } + + + /** + * Return the {@link MailboxEventDispatcher} used by thei {@link MailboxManager} + * + * @return dispatcher + */ + public MailboxEventDispatcher getEventDispatcher() { + return dispatcher; + } + + /** + * Return the {@link MailboxSessionMapperFactory} used by this {@link MailboxManager} + * + * @return mailboxSessionMapperFactory + */ + public MailboxSessionMapperFactory getMapperFactory() { + return mailboxSessionMapperFactory; + } + + public MailboxPathLocker getLocker() { + return locker; + } + + public MailboxACLResolver getAclResolver() { + return aclResolver; + } + + public GroupMembershipResolver getGroupMembershipResolver() { + return groupMembershipResolver; + } + + /** + * Set the {@link AbstractDelegatingMailboxListener} to use with this {@link MailboxManager} instance. If none is set here a {@link HashMapDelegatingMailboxListener} instance will + * be created lazy + * + * @param delegatingListener + */ + public void setDelegatingMailboxListener(AbstractDelegatingMailboxListener delegatingListener) { + this.delegatingListener = delegatingListener; + dispatcher = new MailboxEventDispatcher(getDelegationListener()); + } + + /** + * Set the {@link MessageSearchIndex} which should be used by this {@link MailboxManager}. If none is given this implementation will use a {@link SimpleMessageSearchIndex} + * by default + * + * @param index + */ + public void setMessageSearchIndex(MessageSearchIndex index) { + this.index = index; + } + + /** + * Generate an return the next uid validity + * + * @return uidValidity + */ + protected int randomUidValidity() { + return Math.abs(RANDOM.nextInt()); + } + + @Override + public MailboxSession createSystemSession(String userName, Logger log) { + return createSession(userName, null, log, SessionType.System); + } + + /** + * Create Session + * + * @param userName + * @param log + * @return session + */ + protected MailboxSession createSession(String userName, String password, Logger log, SessionType type) { + return new SimpleMailboxSession(randomId(), userName, password, log, new ArrayList(), getDelimiter(), type); + } + + /** + * Generate and return the next id to use + * + * @return id + */ + protected long randomId() { + return idGenerator.nextId(); + } + + @Override + public char getDelimiter() { + return MailboxConstants.DEFAULT_DELIMITER; + } + + /** + * Log in the user with the given userid and password + * + * @param userid the username + * @param passwd the password + * @return success true if login success false otherwise + */ + private boolean login(String userid, String passwd) { + return authenticator.isAuthentic(userid, passwd); + } + + @Override + public MailboxSession login(String userid, String passwd, Logger log) throws BadCredentialsException, MailboxException { + if (login(userid, passwd)) { + return createSession(userid, passwd, log, SessionType.User); + } else { + throw new BadCredentialsException(); + } + } + + /** + * Close the {@link MailboxSession} if not null + */ + public void logout(MailboxSession session, boolean force) throws MailboxException { + if (session != null) { + session.close(); + } + } + + /** + * Create a {@link MailboxManager} for the given Mailbox. By default this will return a {@link StoreMessageManager}. If + * your implementation needs something different, just override this method + * + * @param mailbox + * @param session + * @return storeMailbox + */ + protected StoreMessageManager createMessageManager(Mailbox mailbox, MailboxSession session) throws MailboxException { + return new StoreMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), mailbox, getAclResolver(), getGroupMembershipResolver()); + } + + /** + * Create a Mailbox for the given mailbox path. This will by default return a {@link SimpleMailbox}. + *

+ * If you need to return something more special just override this method + * + * @param mailboxPath + * @param session + * @throws MailboxException + */ + protected org.apache.james.mailbox.store.mail.model.Mailbox doCreateMailbox(MailboxPath mailboxPath, final MailboxSession session) throws MailboxException { + return new SimpleMailbox(mailboxPath, randomUidValidity()); + } + + @Override + public org.apache.james.mailbox.MessageManager getMailbox(MailboxPath mailboxPath, MailboxSession session) + throws MailboxException { + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + Mailbox mailboxRow = mapper.findMailboxByPath(mailboxPath); + + if (mailboxRow == null) { + session.getLog().info("Mailbox '" + mailboxPath + "' not found."); + throw new MailboxNotFoundException(mailboxPath); + + } else { + session.getLog().debug("Loaded mailbox " + mailboxPath); + + StoreMessageManager m = createMessageManager(mailboxRow, session); + m.setFetchBatchSize(fetchBatchSize); + return m; + } + } + + @Override + public void createMailbox(MailboxPath mailboxPath, final MailboxSession mailboxSession) + throws MailboxException { + mailboxSession.getLog().debug("createMailbox " + mailboxPath); + final int length = mailboxPath.getName().length(); + if (length == 0) { + mailboxSession.getLog().warn("Ignoring mailbox with empty name"); + } else { + if (mailboxPath.getName().charAt(length - 1) == getDelimiter()) + mailboxPath.setName(mailboxPath.getName().substring(0, length - 1)); + if (mailboxExists(mailboxPath, mailboxSession)) + throw new MailboxExistsException(mailboxPath.toString()); + // Create parents first + // If any creation fails then the mailbox will not be created + // TODO: transaction + for (final MailboxPath mailbox : mailboxPath.getHierarchyLevels(getDelimiter())) + + locker.executeWithLock(mailboxSession, mailbox, new LockAwareExecution() { + + public Void execute() throws MailboxException { + if (!mailboxExists(mailbox, mailboxSession)) { + final org.apache.james.mailbox.store.mail.model.Mailbox m = doCreateMailbox(mailbox, mailboxSession); + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession); + mapper.execute(new TransactionalMapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + mapper.save(m); + } + + }); + + // notify listeners + dispatcher.mailboxAdded(mailboxSession, m); + } + return null; + + } + }); + + } + } + + @Override + public void deleteMailbox(final MailboxPath mailboxPath, final MailboxSession session) throws MailboxException { + session.getLog().info("deleteMailbox " + mailboxPath); + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + + Mailbox mailbox = mapper.execute(new Mapper.Transaction>() { + + public Mailbox run() throws MailboxException { + final Mailbox mailbox = mapper.findMailboxByPath(mailboxPath); + if (mailbox == null) { + throw new MailboxNotFoundException("Mailbox not found"); + } + + // We need to create a copy of the mailbox as maybe we can not refer to the real + // mailbox once we remove it + SimpleMailbox m = new SimpleMailbox(mailbox); + mapper.delete(mailbox); + return m; + } + + }); + + dispatcher.mailboxDeleted(session, mailbox); + + } + + @Override + public void renameMailbox(final MailboxPath from, final MailboxPath to, final MailboxSession session) throws MailboxException { + final Logger log = session.getLog(); + if (log.isDebugEnabled()) + log.debug("renameMailbox " + from + " to " + to); + if (mailboxExists(to, session)) { + throw new MailboxExistsException(to.toString()); + } + + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + mapper.execute(new Mapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + // TODO put this into a serilizable transaction + final Mailbox mailbox = mapper.findMailboxByPath(from); + if (mailbox == null) { + throw new MailboxNotFoundException(from); + } + mailbox.setNamespace(to.getNamespace()); + mailbox.setUser(to.getUser()); + mailbox.setName(to.getName()); + mapper.save(mailbox); + + dispatcher.mailboxRenamed(session, from, mailbox); + + // rename submailboxes + final MailboxPath children = new MailboxPath(MailboxConstants.USER_NAMESPACE, from.getUser(), from.getName() + getDelimiter() + "%"); + locker.executeWithLock(session, children, new LockAwareExecution() { + + public Void execute() throws MailboxException { + final List> subMailboxes = mapper.findMailboxWithPathLike(children); + for (Mailbox sub : subMailboxes) { + final String subOriginalName = sub.getName(); + final String subNewName = to.getName() + subOriginalName.substring(from.getName().length()); + final MailboxPath fromPath = new MailboxPath(children, subOriginalName); + sub.setName(subNewName); + mapper.save(sub); + dispatcher.mailboxRenamed(session, fromPath, sub); + + if (log.isDebugEnabled()) + log.debug("Rename mailbox sub-mailbox " + subOriginalName + " to " + subNewName); + } + return null; + + } + }); + } + }); + } + + + @Override + @SuppressWarnings("unchecked") + public List copyMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException { + StoreMessageManager toMailbox = (StoreMessageManager) getMailbox(to, session); + StoreMessageManager fromMailbox = (StoreMessageManager) getMailbox(from, session); + + if (copyBatchSize > 0) { + List copiedRanges = new ArrayList(); + Iterator ranges = set.split(copyBatchSize).iterator(); + while (ranges.hasNext()) { + copiedRanges.addAll(fromMailbox.copyTo(ranges.next(), toMailbox, session)); + } + return copiedRanges; + } else { + return fromMailbox.copyTo(set, toMailbox, session); + } + } + + @Override + @SuppressWarnings("unchecked") + public List moveMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException { + StoreMessageManager toMailbox = (StoreMessageManager) getMailbox(to, session); + StoreMessageManager fromMailbox = (StoreMessageManager) getMailbox(from, session); + + if (moveBatchSize > 0) { + List movedRanges = new ArrayList(); + Iterator ranges = set.split(moveBatchSize).iterator(); + while (ranges.hasNext()) { + movedRanges.addAll(fromMailbox.copyTo(ranges.next(), toMailbox, session)); + } + return movedRanges; + } else { + return fromMailbox.moveTo(set, toMailbox, session); + } + } + + @Override + public List search(final MailboxQuery mailboxExpression, MailboxSession session) + throws MailboxException { + final char localWildcard = mailboxExpression.getLocalWildcard(); + final char freeWildcard = mailboxExpression.getFreeWildcard(); + final String baseName = mailboxExpression.getBase().getName(); + final int baseLength; + if (baseName == null) { + baseLength = 0; + } else { + baseLength = baseName.length(); + } + final String combinedName = mailboxExpression.getCombinedName() + .replace(freeWildcard, SQL_WILDCARD_CHAR) + .replace(localWildcard, SQL_WILDCARD_CHAR); + final MailboxPath search = new MailboxPath(mailboxExpression.getBase(), combinedName); + + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + final List> mailboxes = mapper.findMailboxWithPathLike(search); + final List results = new ArrayList(mailboxes.size()); + for (Mailbox mailbox : mailboxes) { + final String name = mailbox.getName(); + if (name.startsWith(baseName)) { + final String match = name.substring(baseLength); + if (mailboxExpression.isExpressionMatch(match)) { + final MailboxMetaData.Children inferiors; + if (mapper.hasChildren(mailbox, session.getPathDelimiter())) { + inferiors = MailboxMetaData.Children.HAS_CHILDREN; + } else { + inferiors = MailboxMetaData.Children.HAS_NO_CHILDREN; + } + MailboxPath mailboxPath = new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), name); + results.add(new SimpleMailboxMetaData(mailboxPath, getDelimiter(), inferiors, Selectability.NONE)); + } + } + } + Collections.sort(results, new StandardMailboxMetaDataComparator()); + return results; + } + + @Override + public boolean mailboxExists(MailboxPath mailboxPath, MailboxSession session) throws MailboxException { + try { + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + mapper.findMailboxByPath(mailboxPath); + return true; + } catch (MailboxNotFoundException e) { + return false; + } + + } + + @Override + public void addListener(MailboxPath path, MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.addListener(path, listener, session); + } + + /** + * End processing of Request for session + */ + @Override + public void endProcessingRequest(MailboxSession session) { + if (mailboxSessionMapperFactory instanceof RequestAware) { + ((RequestAware) mailboxSessionMapperFactory).endProcessingRequest(session); + } + } + + /** + * Do nothing. Sub classes should override this if needed + */ + @Override + public void startProcessingRequest(MailboxSession session) { + // do nothing + } + + @Override + public List list(MailboxSession session) throws MailboxException { + List mList = new ArrayList(); + List> mailboxes = mailboxSessionMapperFactory.getMailboxMapper(session).list(); + for (int i = 0; i < mailboxes.size(); i++) { + Mailbox m = mailboxes.get(i); + mList.add(new MailboxPath(m.getNamespace(), m.getUser(), m.getName())); + } + return Collections.unmodifiableList(mList); + + } + + @Override + public void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.addGlobalListener(listener, session); + } + + @Override + public void removeListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.removeListener(mailboxPath, listener, session); + + } + + @Override + public void removeGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.removeGlobalListener(listener, session); + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxPath.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxPath.java.svn-base new file mode 100644 index 0000000..a215445 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMailboxPath.java.svn-base @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class StoreMailboxPath extends MailboxPath { + + public StoreMailboxPath(String namespace, String user, String name) { + super(namespace, user, name); + } + + public StoreMailboxPath(MailboxPath mailboxPath) { + super(mailboxPath); + } + + public StoreMailboxPath(MailboxPath mailboxPath, String name) { + super(mailboxPath, name); + } + + public StoreMailboxPath(Mailbox mailbox) { + super(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageManager.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageManager.java.svn-base new file mode 100644 index 0000000..d8b98f7 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageManager.java.svn-base @@ -0,0 +1,870 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedFileInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.TeeInputStream; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSession.User; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.ReadOnlyException; +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.EditMode; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.MessageResultIterator; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.UpdatedFlags; +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; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import org.apache.james.mailbox.store.search.MessageSearchIndex; +import org.apache.james.mailbox.store.streaming.BodyOffsetInputStream; +import org.apache.james.mailbox.store.streaming.CountingInputStream; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder; +import org.apache.james.mime4j.message.HeaderImpl; +import org.apache.james.mime4j.message.MaximalBodyDescriptor; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + +/** + * Base class for {@link org.apache.james.mailbox.MessageManager} + * implementations. + * + * This base class take care of dispatching events to the registered + * {@link MailboxListener} and so help with handling concurrent + * {@link MailboxSession}'s. + * + * + * + */ +public class StoreMessageManager implements org.apache.james.mailbox.MessageManager { + + /** + * The minimal Permanent flags the {@link MessageManager} must support.
+ * + * Be sure this static instance will never get modifed + * later! + */ + protected final static Flags MINIMAL_PERMANET_FLAGS; + static { + MINIMAL_PERMANET_FLAGS = new Flags(); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.ANSWERED); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.DELETED); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.DRAFT); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.FLAGGED); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.SEEN); + } + + private final Mailbox mailbox; + + private final MailboxEventDispatcher dispatcher; + + private final MessageMapperFactory mapperFactory; + + private final MessageSearchIndex index; + + private final MailboxACLResolver aclResolver; + + private final GroupMembershipResolver groupMembershipResolver; + + private MailboxPathLocker locker; + + private int fetchBatchSize; + + public StoreMessageManager(final MessageMapperFactory mapperFactory, final MessageSearchIndex index, final MailboxEventDispatcher dispatcher, final MailboxPathLocker locker, final Mailbox mailbox, final MailboxACLResolver aclResolver, + final GroupMembershipResolver groupMembershipResolver) throws MailboxException { + this.mailbox = mailbox; + this.dispatcher = dispatcher; + this.mapperFactory = mapperFactory; + this.index = index; + this.locker = locker; + this.aclResolver = aclResolver; + this.groupMembershipResolver = groupMembershipResolver; + } + + public void setFetchBatchSize(int fetchBatchSize) { + this.fetchBatchSize = fetchBatchSize; + } + + /** + * Return the {@link MailboxPathLocker} + * + * @return locker + */ + protected MailboxPathLocker getLocker() { + return locker; + } + + /** + * Return the {@link MailboxEventDispatcher} for this Mailbox + * + * @return dispatcher + */ + protected MailboxEventDispatcher getDispatcher() { + return dispatcher; + } + + /** + * Return the underlying {@link Mailbox} + * + * @return mailbox + * @throws MailboxException + */ + + public Mailbox getMailboxEntity() throws MailboxException { + return mailbox; + } + + /** + * Return {@link Flags} which are permanent stored by the mailbox. By + * default this are the following flags:
+ * {@link Flag#ANSWERED}, {@link Flag#DELETED}, {@link Flag#DRAFT}, + * {@link Flag#FLAGGED}, {@link Flag#RECENT}, {@link Flag#SEEN}
+ * + * Which in fact does not allow to permanent store user flags / keywords. + * + * If the sub-class does allow to store "any" user flag / keyword it MUST + * override this method and add {@link Flag#USER} to the list of returned + * {@link Flags}. If only a special set of user flags / keywords should be + * allowed just add them directly. + * + * @param session + * @return flags + */ + protected Flags getPermanentFlags(MailboxSession session) { + + // Return a new flags instance to make sure the static declared flags + // instance will not get modified later. + // + // See MAILBOX-109 + return new Flags(MINIMAL_PERMANET_FLAGS); + } + + /** + * Returns the flags which are shared for the current mailbox, i.e. the + * flags set up so that changes to those flags are visible to another user. + * See RFC 4314 section 5.2. + * + * In this implementation, all permanent flags are shared, ergo we simply + * return {@link #getPermanentFlags(MailboxSession)} + * + * @see UnionMailboxACLResolver#isReadWrite(MailboxACLRights, Flags) + * + * @param session + * @return + */ + protected Flags getSharedPermanentFlags(MailboxSession session) { + return getPermanentFlags(session); + } + + /** + * Return true. If an subclass don't want to store mod-sequences in a + * permanent way just override this and return false + * + * @return true + */ + public boolean isModSeqPermanent(MailboxSession session) { + return true; + } + + /** + * @see org.apache.james.mailbox.MessageManager#expunge(org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.MailboxSession) + */ + public Iterator expunge(final MessageRange set, MailboxSession mailboxSession) throws MailboxException { + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + Map uids = deleteMarkedInMailbox(set, mailboxSession); + + dispatcher.expunged(mailboxSession, uids, getMailboxEntity()); + return uids.keySet().iterator(); + } + + /** + * @see org.apache.james.mailbox.MessageManager#appendMessage(java.io.InputStream, + * java.util.Date, org.apache.james.mailbox.MailboxSession, boolean, + * javax.mail.Flags) + */ + public long appendMessage(final InputStream msgIn, Date internalDate, final MailboxSession mailboxSession, final boolean isRecent, final Flags flagsToBeSet) throws MailboxException { + + File file = null; + TeeInputStream tmpMsgIn = null; + BodyOffsetInputStream bIn = null; + FileOutputStream out = null; + SharedFileInputStream contentIn = null; + + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + + try { + // Create a temporary file and copy the message to it. We will work + // with the file as + // source for the InputStream + file = File.createTempFile("imap", ".msg"); + out = new FileOutputStream(file); + + tmpMsgIn = new TeeInputStream(msgIn, out); + + bIn = new BodyOffsetInputStream(tmpMsgIn); + // Disable line length... This should be handled by the smtp server + // component and not the parser itself + // https://issues.apache.org/jira/browse/IMAP-122 + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + final MimeTokenStream parser = new MimeTokenStream(config, new DefaultBodyDescriptorBuilder()); + + parser.setRecursionMode(RecursionMode.M_NO_RECURSE); + parser.parse(bIn); + final HeaderImpl header = new HeaderImpl(); + + EntityState next = parser.next(); + while (next != EntityState.T_BODY && next != EntityState.T_END_OF_STREAM && next != EntityState.T_START_MULTIPART) { + if (next == EntityState.T_FIELD) { + header.addField(parser.getField()); + } + next = parser.next(); + } + final MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser.getBodyDescriptor(); + final PropertyBuilder propertyBuilder = new PropertyBuilder(); + final String mediaType; + final String mediaTypeFromHeader = descriptor.getMediaType(); + final String subType; + if (mediaTypeFromHeader == null) { + mediaType = "text"; + subType = "plain"; + } else { + mediaType = mediaTypeFromHeader; + subType = descriptor.getSubType(); + } + propertyBuilder.setMediaType(mediaType); + propertyBuilder.setSubType(subType); + propertyBuilder.setContentID(descriptor.getContentId()); + propertyBuilder.setContentDescription(descriptor.getContentDescription()); + propertyBuilder.setContentLocation(descriptor.getContentLocation()); + propertyBuilder.setContentMD5(descriptor.getContentMD5Raw()); + propertyBuilder.setContentTransferEncoding(descriptor.getTransferEncoding()); + propertyBuilder.setContentLanguage(descriptor.getContentLanguage()); + propertyBuilder.setContentDispositionType(descriptor.getContentDispositionType()); + propertyBuilder.setContentDispositionParameters(descriptor.getContentDispositionParameters()); + propertyBuilder.setContentTypeParameters(descriptor.getContentTypeParameters()); + // Add missing types + final String codeset = descriptor.getCharset(); + if (codeset == null) { + if ("TEXT".equalsIgnoreCase(mediaType)) { + propertyBuilder.setCharset("us-ascii"); + } + } else { + propertyBuilder.setCharset(codeset); + } + + final String boundary = descriptor.getBoundary(); + if (boundary != null) { + propertyBuilder.setBoundary(boundary); + } + if ("text".equalsIgnoreCase(mediaType)) { + final CountingInputStream bodyStream = new CountingInputStream(parser.getInputStream()); + bodyStream.readAll(); + long lines = bodyStream.getLineCount(); + bodyStream.close(); + next = parser.next(); + if (next == EntityState.T_EPILOGUE) { + final CountingInputStream epilogueStream = new CountingInputStream(parser.getInputStream()); + epilogueStream.readAll(); + lines += epilogueStream.getLineCount(); + epilogueStream.close(); + + } + propertyBuilder.setTextualLineCount(lines); + } + + final Flags flags; + if (flagsToBeSet == null) { + flags = new Flags(); + } else { + flags = flagsToBeSet; + + // Check if we need to trim the flags + trimFlags(flags, mailboxSession); + + } + if (isRecent) { + flags.add(Flags.Flag.RECENT); + } + if (internalDate == null) { + internalDate = new Date(); + } + byte[] discard = new byte[4096]; + while (tmpMsgIn.read(discard) != -1) { + // consume the rest of the stream so everything get copied to + // the file now + // via the TeeInputStream + } + int bodyStartOctet = (int) bIn.getBodyStartOffset(); + if (bodyStartOctet == -1) { + bodyStartOctet = 0; + } + contentIn = new SharedFileInputStream(file); + final int size = (int) file.length(); + + final Message message = createMessage(internalDate, size, bodyStartOctet, contentIn, flags, propertyBuilder); + return locker.executeWithLock(mailboxSession, new StoreMailboxPath(getMailboxEntity()), new MailboxPathLocker.LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + MessageMetaData data = appendMessageToStore(message, mailboxSession); + + SortedMap uids = new TreeMap(); + uids.put(data.getUid(), data); + dispatcher.added(mailboxSession, uids, getMailboxEntity()); + return data.getUid(); + } + }, true); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } catch (MimeException e) { + throw new MailboxException("Unable to parse message", e); + } finally { + IOUtils.closeQuietly(bIn); + IOUtils.closeQuietly(tmpMsgIn); + IOUtils.closeQuietly(out); + IOUtils.closeQuietly(contentIn); + + // delete the temporary file if one was specified + if (file != null) { + if (!file.delete()) { + // Don't throw an IOException. The message could be appended + // and the temporary file + // will be deleted hopefully some day + } + } + } + + } + + /** + * Create a new {@link Message} for the given data + * + * @param internalDate + * @param size + * @param bodyStartOctet + * @param content + * @param flags + * @return membership + * @throws MailboxException + */ + protected Message createMessage(Date internalDate, final int size, int bodyStartOctet, final SharedInputStream content, final Flags flags, final PropertyBuilder propertyBuilder) throws MailboxException { + return new SimpleMessage(internalDate, size, bodyStartOctet, content, flags, propertyBuilder, getMailboxEntity().getMailboxId()); + } + + /** + * This mailbox is writable + * + * @throws MailboxException + */ + public boolean isWriteable(MailboxSession session) throws MailboxException { + return aclResolver.isReadWrite(myRights(session), getSharedPermanentFlags(session)); + } + + /** + * @see MessageManager#getMetaData(boolean, MailboxSession, + * org.apache.james.mailbox.MessageManager.MetaData.FetchGroup) + */ + public MetaData getMetaData(boolean resetRecent, MailboxSession mailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup fetchGroup) throws MailboxException { + + final List recent; + final Flags permanentFlags = getPermanentFlags(mailboxSession); + final long uidValidity = getMailboxEntity().getUidValidity(); + final long uidNext = mapperFactory.getMessageMapper(mailboxSession).getLastUid(mailbox) + 1; + final long highestModSeq = mapperFactory.getMessageMapper(mailboxSession).getHighestModSeq(mailbox); + final long messageCount; + final long unseenCount; + final Long firstUnseen; + switch (fetchGroup) { + case UNSEEN_COUNT: + unseenCount = countUnseenMessagesInMailbox(mailboxSession); + messageCount = getMessageCount(mailboxSession); + firstUnseen = null; + recent = recent(resetRecent, mailboxSession); + + break; + case FIRST_UNSEEN: + firstUnseen = findFirstUnseenMessageUid(mailboxSession); + messageCount = getMessageCount(mailboxSession); + unseenCount = 0; + recent = recent(resetRecent, mailboxSession); + + break; + case NO_UNSEEN: + firstUnseen = null; + unseenCount = 0; + messageCount = getMessageCount(mailboxSession); + recent = recent(resetRecent, mailboxSession); + + break; + default: + firstUnseen = null; + unseenCount = 0; + messageCount = -1; + // just reset the recent but not include them in the metadata + if (resetRecent) { + recent(resetRecent, mailboxSession); + } + recent = new ArrayList(); + break; + } + MailboxACL resolvedAcl = getResolvedMailboxACL(mailboxSession); + return new MailboxMetaData(recent, permanentFlags, uidValidity, uidNext, highestModSeq, messageCount, unseenCount, firstUnseen, isWriteable(mailboxSession), isModSeqPermanent(mailboxSession), resolvedAcl); + } + + /** + * Check if the given {@link Flags} contains {@link Flags} which are not + * included in the returned {@link Flags} of + * {@link #getPermanentFlags(MailboxSession)}. If any are found, these are + * removed from the given {@link Flags} instance. The only exception is the + * {@link Flag#RECENT} flag. + * + * This flag is never removed! + * + * @param flags + * @param session + */ + private void trimFlags(Flags flags, MailboxSession session) { + + Flags permFlags = getPermanentFlags(session); + + Flag[] systemFlags = flags.getSystemFlags(); + for (int i = 0; i < systemFlags.length; i++) { + Flag f = systemFlags[i]; + + if (f != Flag.RECENT && permFlags.contains(f) == false) { + flags.remove(f); + } + } + // if the permFlags contains the special USER flag we can skip this as + // all user flags are allowed + if (permFlags.contains(Flags.Flag.USER) == false) { + String[] uFlags = flags.getUserFlags(); + for (int i = 0; i < uFlags.length; i++) { + String uFlag = uFlags[i]; + if (permFlags.contains(uFlag) == false) { + flags.remove(uFlag); + } + } + } + + } + + /** + * @see org.apache.james.mailbox.MessageManager#setFlags(javax.mail.Flags, + * boolean, boolean, org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.MailboxSession) + */ + public Map setFlags(final Flags flags, final boolean value, final boolean replace, final MessageRange set, MailboxSession mailboxSession) throws MailboxException { + + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + final SortedMap newFlagsByUid = new TreeMap(); + + trimFlags(flags, mailboxSession); + + final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession); + + Iterator it = messageMapper.execute(new Mapper.Transaction>() { + + public Iterator run() throws MailboxException { + return messageMapper.updateFlags(getMailboxEntity(), flags, value, replace, set); + } + }); + + final SortedMap uFlags = new TreeMap(); + + while (it.hasNext()) { + UpdatedFlags flag = it.next(); + newFlagsByUid.put(flag.getUid(), flag.getNewFlags()); + uFlags.put(flag.getUid(), flag); + } + + dispatcher.flagsUpdated(mailboxSession, new ArrayList(uFlags.keySet()), getMailboxEntity(), new ArrayList(uFlags.values())); + + return newFlagsByUid; + } + + /** + * Copy the {@link MessageRange} to the {@link StoreMessageManager} + * + * @param set + * @param toMailbox + * @param session + * @throws MailboxException + */ + public List copyTo(final MessageRange set, final StoreMessageManager toMailbox, final MailboxSession session) throws MailboxException { + if (!toMailbox.isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()), session.getPathDelimiter()); + } + + return locker.executeWithLock(session, new StoreMailboxPath(toMailbox.getMailboxEntity()), new MailboxPathLocker.LockAwareExecution>() { + + @Override + public List execute() throws MailboxException { + SortedMap copiedUids = copy(set, toMailbox, session); + dispatcher.added(session, copiedUids, toMailbox.getMailboxEntity()); + return MessageRange.toRanges(new ArrayList(copiedUids.keySet())); + } + }, true); + } + + /** + * Move the {@link MessageRange} to the {@link StoreMessageManager} + * + * @param set + * @param toMailbox + * @param session + * @throws MailboxException + */ + public List moveTo(final MessageRange set, final StoreMessageManager toMailbox, final MailboxSession session) throws MailboxException { + if (!isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), session.getPathDelimiter()); + } + if (!toMailbox.isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()), session.getPathDelimiter()); + } + + //TODO lock the from mailbox too, in a non-deadlocking manner - how? + return locker.executeWithLock(session, new StoreMailboxPath(toMailbox.getMailboxEntity()), new MailboxPathLocker.LockAwareExecution>() { + + @Override + public List execute() throws MailboxException { + SortedMap movedUids = move(set, toMailbox, session); + dispatcher.added(session, movedUids, toMailbox.getMailboxEntity()); + return MessageRange.toRanges(new ArrayList(movedUids.keySet())); + } + }, true); + } + + protected MessageMetaData appendMessageToStore(final Message message, MailboxSession session) throws MailboxException { + final MessageMapper mapper = mapperFactory.getMessageMapper(session); + return mapperFactory.getMessageMapper(session).execute(new Mapper.Transaction() { + + public MessageMetaData run() throws MailboxException { + return mapper.add(getMailboxEntity(), message); + } + + }); + } + + /** + * @see org.apache.james.mailbox.MessageManager#getMessageCount(org.apache.james.mailbox.MailboxSession) + */ + public long getMessageCount(MailboxSession mailboxSession) throws MailboxException { + return mapperFactory.getMessageMapper(mailboxSession).countMessagesInMailbox(getMailboxEntity()); + } + + /** + * @see org.apache.james.mailbox.MessageManager#getMessages(org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.model.MessageResult.FetchGroup, + * org.apache.james.mailbox.MailboxSession) + */ + public MessageResultIterator getMessages(final MessageRange set, FetchGroup fetchGroup, MailboxSession mailboxSession) throws MailboxException { + final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession); + return new StoreMessageResultIterator(messageMapper, mailbox, set, fetchBatchSize, fetchGroup); + } + + /** + * Return a List which holds all uids of recent messages and optional reset + * the recent flag on the messages for the uids + * + * @param reset + * @param mailboxSession + * @return list + * @throws MailboxException + */ + protected List recent(final boolean reset, MailboxSession mailboxSession) throws MailboxException { + if (reset) { + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + } + final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession); + + return messageMapper.execute(new Mapper.Transaction>() { + + public List run() throws MailboxException { + final List members = messageMapper.findRecentMessageUidsInMailbox(getMailboxEntity()); + + // Convert to MessageRanges so we may be able to optimize the + // flag update + List ranges = MessageRange.toRanges(members); + for (MessageRange range : ranges) { + if (reset) { + // only call save if we need to + messageMapper.updateFlags(getMailboxEntity(), new Flags(Flag.RECENT), false, false, range); + } + } + return members; + } + + }); + + } + + protected Map deleteMarkedInMailbox(final MessageRange range, final MailboxSession session) throws MailboxException { + + final MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + return messageMapper.execute(new Mapper.Transaction>() { + + public Map run() throws MailboxException { + return messageMapper.expungeMarkedForDeletionInMailbox(getMailboxEntity(), range); + } + + }); + } + + /** + * @see org.apache.james.mailbox.MessageManager#search(org.apache.james.mailbox.model.SearchQuery, + * org.apache.james.mailbox.MailboxSession) + */ + public Iterator search(SearchQuery query, MailboxSession mailboxSession) throws MailboxException { + return index.search(mailboxSession, getMailboxEntity(), query); + } + + private Iterator copy(final Iterator> originalRows, final MailboxSession session) throws MailboxException { + final List copiedRows = new ArrayList(); + final MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + while (originalRows.hasNext()) { + final Message originalMessage = originalRows.next(); + MessageMetaData data = messageMapper.execute(new Mapper.Transaction() { + public MessageMetaData run() throws MailboxException { + return messageMapper.copy(getMailboxEntity(), originalMessage); + + } + + }); + copiedRows.add(data); + } + return copiedRows.iterator(); + } + + private Iterator move(Iterator> originalRows, + MailboxSession session) throws MailboxException { + final List movedRows = new ArrayList(); + final MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + while (originalRows.hasNext()) { + final Message originalMessage = originalRows.next(); + MessageMetaData data = messageMapper.execute(new Mapper.Transaction() { + public MessageMetaData run() throws MailboxException { + return messageMapper.move(getMailboxEntity(), originalMessage); + + } + + }); + movedRows.add(data); + } + return movedRows.iterator(); + } + + + /** + * @see org.apache.james.mailbox.store.AbstractStoreMessageManager#copy(org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.AbstractStoreMessageManager, + * org.apache.james.mailbox.MailboxSession) + */ + private SortedMap copy(MessageRange set, final StoreMessageManager to, final MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + final SortedMap copiedMessages = new TreeMap(); + Iterator> originalRows = messageMapper.findInMailbox(mailbox, set, FetchType.Full, -1); + Iterator ids = to.copy(originalRows, session); + while (ids.hasNext()) { + MessageMetaData data = ids.next(); + copiedMessages.put(data.getUid(), data); + } + + return copiedMessages; + } + + private SortedMap move(MessageRange set, + final StoreMessageManager to, final MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + final SortedMap movedMessages = new TreeMap(); + Iterator> originalRows = messageMapper.findInMailbox(mailbox, set, FetchType.Full, -1); + Iterator ids = to.move(originalRows, session); + while (ids.hasNext()) { + MessageMetaData data = ids.next(); + movedMessages.put(data.getUid(), data); + } + + return movedMessages; + } + + + /** + * Return the count of unseen messages + * + * @param session + * @return count of unseen messages + * @throws MailboxException + */ + protected long countUnseenMessagesInMailbox(MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + return messageMapper.countUnseenMessagesInMailbox(getMailboxEntity()); + } + + /** + * Return the uid of the first unseen message or null of none is found + * + * @param session + * @return uid + * @throws MailboxException + */ + protected Long findFirstUnseenMessageUid(MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + return messageMapper.findFirstUnseenMessageUid(getMailboxEntity()); + } + + /** + * @see org.apache.james.mailbox.MessageManager#hasRight(org.apache.james.mailbox.MailboxACL.MailboxACLRight, + * org.apache.james.mailbox.MailboxSession) + */ + public boolean hasRight(MailboxACLRight right, MailboxSession session) throws UnsupportedRightException { + User user = session.getUser(); + String userName = user != null ? user.getUserName() : null; + + return aclResolver.hasRight(userName, groupMembershipResolver, right, mailbox.getACL(), mailbox.getUser(), isGroupFolder(session)); + } + + /** + * @see org.apache.james.mailbox.MessageManager#myRights(org.apache.james.mailbox.MailboxSession) + */ + @Override + public MailboxACLRights myRights(MailboxSession session) throws MailboxException { + User user = session.getUser(); + if (user != null) { + return aclResolver.resolveRights(user.getUserName(), groupMembershipResolver, mailbox.getACL(), mailbox.getUser(), isGroupFolder(session)); + } else { + return SimpleMailboxACL.NO_RIGHTS; + } + } + + /** + * @see org.apache.james.mailbox.MessageManager#listRigths(java.lang.String, org.apache.james.mailbox.MailboxSession) + */ + public MailboxACLRights[] listRigths(final MailboxACLEntryKey key, MailboxSession session) throws UnsupportedRightException { + return aclResolver.listRights(key, groupMembershipResolver, mailbox.getUser(), isGroupFolder(session)); + } + + /** + * @throws UnsupportedRightException + * @see org.apache.james.mailbox.MessageManager#setRights(java.lang.String, org.apache.james.mailbox.model.MailboxACL.EditMode, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + @Override + public void setRights(MailboxACLEntryKey mailboxACLEntryKey, EditMode editMode, MailboxACLRights mailboxAclRights) throws UnsupportedRightException { + MailboxACL acl = mailbox.getACL(); + if (acl == null) { + acl = SimpleMailboxACL.EMPTY; + } + switch (editMode) { + case ADD: + acl = acl.union(mailboxACLEntryKey, mailboxAclRights); + break; + case REMOVE: + acl = acl.except(mailboxACLEntryKey, mailboxAclRights); + break; + case REPLACE: + acl = acl.replace(mailboxACLEntryKey, mailboxAclRights); + break; + default: + throw new IllegalStateException("Unexpected "+ EditMode.class.getName() +"."+ editMode); + } + mailbox.setACL(acl); + } + + /** + * Applies the global ACL (if there are any) to the mailbox ACL. + * + * @param mailboxSession + * @return the ACL of the present mailbox merged with the global ACL (if + * there are any). + * @throws UnsupportedRightException + */ + protected MailboxACL getResolvedMailboxACL(MailboxSession mailboxSession) throws UnsupportedRightException { + return aclResolver.applyGlobalACL(mailbox.getACL(), isGroupFolder(mailboxSession)); + } + + /** + * Returns true if the current mailbox does not reside neither in private + * nor other users' namespace. + * + * @param session + * @return + */ + protected boolean isGroupFolder(MailboxSession session) { + final String ns = mailbox.getNamespace(); + return ns == null || (!ns.equals(session.getPersonalSpace()) && !ns.equals(session.getOtherUsersSpace())); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIterator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIterator.java.svn-base new file mode 100644 index 0000000..0800cdd --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIterator.java.svn-base @@ -0,0 +1,292 @@ +/**************************************************************** + * 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 java.util.Date; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.Headers; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResultIterator; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +public class StoreMessageResultIterator implements MessageResultIterator { + + private Iterator> next = null; + private MailboxException exception; + private Mailbox mailbox; + private FetchGroup group; + private long from; + private long cursor; + private long to; + private int batchSize; + private Type type; + private MessageMapper mapper; + private FetchType ftype; + + public StoreMessageResultIterator(MessageMapper mapper, Mailbox mailbox, MessageRange range, int batchSize, org.apache.james.mailbox.model.MessageResult.FetchGroup group) { + this.mailbox = mailbox; + this.group = group; + this.mapper = mapper; + this.from = range.getUidFrom(); + this.cursor = this.from; + this.to = range.getUidTo(); + this.batchSize = batchSize; + this.type = range.getType(); + this.ftype = getFetchType(group); + } + + /** + * Use the passed {@link FetchGroup} and calculate the right + * {@link FetchType} for it + * + * @param group + * @return fetchType + */ + private static final FetchType getFetchType(FetchGroup group) { + int content = group.content(); + boolean headers = false; + boolean body = false; + boolean full = false; + + if ((content & FetchGroup.HEADERS) > 0) { + headers = true; + content -= FetchGroup.HEADERS; + } + if ((content & FetchGroup.BODY_CONTENT) > 0) { + body = true; + content -= FetchGroup.BODY_CONTENT; + } + + if ((content & FetchGroup.FULL_CONTENT) > 0) { + full = true; + content -= FetchGroup.FULL_CONTENT; + } + + if ((content & FetchGroup.MIME_DESCRIPTOR) > 0) { + // If we need the mimedescriptor we MAY need the full content later + // too. + // This gives us no other choice then request it + full = true; + content -= FetchGroup.MIME_DESCRIPTOR; + } + if (full || (body && headers)) { + return FetchType.Full; + } else if (body) { + return FetchType.Body; + } else if (headers) { + return FetchType.Headers; + } else { + return FetchType.Metadata; + } + } + + @Override + public boolean hasNext() { + if (cursor > to) + return false; + + if (next == null || !next.hasNext()) { + try { + readBatch(); + } catch (MailboxException e) { + this.exception = e; + return false; + } + } + + return next.hasNext(); + } + + private void readBatch() throws MailboxException { + MessageRange range; + switch (type) { + default: + case ALL: + // In case of all, we start on cursor and don't specify a to + range = MessageRange.from(cursor); + break; + case FROM: + range = MessageRange.from(cursor); + break; + case ONE: + range = MessageRange.one(cursor); + break; + case RANGE: + range = MessageRange.range(cursor, to); + break; + } + next = mapper.findInMailbox(mailbox, range, ftype, batchSize); + } + + @Override + public MessageResult next() { + if (next == null || !next.hasNext()) + throw new NoSuchElementException(); + + final Message message = next.next(); + MessageResult result; + try { + result = ResultUtils.loadMessageResult(message, group); + cursor = result.getUid(); + } catch (MailboxException e) { + result = new UnloadedMessageResult(message, e); + } + + cursor++; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read only"); + } + + @Override + public MailboxException getException() { + return exception; + } + + private static final class UnloadedMessageResult implements MessageResult { + private final MailboxException exception; + + private final Date internalDate; + + private final long size; + + private final long uid; + + private final Flags flags; + + private long modSeq = -1; + + public UnloadedMessageResult(final Message message, final MailboxException exception) { + super(); + internalDate = message.getInternalDate(); + size = message.getFullContentOctets(); + uid = message.getUid(); + flags = message.createFlags(); + modSeq = message.getModSeq(); + this.exception = exception; + } + + public Flags getFlags() { + return flags; + } + + public Content getFullContent() throws MailboxException { + throw exception; + } + + public Date getInternalDate() { + return internalDate; + } + + public Content getBody() throws MailboxException { + throw exception; + } + + public long getSize() { + return size; + } + + public long getUid() { + return uid; + } + + public int compareTo(MessageResult that) { + // Java 1.5 return (int) Math.signum(uid - that.getUid()); + long diff = uid - that.getUid(); + return (int) diff == 0 ? 0 : diff > 0 ? 1 : -1; + } + + @Override + public int hashCode() { + int ret = 19 * 37; + ret = ret * 37 + exception.hashCode(); + ret = ret * 37 + internalDate.hashCode(); + ret = ret * 37 + (int)size; + ret = ret * 37 + (int)uid; + ret = ret * 37 + flags.hashCode(); + ret = ret * 37 + (int)modSeq; + return ret; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof UnloadedMessageResult) { + @SuppressWarnings("unchecked") + UnloadedMessageResult that = (UnloadedMessageResult)obj; + return (size == that.size) && (uid == that.uid) && (modSeq == that.modSeq) && exception.equals(that.exception) + && internalDate.equals(that.internalDate) && flags.equals(that.flags); + } + return false; + } + + public Content getFullContent(MimePath path) throws MailboxException { + throw exception; + } + + public Iterator

iterateHeaders(MimePath path) throws MailboxException { + throw exception; + } + + public Iterator
iterateMimeHeaders(MimePath path) throws MailboxException { + throw exception; + } + + public Content getBody(MimePath path) throws MailboxException { + throw exception; + } + + public Content getMimeBody(MimePath path) throws MailboxException { + throw exception; + } + + public MimeDescriptor getMimeDescriptor() throws MailboxException { + throw exception; + } + + public long getModSeq() { + return modSeq; + } + + @Override + public Headers getHeaders() throws MailboxException { + throw exception; + } + + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreSubscriptionManager.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreSubscriptionManager.java.svn-base new file mode 100644 index 0000000..6a42a98 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/.svn/text-base/StoreSubscriptionManager.java.svn-base @@ -0,0 +1,135 @@ +/**************************************************************** + * 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 java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.RequestAware; +import org.apache.james.mailbox.SubscriptionManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapperFactory; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; + +/** + * Manages subscriptions for Users and Mailboxes. + */ +public class StoreSubscriptionManager implements SubscriptionManager { + + private static final int INITIAL_SIZE = 32; + + protected SubscriptionMapperFactory mapperFactory; + + public StoreSubscriptionManager(final SubscriptionMapperFactory mapperFactory) { + this.mapperFactory = mapperFactory; + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#subscribe(org.apache.james.mailbox.MailboxSession, java.lang.String) + */ + public void subscribe(final MailboxSession session, final String mailbox) throws SubscriptionException { + final SubscriptionMapper mapper = mapperFactory.getSubscriptionMapper(session); + try { + mapper.execute(new Mapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + final Subscription subscription = mapper.findMailboxSubscriptionForUser(session.getUser().getUserName(), mailbox); + if (subscription == null) { + final Subscription newSubscription = createSubscription(session, mailbox); + mapper.save(newSubscription); + } + } + + }); + } catch (MailboxException e) { + throw new SubscriptionException(e); + } + } + + /** + * Create Subscription for the given user and mailbox. By default a {@link SimpleSubscription} will get returned. + * + * If you need something more special just override this method + * + * @param session + * @param mailbox + * @return subscription + */ + protected Subscription createSubscription(final MailboxSession session, final String mailbox) { + return new SimpleSubscription(session.getUser().getUserName(), mailbox); + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#subscriptions(org.apache.james.mailbox.MailboxSession) + */ + public Collection subscriptions(final MailboxSession session) throws SubscriptionException { + final SubscriptionMapper mapper = mapperFactory.getSubscriptionMapper(session); + final List subscriptions = mapper.findSubscriptionsForUser(session.getUser().getUserName()); + final Collection results = new HashSet(INITIAL_SIZE); + for (Subscription subscription:subscriptions) { + results.add(subscription.getMailbox()); + } + return results; + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#unsubscribe(org.apache.james.mailbox.MailboxSession, java.lang.String) + */ + public void unsubscribe(final MailboxSession session, final String mailbox) throws SubscriptionException { + final SubscriptionMapper mapper = mapperFactory.getSubscriptionMapper(session); + try { + mapper.execute(new Mapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + final Subscription subscription = mapper.findMailboxSubscriptionForUser(session.getUser().getUserName(), mailbox); + if (subscription != null) { + mapper.delete(subscription); + } + } + + }); + } catch (MailboxException e) { + throw new SubscriptionException(e); + } + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#endProcessingRequest(org.apache.james.mailbox.MailboxSession) + */ + public void endProcessingRequest(MailboxSession session) { + if (mapperFactory instanceof RequestAware) { + ((RequestAware)mapperFactory).endProcessingRequest(session); + } + } + + /** + * Do nothing, Sub classes should override this if needed + */ + public void startProcessingRequest(MailboxSession session) { + // Do nothing + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java new file mode 100644 index 0000000..e33673c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractDelegatingMailboxListener.java @@ -0,0 +1,185 @@ +/**************************************************************** + * 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 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/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxPathLocker.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxPathLocker.java new file mode 100644 index 0000000..aa7dea9 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxPathLocker.java @@ -0,0 +1,63 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + + +public abstract class AbstractMailboxPathLocker implements MailboxPathLocker{ + + @Override + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution) throws MailboxException { + return executeWithLock(session, path, execution, true); + } + + @Override + public T executeWithLock(MailboxSession session, MailboxPath path, LockAwareExecution execution, boolean writeLock) throws MailboxException { + try { + lock(session, path, writeLock); + return execution.execute(); + } finally { + unlock(session, path, writeLock); + } + } + + + /** + * Perform lock + * + * @param session + * @param path + * @throws MailboxException + */ + protected abstract void lock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException; + + /** + * Release lock + * + * @param session + * @param path + * @throws MailboxException + */ + protected abstract void unlock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxSessionIdGenerator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxSessionIdGenerator.java new file mode 100644 index 0000000..a7b4cee --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/AbstractMailboxSessionIdGenerator.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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSessionIdGenerator; + +public abstract class AbstractMailboxSessionIdGenerator implements MailboxSessionIdGenerator { + + @Override + public long nextId() { + long id; + while ((id = generateNextId()) == MailboxSession.SYSTEM_SESSION_ID) { + // loop till the id is not 0L + // + // See also MAILBOX-149 + } + return id; + } + + + /** + * Generate the next id to use + * + * + * @return id + */ + protected abstract long generateNextId(); + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/Authenticator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/Authenticator.java new file mode 100644 index 0000000..71e6eb5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/Authenticator.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; + +/** + * Authenticates user credentials. + */ +public interface Authenticator { + + /** + * Is the given user authentic? + * + * @param userid not null + * @param passwd not null + * @return true when the user is authentic, + * false otherwise + */ + boolean isAuthentic(String userid, CharSequence passwd); +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.java new file mode 100644 index 0000000..ba08d76 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/HashMapDelegatingMailboxListener.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; + +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 + protected Map> getListeners() { + return listeners; + } + + @Override + protected List getGlobalListeners() { + return globalListeners; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/JVMMailboxPathLocker.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/JVMMailboxPathLocker.java new file mode 100644 index 0000000..beed7f1 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/JVMMailboxPathLocker.java @@ -0,0 +1,74 @@ +/**************************************************************** + * 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.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * {@link MailboxPathLocker} implementation which helps to synchronize the access the + * same MailboxPath. This is done using one {@link ReentrantReadWriteLock} + * per {@link MailboxPath} so its only usable in a single JVM. + */ +public final class JVMMailboxPathLocker extends AbstractMailboxPathLocker { + + private final ConcurrentHashMap paths = new ConcurrentHashMap(); + + + @Override + protected void lock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException { + ReadWriteLock lock = paths.get(path); + if (lock == null) { + lock = new ReentrantReadWriteLock(); + ReadWriteLock storedLock = paths.putIfAbsent(path, lock); + if (storedLock != null) { + lock = storedLock; + } + } + getLock(lock, writeLock).lock(); + } + + + @Override + protected void unlock(MailboxSession session, MailboxPath path, boolean writeLock) throws MailboxException { + ReadWriteLock lock = paths.get(path); + + if (lock != null) { + getLock(lock, writeLock).unlock(); + } + } + + private Lock getLock(ReadWriteLock lock, boolean writeLock) { + Lock l; + if (writeLock) { + l = lock.writeLock(); + } else { + l = lock.readLock(); + } + return l; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java new file mode 100644 index 0000000..d72c9ee --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java @@ -0,0 +1,171 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; + +/** + * A {@link MimeDescriptor} implementation which tries to optimize the way the data + * is loading by using it in a lazy fashion whenever possible. + * + * + */ +public class LazyMimeDescriptor implements MimeDescriptor{ + + private final Message message; + private final MessageResult result; + private PropertyBuilder pbuilder; + + public LazyMimeDescriptor(final MessageResult result, final Message message) { + this.message = message; + this.result = result; + } + + + @Override + public Iterator
headers() throws MailboxException { + return result.getHeaders().headers(); + } + + @Override + public InputStream getInputStream() throws IOException { + try { + return result.getHeaders().getInputStream(); + } catch (MailboxException e) { + throw new IOException("Unable to retrieve content", e); + } + } + + @Override + public long size() throws MailboxException { + return result.getHeaders().size(); + } + + @Override + public String getMimeType() { + return message.getMediaType(); + } + + @Override + public String getMimeSubType() { + return message.getSubType(); + } + + @Override + public String getContentID() { + return getPropertyBuilder().getContentID(); + } + + @Override + public String getContentDescription() { + return getPropertyBuilder().getContentDescription(); + } + + @Override + public String getContentLocation() { + return getPropertyBuilder().getContentLocation(); + } + + @Override + public String getContentMD5() { + return getPropertyBuilder().getContentMD5(); + } + + @Override + public String getTransferContentEncoding() { + return getPropertyBuilder().getContentTransferEncoding(); + } + + @Override + public List getLanguages() { + return getPropertyBuilder().getContentLanguage(); + } + + @Override + public String getDisposition() { + return getPropertyBuilder().getContentDispositionType(); + } + + @Override + public Map getDispositionParams() { + return getPropertyBuilder().getContentDispositionParameters(); + + } + + @Override + public long getLines() { + Long count = message.getTextualLineCount(); + if (count == null) { + return -1; + } else { + return count; + } + } + + @Override + public long getBodyOctets() { + return message.getBodyOctets(); + } + + @SuppressWarnings("unchecked") + @Override + public Iterator parts() { + return Collections.EMPTY_LIST.iterator(); + } + + /** + * Return null + */ + @Override + public MimeDescriptor embeddedMessage() { + return null; + } + + @Override + public Map contentTypeParameters() { + return getPropertyBuilder().getContentTypeParameters(); + } + + /** + * Return a {@link PropertyBuilder} which is created in a lazy fashion if it not exist yet. + * This is done as it may be expensive to retrieve the properties of the message. + * + * @return pbuilder + */ + private PropertyBuilder getPropertyBuilder() { + if (pbuilder == null) { + pbuilder = new PropertyBuilder(message.getProperties()); + } + return pbuilder; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java new file mode 100644 index 0000000..bf0d7a5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxEventDispatcher.java @@ -0,0 +1,296 @@ +/**************************************************************** + * 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 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.mail.model.Mailbox; + +/** + * Helper class to dispatch {@link org.apache.james.mailbox.MailboxListener.Event}'s to registerend MailboxListener + */ +public class MailboxEventDispatcher { + + + private final MailboxListener listener; + + public MailboxEventDispatcher(MailboxListener listener) { + this.listener = listener; + } + + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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); + } + + + + /** + * 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; + } + } + + 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); + } + + /** + * 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); + } + + 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/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxMetaData.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxMetaData.java new file mode 100644 index 0000000..a04d2af --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxMetaData.java @@ -0,0 +1,156 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.List; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.model.MailboxACL; + +/** + * Describes the current state of a mailbox. + */ +public class MailboxMetaData implements MessageManager.MetaData { + + private final long recentCount; + private final List recent; + private final Flags premanentFlags; + private final long uidValidity; + private final long nextUid; + private final long messageCount; + private final long unseenCount; + private final Long firstUnseen; + private final boolean writeable; + private final long highestModSeq; + private final boolean modSeqPermanent; + private final MailboxACL acl; + + public MailboxMetaData(final List recent, final Flags premanentFlags, final long uidValidity, final long nextUid, final long highestModSeq, final long messageCount, final long unseenCount, final Long firstUnseen, final boolean writeable, final boolean modSeqPermanent, MailboxACL acl) { + super(); + if (recent == null) { + this.recent = new ArrayList(); + } else { + this.recent = recent; + + } + this.highestModSeq = highestModSeq; + recentCount = this.recent.size(); + + this.premanentFlags = premanentFlags; + this.uidValidity = uidValidity; + this.nextUid = nextUid; + this.messageCount = messageCount; + this.unseenCount = unseenCount; + this.firstUnseen = firstUnseen; + this.writeable = writeable; + this.modSeqPermanent = modSeqPermanent; + this.acl = acl; + } + + /** + * @see MailboxMetaData#countRecent() + */ + public long countRecent() { + return recentCount; + } + + /** + * @see MailboxMetaData#getPermanentFlags() + */ + public Flags getPermanentFlags() { + return premanentFlags; + } + + /** + * @see MailboxMetaData#getRecent() + */ + public List getRecent() { + return recent; + } + + /** + * @see MailboxMetaData#getUidValidity() + */ + public long getUidValidity() { + return uidValidity; + } + + /** + * @see MailboxMetaData#getUidNext() + */ + public long getUidNext() { + return nextUid; + } + + /** + * @see MailboxMetaData#getMessageCount() + */ + public long getMessageCount() { + return messageCount; + } + + /** + * @see MailboxMetaData#getUnseenCount() + */ + public long getUnseenCount() { + return unseenCount; + } + + /** + * @see MailboxMetaData#getFirstUnseen() + */ + public Long getFirstUnseen() { + return firstUnseen; + } + + /** + * @see MailboxMetaData#isWriteable() + */ + public boolean isWriteable() { + return writeable; + } + + /** + * @see MailboxMetaData#getHighestModSeq() + */ + public long getHighestModSeq() { + return highestModSeq; + } + + /** + * @see MailboxMetaData#isModSeqPermanent() + */ + public boolean isModSeqPermanent() { + return modSeqPermanent; + } + + /* + * (non-Javadoc) + * + * @see org.apache.james.mailbox.MessageManager.MetaData#getACL() + */ + @Override + public MailboxACL getACL() { + return acl; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxSessionMapperFactory.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxSessionMapperFactory.java new file mode 100644 index 0000000..31757ef --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MailboxSessionMapperFactory.java @@ -0,0 +1,142 @@ +/**************************************************************** + * 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.MailboxSession; +import org.apache.james.mailbox.RequestAware; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.mail.MailboxMapper; +import org.apache.james.mailbox.store.mail.MailboxMapperFactory; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapperFactory; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapperFactory; + +/** + * Maintain mapper instances by {@link MailboxSession}. So only one mapper instance is used + * in a {@link MailboxSession} + */ +public abstract class MailboxSessionMapperFactory implements RequestAware, MailboxMapperFactory, MessageMapperFactory, SubscriptionMapperFactory{ + + protected final static String MESSAGEMAPPER ="MESSAGEMAPPER"; + protected final static String MAILBOXMAPPER ="MAILBOXMAPPER"; + protected final static String SUBSCRIPTIONMAPPER ="SUBSCRIPTIONMAPPER"; + + + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapperFactory#getMessageMapper(MailboxSession) + */ + @SuppressWarnings("unchecked") + public MessageMapper getMessageMapper(MailboxSession session) throws MailboxException { + MessageMapper mapper = (MessageMapper) session.getAttributes().get(MESSAGEMAPPER); + if (mapper == null) { + mapper = createMessageMapper(session); + session.getAttributes().put(MESSAGEMAPPER, mapper); + } + return mapper; + } + + /** + * Create a {@link MessageMapper} instance which will get reused during the whole {@link MailboxSession} + * + * @param session + * @return messageMapper + * @throws MailboxException + */ + public abstract MessageMapper createMessageMapper(MailboxSession session) throws MailboxException; + + + /** + * @see org.apache.james.mailbox.store.mail.MailboxMapperFactory#getMailboxMapper(MailboxSession) + */ + @SuppressWarnings("unchecked") + public MailboxMapper getMailboxMapper(MailboxSession session) throws MailboxException { + MailboxMapper mapper = (MailboxMapper) session.getAttributes().get(MAILBOXMAPPER); + if (mapper == null) { + mapper = createMailboxMapper(session); + session.getAttributes().put(MAILBOXMAPPER, mapper); + } + return mapper; + } + + /** + * Create a {@link MailboxMapper} instance which will get reused during the whole {@link MailboxSession} + * + * @param session + * @return mailboxMapper + * @throws MailboxException + */ + public abstract MailboxMapper createMailboxMapper(MailboxSession session) throws MailboxException; + + /** + * Create a {@link SubscriptionMapper} instance or return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + public SubscriptionMapper getSubscriptionMapper(MailboxSession session) throws SubscriptionException { + SubscriptionMapper mapper = (SubscriptionMapper) session.getAttributes().get(SUBSCRIPTIONMAPPER); + if (mapper == null) { + mapper = createSubscriptionMapper(session); + session.getAttributes().put(SUBSCRIPTIONMAPPER, mapper); + } + return mapper; + } + + /** + * Create a {@link SubscriptionMapper} instance which will get reused during the whole {@link MailboxSession} + * @param session + * @return subscriptionMapper + * @throws SubscriptionException + */ + public abstract SubscriptionMapper createSubscriptionMapper(MailboxSession session) throws SubscriptionException; + + /** + * Call endRequest on {@link Mapper} instances + * + * @param session + */ + @SuppressWarnings("unchecked") + public void endProcessingRequest(MailboxSession session) { + if (session == null) return; + MessageMapper messageMapper = (MessageMapper) session.getAttributes().get(MESSAGEMAPPER); + MailboxMapper mailboxMapper = (MailboxMapper) session.getAttributes().get(MAILBOXMAPPER); + SubscriptionMapper subscriptionMapper = (SubscriptionMapper) session.getAttributes().get(SUBSCRIPTIONMAPPER); + if (messageMapper != null) + messageMapper.endRequest(); + if (mailboxMapper != null) + mailboxMapper.endRequest(); + if (subscriptionMapper != null) + subscriptionMapper.endRequest(); + } + + /** + * Do nothing + * + */ + public void startProcessingRequest(MailboxSession session) { + // Do nothing + + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java new file mode 100644 index 0000000..0ed73c4 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java @@ -0,0 +1,422 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.Headers; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.streaming.InputStreamContent; +import org.apache.james.mailbox.store.streaming.InputStreamContent.Type; +import org.apache.james.mime4j.MimeException; + +/** + * Bean based implementation. + */ +public class MessageResultImpl implements MessageResult { + + private final Map partsByPath = new HashMap(); + + private MimeDescriptor mimeDescriptor; + + private final Message message; + + private HeadersImpl headers; + private Content fullContent; + private Content bodyContent; + + + public MessageResultImpl(Message message) throws IOException { + this.message = message; + this.headers = new HeadersImpl(message); + + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getUid() + */ + public long getUid() { + return message.getUid(); + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getInternalDate() + */ + public Date getInternalDate() { + return message.getInternalDate(); + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getFlags() + */ + public Flags getFlags() { + return message.createFlags(); + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getSize() + */ + public long getSize() { + return message.getFullContentOctets(); + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(MessageResult that) { + if (getUid() > 0 && that.getUid() > 0) { + // TODO: this seems inefficient + return Long.valueOf(getUid()).compareTo(Long.valueOf(that.getUid())); + } else { + // TODO: throwing an undocumented untyped runtime seems wrong + // TODO: if uids must be greater than zero then this should be + // enforced + // TODO: on the way in + // TODO: probably an IllegalArgumentException would be better + throw new RuntimeException("can't compare"); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return 37 * 17 + (int)getUid(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MessageResultImpl) { + MessageResultImpl that = (MessageResultImpl)obj; + return this.headers.equals(that.headers) && this.message.equals(that.message); +// return this.message.equals(that.message); + } + return false; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getFullContent() + */ + public final Content getFullContent() throws IOException { + if (fullContent == null) { + fullContent = new InputStreamContent(message, Type.Full); + } + return fullContent; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getBody() + */ + public final Content getBody() throws IOException { + if (bodyContent == null) { + bodyContent = new InputStreamContent(message, Type.Body); + } + return bodyContent; + } + + + /** + * Renders suitably for logging. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + String retValue = "MessageResultImpl ( " + "uid = " + getUid() + TAB + "flags = " + getFlags() + TAB + "size = " + getSize() + TAB + "internalDate = " + getInternalDate()+ ")"; + + return retValue; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#getBody(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Content getBody(MimePath path) throws MailboxException { + final Content result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getBody(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#getMimeBody(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Content getMimeBody(MimePath path) throws MailboxException { + final Content result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getMimeBody(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#getFullContent(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Content getFullContent(MimePath path) throws MailboxException { + final Content result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getFull(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#iterateHeaders(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Iterator
iterateHeaders(MimePath path) throws MailboxException { + final Iterator
result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getHeaders(); + } + return result; + } + + /** + * @see + * org.apache.james.mailbox.model.MessageResult#iterateMimeHeaders(org.apache.james.mailbox.model.MessageResult.MimePath) + */ + public Iterator
iterateMimeHeaders(MimePath path) throws MailboxException { + final Iterator
result; + final PartContent partContent = getPartContent(path); + if (partContent == null) { + result = null; + } else { + result = partContent.getMimeHeaders(); + } + return result; + } + + public void setBodyContent(MimePath path, Content content) { + final PartContent partContent = getPartContent(path); + partContent.setBody(content); + } + + public void setMimeBodyContent(MimePath path, Content content) { + final PartContent partContent = getPartContent(path); + partContent.setMimeBody(content); + } + + public void setFullContent(MimePath path, Content content) { + final PartContent partContent = getPartContent(path); + partContent.setFull(content); + } + + public void setHeaders(MimePath path, Iterator
headers) { + final PartContent partContent = getPartContent(path); + partContent.setHeaders(headers); + } + + public void setMimeHeaders(MimePath path, Iterator
headers) { + final PartContent partContent = getPartContent(path); + partContent.setMimeHeaders(headers); + } + + private PartContent getPartContent(MimePath path) { + PartContent result = (PartContent) partsByPath.get(path); + if (result == null) { + result = new PartContent(); + partsByPath.put(path, result); + } + return result; + } + + private static final class PartContent { + private Content body; + + private Content mimeBody; + + private Content full; + + private Iterator
headers; + + private Iterator
mimeHeaders; + + private int content; + + public Content getBody() { + return body; + } + + public void setBody(Content body) { + content = content | FetchGroup.BODY_CONTENT; + this.body = body; + } + + public Content getMimeBody() { + return mimeBody; + } + + public void setMimeBody(Content mimeBody) { + content = content | FetchGroup.MIME_CONTENT; + this.mimeBody = mimeBody; + } + + public Content getFull() { + return full; + } + + public void setFull(Content full) { + content = content | FetchGroup.FULL_CONTENT; + this.full = full; + } + + public Iterator
getHeaders() { + return headers; + } + + public void setHeaders(Iterator
headers) { + content = content | FetchGroup.HEADERS; + this.headers = headers; + } + + public Iterator
getMimeHeaders() { + return mimeHeaders; + } + + public void setMimeHeaders(Iterator
mimeHeaders) { + content = content | FetchGroup.MIME_HEADERS; + this.mimeHeaders = mimeHeaders; + } + } + + /** + * @see org.apache.james.mailbox.model.MessageResult#getMimeDescriptor() + */ + public MimeDescriptor getMimeDescriptor() throws MailboxException { + + // check if we need to create the MimeDescriptor which is done in a lazy fashion because + // it can be relative expensive on big messages and slow mailbox implementations + if (mimeDescriptor == null) { + try { + if (MimeDescriptorImpl.isComposite(message.getMediaType())) { + mimeDescriptor = MimeDescriptorImpl.build(getFullContent().getInputStream()); + } else { + mimeDescriptor = new LazyMimeDescriptor(this, message); + } + } catch (IOException e) { + throw new MailboxException("Unable to create the MimeDescriptor", e); + } catch (MimeException e) { + throw new MailboxException("Unable to create the MimeDescriptor", e); + } + } + return mimeDescriptor; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getModSeq() + */ + public long getModSeq() { + return message.getModSeq(); + } + + @Override + public Headers getHeaders() throws MailboxException { + if (headers == null) { + headers = new HeadersImpl(message); + } + return headers; + } + + private final class HeadersImpl implements Headers { + + private Message msg; + private List
headers; + + public HeadersImpl(Message msg) { + this.msg = msg; + } + + @Override + public int hashCode() { + return 39 * 19 + message.hashCode(); + } + + @Override + public boolean equals (Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof HeadersImpl) { + return msg.equals(((HeadersImpl)obj).msg); + } + return false; + } + + @Override + public InputStream getInputStream() throws IOException { + return msg.getHeaderContent(); + } + + @Override + public long size() { + return msg.getFullContentOctets() - msg.getBodyOctets(); + } + + @Override + public Iterator
headers() throws MailboxException { + if (headers == null) { + try { + headers = ResultUtils.createHeaders(message); + } catch (IOException e) { + throw new MailboxException("Unable to parse headers", e); + } + } + return headers.iterator(); + } + + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MimeDescriptorImpl.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MimeDescriptorImpl.java new file mode 100644 index 0000000..232e5cb --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/MimeDescriptorImpl.java @@ -0,0 +1,357 @@ +/**************************************************************** + * 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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.store.streaming.CountingInputStream; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder; +import org.apache.james.mime4j.message.MaximalBodyDescriptor; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + +public class MimeDescriptorImpl implements MimeDescriptor { + + private final static Charset US_ASCII = Charset.forName("US-ASCII"); + + + /** + * Is this a composite media type (as per RFC2045)? + * + * TODO: Move to Mime4j + * @param mediaType possibly null + * @return true when the type is composite, + * false otherwise + */ + public static boolean isComposite(String mediaType) { + return "message".equalsIgnoreCase(mediaType) || "multipart".equalsIgnoreCase(mediaType); + } + + public static MimeDescriptorImpl build(final InputStream stream) throws IOException, MimeException { + // Disable line length limit + // See https://issues.apache.org/jira/browse/IMAP-132 + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + // + final MimeTokenStream parser = new MimeTokenStream(config, new DefaultBodyDescriptorBuilder()); + + parser.parse(stream); + + // TODO: Shouldn't this get set before we call the parse ? + parser.setRecursionMode(RecursionMode.M_NO_RECURSE); + return createDescriptor(parser); + } + + private static MimeDescriptorImpl createDescriptor( + final MimeTokenStream parser) throws IOException, MimeException { + EntityState next = parser.next(); + final Collection headers = new ArrayList(); + while (next != EntityState.T_BODY + && next != EntityState.T_END_OF_STREAM + && next != EntityState.T_START_MULTIPART) { + if (next == EntityState.T_FIELD) { + headers.add(new ResultHeader(parser.getField().getName(), parser + .getField().getBody().trim())); + } + next = parser.next(); + } + + final MimeDescriptorImpl mimeDescriptorImpl; + switch (next) { + case T_BODY: + mimeDescriptorImpl = simplePartDescriptor(parser, headers); + break; + case T_START_MULTIPART: + mimeDescriptorImpl = compositePartDescriptor(parser, headers); + break; + case T_END_OF_STREAM: + throw new MimeException("Premature end of stream"); + default: + throw new MimeException("Unexpected parse state"); + } + return mimeDescriptorImpl; + } + + private static MimeDescriptorImpl compositePartDescriptor( + final MimeTokenStream parser, final Collection headers) + throws IOException, MimeException { + MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser + .getBodyDescriptor(); + MimeDescriptorImpl mimeDescriptor = createDescriptor(0, 0, descriptor, + null, headers); + EntityState next = parser.next(); + while (next != EntityState.T_END_MULTIPART + && next != EntityState.T_END_OF_STREAM) { + if (next == EntityState.T_START_BODYPART) { + mimeDescriptor.addPart(createDescriptor(parser)); + } + next = parser.next(); + } + return mimeDescriptor; + } + + private static MimeDescriptorImpl simplePartDescriptor( + final MimeTokenStream parser, final Collection headers) + throws IOException, MimeException { + MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser + .getBodyDescriptor(); + final MimeDescriptorImpl mimeDescriptorImpl; + if ("message".equalsIgnoreCase(descriptor.getMediaType()) + && "rfc822".equalsIgnoreCase(descriptor.getSubType())) { + final CountingInputStream messageStream = new CountingInputStream( + parser.getDecodedInputStream()); + MimeDescriptorImpl embeddedMessageDescriptor = build(messageStream); + final int octetCount = messageStream.getOctetCount(); + final int lineCount = messageStream.getLineCount(); + + mimeDescriptorImpl = createDescriptor(octetCount, lineCount, + descriptor, embeddedMessageDescriptor, headers); + } else { + final InputStream body = parser.getInputStream(); + long bodyOctets = 0; + long lines = 0; + for (int n = body.read(); n >= 0; n = body.read()) { + if (n == '\r') { + lines++; + } + bodyOctets++; + } + + mimeDescriptorImpl = createDescriptor(bodyOctets, lines, + descriptor, null, headers); + } + return mimeDescriptorImpl; + } + + private static MimeDescriptorImpl createDescriptor(long bodyOctets, + long lines, MaximalBodyDescriptor descriptor, + MimeDescriptor embeddedMessage, final Collection headers) { + final String contentDescription = descriptor.getContentDescription(); + final String contentId = descriptor.getContentId(); + + final String subType = descriptor.getSubType(); + final String type = descriptor.getMediaType(); + final String transferEncoding = descriptor.getTransferEncoding(); + final Map contentTypeParameters = new TreeMap(descriptor.getContentTypeParameters()); + final String codeset = descriptor.getCharset(); + if (codeset == null) { + if ("TEXT".equals(type)) { + contentTypeParameters.put("charset", "us-ascii"); + } + } else { + contentTypeParameters.put("charset", codeset); + } + final String boundary = descriptor.getBoundary(); + if (boundary != null) { + contentTypeParameters.put("boundary", boundary); + } + + final List languages = descriptor.getContentLanguage(); + final String disposition = descriptor.getContentDispositionType(); + final Map dispositionParams = descriptor + .getContentDispositionParameters(); + final Collection parts = new ArrayList(); + final String location = descriptor.getContentLocation(); + final String md5 = descriptor.getContentMD5Raw(); + final MimeDescriptorImpl mimeDescriptorImpl = new MimeDescriptorImpl( + bodyOctets, contentDescription, contentId, lines, subType, + type, transferEncoding, headers, contentTypeParameters, + languages, disposition, dispositionParams, embeddedMessage, + parts, location, md5); + return mimeDescriptorImpl; + } + + private final long bodyOctets; + + private final String contentDescription; + + private final String contentId; + + private final long lines; + + private final String subType; + + private final String type; + + private final String transferEncoding; + + private final List languages; + + private final Collection headers; + + private final Map contentTypeParameters; + + private final String disposition; + + private final Map dispositionParams; + + private final MimeDescriptor embeddedMessage; + + private final Collection parts; + + private final String location; + + private final String md5; + + + public MimeDescriptorImpl(final long bodyOctets, + final String contentDescription, final String contentId, + final long lines, final String subType, final String type, + final String transferEncoding, final Collection headers, + final Map contentTypeParameters, final List languages, + String disposition, Map dispositionParams, + final MimeDescriptor embeddedMessage, final Collection parts, + final String location, final String md5) { + super(); + this.type = type; + this.bodyOctets = bodyOctets; + this.contentDescription = contentDescription; + this.contentId = contentId; + this.lines = lines; + this.subType = subType; + this.transferEncoding = transferEncoding; + this.headers = headers; + this.contentTypeParameters = contentTypeParameters; + this.embeddedMessage = embeddedMessage; + this.parts = parts; + this.languages = languages; + this.disposition = disposition; + this.dispositionParams = dispositionParams; + this.location = location; + this.md5 = md5; + } + + public Map contentTypeParameters() { + return contentTypeParameters; + } + + public MimeDescriptor embeddedMessage() { + return embeddedMessage; + } + + public long getBodyOctets() { + return bodyOctets; + } + + public String getContentDescription() { + return contentDescription; + } + + public String getContentID() { + return contentId; + } + + public long getLines() { + return lines; + } + + public String getMimeSubType() { + return subType; + } + + public String getMimeType() { + return type; + } + + public String getTransferContentEncoding() { + return transferEncoding; + } + + public Iterator headers() { + return headers.iterator(); + } + + public Iterator parts() { + return parts.iterator(); + } + + private void addPart(MimeDescriptor descriptor) { + parts.add(descriptor); + } + + public List getLanguages() { + return languages; + } + + public String getDisposition() { + return disposition; + } + + public Map getDispositionParams() { + return dispositionParams; + } + + public String getContentLocation() { + return location; + } + + public String getContentMD5() { + return md5; + } + + @Override + public InputStream getInputStream() throws IOException { + StringBuilder sb = new StringBuilder(); + Iterator hIt = headers.iterator(); + while(hIt.hasNext()) { + MessageResult.Header header = hIt.next(); + try { + sb.append(header.getName()).append(": " ).append(header.getValue()).append("\r\n"); + } catch (MailboxException e) { + throw new IOException("Unable to read headers", e); + } + } + sb.append("\r\n"); + return new ByteArrayInputStream(sb.toString().getBytes(US_ASCII)); + } + + @Override + public long size() throws MailboxException { + long result = 0; + for (final Iterator it = headers.iterator(); it.hasNext();) { + final MessageResult.Header header = it.next(); + if (header != null) { + result += header.size(); + result += 2; + } + } + + // Add for CLRF + result +=2; + return result; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/RandomMailboxSessionIdGenerator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/RandomMailboxSessionIdGenerator.java new file mode 100644 index 0000000..8f51230 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/RandomMailboxSessionIdGenerator.java @@ -0,0 +1,40 @@ +/**************************************************************** + * 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 java.util.Random; + +import org.apache.james.mailbox.MailboxSessionIdGenerator; + + +/** + * {@link MailboxSessionIdGenerator} which use a {@link Random} to generate the next Id to use + * + * + */ +public class RandomMailboxSessionIdGenerator extends AbstractMailboxSessionIdGenerator { + private final static Random RANDOM = new Random(); + + @Override + protected long generateNextId() { + return RANDOM.nextLong(); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultHeader.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultHeader.java new file mode 100644 index 0000000..904c9aa --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultHeader.java @@ -0,0 +1,78 @@ +/**************************************************************** + * 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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult; + +public final class ResultHeader implements MessageResult.Header { + private final String name; + + private final String value; + + private final long size; + private final static Charset US_ASCII = Charset.forName("US-ASCII"); + + public ResultHeader(String name, String value) { + this.name = name; + this.value = value; + size = name.length() + value.length() + 2; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult.Header#getName() + */ + public String getName() throws MailboxException { + return name; + } + + /** + * @see org.apache.james.mailbox.model.MessageResult.Header#getValue() + */ + public String getValue() throws MailboxException { + return value; + } + + /** + * @see org.apache.james.mailbox.model.Content#size() + */ + public long size() { + return size; + } + + public String toString() { + return "[HEADER " + name + ": " + value + "]"; + } + + /** + * @see org.apache.james.mailbox.model.InputStreamContent#getInputStream() + */ + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream((name + ": " + value).getBytes(US_ASCII)); + } +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultUtils.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultUtils.java new file mode 100644 index 0000000..33db193 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/ResultUtils.java @@ -0,0 +1,264 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.MessageResult.MimePath; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.streaming.PartContentBuilder; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.parser.AbstractContentHandler; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.stream.Field; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; + +/** + * + */ +public class ResultUtils { + + public static final byte[] BYTES_NEW_LINE = { 0x0D, 0x0A }; + + public static final byte[] BYTES_HEADER_FIELD_VALUE_SEP = { 0x3A, 0x20 }; + + static final Charset US_ASCII = Charset.forName("US-ASCII"); + + public static List createHeaders(final Message document) throws IOException { + final List results = new ArrayList(); + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + final MimeStreamParser parser = new MimeStreamParser(config); + parser.setContentHandler(new AbstractContentHandler() { + @Override + public void endHeader() { + parser.stop(); + } + @Override + public void field(Field field) throws MimeException { + String fieldValue; + if (field instanceof RawField) { + // check if we can access the body in the raw form so no unfolding was done under the hood + ByteSequence raw = field.getRaw(); + int len = raw.length(); + int off = ((RawField) field).getDelimiterIdx() + 1; + if (len > off + 1 && (raw.byteAt(off) & 0xff) == 0x20) off++; + + fieldValue = ContentUtil.decode(raw, off, len - off); + } else { + fieldValue = field.getBody(); + } + if (fieldValue.endsWith("\r\f")) { + fieldValue = fieldValue.substring(0,fieldValue.length() - 2); + } + if (fieldValue.startsWith(" ")) { + fieldValue = fieldValue.substring(1); + } + + final ResultHeader resultHeader = new ResultHeader(field.getName(), fieldValue); + results.add(resultHeader); + } + }); + try { + parser.parse(document.getHeaderContent()); + } catch (MimeException e) { + throw new IOException("Unable to parse headers of message " + document, e); + } + return results; + } + + + + + /** + * Return the {@link MessageResult} for the given {@link Message} and {@link FetchGroup} + * + * @param message + * @param fetchGroup + * @return result + * @throws MailboxException + */ + public static MessageResult loadMessageResult(final Message message, final FetchGroup fetchGroup) throws MailboxException { + try { + + MessageResultImpl messageResult = new MessageResultImpl(message); + if (fetchGroup != null) { + int content = fetchGroup.content(); + + if ((content & FetchGroup.HEADERS) > 0) { + content -= FetchGroup.HEADERS; + } + if ((content & FetchGroup.BODY_CONTENT) > 0) { + content -= FetchGroup.BODY_CONTENT; + } + if ((content & FetchGroup.FULL_CONTENT) > 0) { + content -= FetchGroup.FULL_CONTENT; + } + if ((content & FetchGroup.MIME_DESCRIPTOR) > 0) { + content -= FetchGroup.MIME_DESCRIPTOR; + } + if (content != 0) { + throw new UnsupportedOperationException("Unsupported result: " + content); + } + + addPartContent(fetchGroup, message, messageResult); + } + return messageResult; + + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } catch (MimeException e) { + throw new MailboxException("Unable to parse message", e); + } + + } + + private static void addPartContent(final FetchGroup fetchGroup, + Message message, MessageResultImpl messageResult) + throws MailboxException, IOException, + MimeException { + Collection partContent = fetchGroup.getPartContentDescriptors(); + if (partContent != null) { + for (FetchGroup.PartContentDescriptor descriptor: partContent) { + addPartContent(descriptor, message, messageResult); + } + } + } + + private static void addPartContent( + FetchGroup.PartContentDescriptor descriptor, Message message, + MessageResultImpl messageResult) throws + MailboxException, IOException, MimeException { + final MimePath mimePath = descriptor.path(); + final int content = descriptor.content(); + if ((content & MessageResult.FetchGroup.FULL_CONTENT) > 0) { + addFullContent(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.BODY_CONTENT) > 0) { + addBodyContent(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.MIME_CONTENT) > 0) { + addMimeBodyContent(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.HEADERS) > 0) { + addHeaders(message, messageResult, mimePath); + } + if ((content & MessageResult.FetchGroup.MIME_HEADERS) > 0) { + addMimeHeaders(message, messageResult, mimePath); + } + } + + private static PartContentBuilder build(int[] path, final Message message) + throws IOException, MimeException { + final InputStream stream = message.getFullContent(); + PartContentBuilder result = new PartContentBuilder(); + result.parse(stream); + try { + for (int i = 0; i < path.length; i++) { + final int next = path[i]; + result.to(next); + } + } catch (PartContentBuilder.PartNotFoundException e) { + // Missing parts should return zero sized content + // See http://markmail.org/message/2jconrj7scvdi5dj + result.markEmpty(); + } + return result; + } + + + + private static final int[] path(MimePath mimePath) { + final int[] result; + if (mimePath == null) { + result = null; + } else { + result = mimePath.getPositions(); + } + return result; + } + + private static void addHeaders(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws IOException, MimeException { + final int[] path = path(mimePath); + if (path != null) { + + final PartContentBuilder builder = build(path, message); + final List headers = builder.getMessageHeaders(); + messageResult.setHeaders(mimePath, headers.iterator()); + } + } + + private static void addMimeHeaders(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws IOException, MimeException { + final int[] path = path(mimePath); + if (path != null) { + final PartContentBuilder builder = build(path, message); + final List headers = builder.getMimeHeaders(); + messageResult.setMimeHeaders(mimePath, headers.iterator()); + } + } + + private static void addBodyContent(Message message, + MessageResultImpl messageResult, MimePath mimePath) throws IOException, MimeException { + final int[] path = path(mimePath); + if (path != null) { + final PartContentBuilder builder = build(path, message); + final Content content = builder.getMessageBodyContent(); + messageResult.setBodyContent(mimePath, content); + } + } + + private static void addMimeBodyContent(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws IOException, MimeException { + final int[] path = path(mimePath); + final PartContentBuilder builder = build(path, message); + final Content content = builder.getMimeBodyContent(); + messageResult.setMimeBodyContent(mimePath, content); + } + + private static void addFullContent(Message message, + MessageResultImpl messageResult, MimePath mimePath) + throws MailboxException, IOException, + MimeException { + final int[] path = path(mimePath); + if (path != null) { + final PartContentBuilder builder = build(path, message); + final Content content = builder.getFullContent(); + messageResult.setFullContent(mimePath, content); + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxMetaData.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxMetaData.java new file mode 100644 index 0000000..30ac760 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxMetaData.java @@ -0,0 +1,125 @@ +/**************************************************************** + * 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.StandardMailboxMetaDataComparator; +import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.model.MailboxPath; + +public class SimpleMailboxMetaData implements MailboxMetaData, Comparable { + + public static MailboxMetaData createNoSelect(MailboxPath path, char delimiter) { + return new SimpleMailboxMetaData(path, delimiter, Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.NOSELECT); + } + + private final MailboxPath path; + + private final char delimiter; + + private final Children inferiors; + + private final Selectability selectability; + + public SimpleMailboxMetaData(MailboxPath path, char delimiter) { + this(path, delimiter, Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.NONE); + } + + public SimpleMailboxMetaData(final MailboxPath path, final char delimiter, final Children inferiors, final Selectability selectability) { + super(); + this.path = path; + this.delimiter = delimiter; + this.inferiors = inferiors; + this.selectability = selectability; + } + + /** + * Is this mailbox \Noinferiors as per RFC3501. + * + * @return true if marked, false otherwise + */ + public final Children inferiors() { + return inferiors; + } + + /** + * Gets the RFC3501 Selectability flag. + */ + public final Selectability getSelectability() { + return selectability; + } + + /** + * @see org.apache.james.mailbox.model.MailboxMetaData#getHierarchyDelimiter() + */ + public char getHierarchyDelimiter() { + return delimiter; + } + + /** + * @see org.apache.james.mailbox.model.MailboxMetaData#getPath() + */ + public MailboxPath getPath() { + return path; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "ListResult: " + path; + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((path == null) ? 0 : path.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SimpleMailboxMetaData other = (SimpleMailboxMetaData) obj; + if (path == null) { + if (other.path != null) + return false; + } else if (!path.equals(other.path)) + return false; + return true; + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(MailboxMetaData o) { + return StandardMailboxMetaDataComparator.order(this, o); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxSession.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxSession.java new file mode 100644 index 0000000..9200eb1 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMailboxSession.java @@ -0,0 +1,203 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.model.MailboxConstants; +import org.slf4j.Logger; + +/** + * Describes a mailbox session. + */ +public class SimpleMailboxSession implements MailboxSession, MailboxSession.User { + + private final Collection sharedSpaces; + + private final String otherUsersSpace; + + private final String personalSpace; + + private final long sessionId; + + private final Logger log; + + private final String userName; + + private final String password; + + private boolean open = true; + + private final List localePreferences; + + private final Map attributes; + + private final char pathSeparator; + + private final SessionType type; + + + public SimpleMailboxSession(final long sessionId, final String userName, final String password, + final Logger log, final List localePreferences, char pathSeparator, SessionType type) { + this(sessionId, userName, password, log, localePreferences, new ArrayList(), null, pathSeparator, type); + } + + public SimpleMailboxSession(final long sessionId, final String userName, final String password, + final Logger log, final List localePreferences, List sharedSpaces, String otherUsersSpace, char pathSeparator, SessionType type) { + this.sessionId = sessionId; + this.log = log; + this.userName = userName; + this.password = password; + this.otherUsersSpace = otherUsersSpace; + this.sharedSpaces = sharedSpaces; + this.type = type; + if (otherUsersSpace == null && (sharedSpaces == null || sharedSpaces.isEmpty())) { + this.personalSpace = ""; + } else { + this.personalSpace = MailboxConstants.USER_NAMESPACE; + } + + this.localePreferences = localePreferences; + this.attributes = new HashMap(); + this.pathSeparator = pathSeparator; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getLog() + */ + public Logger getLog() { + return log; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#close() + */ + public void close() { + open = false; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getSessionId() + */ + public long getSessionId() { + return sessionId; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#isOpen() + */ + public boolean isOpen() { + return open; + } + + /** + * Renders suitably for logging. + * + * @return a String representation of this object. + */ + public String toString() { + final String TAB = " "; + + String retValue = "MailboxSession ( " + "sessionId = " + + this.sessionId + TAB + "open = " + this.open + TAB + " )"; + + return retValue; + } + + /** + * Gets the user executing this session. + * @return not null + */ + public User getUser() { + return this; + } + + /** + * Gets the name of the user executing this session. + * + * @return not null + */ + public String getUserName() { + return userName; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getOtherUsersSpace() + */ + public String getOtherUsersSpace() { + return otherUsersSpace; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getPersonalSpace() + */ + public String getPersonalSpace() { + return personalSpace; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getSharedSpaces() + */ + public Collection getSharedSpaces() { + return sharedSpaces; + } + + /** + * @see org.apache.james.mailbox.MailboxSession.User#getLocalePreferences() + */ + public List getLocalePreferences() { + return localePreferences; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getAttributes() + */ + public Map getAttributes() { + return attributes; + } + + /** + * @see org.apache.james.mailbox.MailboxSession.User#getPassword() + */ + public String getPassword() { + return password; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getPathDelimiter() + */ + public char getPathDelimiter() { + return pathSeparator; + } + + /** + * @see org.apache.james.mailbox.MailboxSession#getType() + */ + public SessionType getType() { + return type; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMessageMetaData.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMessageMetaData.java new file mode 100644 index 0000000..a744c05 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/SimpleMessageMetaData.java @@ -0,0 +1,100 @@ +/**************************************************************** + * 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 java.util.Date; + +import javax.mail.Flags; + +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.store.mail.model.Message; + + +public class SimpleMessageMetaData implements MessageMetaData{ + private long uid; + private Flags flags; + private long size; + private Date internalDate; + private long modSeq; + + public SimpleMessageMetaData(long uid, long modSeq, Flags flags, long size, Date internalDate) { + this.uid = uid; + this.flags = flags; + this.size = size; + this.modSeq = modSeq; + this.internalDate = internalDate; + } + + public SimpleMessageMetaData(Message message) { + this(message.getUid(), message.getModSeq(), message.createFlags(), message.getFullContentOctets(), message.getInternalDate()); + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getFlags() + */ + public Flags getFlags() { + return flags; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getSize() + */ + public long getSize() { + return size; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getInternalDate() + */ + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getUid() + */ + public long getUid() { + return uid; + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof SimpleMessageMetaData) { + return uid == ((SimpleMessageMetaData) obj).getUid(); + } + return false; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + /** + * @see org.apache.james.mailbox.model.MessageMetaData#getModSeq() + */ + public long getModSeq() { + return modSeq; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java new file mode 100644 index 0000000..3cf22ae --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java @@ -0,0 +1,593 @@ +/**************************************************************** + * 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.MailboxListener; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSession.SessionType; +import org.apache.james.mailbox.MailboxSessionIdGenerator; +import org.apache.james.mailbox.RequestAware; +import org.apache.james.mailbox.StandardMailboxMetaDataComparator; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxConstants; +import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.model.MailboxMetaData.Selectability; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MailboxQuery; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.store.mail.MailboxMapper; +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.search.ListeningMessageSearchIndex; +import org.apache.james.mailbox.store.search.MessageSearchIndex; +import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Random; + +/** + * This base class of an {@link MailboxManager} implementation provides a high-level api for writing your own + * {@link MailboxManager} implementation. If you plan to write your own {@link MailboxManager} its most times so easiest + * to extend just this class or use it directly. + *

+ * If you need a more low-level api just implement {@link MailboxManager} directly + * + * @param + */ +public class StoreMailboxManager implements MailboxManager { + + public static final char SQL_WILDCARD_CHAR = '%'; + public static final int DEFAULT_FETCH_BATCH_SIZE = 200; + + private MailboxEventDispatcher dispatcher; + private AbstractDelegatingMailboxListener delegatingListener = null; + private final MailboxSessionMapperFactory mailboxSessionMapperFactory; + + private final Authenticator authenticator; + + private final MailboxACLResolver aclResolver; + + private final GroupMembershipResolver groupMembershipResolver; + + private final static Random RANDOM = new Random(); + + private int copyBatchSize = 0; + + private int moveBatchSize = 0; + + private MailboxPathLocker locker; + + private MessageSearchIndex index; + + private MailboxSessionIdGenerator idGenerator; + + private int fetchBatchSize = DEFAULT_FETCH_BATCH_SIZE; + + + public StoreMailboxManager(MailboxSessionMapperFactory mailboxSessionMapperFactory, final Authenticator authenticator, final MailboxPathLocker locker, final MailboxACLResolver aclResolver, final GroupMembershipResolver groupMembershipResolver) { + this.authenticator = authenticator; + this.locker = locker; + this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; + this.aclResolver = aclResolver; + this.groupMembershipResolver = groupMembershipResolver; + } + + public StoreMailboxManager(MailboxSessionMapperFactory mailboxSessionMapperFactory, final Authenticator authenticator, final MailboxACLResolver aclResolver, final GroupMembershipResolver groupMembershipResolver) { + this(mailboxSessionMapperFactory, authenticator, new JVMMailboxPathLocker(), aclResolver, groupMembershipResolver); + } + + public void setMailboxSessionIdGenerator(MailboxSessionIdGenerator idGenerator) { + this.idGenerator = idGenerator; + } + + public void setCopyBatchSize(int copyBatchSize) { + this.copyBatchSize = copyBatchSize; + } + + public void setMoveBatchSize(int moveBatchSize) { + this.moveBatchSize = moveBatchSize; + } + + public void setFetchBatchSize(int fetchBatchSize) { + this.fetchBatchSize = fetchBatchSize; + } + + + /** + * Init the {@link MailboxManager} + * + * @throws MailboxException + */ + @SuppressWarnings("rawtypes") + public void init() throws MailboxException { + // The dispatcher need to have the delegating listener added + dispatcher = new MailboxEventDispatcher(getDelegationListener()); + + if (index == null) { + index = new SimpleMessageSearchIndex(mailboxSessionMapperFactory); + } + if (index instanceof ListeningMessageSearchIndex) { + addGlobalListener((ListeningMessageSearchIndex) index, null); + } + + if (idGenerator == null) { + idGenerator = new RandomMailboxSessionIdGenerator(); + } + } + + /** + * Return the {@link AbstractDelegatingMailboxListener} which is used by this {@link MailboxManager} + * + * @return delegatingListener + */ + public AbstractDelegatingMailboxListener getDelegationListener() { + if (delegatingListener == null) { + delegatingListener = new HashMapDelegatingMailboxListener(); + } + return delegatingListener; + } + + + /** + * Return the {@link MessageSearchIndex} used by this {@link MailboxManager} + * + * @return index + */ + public MessageSearchIndex getMessageSearchIndex() { + return index; + } + + + /** + * Return the {@link MailboxEventDispatcher} used by thei {@link MailboxManager} + * + * @return dispatcher + */ + public MailboxEventDispatcher getEventDispatcher() { + return dispatcher; + } + + /** + * Return the {@link MailboxSessionMapperFactory} used by this {@link MailboxManager} + * + * @return mailboxSessionMapperFactory + */ + public MailboxSessionMapperFactory getMapperFactory() { + return mailboxSessionMapperFactory; + } + + public MailboxPathLocker getLocker() { + return locker; + } + + public MailboxACLResolver getAclResolver() { + return aclResolver; + } + + public GroupMembershipResolver getGroupMembershipResolver() { + return groupMembershipResolver; + } + + /** + * Set the {@link AbstractDelegatingMailboxListener} to use with this {@link MailboxManager} instance. If none is set here a {@link HashMapDelegatingMailboxListener} instance will + * be created lazy + * + * @param delegatingListener + */ + public void setDelegatingMailboxListener(AbstractDelegatingMailboxListener delegatingListener) { + this.delegatingListener = delegatingListener; + dispatcher = new MailboxEventDispatcher(getDelegationListener()); + } + + /** + * Set the {@link MessageSearchIndex} which should be used by this {@link MailboxManager}. If none is given this implementation will use a {@link SimpleMessageSearchIndex} + * by default + * + * @param index + */ + public void setMessageSearchIndex(MessageSearchIndex index) { + this.index = index; + } + + /** + * Generate an return the next uid validity + * + * @return uidValidity + */ + protected int randomUidValidity() { + return Math.abs(RANDOM.nextInt()); + } + + @Override + public MailboxSession createSystemSession(String userName, Logger log) { + return createSession(userName, null, log, SessionType.System); + } + + /** + * Create Session + * + * @param userName + * @param log + * @return session + */ + protected MailboxSession createSession(String userName, String password, Logger log, SessionType type) { + return new SimpleMailboxSession(randomId(), userName, password, log, new ArrayList(), getDelimiter(), type); + } + + /** + * Generate and return the next id to use + * + * @return id + */ + protected long randomId() { + return idGenerator.nextId(); + } + + @Override + public char getDelimiter() { + return MailboxConstants.DEFAULT_DELIMITER; + } + + /** + * Log in the user with the given userid and password + * + * @param userid the username + * @param passwd the password + * @return success true if login success false otherwise + */ + private boolean login(String userid, String passwd) { + return authenticator.isAuthentic(userid, passwd); + } + + @Override + public MailboxSession login(String userid, String passwd, Logger log) throws BadCredentialsException, MailboxException { + if (login(userid, passwd)) { + return createSession(userid, passwd, log, SessionType.User); + } else { + throw new BadCredentialsException(); + } + } + + /** + * Close the {@link MailboxSession} if not null + */ + public void logout(MailboxSession session, boolean force) throws MailboxException { + if (session != null) { + session.close(); + } + } + + /** + * Create a {@link MailboxManager} for the given Mailbox. By default this will return a {@link StoreMessageManager}. If + * your implementation needs something different, just override this method + * + * @param mailbox + * @param session + * @return storeMailbox + */ + protected StoreMessageManager createMessageManager(Mailbox mailbox, MailboxSession session) throws MailboxException { + return new StoreMessageManager(getMapperFactory(), getMessageSearchIndex(), getEventDispatcher(), getLocker(), mailbox, getAclResolver(), getGroupMembershipResolver()); + } + + /** + * Create a Mailbox for the given mailbox path. This will by default return a {@link SimpleMailbox}. + *

+ * If you need to return something more special just override this method + * + * @param mailboxPath + * @param session + * @throws MailboxException + */ + protected org.apache.james.mailbox.store.mail.model.Mailbox doCreateMailbox(MailboxPath mailboxPath, final MailboxSession session) throws MailboxException { + return new SimpleMailbox(mailboxPath, randomUidValidity()); + } + + @Override + public org.apache.james.mailbox.MessageManager getMailbox(MailboxPath mailboxPath, MailboxSession session) + throws MailboxException { + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + Mailbox mailboxRow = mapper.findMailboxByPath(mailboxPath); + + if (mailboxRow == null) { + session.getLog().info("Mailbox '" + mailboxPath + "' not found."); + throw new MailboxNotFoundException(mailboxPath); + + } else { + session.getLog().debug("Loaded mailbox " + mailboxPath); + + StoreMessageManager m = createMessageManager(mailboxRow, session); + m.setFetchBatchSize(fetchBatchSize); + return m; + } + } + + @Override + public void createMailbox(MailboxPath mailboxPath, final MailboxSession mailboxSession) + throws MailboxException { + mailboxSession.getLog().debug("createMailbox " + mailboxPath); + final int length = mailboxPath.getName().length(); + if (length == 0) { + mailboxSession.getLog().warn("Ignoring mailbox with empty name"); + } else { + if (mailboxPath.getName().charAt(length - 1) == getDelimiter()) + mailboxPath.setName(mailboxPath.getName().substring(0, length - 1)); + if (mailboxExists(mailboxPath, mailboxSession)) + throw new MailboxExistsException(mailboxPath.toString()); + // Create parents first + // If any creation fails then the mailbox will not be created + // TODO: transaction + for (final MailboxPath mailbox : mailboxPath.getHierarchyLevels(getDelimiter())) + + locker.executeWithLock(mailboxSession, mailbox, new LockAwareExecution() { + + public Void execute() throws MailboxException { + if (!mailboxExists(mailbox, mailboxSession)) { + final org.apache.james.mailbox.store.mail.model.Mailbox m = doCreateMailbox(mailbox, mailboxSession); + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession); + mapper.execute(new TransactionalMapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + mapper.save(m); + } + + }); + + // notify listeners + dispatcher.mailboxAdded(mailboxSession, m); + } + return null; + + } + }); + + } + } + + @Override + public void deleteMailbox(final MailboxPath mailboxPath, final MailboxSession session) throws MailboxException { + session.getLog().info("deleteMailbox " + mailboxPath); + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + + Mailbox mailbox = mapper.execute(new Mapper.Transaction>() { + + public Mailbox run() throws MailboxException { + final Mailbox mailbox = mapper.findMailboxByPath(mailboxPath); + if (mailbox == null) { + throw new MailboxNotFoundException("Mailbox not found"); + } + + // We need to create a copy of the mailbox as maybe we can not refer to the real + // mailbox once we remove it + SimpleMailbox m = new SimpleMailbox(mailbox); + mapper.delete(mailbox); + return m; + } + + }); + + dispatcher.mailboxDeleted(session, mailbox); + + } + + @Override + public void renameMailbox(final MailboxPath from, final MailboxPath to, final MailboxSession session) throws MailboxException { + final Logger log = session.getLog(); + if (log.isDebugEnabled()) + log.debug("renameMailbox " + from + " to " + to); + if (mailboxExists(to, session)) { + throw new MailboxExistsException(to.toString()); + } + + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + mapper.execute(new Mapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + // TODO put this into a serilizable transaction + final Mailbox mailbox = mapper.findMailboxByPath(from); + if (mailbox == null) { + throw new MailboxNotFoundException(from); + } + mailbox.setNamespace(to.getNamespace()); + mailbox.setUser(to.getUser()); + mailbox.setName(to.getName()); + mapper.save(mailbox); + + dispatcher.mailboxRenamed(session, from, mailbox); + + // rename submailboxes + final MailboxPath children = new MailboxPath(MailboxConstants.USER_NAMESPACE, from.getUser(), from.getName() + getDelimiter() + "%"); + locker.executeWithLock(session, children, new LockAwareExecution() { + + public Void execute() throws MailboxException { + final List> subMailboxes = mapper.findMailboxWithPathLike(children); + for (Mailbox sub : subMailboxes) { + final String subOriginalName = sub.getName(); + final String subNewName = to.getName() + subOriginalName.substring(from.getName().length()); + final MailboxPath fromPath = new MailboxPath(children, subOriginalName); + sub.setName(subNewName); + mapper.save(sub); + dispatcher.mailboxRenamed(session, fromPath, sub); + + if (log.isDebugEnabled()) + log.debug("Rename mailbox sub-mailbox " + subOriginalName + " to " + subNewName); + } + return null; + + } + }); + } + }); + } + + + @Override + @SuppressWarnings("unchecked") + public List copyMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException { + StoreMessageManager toMailbox = (StoreMessageManager) getMailbox(to, session); + StoreMessageManager fromMailbox = (StoreMessageManager) getMailbox(from, session); + + if (copyBatchSize > 0) { + List copiedRanges = new ArrayList(); + Iterator ranges = set.split(copyBatchSize).iterator(); + while (ranges.hasNext()) { + copiedRanges.addAll(fromMailbox.copyTo(ranges.next(), toMailbox, session)); + } + return copiedRanges; + } else { + return fromMailbox.copyTo(set, toMailbox, session); + } + } + + @Override + @SuppressWarnings("unchecked") + public List moveMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException { + StoreMessageManager toMailbox = (StoreMessageManager) getMailbox(to, session); + StoreMessageManager fromMailbox = (StoreMessageManager) getMailbox(from, session); + + if (moveBatchSize > 0) { + List movedRanges = new ArrayList(); + Iterator ranges = set.split(moveBatchSize).iterator(); + while (ranges.hasNext()) { + movedRanges.addAll(fromMailbox.copyTo(ranges.next(), toMailbox, session)); + } + return movedRanges; + } else { + return fromMailbox.moveTo(set, toMailbox, session); + } + } + + @Override + public List search(final MailboxQuery mailboxExpression, MailboxSession session) + throws MailboxException { + final char localWildcard = mailboxExpression.getLocalWildcard(); + final char freeWildcard = mailboxExpression.getFreeWildcard(); + final String baseName = mailboxExpression.getBase().getName(); + final int baseLength; + if (baseName == null) { + baseLength = 0; + } else { + baseLength = baseName.length(); + } + final String combinedName = mailboxExpression.getCombinedName() + .replace(freeWildcard, SQL_WILDCARD_CHAR) + .replace(localWildcard, SQL_WILDCARD_CHAR); + final MailboxPath search = new MailboxPath(mailboxExpression.getBase(), combinedName); + + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + final List> mailboxes = mapper.findMailboxWithPathLike(search); + final List results = new ArrayList(mailboxes.size()); + for (Mailbox mailbox : mailboxes) { + final String name = mailbox.getName(); + if (name.startsWith(baseName)) { + final String match = name.substring(baseLength); + if (mailboxExpression.isExpressionMatch(match)) { + final MailboxMetaData.Children inferiors; + if (mapper.hasChildren(mailbox, session.getPathDelimiter())) { + inferiors = MailboxMetaData.Children.HAS_CHILDREN; + } else { + inferiors = MailboxMetaData.Children.HAS_NO_CHILDREN; + } + MailboxPath mailboxPath = new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), name); + results.add(new SimpleMailboxMetaData(mailboxPath, getDelimiter(), inferiors, Selectability.NONE)); + } + } + } + Collections.sort(results, new StandardMailboxMetaDataComparator()); + return results; + } + + @Override + public boolean mailboxExists(MailboxPath mailboxPath, MailboxSession session) throws MailboxException { + try { + final MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); + mapper.findMailboxByPath(mailboxPath); + return true; + } catch (MailboxNotFoundException e) { + return false; + } + + } + + @Override + public void addListener(MailboxPath path, MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.addListener(path, listener, session); + } + + /** + * End processing of Request for session + */ + @Override + public void endProcessingRequest(MailboxSession session) { + if (mailboxSessionMapperFactory instanceof RequestAware) { + ((RequestAware) mailboxSessionMapperFactory).endProcessingRequest(session); + } + } + + /** + * Do nothing. Sub classes should override this if needed + */ + @Override + public void startProcessingRequest(MailboxSession session) { + // do nothing + } + + @Override + public List list(MailboxSession session) throws MailboxException { + List mList = new ArrayList(); + List> mailboxes = mailboxSessionMapperFactory.getMailboxMapper(session).list(); + for (int i = 0; i < mailboxes.size(); i++) { + Mailbox m = mailboxes.get(i); + mList.add(new MailboxPath(m.getNamespace(), m.getUser(), m.getName())); + } + return Collections.unmodifiableList(mList); + + } + + @Override + public void addGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.addGlobalListener(listener, session); + } + + @Override + public void removeListener(MailboxPath mailboxPath, MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.removeListener(mailboxPath, listener, session); + + } + + @Override + public void removeGlobalListener(MailboxListener listener, MailboxSession session) throws MailboxException { + delegatingListener.removeGlobalListener(listener, session); + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxPath.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxPath.java new file mode 100644 index 0000000..a215445 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxPath.java @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class StoreMailboxPath extends MailboxPath { + + public StoreMailboxPath(String namespace, String user, String name) { + super(namespace, user, name); + } + + public StoreMailboxPath(MailboxPath mailboxPath) { + super(mailboxPath); + } + + public StoreMailboxPath(MailboxPath mailboxPath, String name) { + super(mailboxPath, name); + } + + public StoreMailboxPath(Mailbox mailbox) { + super(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java new file mode 100644 index 0000000..d8b98f7 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java @@ -0,0 +1,870 @@ +/**************************************************************** + * 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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedFileInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.TeeInputStream; +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSession.User; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.ReadOnlyException; +import org.apache.james.mailbox.exception.UnsupportedRightException; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.EditMode; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.MessageResultIterator; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.model.UpdatedFlags; +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; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import org.apache.james.mailbox.store.search.MessageSearchIndex; +import org.apache.james.mailbox.store.streaming.BodyOffsetInputStream; +import org.apache.james.mailbox.store.streaming.CountingInputStream; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder; +import org.apache.james.mime4j.message.HeaderImpl; +import org.apache.james.mime4j.message.MaximalBodyDescriptor; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + +/** + * Base class for {@link org.apache.james.mailbox.MessageManager} + * implementations. + * + * This base class take care of dispatching events to the registered + * {@link MailboxListener} and so help with handling concurrent + * {@link MailboxSession}'s. + * + * + * + */ +public class StoreMessageManager implements org.apache.james.mailbox.MessageManager { + + /** + * The minimal Permanent flags the {@link MessageManager} must support.
+ * + * Be sure this static instance will never get modifed + * later! + */ + protected final static Flags MINIMAL_PERMANET_FLAGS; + static { + MINIMAL_PERMANET_FLAGS = new Flags(); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.ANSWERED); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.DELETED); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.DRAFT); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.FLAGGED); + MINIMAL_PERMANET_FLAGS.add(Flags.Flag.SEEN); + } + + private final Mailbox mailbox; + + private final MailboxEventDispatcher dispatcher; + + private final MessageMapperFactory mapperFactory; + + private final MessageSearchIndex index; + + private final MailboxACLResolver aclResolver; + + private final GroupMembershipResolver groupMembershipResolver; + + private MailboxPathLocker locker; + + private int fetchBatchSize; + + public StoreMessageManager(final MessageMapperFactory mapperFactory, final MessageSearchIndex index, final MailboxEventDispatcher dispatcher, final MailboxPathLocker locker, final Mailbox mailbox, final MailboxACLResolver aclResolver, + final GroupMembershipResolver groupMembershipResolver) throws MailboxException { + this.mailbox = mailbox; + this.dispatcher = dispatcher; + this.mapperFactory = mapperFactory; + this.index = index; + this.locker = locker; + this.aclResolver = aclResolver; + this.groupMembershipResolver = groupMembershipResolver; + } + + public void setFetchBatchSize(int fetchBatchSize) { + this.fetchBatchSize = fetchBatchSize; + } + + /** + * Return the {@link MailboxPathLocker} + * + * @return locker + */ + protected MailboxPathLocker getLocker() { + return locker; + } + + /** + * Return the {@link MailboxEventDispatcher} for this Mailbox + * + * @return dispatcher + */ + protected MailboxEventDispatcher getDispatcher() { + return dispatcher; + } + + /** + * Return the underlying {@link Mailbox} + * + * @return mailbox + * @throws MailboxException + */ + + public Mailbox getMailboxEntity() throws MailboxException { + return mailbox; + } + + /** + * Return {@link Flags} which are permanent stored by the mailbox. By + * default this are the following flags:
+ * {@link Flag#ANSWERED}, {@link Flag#DELETED}, {@link Flag#DRAFT}, + * {@link Flag#FLAGGED}, {@link Flag#RECENT}, {@link Flag#SEEN}
+ * + * Which in fact does not allow to permanent store user flags / keywords. + * + * If the sub-class does allow to store "any" user flag / keyword it MUST + * override this method and add {@link Flag#USER} to the list of returned + * {@link Flags}. If only a special set of user flags / keywords should be + * allowed just add them directly. + * + * @param session + * @return flags + */ + protected Flags getPermanentFlags(MailboxSession session) { + + // Return a new flags instance to make sure the static declared flags + // instance will not get modified later. + // + // See MAILBOX-109 + return new Flags(MINIMAL_PERMANET_FLAGS); + } + + /** + * Returns the flags which are shared for the current mailbox, i.e. the + * flags set up so that changes to those flags are visible to another user. + * See RFC 4314 section 5.2. + * + * In this implementation, all permanent flags are shared, ergo we simply + * return {@link #getPermanentFlags(MailboxSession)} + * + * @see UnionMailboxACLResolver#isReadWrite(MailboxACLRights, Flags) + * + * @param session + * @return + */ + protected Flags getSharedPermanentFlags(MailboxSession session) { + return getPermanentFlags(session); + } + + /** + * Return true. If an subclass don't want to store mod-sequences in a + * permanent way just override this and return false + * + * @return true + */ + public boolean isModSeqPermanent(MailboxSession session) { + return true; + } + + /** + * @see org.apache.james.mailbox.MessageManager#expunge(org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.MailboxSession) + */ + public Iterator expunge(final MessageRange set, MailboxSession mailboxSession) throws MailboxException { + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + Map uids = deleteMarkedInMailbox(set, mailboxSession); + + dispatcher.expunged(mailboxSession, uids, getMailboxEntity()); + return uids.keySet().iterator(); + } + + /** + * @see org.apache.james.mailbox.MessageManager#appendMessage(java.io.InputStream, + * java.util.Date, org.apache.james.mailbox.MailboxSession, boolean, + * javax.mail.Flags) + */ + public long appendMessage(final InputStream msgIn, Date internalDate, final MailboxSession mailboxSession, final boolean isRecent, final Flags flagsToBeSet) throws MailboxException { + + File file = null; + TeeInputStream tmpMsgIn = null; + BodyOffsetInputStream bIn = null; + FileOutputStream out = null; + SharedFileInputStream contentIn = null; + + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + + try { + // Create a temporary file and copy the message to it. We will work + // with the file as + // source for the InputStream + file = File.createTempFile("imap", ".msg"); + out = new FileOutputStream(file); + + tmpMsgIn = new TeeInputStream(msgIn, out); + + bIn = new BodyOffsetInputStream(tmpMsgIn); + // Disable line length... This should be handled by the smtp server + // component and not the parser itself + // https://issues.apache.org/jira/browse/IMAP-122 + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + final MimeTokenStream parser = new MimeTokenStream(config, new DefaultBodyDescriptorBuilder()); + + parser.setRecursionMode(RecursionMode.M_NO_RECURSE); + parser.parse(bIn); + final HeaderImpl header = new HeaderImpl(); + + EntityState next = parser.next(); + while (next != EntityState.T_BODY && next != EntityState.T_END_OF_STREAM && next != EntityState.T_START_MULTIPART) { + if (next == EntityState.T_FIELD) { + header.addField(parser.getField()); + } + next = parser.next(); + } + final MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser.getBodyDescriptor(); + final PropertyBuilder propertyBuilder = new PropertyBuilder(); + final String mediaType; + final String mediaTypeFromHeader = descriptor.getMediaType(); + final String subType; + if (mediaTypeFromHeader == null) { + mediaType = "text"; + subType = "plain"; + } else { + mediaType = mediaTypeFromHeader; + subType = descriptor.getSubType(); + } + propertyBuilder.setMediaType(mediaType); + propertyBuilder.setSubType(subType); + propertyBuilder.setContentID(descriptor.getContentId()); + propertyBuilder.setContentDescription(descriptor.getContentDescription()); + propertyBuilder.setContentLocation(descriptor.getContentLocation()); + propertyBuilder.setContentMD5(descriptor.getContentMD5Raw()); + propertyBuilder.setContentTransferEncoding(descriptor.getTransferEncoding()); + propertyBuilder.setContentLanguage(descriptor.getContentLanguage()); + propertyBuilder.setContentDispositionType(descriptor.getContentDispositionType()); + propertyBuilder.setContentDispositionParameters(descriptor.getContentDispositionParameters()); + propertyBuilder.setContentTypeParameters(descriptor.getContentTypeParameters()); + // Add missing types + final String codeset = descriptor.getCharset(); + if (codeset == null) { + if ("TEXT".equalsIgnoreCase(mediaType)) { + propertyBuilder.setCharset("us-ascii"); + } + } else { + propertyBuilder.setCharset(codeset); + } + + final String boundary = descriptor.getBoundary(); + if (boundary != null) { + propertyBuilder.setBoundary(boundary); + } + if ("text".equalsIgnoreCase(mediaType)) { + final CountingInputStream bodyStream = new CountingInputStream(parser.getInputStream()); + bodyStream.readAll(); + long lines = bodyStream.getLineCount(); + bodyStream.close(); + next = parser.next(); + if (next == EntityState.T_EPILOGUE) { + final CountingInputStream epilogueStream = new CountingInputStream(parser.getInputStream()); + epilogueStream.readAll(); + lines += epilogueStream.getLineCount(); + epilogueStream.close(); + + } + propertyBuilder.setTextualLineCount(lines); + } + + final Flags flags; + if (flagsToBeSet == null) { + flags = new Flags(); + } else { + flags = flagsToBeSet; + + // Check if we need to trim the flags + trimFlags(flags, mailboxSession); + + } + if (isRecent) { + flags.add(Flags.Flag.RECENT); + } + if (internalDate == null) { + internalDate = new Date(); + } + byte[] discard = new byte[4096]; + while (tmpMsgIn.read(discard) != -1) { + // consume the rest of the stream so everything get copied to + // the file now + // via the TeeInputStream + } + int bodyStartOctet = (int) bIn.getBodyStartOffset(); + if (bodyStartOctet == -1) { + bodyStartOctet = 0; + } + contentIn = new SharedFileInputStream(file); + final int size = (int) file.length(); + + final Message message = createMessage(internalDate, size, bodyStartOctet, contentIn, flags, propertyBuilder); + return locker.executeWithLock(mailboxSession, new StoreMailboxPath(getMailboxEntity()), new MailboxPathLocker.LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + MessageMetaData data = appendMessageToStore(message, mailboxSession); + + SortedMap uids = new TreeMap(); + uids.put(data.getUid(), data); + dispatcher.added(mailboxSession, uids, getMailboxEntity()); + return data.getUid(); + } + }, true); + + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } catch (MimeException e) { + throw new MailboxException("Unable to parse message", e); + } finally { + IOUtils.closeQuietly(bIn); + IOUtils.closeQuietly(tmpMsgIn); + IOUtils.closeQuietly(out); + IOUtils.closeQuietly(contentIn); + + // delete the temporary file if one was specified + if (file != null) { + if (!file.delete()) { + // Don't throw an IOException. The message could be appended + // and the temporary file + // will be deleted hopefully some day + } + } + } + + } + + /** + * Create a new {@link Message} for the given data + * + * @param internalDate + * @param size + * @param bodyStartOctet + * @param content + * @param flags + * @return membership + * @throws MailboxException + */ + protected Message createMessage(Date internalDate, final int size, int bodyStartOctet, final SharedInputStream content, final Flags flags, final PropertyBuilder propertyBuilder) throws MailboxException { + return new SimpleMessage(internalDate, size, bodyStartOctet, content, flags, propertyBuilder, getMailboxEntity().getMailboxId()); + } + + /** + * This mailbox is writable + * + * @throws MailboxException + */ + public boolean isWriteable(MailboxSession session) throws MailboxException { + return aclResolver.isReadWrite(myRights(session), getSharedPermanentFlags(session)); + } + + /** + * @see MessageManager#getMetaData(boolean, MailboxSession, + * org.apache.james.mailbox.MessageManager.MetaData.FetchGroup) + */ + public MetaData getMetaData(boolean resetRecent, MailboxSession mailboxSession, org.apache.james.mailbox.MessageManager.MetaData.FetchGroup fetchGroup) throws MailboxException { + + final List recent; + final Flags permanentFlags = getPermanentFlags(mailboxSession); + final long uidValidity = getMailboxEntity().getUidValidity(); + final long uidNext = mapperFactory.getMessageMapper(mailboxSession).getLastUid(mailbox) + 1; + final long highestModSeq = mapperFactory.getMessageMapper(mailboxSession).getHighestModSeq(mailbox); + final long messageCount; + final long unseenCount; + final Long firstUnseen; + switch (fetchGroup) { + case UNSEEN_COUNT: + unseenCount = countUnseenMessagesInMailbox(mailboxSession); + messageCount = getMessageCount(mailboxSession); + firstUnseen = null; + recent = recent(resetRecent, mailboxSession); + + break; + case FIRST_UNSEEN: + firstUnseen = findFirstUnseenMessageUid(mailboxSession); + messageCount = getMessageCount(mailboxSession); + unseenCount = 0; + recent = recent(resetRecent, mailboxSession); + + break; + case NO_UNSEEN: + firstUnseen = null; + unseenCount = 0; + messageCount = getMessageCount(mailboxSession); + recent = recent(resetRecent, mailboxSession); + + break; + default: + firstUnseen = null; + unseenCount = 0; + messageCount = -1; + // just reset the recent but not include them in the metadata + if (resetRecent) { + recent(resetRecent, mailboxSession); + } + recent = new ArrayList(); + break; + } + MailboxACL resolvedAcl = getResolvedMailboxACL(mailboxSession); + return new MailboxMetaData(recent, permanentFlags, uidValidity, uidNext, highestModSeq, messageCount, unseenCount, firstUnseen, isWriteable(mailboxSession), isModSeqPermanent(mailboxSession), resolvedAcl); + } + + /** + * Check if the given {@link Flags} contains {@link Flags} which are not + * included in the returned {@link Flags} of + * {@link #getPermanentFlags(MailboxSession)}. If any are found, these are + * removed from the given {@link Flags} instance. The only exception is the + * {@link Flag#RECENT} flag. + * + * This flag is never removed! + * + * @param flags + * @param session + */ + private void trimFlags(Flags flags, MailboxSession session) { + + Flags permFlags = getPermanentFlags(session); + + Flag[] systemFlags = flags.getSystemFlags(); + for (int i = 0; i < systemFlags.length; i++) { + Flag f = systemFlags[i]; + + if (f != Flag.RECENT && permFlags.contains(f) == false) { + flags.remove(f); + } + } + // if the permFlags contains the special USER flag we can skip this as + // all user flags are allowed + if (permFlags.contains(Flags.Flag.USER) == false) { + String[] uFlags = flags.getUserFlags(); + for (int i = 0; i < uFlags.length; i++) { + String uFlag = uFlags[i]; + if (permFlags.contains(uFlag) == false) { + flags.remove(uFlag); + } + } + } + + } + + /** + * @see org.apache.james.mailbox.MessageManager#setFlags(javax.mail.Flags, + * boolean, boolean, org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.MailboxSession) + */ + public Map setFlags(final Flags flags, final boolean value, final boolean replace, final MessageRange set, MailboxSession mailboxSession) throws MailboxException { + + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + final SortedMap newFlagsByUid = new TreeMap(); + + trimFlags(flags, mailboxSession); + + final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession); + + Iterator it = messageMapper.execute(new Mapper.Transaction>() { + + public Iterator run() throws MailboxException { + return messageMapper.updateFlags(getMailboxEntity(), flags, value, replace, set); + } + }); + + final SortedMap uFlags = new TreeMap(); + + while (it.hasNext()) { + UpdatedFlags flag = it.next(); + newFlagsByUid.put(flag.getUid(), flag.getNewFlags()); + uFlags.put(flag.getUid(), flag); + } + + dispatcher.flagsUpdated(mailboxSession, new ArrayList(uFlags.keySet()), getMailboxEntity(), new ArrayList(uFlags.values())); + + return newFlagsByUid; + } + + /** + * Copy the {@link MessageRange} to the {@link StoreMessageManager} + * + * @param set + * @param toMailbox + * @param session + * @throws MailboxException + */ + public List copyTo(final MessageRange set, final StoreMessageManager toMailbox, final MailboxSession session) throws MailboxException { + if (!toMailbox.isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()), session.getPathDelimiter()); + } + + return locker.executeWithLock(session, new StoreMailboxPath(toMailbox.getMailboxEntity()), new MailboxPathLocker.LockAwareExecution>() { + + @Override + public List execute() throws MailboxException { + SortedMap copiedUids = copy(set, toMailbox, session); + dispatcher.added(session, copiedUids, toMailbox.getMailboxEntity()); + return MessageRange.toRanges(new ArrayList(copiedUids.keySet())); + } + }, true); + } + + /** + * Move the {@link MessageRange} to the {@link StoreMessageManager} + * + * @param set + * @param toMailbox + * @param session + * @throws MailboxException + */ + public List moveTo(final MessageRange set, final StoreMessageManager toMailbox, final MailboxSession session) throws MailboxException { + if (!isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), session.getPathDelimiter()); + } + if (!toMailbox.isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()), session.getPathDelimiter()); + } + + //TODO lock the from mailbox too, in a non-deadlocking manner - how? + return locker.executeWithLock(session, new StoreMailboxPath(toMailbox.getMailboxEntity()), new MailboxPathLocker.LockAwareExecution>() { + + @Override + public List execute() throws MailboxException { + SortedMap movedUids = move(set, toMailbox, session); + dispatcher.added(session, movedUids, toMailbox.getMailboxEntity()); + return MessageRange.toRanges(new ArrayList(movedUids.keySet())); + } + }, true); + } + + protected MessageMetaData appendMessageToStore(final Message message, MailboxSession session) throws MailboxException { + final MessageMapper mapper = mapperFactory.getMessageMapper(session); + return mapperFactory.getMessageMapper(session).execute(new Mapper.Transaction() { + + public MessageMetaData run() throws MailboxException { + return mapper.add(getMailboxEntity(), message); + } + + }); + } + + /** + * @see org.apache.james.mailbox.MessageManager#getMessageCount(org.apache.james.mailbox.MailboxSession) + */ + public long getMessageCount(MailboxSession mailboxSession) throws MailboxException { + return mapperFactory.getMessageMapper(mailboxSession).countMessagesInMailbox(getMailboxEntity()); + } + + /** + * @see org.apache.james.mailbox.MessageManager#getMessages(org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.model.MessageResult.FetchGroup, + * org.apache.james.mailbox.MailboxSession) + */ + public MessageResultIterator getMessages(final MessageRange set, FetchGroup fetchGroup, MailboxSession mailboxSession) throws MailboxException { + final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession); + return new StoreMessageResultIterator(messageMapper, mailbox, set, fetchBatchSize, fetchGroup); + } + + /** + * Return a List which holds all uids of recent messages and optional reset + * the recent flag on the messages for the uids + * + * @param reset + * @param mailboxSession + * @return list + * @throws MailboxException + */ + protected List recent(final boolean reset, MailboxSession mailboxSession) throws MailboxException { + if (reset) { + if (!isWriteable(mailboxSession)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + } + } + final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession); + + return messageMapper.execute(new Mapper.Transaction>() { + + public List run() throws MailboxException { + final List members = messageMapper.findRecentMessageUidsInMailbox(getMailboxEntity()); + + // Convert to MessageRanges so we may be able to optimize the + // flag update + List ranges = MessageRange.toRanges(members); + for (MessageRange range : ranges) { + if (reset) { + // only call save if we need to + messageMapper.updateFlags(getMailboxEntity(), new Flags(Flag.RECENT), false, false, range); + } + } + return members; + } + + }); + + } + + protected Map deleteMarkedInMailbox(final MessageRange range, final MailboxSession session) throws MailboxException { + + final MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + return messageMapper.execute(new Mapper.Transaction>() { + + public Map run() throws MailboxException { + return messageMapper.expungeMarkedForDeletionInMailbox(getMailboxEntity(), range); + } + + }); + } + + /** + * @see org.apache.james.mailbox.MessageManager#search(org.apache.james.mailbox.model.SearchQuery, + * org.apache.james.mailbox.MailboxSession) + */ + public Iterator search(SearchQuery query, MailboxSession mailboxSession) throws MailboxException { + return index.search(mailboxSession, getMailboxEntity(), query); + } + + private Iterator copy(final Iterator> originalRows, final MailboxSession session) throws MailboxException { + final List copiedRows = new ArrayList(); + final MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + while (originalRows.hasNext()) { + final Message originalMessage = originalRows.next(); + MessageMetaData data = messageMapper.execute(new Mapper.Transaction() { + public MessageMetaData run() throws MailboxException { + return messageMapper.copy(getMailboxEntity(), originalMessage); + + } + + }); + copiedRows.add(data); + } + return copiedRows.iterator(); + } + + private Iterator move(Iterator> originalRows, + MailboxSession session) throws MailboxException { + final List movedRows = new ArrayList(); + final MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + while (originalRows.hasNext()) { + final Message originalMessage = originalRows.next(); + MessageMetaData data = messageMapper.execute(new Mapper.Transaction() { + public MessageMetaData run() throws MailboxException { + return messageMapper.move(getMailboxEntity(), originalMessage); + + } + + }); + movedRows.add(data); + } + return movedRows.iterator(); + } + + + /** + * @see org.apache.james.mailbox.store.AbstractStoreMessageManager#copy(org.apache.james.mailbox.model.MessageRange, + * org.apache.james.mailbox.store.AbstractStoreMessageManager, + * org.apache.james.mailbox.MailboxSession) + */ + private SortedMap copy(MessageRange set, final StoreMessageManager to, final MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + final SortedMap copiedMessages = new TreeMap(); + Iterator> originalRows = messageMapper.findInMailbox(mailbox, set, FetchType.Full, -1); + Iterator ids = to.copy(originalRows, session); + while (ids.hasNext()) { + MessageMetaData data = ids.next(); + copiedMessages.put(data.getUid(), data); + } + + return copiedMessages; + } + + private SortedMap move(MessageRange set, + final StoreMessageManager to, final MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + final SortedMap movedMessages = new TreeMap(); + Iterator> originalRows = messageMapper.findInMailbox(mailbox, set, FetchType.Full, -1); + Iterator ids = to.move(originalRows, session); + while (ids.hasNext()) { + MessageMetaData data = ids.next(); + movedMessages.put(data.getUid(), data); + } + + return movedMessages; + } + + + /** + * Return the count of unseen messages + * + * @param session + * @return count of unseen messages + * @throws MailboxException + */ + protected long countUnseenMessagesInMailbox(MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + return messageMapper.countUnseenMessagesInMailbox(getMailboxEntity()); + } + + /** + * Return the uid of the first unseen message or null of none is found + * + * @param session + * @return uid + * @throws MailboxException + */ + protected Long findFirstUnseenMessageUid(MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + return messageMapper.findFirstUnseenMessageUid(getMailboxEntity()); + } + + /** + * @see org.apache.james.mailbox.MessageManager#hasRight(org.apache.james.mailbox.MailboxACL.MailboxACLRight, + * org.apache.james.mailbox.MailboxSession) + */ + public boolean hasRight(MailboxACLRight right, MailboxSession session) throws UnsupportedRightException { + User user = session.getUser(); + String userName = user != null ? user.getUserName() : null; + + return aclResolver.hasRight(userName, groupMembershipResolver, right, mailbox.getACL(), mailbox.getUser(), isGroupFolder(session)); + } + + /** + * @see org.apache.james.mailbox.MessageManager#myRights(org.apache.james.mailbox.MailboxSession) + */ + @Override + public MailboxACLRights myRights(MailboxSession session) throws MailboxException { + User user = session.getUser(); + if (user != null) { + return aclResolver.resolveRights(user.getUserName(), groupMembershipResolver, mailbox.getACL(), mailbox.getUser(), isGroupFolder(session)); + } else { + return SimpleMailboxACL.NO_RIGHTS; + } + } + + /** + * @see org.apache.james.mailbox.MessageManager#listRigths(java.lang.String, org.apache.james.mailbox.MailboxSession) + */ + public MailboxACLRights[] listRigths(final MailboxACLEntryKey key, MailboxSession session) throws UnsupportedRightException { + return aclResolver.listRights(key, groupMembershipResolver, mailbox.getUser(), isGroupFolder(session)); + } + + /** + * @throws UnsupportedRightException + * @see org.apache.james.mailbox.MessageManager#setRights(java.lang.String, org.apache.james.mailbox.model.MailboxACL.EditMode, org.apache.james.mailbox.model.MailboxACL.MailboxACLRights) + */ + @Override + public void setRights(MailboxACLEntryKey mailboxACLEntryKey, EditMode editMode, MailboxACLRights mailboxAclRights) throws UnsupportedRightException { + MailboxACL acl = mailbox.getACL(); + if (acl == null) { + acl = SimpleMailboxACL.EMPTY; + } + switch (editMode) { + case ADD: + acl = acl.union(mailboxACLEntryKey, mailboxAclRights); + break; + case REMOVE: + acl = acl.except(mailboxACLEntryKey, mailboxAclRights); + break; + case REPLACE: + acl = acl.replace(mailboxACLEntryKey, mailboxAclRights); + break; + default: + throw new IllegalStateException("Unexpected "+ EditMode.class.getName() +"."+ editMode); + } + mailbox.setACL(acl); + } + + /** + * Applies the global ACL (if there are any) to the mailbox ACL. + * + * @param mailboxSession + * @return the ACL of the present mailbox merged with the global ACL (if + * there are any). + * @throws UnsupportedRightException + */ + protected MailboxACL getResolvedMailboxACL(MailboxSession mailboxSession) throws UnsupportedRightException { + return aclResolver.applyGlobalACL(mailbox.getACL(), isGroupFolder(mailboxSession)); + } + + /** + * Returns true if the current mailbox does not reside neither in private + * nor other users' namespace. + * + * @param session + * @return + */ + protected boolean isGroupFolder(MailboxSession session) { + final String ns = mailbox.getNamespace(); + return ns == null || (!ns.equals(session.getPersonalSpace()) && !ns.equals(session.getOtherUsersSpace())); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java new file mode 100644 index 0000000..0800cdd --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java @@ -0,0 +1,292 @@ +/**************************************************************** + * 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 java.util.Date; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.Headers; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResultIterator; +import org.apache.james.mailbox.model.MimeDescriptor; +import org.apache.james.mailbox.model.MessageRange.Type; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +public class StoreMessageResultIterator implements MessageResultIterator { + + private Iterator> next = null; + private MailboxException exception; + private Mailbox mailbox; + private FetchGroup group; + private long from; + private long cursor; + private long to; + private int batchSize; + private Type type; + private MessageMapper mapper; + private FetchType ftype; + + public StoreMessageResultIterator(MessageMapper mapper, Mailbox mailbox, MessageRange range, int batchSize, org.apache.james.mailbox.model.MessageResult.FetchGroup group) { + this.mailbox = mailbox; + this.group = group; + this.mapper = mapper; + this.from = range.getUidFrom(); + this.cursor = this.from; + this.to = range.getUidTo(); + this.batchSize = batchSize; + this.type = range.getType(); + this.ftype = getFetchType(group); + } + + /** + * Use the passed {@link FetchGroup} and calculate the right + * {@link FetchType} for it + * + * @param group + * @return fetchType + */ + private static final FetchType getFetchType(FetchGroup group) { + int content = group.content(); + boolean headers = false; + boolean body = false; + boolean full = false; + + if ((content & FetchGroup.HEADERS) > 0) { + headers = true; + content -= FetchGroup.HEADERS; + } + if ((content & FetchGroup.BODY_CONTENT) > 0) { + body = true; + content -= FetchGroup.BODY_CONTENT; + } + + if ((content & FetchGroup.FULL_CONTENT) > 0) { + full = true; + content -= FetchGroup.FULL_CONTENT; + } + + if ((content & FetchGroup.MIME_DESCRIPTOR) > 0) { + // If we need the mimedescriptor we MAY need the full content later + // too. + // This gives us no other choice then request it + full = true; + content -= FetchGroup.MIME_DESCRIPTOR; + } + if (full || (body && headers)) { + return FetchType.Full; + } else if (body) { + return FetchType.Body; + } else if (headers) { + return FetchType.Headers; + } else { + return FetchType.Metadata; + } + } + + @Override + public boolean hasNext() { + if (cursor > to) + return false; + + if (next == null || !next.hasNext()) { + try { + readBatch(); + } catch (MailboxException e) { + this.exception = e; + return false; + } + } + + return next.hasNext(); + } + + private void readBatch() throws MailboxException { + MessageRange range; + switch (type) { + default: + case ALL: + // In case of all, we start on cursor and don't specify a to + range = MessageRange.from(cursor); + break; + case FROM: + range = MessageRange.from(cursor); + break; + case ONE: + range = MessageRange.one(cursor); + break; + case RANGE: + range = MessageRange.range(cursor, to); + break; + } + next = mapper.findInMailbox(mailbox, range, ftype, batchSize); + } + + @Override + public MessageResult next() { + if (next == null || !next.hasNext()) + throw new NoSuchElementException(); + + final Message message = next.next(); + MessageResult result; + try { + result = ResultUtils.loadMessageResult(message, group); + cursor = result.getUid(); + } catch (MailboxException e) { + result = new UnloadedMessageResult(message, e); + } + + cursor++; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read only"); + } + + @Override + public MailboxException getException() { + return exception; + } + + private static final class UnloadedMessageResult implements MessageResult { + private final MailboxException exception; + + private final Date internalDate; + + private final long size; + + private final long uid; + + private final Flags flags; + + private long modSeq = -1; + + public UnloadedMessageResult(final Message message, final MailboxException exception) { + super(); + internalDate = message.getInternalDate(); + size = message.getFullContentOctets(); + uid = message.getUid(); + flags = message.createFlags(); + modSeq = message.getModSeq(); + this.exception = exception; + } + + public Flags getFlags() { + return flags; + } + + public Content getFullContent() throws MailboxException { + throw exception; + } + + public Date getInternalDate() { + return internalDate; + } + + public Content getBody() throws MailboxException { + throw exception; + } + + public long getSize() { + return size; + } + + public long getUid() { + return uid; + } + + public int compareTo(MessageResult that) { + // Java 1.5 return (int) Math.signum(uid - that.getUid()); + long diff = uid - that.getUid(); + return (int) diff == 0 ? 0 : diff > 0 ? 1 : -1; + } + + @Override + public int hashCode() { + int ret = 19 * 37; + ret = ret * 37 + exception.hashCode(); + ret = ret * 37 + internalDate.hashCode(); + ret = ret * 37 + (int)size; + ret = ret * 37 + (int)uid; + ret = ret * 37 + flags.hashCode(); + ret = ret * 37 + (int)modSeq; + return ret; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof UnloadedMessageResult) { + @SuppressWarnings("unchecked") + UnloadedMessageResult that = (UnloadedMessageResult)obj; + return (size == that.size) && (uid == that.uid) && (modSeq == that.modSeq) && exception.equals(that.exception) + && internalDate.equals(that.internalDate) && flags.equals(that.flags); + } + return false; + } + + public Content getFullContent(MimePath path) throws MailboxException { + throw exception; + } + + public Iterator

iterateHeaders(MimePath path) throws MailboxException { + throw exception; + } + + public Iterator
iterateMimeHeaders(MimePath path) throws MailboxException { + throw exception; + } + + public Content getBody(MimePath path) throws MailboxException { + throw exception; + } + + public Content getMimeBody(MimePath path) throws MailboxException { + throw exception; + } + + public MimeDescriptor getMimeDescriptor() throws MailboxException { + throw exception; + } + + public long getModSeq() { + return modSeq; + } + + @Override + public Headers getHeaders() throws MailboxException { + throw exception; + } + + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java new file mode 100644 index 0000000..6a42a98 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java @@ -0,0 +1,135 @@ +/**************************************************************** + * 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 java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.RequestAware; +import org.apache.james.mailbox.SubscriptionManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.user.SubscriptionMapper; +import org.apache.james.mailbox.store.user.SubscriptionMapperFactory; +import org.apache.james.mailbox.store.user.model.Subscription; +import org.apache.james.mailbox.store.user.model.impl.SimpleSubscription; + +/** + * Manages subscriptions for Users and Mailboxes. + */ +public class StoreSubscriptionManager implements SubscriptionManager { + + private static final int INITIAL_SIZE = 32; + + protected SubscriptionMapperFactory mapperFactory; + + public StoreSubscriptionManager(final SubscriptionMapperFactory mapperFactory) { + this.mapperFactory = mapperFactory; + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#subscribe(org.apache.james.mailbox.MailboxSession, java.lang.String) + */ + public void subscribe(final MailboxSession session, final String mailbox) throws SubscriptionException { + final SubscriptionMapper mapper = mapperFactory.getSubscriptionMapper(session); + try { + mapper.execute(new Mapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + final Subscription subscription = mapper.findMailboxSubscriptionForUser(session.getUser().getUserName(), mailbox); + if (subscription == null) { + final Subscription newSubscription = createSubscription(session, mailbox); + mapper.save(newSubscription); + } + } + + }); + } catch (MailboxException e) { + throw new SubscriptionException(e); + } + } + + /** + * Create Subscription for the given user and mailbox. By default a {@link SimpleSubscription} will get returned. + * + * If you need something more special just override this method + * + * @param session + * @param mailbox + * @return subscription + */ + protected Subscription createSubscription(final MailboxSession session, final String mailbox) { + return new SimpleSubscription(session.getUser().getUserName(), mailbox); + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#subscriptions(org.apache.james.mailbox.MailboxSession) + */ + public Collection subscriptions(final MailboxSession session) throws SubscriptionException { + final SubscriptionMapper mapper = mapperFactory.getSubscriptionMapper(session); + final List subscriptions = mapper.findSubscriptionsForUser(session.getUser().getUserName()); + final Collection results = new HashSet(INITIAL_SIZE); + for (Subscription subscription:subscriptions) { + results.add(subscription.getMailbox()); + } + return results; + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#unsubscribe(org.apache.james.mailbox.MailboxSession, java.lang.String) + */ + public void unsubscribe(final MailboxSession session, final String mailbox) throws SubscriptionException { + final SubscriptionMapper mapper = mapperFactory.getSubscriptionMapper(session); + try { + mapper.execute(new Mapper.VoidTransaction() { + + public void runVoid() throws MailboxException { + final Subscription subscription = mapper.findMailboxSubscriptionForUser(session.getUser().getUserName(), mailbox); + if (subscription != null) { + mapper.delete(subscription); + } + } + + }); + } catch (MailboxException e) { + throw new SubscriptionException(e); + } + } + + /** + * @see org.apache.james.mailbox.SubscriptionManager#endProcessingRequest(org.apache.james.mailbox.MailboxSession) + */ + public void endProcessingRequest(MailboxSession session) { + if (mapperFactory instanceof RequestAware) { + ((RequestAware)mapperFactory).endProcessingRequest(session); + } + } + + /** + * Do nothing, Sub classes should override this if needed + */ + public void startProcessingRequest(MailboxSession session) { + // Do nothing + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops new file mode 100644 index 0000000..8d57786 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops @@ -0,0 +1,59 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1509308/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail +END +MailboxMapper.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java +END +MessageMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapperFactory.java +END +ModSeqProvider.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/ModSeqProvider.java +END +AbstractLockingUidProvider.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingUidProvider.java +END +MailboxMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapperFactory.java +END +MessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java +END +UidProvider.java +K 25 +svn:wc:ra_dav:version-url +V 120 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/UidProvider.java +END +AbstractMessageMapper.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1509308/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java +END +AbstractLockingModSeqProvider.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingModSeqProvider.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries new file mode 100644 index 0000000..dad3d51 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries @@ -0,0 +1,337 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail +http://svn.apache.org/repos/asf + + + +2013-08-01T15:48:11.483339Z +1509308 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + +MailboxMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +f730f556d5c0457ece079f2bfd0625ce +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +3415 + +MessageMapperFactory.java +file + + + + +2013-09-02T02:54:39.000000Z +094f642b674a5f932a558390020fa86a +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1673 + +ModSeqProvider.java +file + + + + +2013-09-02T02:54:39.000000Z +f934d6d9ab2522886d0c5f1d8d04d58b +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2400 + +AbstractLockingUidProvider.java +file + + + + +2013-09-02T02:54:39.000000Z +712c775b1036d1e024cd127b8c61e41b +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2732 + +MailboxMapperFactory.java +file + + + + +2013-09-02T02:54:39.000000Z +20e41a08fa917eca5b84b348cd1069cb +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1664 + +MessageMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +b148c0a5e8707b6b80f421302391532f +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +8354 + +UidProvider.java +file + + + + +2013-09-02T02:54:39.000000Z +69ba0d0e287a8c44007b95e11d4c89b1 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2346 + +AbstractMessageMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +b1fefb40d5fe95bf4a10af82525ea383 +2013-08-01T15:48:11.483339Z +1509308 +eric + + + + + + + + + + + + + + + + + + + + + +7211 + +AbstractLockingModSeqProvider.java +file + + + + +2013-09-02T02:54:39.000000Z +c7c94729fb21709de4faadbcd7971bf9 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2769 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingModSeqProvider.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingModSeqProvider.java.svn-base new file mode 100644 index 0000000..d068a39 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingModSeqProvider.java.svn-base @@ -0,0 +1,63 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.StoreMailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Abstract base implementation of {@link ModSeqProvider} which uses the given {@link MailboxPathLocker} to lock the {@link Mailbox} during the mod-seq generation. + * + * + * @param + */ +public abstract class AbstractLockingModSeqProvider implements ModSeqProvider{ + + private final MailboxPathLocker locker; + + public AbstractLockingModSeqProvider(MailboxPathLocker locker) { + this.locker = locker; + } + + @Override + public long nextModSeq(final MailboxSession session, final Mailbox mailbox) throws MailboxException { + return locker.executeWithLock(session, new StoreMailboxPath(mailbox), new LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + return lockedNextModSeq(session, mailbox); + } + }); + } + + /** + * Generate the next mod-seq for the given {@link Mailbox} while holding a lock on it. + * + * @param session + * @param mailbox + * @return nextModSeq + * @throws MailboxException + */ + protected abstract long lockedNextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingUidProvider.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingUidProvider.java.svn-base new file mode 100644 index 0000000..383c231 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractLockingUidProvider.java.svn-base @@ -0,0 +1,65 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.StoreMailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; + + +/** + * Abstract base implementation of {@link UidProvider} which used the given {@link MailboxPathLocker} to + * lock the {@link Mailbox} while the next uid is generated + * + * + * @param + */ +public abstract class AbstractLockingUidProvider implements UidProvider{ + + private final MailboxPathLocker locker; + + public AbstractLockingUidProvider(MailboxPathLocker locker) { + this.locker = locker; + } + + @Override + public long nextUid(final MailboxSession session, final Mailbox mailbox) throws MailboxException { + return locker.executeWithLock(session, new StoreMailboxPath(mailbox), new LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + return lockedNextUid(session, mailbox); + } + }); + } + + /** + * Generate the next uid to use while the {@link Mailbox} is locked + * + * @param session + * @param mailbox + * @return nextUid + * @throws MailboxException + */ + protected abstract long lockedNextUid(MailboxSession session, Mailbox mailbox) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractMessageMapper.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractMessageMapper.java.svn-base new file mode 100644 index 0000000..f45e075 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/AbstractMessageMapper.java.svn-base @@ -0,0 +1,170 @@ +/**************************************************************** + * 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; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * Abstract base class for {@link MessageMapper} implementation + * which already takes care of most uid / mod-seq handling. + * + * @param + */ +public abstract class AbstractMessageMapper extends TransactionalMapper implements MessageMapper{ + protected final MailboxSession mailboxSession; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + public AbstractMessageMapper(MailboxSession mailboxSession, UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this.mailboxSession = mailboxSession; + this.uidProvider = uidProvider; + this.modSeqProvider = modSeqProvider; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#getHighestModSeq(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + return modSeqProvider.highestModSeq(mailboxSession, mailbox); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#getLastUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long getLastUid(Mailbox mailbox) throws MailboxException { + return uidProvider.lastUid(mailboxSession, mailbox); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, javax.mail.Flags, boolean, boolean, org.apache.james.mailbox.model.MessageRange) + */ + public Iterator updateFlags(final Mailbox mailbox, final Flags flags, final boolean value, final boolean replace, final MessageRange set) throws MailboxException { + final List updatedFlags = new ArrayList(); + Iterator> messages = findInMailbox(mailbox, set, FetchType.Metadata, -1); + + long modSeq = -1; + if (messages.hasNext()) { + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + } + while(messages.hasNext()) { + final Message member = messages.next(); + Flags originalFlags = member.createFlags(); + if (replace) { + member.setFlags(flags); + } else { + Flags current = member.createFlags(); + if (value) { + current.add(flags); + } else { + current.remove(flags); + } + member.setFlags(current); + } + Flags newFlags = member.createFlags(); + if (UpdatedFlags.flagsChanged(originalFlags, newFlags)) { + // increase the mod-seq as we changed the flags + member.setModSeq(modSeq); + save(mailbox, member); + } + + + UpdatedFlags uFlags = new UpdatedFlags(member.getUid(), member.getModSeq(), originalFlags, newFlags); + + updatedFlags.add(uFlags); + + } + + return updatedFlags.iterator(); + + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#add(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + public MessageMetaData add(final Mailbox mailbox, Message message) throws MailboxException { + message.setUid(uidProvider.nextUid(mailboxSession, mailbox)); + + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + message.setModSeq(modSeqProvider.nextModSeq(mailboxSession, mailbox)); + } + MessageMetaData data = save(mailbox, message); + + return data; + + } + + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + public MessageMetaData copy(final Mailbox mailbox, final Message original) throws MailboxException { + long uid = uidProvider.nextUid(mailboxSession, mailbox); + long modSeq = -1; + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + final MessageMetaData metaData = copy(mailbox, uid, modSeq, original); + + return metaData; + } + + + + + /** + * Save the {@link Message} for the given {@link Mailbox} and return the {@link MessageMetaData} + * + * @param mailbox + * @param message + * @return metaData + * @throws MailboxException + */ + protected abstract MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException; + + + /** + * Copy the Message to the Mailbox, using the given uid and modSeq for the new Message + * + * @param mailbox + * @param uid + * @param modSeq + * @param original + * @return metaData + * @throws MailboxException + */ + protected abstract MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapper.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapper.java.svn-base new file mode 100644 index 0000000..25fdc03 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapper.java.svn-base @@ -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.mail; + +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.transaction.Mapper; + +/** + * Mapper for {@link Mailbox} actions. A {@link MailboxMapper} has a lifecycle from the start of a request + * to the end of the request. + * + */ +public interface MailboxMapper extends Mapper { + + /** + * Save the give {@link Mailbox} to the underlying storage + * + * @param mailbox + * @throws MailboxException + */ + void save(Mailbox mailbox) throws MailboxException; + + /** + * Delete the given {@link Mailbox} from the underlying storage + * + * @param mailbox + * @throws MailboxException + */ + void delete(Mailbox mailbox) throws MailboxException; + + + /** + * Return the {@link Mailbox} for the given name + * + * @param mailboxName + * @return mailbox + * @throws MailboxException + * @throws MailboxNotFoundException + */ + Mailbox findMailboxByPath(MailboxPath mailboxName) + throws MailboxException, MailboxNotFoundException; + + /** + * Return a List of {@link Mailbox} which name is like the given name + * + * @param mailboxPath + * @return mailboxList + * @throws MailboxException + */ + List> findMailboxWithPathLike(MailboxPath mailboxPath) + throws MailboxException; + + /** + * Return if the given {@link Mailbox} has children + * + * @param mailbox not null + * @param delimiter path delimiter + * @return true when the mailbox has children, false otherwise + * @throws MailboxException + * @throws MailboxNotFoundException + */ + boolean hasChildren(Mailbox mailbox, char delimiter) + throws MailboxException, MailboxNotFoundException; + + /** + * Return a unmodifable {@link List} of all {@link Mailbox} + * + * @return mailboxList + * @throws MailboxException + */ + List> list() throws MailboxException; +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapperFactory.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapperFactory.java.svn-base new file mode 100644 index 0000000..cc67861 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MailboxMapperFactory.java.svn-base @@ -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.mail; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; + +public interface MailboxMapperFactory { + + /** + * Create a {@link MailboxMapper} instance or return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + MailboxMapper getMailboxMapper(MailboxSession session) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapper.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapper.java.svn-base new file mode 100644 index 0000000..ddf4f94 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapper.java.svn-base @@ -0,0 +1,233 @@ +/**************************************************************** + * 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; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.transaction.Mapper; + +/** + * Maps {@link Message} in a {@link org.apache.james.mailbox.MessageManager}. A {@link MessageMapper} has a lifecycle from the start of a request + * to the end of the request. + */ +public interface MessageMapper extends Mapper { + + /** + * Return a {@link Iterator} which holds the messages for the given criterias + * The list must be ordered by the {@link Message} uid + * + * @param mailbox The mailbox to search + * @param set message range for batch processing + * @param type + * @param limit the maximal limit of returned {@link Message}'s. Use -1 to set no limit. In any case the caller MUST not expect the limit to get applied in all cases as the implementation + * MAY just ignore it + * @throws MailboxException + */ + Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType type, int limit) + throws MailboxException; + + /** + * Return a {@link Iterator} which holds the uids for all deleted Messages for the given {@link MessageRange} which are marked for deletion + * The list must be ordered + * @param mailbox + * @param set + * @return uids + * @throws MailboxException + */ + Map expungeMarkedForDeletionInMailbox( + Mailbox mailbox, final MessageRange set) + throws MailboxException; + + /** + * Return the count of messages in the mailbox + * + * @param mailbox + * @return count + * @throws MailboxException + */ + long countMessagesInMailbox(Mailbox mailbox) + throws MailboxException; + + /** + * Return the count of unseen messages in the mailbox + * + * @param mailbox + * @return unseenCount + * @throws StorageException + */ + long countUnseenMessagesInMailbox(Mailbox mailbox) + throws MailboxException; + + + /** + * Delete the given {@link Message} + * + * @param mailbox + * @param message + * @throws StorageException + */ + void delete(Mailbox mailbox, Message message) throws MailboxException; + + /** + * Return the uid of the first unseen message. If non can be found null will get returned + * + * + * @param mailbox + * @return uid or null + * @throws StorageException + */ + Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException; + + /** + * Return a List of {@link Message} which are recent. + * The list must be ordered by the {@link Message} uid. + * + * @param mailbox + * @return recentList + * @throws StorageException + */ + List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException; + + + /** + * Add the given {@link Message} to the underlying storage. Be aware that implementation may choose to replace the uid of the given message while storing. + * So you should only depend on the returned uid. + * + * + * @param mailbox + * @param message + * @return uid + * @throws StorageException + */ + MessageMetaData add(Mailbox mailbox, Message message) throws MailboxException; + + /** + * Update flags for the given {@link MessageRange}. Only the flags may be modified after a message was saved to a mailbox. + * + * @param mailbox + * @param flags + * @param value + * @param replace + * @param set + * @return updatedFlags + * @throws MailboxException + */ + Iterator updateFlags(Mailbox mailbox, final Flags flags, final boolean value, final boolean replace, + final MessageRange set) throws MailboxException; + + /** + * Copy the given {@link Message} to a new mailbox and return the uid of the copy. Be aware that the given uid is just a suggestion for the uid of the copied + * message. Implementation may choose to use a different one, so only depend on the returned uid! + * + * @param mailbox the Mailbox to copy to + * @param original the original to copy + * @throws StorageException + */ + MessageMetaData copy(Mailbox mailbox,Message original) throws MailboxException; + + /** + * Move the given {@link Message} to a new mailbox and return the uid of the moved. Be aware that the given uid is just a suggestion for the uid of the moved + * message. Implementation may choose to use a different one, so only depend on the returned uid! + * + * @param mailbox the Mailbox to move to + * @param original the original to move + * @throws StorageException + */ + MessageMetaData move(Mailbox mailbox,Message original) throws MailboxException; + + + /** + * Return the last uid which were used for storing a Message in the {@link Mailbox} + * + * @param mailbox + * @return lastUid + * @throws MailboxException + */ + long getLastUid(Mailbox mailbox) throws MailboxException;; + + + /** + * Return the higest mod-sequence which were used for storing a Message in the {@link Mailbox} + * + * @param mailbox + * @return lastUid + * @throws MailboxException + */ + long getHighestModSeq(Mailbox mailbox) throws MailboxException; + + /** + * Specify what data needs to get filled in a {@link Message} before returning it + * + * + */ + public static enum FetchType { + + /** + * Fetch only the meta data of the {@link Message} which includes: + *

+ * {@link Message#getUid()} + * {@link Message#getModSeq()} + * {@link Message#getBodyOctets()} + * {@link Message#getFullContentOctets()} + * {@link Message#getInternalDate()} + * {@link Message#getMailboxId()} + * {@link Message#getMediaType()} + * {@link Message#getModSeq()} + * {@link Message#getSubType()} + * {@link Message#getTextualLineCount()} + *

+ */ + Metadata, + /** + * Fetch the {@link #Metadata}, {@link Property}'s and the {@link #Headers}'s for the {@link Message}. This includes: + * + *

+ * {@link Message#getProperties()} + * {@link Message#getHeaderContent()} + *

+ */ + Headers, + /** + * Fetch the {@link #Metadata} and the Body for the {@link Message}. This includes: + * + *

+ * {@link Message#getBodyContent()} + *

+ */ + Body, + + /** + * Fetch the complete {@link Message} + * + */ + Full + } + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapperFactory.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapperFactory.java.svn-base new file mode 100644 index 0000000..c9aebd0 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/MessageMapperFactory.java.svn-base @@ -0,0 +1,34 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; + +public interface MessageMapperFactory { + + /** + * Create a {@link MessageMapper} instance of return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + MessageMapper getMessageMapper(MailboxSession session) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ModSeqProvider.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ModSeqProvider.java.svn-base new file mode 100644 index 0000000..b134a6b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ModSeqProvider.java.svn-base @@ -0,0 +1,57 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Take care of provide mod-seqences for a given {@link Mailbox}. Be aware that implementations + * need to be thread-safe! + * + * + * @param + */ +public interface ModSeqProvider { + + /** + * Return the next mod-sequence which can be used for the {@link Mailbox}. + * Its important that the returned mod-sequence is higher then the last used and that the next call of this method does return a higher + * one. + * + * The first mod-seq must be >= 1 + * + * @param session + * @param mailbox + * @return modSeq + * @throws MailboxException + */ + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException; + + /** + * Return the highest mod-sequence which were used for the {@link Mailbox} + * + * @param session + * @param mailbox + * @return highest + * @throws MailboxException + */ + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/UidProvider.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/UidProvider.java.svn-base new file mode 100644 index 0000000..ae7e1fd --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/UidProvider.java.svn-base @@ -0,0 +1,55 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Take care of provide uids for a given {@link Mailbox}. Be aware that implementations + * need to be thread-safe! + * + * + * @param + */ +public interface UidProvider { + + /** + * Return the next uid which can be used while append a Message to the {@link Mailbox}. + * Its important that the returned uid is higher then the last used and that the next call of this method does return a higher + * one + * + * @param session + * @param mailbox + * @return nextUid + * @throws MailboxException + */ + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException; + + /** + * Return the last uid which were used for storing a Message in the {@link Mailbox} + * + * @param session + * @param mailbox + * @return lastUid + * @throws MailboxException + */ + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingModSeqProvider.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingModSeqProvider.java new file mode 100644 index 0000000..d068a39 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingModSeqProvider.java @@ -0,0 +1,63 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.StoreMailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Abstract base implementation of {@link ModSeqProvider} which uses the given {@link MailboxPathLocker} to lock the {@link Mailbox} during the mod-seq generation. + * + * + * @param + */ +public abstract class AbstractLockingModSeqProvider implements ModSeqProvider{ + + private final MailboxPathLocker locker; + + public AbstractLockingModSeqProvider(MailboxPathLocker locker) { + this.locker = locker; + } + + @Override + public long nextModSeq(final MailboxSession session, final Mailbox mailbox) throws MailboxException { + return locker.executeWithLock(session, new StoreMailboxPath(mailbox), new LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + return lockedNextModSeq(session, mailbox); + } + }); + } + + /** + * Generate the next mod-seq for the given {@link Mailbox} while holding a lock on it. + * + * @param session + * @param mailbox + * @return nextModSeq + * @throws MailboxException + */ + protected abstract long lockedNextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingUidProvider.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingUidProvider.java new file mode 100644 index 0000000..383c231 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractLockingUidProvider.java @@ -0,0 +1,65 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxPathLocker; +import org.apache.james.mailbox.MailboxPathLocker.LockAwareExecution; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.StoreMailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; + + +/** + * Abstract base implementation of {@link UidProvider} which used the given {@link MailboxPathLocker} to + * lock the {@link Mailbox} while the next uid is generated + * + * + * @param + */ +public abstract class AbstractLockingUidProvider implements UidProvider{ + + private final MailboxPathLocker locker; + + public AbstractLockingUidProvider(MailboxPathLocker locker) { + this.locker = locker; + } + + @Override + public long nextUid(final MailboxSession session, final Mailbox mailbox) throws MailboxException { + return locker.executeWithLock(session, new StoreMailboxPath(mailbox), new LockAwareExecution() { + + @Override + public Long execute() throws MailboxException { + return lockedNextUid(session, mailbox); + } + }); + } + + /** + * Generate the next uid to use while the {@link Mailbox} is locked + * + * @param session + * @param mailbox + * @return nextUid + * @throws MailboxException + */ + protected abstract long lockedNextUid(MailboxSession session, Mailbox mailbox) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java new file mode 100644 index 0000000..f45e075 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java @@ -0,0 +1,170 @@ +/**************************************************************** + * 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; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.transaction.TransactionalMapper; + +/** + * Abstract base class for {@link MessageMapper} implementation + * which already takes care of most uid / mod-seq handling. + * + * @param + */ +public abstract class AbstractMessageMapper extends TransactionalMapper implements MessageMapper{ + protected final MailboxSession mailboxSession; + private final UidProvider uidProvider; + private final ModSeqProvider modSeqProvider; + + public AbstractMessageMapper(MailboxSession mailboxSession, UidProvider uidProvider, ModSeqProvider modSeqProvider) { + this.mailboxSession = mailboxSession; + this.uidProvider = uidProvider; + this.modSeqProvider = modSeqProvider; + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#getHighestModSeq(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + return modSeqProvider.highestModSeq(mailboxSession, mailbox); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#getLastUid(org.apache.james.mailbox.store.mail.model.Mailbox) + */ + public long getLastUid(Mailbox mailbox) throws MailboxException { + return uidProvider.lastUid(mailboxSession, mailbox); + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, javax.mail.Flags, boolean, boolean, org.apache.james.mailbox.model.MessageRange) + */ + public Iterator updateFlags(final Mailbox mailbox, final Flags flags, final boolean value, final boolean replace, final MessageRange set) throws MailboxException { + final List updatedFlags = new ArrayList(); + Iterator> messages = findInMailbox(mailbox, set, FetchType.Metadata, -1); + + long modSeq = -1; + if (messages.hasNext()) { + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + } + while(messages.hasNext()) { + final Message member = messages.next(); + Flags originalFlags = member.createFlags(); + if (replace) { + member.setFlags(flags); + } else { + Flags current = member.createFlags(); + if (value) { + current.add(flags); + } else { + current.remove(flags); + } + member.setFlags(current); + } + Flags newFlags = member.createFlags(); + if (UpdatedFlags.flagsChanged(originalFlags, newFlags)) { + // increase the mod-seq as we changed the flags + member.setModSeq(modSeq); + save(mailbox, member); + } + + + UpdatedFlags uFlags = new UpdatedFlags(member.getUid(), member.getModSeq(), originalFlags, newFlags); + + updatedFlags.add(uFlags); + + } + + return updatedFlags.iterator(); + + } + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#add(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + public MessageMetaData add(final Mailbox mailbox, Message message) throws MailboxException { + message.setUid(uidProvider.nextUid(mailboxSession, mailbox)); + + // if a mailbox does not support mod-sequences the provider may be null + if (modSeqProvider != null) { + message.setModSeq(modSeqProvider.nextModSeq(mailboxSession, mailbox)); + } + MessageMetaData data = save(mailbox, message); + + return data; + + } + + + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + public MessageMetaData copy(final Mailbox mailbox, final Message original) throws MailboxException { + long uid = uidProvider.nextUid(mailboxSession, mailbox); + long modSeq = -1; + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + final MessageMetaData metaData = copy(mailbox, uid, modSeq, original); + + return metaData; + } + + + + + /** + * Save the {@link Message} for the given {@link Mailbox} and return the {@link MessageMetaData} + * + * @param mailbox + * @param message + * @return metaData + * @throws MailboxException + */ + protected abstract MessageMetaData save(Mailbox mailbox, Message message) throws MailboxException; + + + /** + * Copy the Message to the Mailbox, using the given uid and modSeq for the new Message + * + * @param mailbox + * @param uid + * @param modSeq + * @param original + * @return metaData + * @throws MailboxException + */ + protected abstract MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java new file mode 100644 index 0000000..25fdc03 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.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.mail; + +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.transaction.Mapper; + +/** + * Mapper for {@link Mailbox} actions. A {@link MailboxMapper} has a lifecycle from the start of a request + * to the end of the request. + * + */ +public interface MailboxMapper extends Mapper { + + /** + * Save the give {@link Mailbox} to the underlying storage + * + * @param mailbox + * @throws MailboxException + */ + void save(Mailbox mailbox) throws MailboxException; + + /** + * Delete the given {@link Mailbox} from the underlying storage + * + * @param mailbox + * @throws MailboxException + */ + void delete(Mailbox mailbox) throws MailboxException; + + + /** + * Return the {@link Mailbox} for the given name + * + * @param mailboxName + * @return mailbox + * @throws MailboxException + * @throws MailboxNotFoundException + */ + Mailbox findMailboxByPath(MailboxPath mailboxName) + throws MailboxException, MailboxNotFoundException; + + /** + * Return a List of {@link Mailbox} which name is like the given name + * + * @param mailboxPath + * @return mailboxList + * @throws MailboxException + */ + List> findMailboxWithPathLike(MailboxPath mailboxPath) + throws MailboxException; + + /** + * Return if the given {@link Mailbox} has children + * + * @param mailbox not null + * @param delimiter path delimiter + * @return true when the mailbox has children, false otherwise + * @throws MailboxException + * @throws MailboxNotFoundException + */ + boolean hasChildren(Mailbox mailbox, char delimiter) + throws MailboxException, MailboxNotFoundException; + + /** + * Return a unmodifable {@link List} of all {@link Mailbox} + * + * @return mailboxList + * @throws MailboxException + */ + List> list() throws MailboxException; +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapperFactory.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapperFactory.java new file mode 100644 index 0000000..cc67861 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapperFactory.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.mail; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; + +public interface MailboxMapperFactory { + + /** + * Create a {@link MailboxMapper} instance or return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + MailboxMapper getMailboxMapper(MailboxSession session) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java new file mode 100644 index 0000000..ddf4f94 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java @@ -0,0 +1,233 @@ +/**************************************************************** + * 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; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; +import org.apache.james.mailbox.store.transaction.Mapper; + +/** + * Maps {@link Message} in a {@link org.apache.james.mailbox.MessageManager}. A {@link MessageMapper} has a lifecycle from the start of a request + * to the end of the request. + */ +public interface MessageMapper extends Mapper { + + /** + * Return a {@link Iterator} which holds the messages for the given criterias + * The list must be ordered by the {@link Message} uid + * + * @param mailbox The mailbox to search + * @param set message range for batch processing + * @param type + * @param limit the maximal limit of returned {@link Message}'s. Use -1 to set no limit. In any case the caller MUST not expect the limit to get applied in all cases as the implementation + * MAY just ignore it + * @throws MailboxException + */ + Iterator> findInMailbox(Mailbox mailbox, MessageRange set, FetchType type, int limit) + throws MailboxException; + + /** + * Return a {@link Iterator} which holds the uids for all deleted Messages for the given {@link MessageRange} which are marked for deletion + * The list must be ordered + * @param mailbox + * @param set + * @return uids + * @throws MailboxException + */ + Map expungeMarkedForDeletionInMailbox( + Mailbox mailbox, final MessageRange set) + throws MailboxException; + + /** + * Return the count of messages in the mailbox + * + * @param mailbox + * @return count + * @throws MailboxException + */ + long countMessagesInMailbox(Mailbox mailbox) + throws MailboxException; + + /** + * Return the count of unseen messages in the mailbox + * + * @param mailbox + * @return unseenCount + * @throws StorageException + */ + long countUnseenMessagesInMailbox(Mailbox mailbox) + throws MailboxException; + + + /** + * Delete the given {@link Message} + * + * @param mailbox + * @param message + * @throws StorageException + */ + void delete(Mailbox mailbox, Message message) throws MailboxException; + + /** + * Return the uid of the first unseen message. If non can be found null will get returned + * + * + * @param mailbox + * @return uid or null + * @throws StorageException + */ + Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException; + + /** + * Return a List of {@link Message} which are recent. + * The list must be ordered by the {@link Message} uid. + * + * @param mailbox + * @return recentList + * @throws StorageException + */ + List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException; + + + /** + * Add the given {@link Message} to the underlying storage. Be aware that implementation may choose to replace the uid of the given message while storing. + * So you should only depend on the returned uid. + * + * + * @param mailbox + * @param message + * @return uid + * @throws StorageException + */ + MessageMetaData add(Mailbox mailbox, Message message) throws MailboxException; + + /** + * Update flags for the given {@link MessageRange}. Only the flags may be modified after a message was saved to a mailbox. + * + * @param mailbox + * @param flags + * @param value + * @param replace + * @param set + * @return updatedFlags + * @throws MailboxException + */ + Iterator updateFlags(Mailbox mailbox, final Flags flags, final boolean value, final boolean replace, + final MessageRange set) throws MailboxException; + + /** + * Copy the given {@link Message} to a new mailbox and return the uid of the copy. Be aware that the given uid is just a suggestion for the uid of the copied + * message. Implementation may choose to use a different one, so only depend on the returned uid! + * + * @param mailbox the Mailbox to copy to + * @param original the original to copy + * @throws StorageException + */ + MessageMetaData copy(Mailbox mailbox,Message original) throws MailboxException; + + /** + * Move the given {@link Message} to a new mailbox and return the uid of the moved. Be aware that the given uid is just a suggestion for the uid of the moved + * message. Implementation may choose to use a different one, so only depend on the returned uid! + * + * @param mailbox the Mailbox to move to + * @param original the original to move + * @throws StorageException + */ + MessageMetaData move(Mailbox mailbox,Message original) throws MailboxException; + + + /** + * Return the last uid which were used for storing a Message in the {@link Mailbox} + * + * @param mailbox + * @return lastUid + * @throws MailboxException + */ + long getLastUid(Mailbox mailbox) throws MailboxException;; + + + /** + * Return the higest mod-sequence which were used for storing a Message in the {@link Mailbox} + * + * @param mailbox + * @return lastUid + * @throws MailboxException + */ + long getHighestModSeq(Mailbox mailbox) throws MailboxException; + + /** + * Specify what data needs to get filled in a {@link Message} before returning it + * + * + */ + public static enum FetchType { + + /** + * Fetch only the meta data of the {@link Message} which includes: + *

+ * {@link Message#getUid()} + * {@link Message#getModSeq()} + * {@link Message#getBodyOctets()} + * {@link Message#getFullContentOctets()} + * {@link Message#getInternalDate()} + * {@link Message#getMailboxId()} + * {@link Message#getMediaType()} + * {@link Message#getModSeq()} + * {@link Message#getSubType()} + * {@link Message#getTextualLineCount()} + *

+ */ + Metadata, + /** + * Fetch the {@link #Metadata}, {@link Property}'s and the {@link #Headers}'s for the {@link Message}. This includes: + * + *

+ * {@link Message#getProperties()} + * {@link Message#getHeaderContent()} + *

+ */ + Headers, + /** + * Fetch the {@link #Metadata} and the Body for the {@link Message}. This includes: + * + *

+ * {@link Message#getBodyContent()} + *

+ */ + Body, + + /** + * Fetch the complete {@link Message} + * + */ + Full + } + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapperFactory.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapperFactory.java new file mode 100644 index 0000000..c9aebd0 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapperFactory.java @@ -0,0 +1,34 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; + +public interface MessageMapperFactory { + + /** + * Create a {@link MessageMapper} instance of return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + MessageMapper getMessageMapper(MailboxSession session) throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ModSeqProvider.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ModSeqProvider.java new file mode 100644 index 0000000..b134a6b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ModSeqProvider.java @@ -0,0 +1,57 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Take care of provide mod-seqences for a given {@link Mailbox}. Be aware that implementations + * need to be thread-safe! + * + * + * @param + */ +public interface ModSeqProvider { + + /** + * Return the next mod-sequence which can be used for the {@link Mailbox}. + * Its important that the returned mod-sequence is higher then the last used and that the next call of this method does return a higher + * one. + * + * The first mod-seq must be >= 1 + * + * @param session + * @param mailbox + * @return modSeq + * @throws MailboxException + */ + public long nextModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException; + + /** + * Return the highest mod-sequence which were used for the {@link Mailbox} + * + * @param session + * @param mailbox + * @return highest + * @throws MailboxException + */ + public long highestModSeq(MailboxSession session, Mailbox mailbox) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/UidProvider.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/UidProvider.java new file mode 100644 index 0000000..ae7e1fd --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/UidProvider.java @@ -0,0 +1,55 @@ +/**************************************************************** + * 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; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * Take care of provide uids for a given {@link Mailbox}. Be aware that implementations + * need to be thread-safe! + * + * + * @param + */ +public interface UidProvider { + + /** + * Return the next uid which can be used while append a Message to the {@link Mailbox}. + * Its important that the returned uid is higher then the last used and that the next call of this method does return a higher + * one + * + * @param session + * @param mailbox + * @return nextUid + * @throws MailboxException + */ + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException; + + /** + * Return the last uid which were used for storing a Message in the {@link Mailbox} + * + * @param session + * @param mailbox + * @return lastUid + * @throws MailboxException + */ + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops new file mode 100644 index 0000000..1ff8dda --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model +END +Message.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java +END +Property.java +K 25 +svn:wc:ra_dav:version-url +V 123 +/repos/asf/!svn/ver/1136992/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/Property.java +END +StandardNames.java +K 25 +svn:wc:ra_dav:version-url +V 128 +/repos/asf/!svn/ver/1041812/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java +END +AbstractMessage.java +K 25 +svn:wc:ra_dav:version-url +V 130 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/AbstractMessage.java +END +Mailbox.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/Mailbox.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/entries new file mode 100644 index 0000000..7748706 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/entries @@ -0,0 +1,201 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +impl +dir + +Message.java +file + + + + +2013-09-02T02:54:39.000000Z +67f87d9adc13719d11336c556c0d3f1f +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +5525 + +Property.java +file + + + + +2013-09-02T02:54:39.000000Z +857ea9d0a3cd30bb1bd14c563d3fbed6 +2011-06-17T19:21:43.078295Z +1136992 +norman + + + + + + + + + + + + + + + + + + + + + +2417 + +StandardNames.java +file + + + + +2013-09-02T02:54:39.000000Z +76c3ba34444ecb8bcf326ae8a8037acc +2010-09-06T18:59:58.520525Z +993124 +norman + + + + + + + + + + + + + + + + + + + + + +9678 + +AbstractMessage.java +file + + + + +2013-09-02T02:54:39.000000Z +dbcf08a30ff54d5639202ab2eb1153d5 +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +3737 + +Mailbox.java +file + + + + +2013-09-02T02:54:39.000000Z +7151d16821bdd591c28c63beecf78be2 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +2588 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessage.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessage.java.svn-base new file mode 100644 index 0000000..c40ec20 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessage.java.svn-base @@ -0,0 +1,123 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; + +import javax.mail.Flags; + + + +/** + * Abstract base class for {@link Message} + * + */ +public abstract class AbstractMessage implements Message { + + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Message other) { + return (int) (getUid() - other.getUid()); + } + + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#createFlags() + */ + public final Flags createFlags() { + final Flags flags = new Flags(); + + if (isAnswered()) { + flags.add(Flags.Flag.ANSWERED); + } + if (isDeleted()) { + flags.add(Flags.Flag.DELETED); + } + if (isDraft()) { + flags.add(Flags.Flag.DRAFT); + } + if (isFlagged()) { + flags.add(Flags.Flag.FLAGGED); + } + if (isRecent()) { + flags.add(Flags.Flag.RECENT); + } + if (isSeen()) { + flags.add(Flags.Flag.SEEN); + } + String[] userFlags = createUserFlags(); + if (userFlags != null && userFlags.length > 0) { + for (int i = 0; i < userFlags.length; i++) { + flags.add(userFlags[i]); + } + } + return flags; + } + + + /** + * Return all stored user flags or null if none are stored. By default this return null as no user flags are stored + * permanent. This method SHOULD get overridden, If the implementation supports to store user flags. + * + * @return userFlags + */ + protected String[] createUserFlags() { + return null; + } + + + /** + * The number of octets contained in the body of this part. + * + * @return number of octets + */ + public long getBodyOctets() { + return getFullContentOctets() - getBodyStartOctet(); + } + + /** + * Return the start octet of the body + * + * @return startOctet + */ + protected abstract int getBodyStartOctet(); + + + + + /** + * This implementation just concat {@link #getHeaderContent()} and {@link #getBodyContent()}. + * + * Implementation should override this if they can provide a more performant solution + * + * @return content + * @throws exception + */ + public InputStream getFullContent() throws IOException { + return new SequenceInputStream(getHeaderContent(), getBodyContent()); + } + + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Mailbox.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Mailbox.java.svn-base new file mode 100644 index 0000000..de18d15 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Mailbox.java.svn-base @@ -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.store.mail.model; + +import org.apache.james.mailbox.model.MailboxACL; + +/** + * Models long term mailbox data. + */ +public interface Mailbox { + + /** + * Gets the unique mailbox ID. + * @return mailbox id + */ + Id getMailboxId(); + + /** + * Gets the current namespace for this mailbox. + * @return not null + */ + String getNamespace(); + + /** + * Sets the current namespace for this mailbox. + * @param namespace not null + */ + void setNamespace(String namespace); + + /** + * Gets the current user for this mailbox. + * @return not null + */ + String getUser(); + + /** + * Sets the current user for this mailbox. + * @param user not null + */ + void setUser(String user); + + /** + * Gets the current name for this mailbox. + * @return not null + */ + String getName(); + + /** + * Sets the current name for this mailbox. + * @param name not null + */ + void setName(String name); + + /** + * Gets the current UID VALIDITY for this mailbox. + * @return uid validity + */ + long getUidValidity(); + + + /** + * Gets the current ACL for this mailbox. + * + * @return ACL + */ + MailboxACL getACL(); + + /** + * Sets the current ACL for this mailbox. + * + * @param acl + */ + void setACL(MailboxACL acl); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Message.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Message.java.svn-base new file mode 100644 index 0000000..39aae5f --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Message.java.svn-base @@ -0,0 +1,210 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.List; + +import javax.mail.Flags; + +/** + * A MIME message, consisting of meta-data (including MIME headers) + * plus body content. In the case of multipart documents, this body content + * has internal structure described by the meta-data. + */ +public interface Message extends Comparable>{ + + Date getInternalDate(); + + /** + * Return the mailbox id of the linked mailbox + * + * @return mailboxId + */ + Id getMailboxId(); + + /** + * Return the uid + * + * @return uid + */ + long getUid(); + + /** + * Set the uid for the message. This must be called before the message is added to the store + * and must be unique / sequential. + * + * @param uid + */ + void setUid(long uid); + + + + /** + * Set the mod-sequence for the message. This must be called before the message is added to the store + * or any flags are changed. This must be unique / sequential. + * + * @param modSeq + */ + void setModSeq(long modSeq); + + /** + * Return the mod-sequence for the message + * + * @return message + */ + long getModSeq(); + + /** + * Return if it was marked as answered + * + * @return answered + */ + boolean isAnswered(); + + /** + * Return if it was mark as deleted + * + * @return deleted + */ + boolean isDeleted(); + + /** + * Return if it was mark as draft + * + * @return draft + */ + boolean isDraft(); + + /** + * Return if it was flagged + * + * @return flagged + */ + boolean isFlagged(); + + /** + * Return if it was marked as recent + * + * @return recent + */ + boolean isRecent(); + + /** + * Return if it was marked as seen + * + * @return seen + */ + boolean isSeen(); + + + /** + * Set the Flags + * + * @param flags + */ + void setFlags(Flags flags); + + /** + * Creates a new flags instance populated + * with the current flag data. + * + * @return new instance, not null + */ + Flags createFlags(); + + + /** + * Gets the body content of the document. Headers are excluded. + * + * Be aware that this method need to return a new fresh {@link InputStream} + * on every call, which basicly means it need to start at position 0 + * @return body, not null + */ + InputStream getBodyContent() throws IOException; + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + String getMediaType(); + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + String getSubType(); + + /** + * The number of octets contained in the body of this document. + * + * @return number of octets + */ + long getBodyOctets(); + + /** + * The number of octets contained in the full content of this document. + * + * @return number of octets + */ + long getFullContentOctets(); + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + public Long getTextualLineCount(); + + /** + * Gets the header as {@link InputStream}. This MUST INCLUDE the CRLF terminator + * + * Be aware that this method need to return a new fresh {@link InputStream} + * on every call + * + * @return header + * @throws IOException + */ + InputStream getHeaderContent() throws IOException; + + /** + *Returns the full raw content of the Message via an {@link InputStream}. + * + * Be aware that this method need to return a new fresh {@link InputStream} + * on every call + * + * @return content + * @throws IOException + */ + InputStream getFullContent() throws IOException; + + + /** + * Gets a read-only list of meta-data properties. + * For properties with multiple values, this list will contain + * several enteries with the same namespace and local name. + * + * @return unmodifiable list of meta-data, not null + */ + List getProperties(); +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Property.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Property.java.svn-base new file mode 100644 index 0000000..742204e --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/Property.java.svn-base @@ -0,0 +1,61 @@ +/**************************************************************** + * 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; + + +/** + *

Values a namespaced property.

+ *

+ * The full name of a namespaced property consists of + * a local part ({@link #getLocalName()}) and a namespace ({@link #getNamespace()()}). + * This is similar - in concept - the local part and namespace of a QName + * in XML. + *

+ * Conventionally, the namespace + * is an URI + * and the name is simple, leading to a natural mapping into + * RDF. + * For example - namespace "http://james.apache.org/rfc2045", + * name "Content-Transfer-Encoding", value "BASE64" mapping to + * predicate "http://james.apache.org/rfc2045#Content-Transfer-Encoding", + * object "BASE64". + *

+ */ +public interface Property { + + /** + * Gets the namespace for the name. + * @return not null + */ + String getNamespace(); + + /** + * Gets the local part of the name of the property. + * @return not null + */ + String getLocalName(); + + /** + * Gets the value for this property. + * @return not null + */ + String getValue(); + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/StandardNames.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/StandardNames.java.svn-base new file mode 100644 index 0000000..a50eaf5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/StandardNames.java.svn-base @@ -0,0 +1,223 @@ +/**************************************************************** + * 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; + +/** + *

Conventional meta-data names for standard properties.

+ *

Conventions

+ *

+ * The domain containing the namespace should be controlled by the definer. + * So, all properties defined here begin with http://james.apache.org. + * This is a simple way to prevent namespace collisions with expansion terms. + *

+ * Local names are lower case. + *

+ * Namespaces derived from the RFC in which the term is + * defined are preferred. So, terms defined in + * RFC2045 + * would be based in the http://james.apache.org/rfc2045 space. + *

    + *
  • + * For unstructured fields this base is used directly as the namespace. + *
  • + * For structure fields, the capitalised name (as used in the RFC text) + * is suffixed to this base. + *
      + *
    • + * Elements are named from the grammar. + *
    • + *
    • + * Parameters of structured fields use a namespace rooted in this + * base suffixed with params. This namespace should contains only those + * parameters. The name of each property is the name of the parameter, converted + * to lower case. So, by iterating through all properties in the sufffixed namespace, + * every parameter can be named and value. + *
    • + *
    + *
  • + *
+ *

Examples

+ *
Content-Type
+ * Content-Type is defined in RFC2045. + * So, the namespaces are based in http://james.apache.org/rfc2045/. + * It is a structure field with parameters. It's direct properties are spaced in + * http://james.apache.org/rfc2045/Content-Type/. + * It's parameters are spaced in + * http://james.apache.org/rfc2045/Content-Type/params. + *

+ * So, for Content-Type: text/plain ; charset=us-ascii: + *

+ * + * + * + * + * + *
NamespaceNameValue
http://james.apache.org/rfc2045/Content-Type/typetext
http://james.apache.org/rfc2045/Content-Type/subtypeplain
http://james.apache.org/rfc2045/Content-Type/paramscharsetus-ascii
+ */ +public class StandardNames { + /** + * Namespace for MIME. + */ + public static final String NAMESPACE_RFC_2045 = "http://james.apache.org/rfc2045"; + + /** + * Namespace for MIME. + */ + public static final String MIME_CONTENT_TYPE_SPACE = NAMESPACE_RFC_2045 + "/Content-Type"; + + /** + * Namespace for MIME Content-Type + * parameter space. + */ + public static final String MIME_CONTENT_TYPE_PARAMETER_SPACE = MIME_CONTENT_TYPE_SPACE + "/params"; + + /** + * Namespace for MIME Content-Type + * "charset" parameter. + */ + public static final String MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME = "charset"; + + /** + * Namespace for MIME Content-Type + * "boundary" parameter. + */ + public static final String MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME = "boundary"; + + /** + * Namespace for MIME mime type properties. + * A distinct namespace is required to distinguish these properties from the Content-Type + * parameters. + * @see #NAMESPACE_RFC_2045 + */ + public static final String MIME_MIME_TYPE_SPACE = MIME_CONTENT_TYPE_SPACE; + + /** + * Local name for MIME media type property + */ + public static final String MIME_MEDIA_TYPE_NAME = "type"; + + /** + * Local name for MIME sub type property + */ + public static final String MIME_SUB_TYPE_NAME = "subtype"; + + /** + * Namespace for MIME Content-ID property. + * @see #MIME_CONTENT_TYPE_SPACE + */ + public static final String MIME_CONTENT_ID_SPACE = NAMESPACE_RFC_2045; + + /** + * Local name for MIME Content-ID property + */ + public static final String MIME_CONTENT_ID_NAME = "Content-ID"; + + /** + * Namespace for MIME Content-Description property. + * @see #NAMESPACE_RFC_2045 + */ + public static final String MIME_CONTENT_DESCRIPTION_SPACE = NAMESPACE_RFC_2045; + + /** + * Local name for MIME Content-Description property + */ + public static final String MIME_CONTENT_DESCRIPTION_NAME = "Content-Description"; + + /** + * Namespace for MIME Content-Transfer-Encoding property. + * @see #NAMESPACE_RFC_2045 + */ + public static final String MIME_CONTENT_TRANSFER_ENCODING_SPACE = NAMESPACE_RFC_2045; + + /** + * Local name for MIME Content-Transfer-Encoding property + */ + public static final String MIME_CONTENT_TRANSFER_ENCODING_NAME = "Content-Transfer-Encoding"; + + /** + * Namespace for MIME. + */ + public static final String NAMESPACE_RFC_2557 = "http://james.apache.org/rfc2557"; + + /** + * Namespace for RFC2557 Content-Location property. + * @see #NAMESPACE_RFC_2557 + */ + public static final String MIME_CONTENT_LOCATION_SPACE = NAMESPACE_RFC_2557; + + /** + * Local name for RFC2557 Content-Location property + */ + public static final String MIME_CONTENT_LOCATION_NAME = "Content-Location"; + + /** + * Namespace for RFC 1864 - The Content-MD5 Header Field. + */ + public static final String NAMESPACE_RFC_1864 = "http://james.apache.org/rfc1864"; + + /** + * Namespace for RFC1864 Content-MD5 property. + * @see #NAMESPACE_RFC_1864 + */ + public static final String MIME_CONTENT_MD5_SPACE = NAMESPACE_RFC_1864; + + /** + * Local name for RFC1864 Content-MD5 property + */ + public static final String MIME_CONTENT_MD5_NAME = "Content-MD5"; + + /** + * Namespace for RFC 1766 - Tags for the Identification of Languages. + */ + public static final String NAMESPACE_RFC_1766 = "http://james.apache.org/rfc1766"; + + /** + * Namespace for RFC1766 Content-Language property. + * @see #NAMESPACE_RFC_1766 + */ + public static final String MIME_CONTENT_LANGUAGE_SPACE = NAMESPACE_RFC_1766; + + /** + * Local name for RFC1864 Content-Language property + */ + public static final String MIME_CONTENT_LANGUAGE_NAME = "Content-Language"; + + /** + * Namespace for RFC 2183- + * Communicating Presentation Information in Internet Messages. + */ + public static final String NAMESPACE_RFC_2183 = "http://james.apache.org/rfc2183"; + + /** + * Namespace for RFC2183 Content-Disposition property. + * @see #NAMESPACE_RFC_2183 + */ + public static final String MIME_CONTENT_DISPOSITION_SPACE = NAMESPACE_RFC_2183 + "Content-Disposition"; + + /** + * Local name for RFC2183 Content-Disposition property + */ + public static final String MIME_CONTENT_DISPOSITION_TYPE_NAME = "disposition-type"; + + /** + * Namespace for RFC2183 Content-Disposition property. + * @see #NAMESPACE_RFC_2183 + */ + public static final String MIME_CONTENT_DISPOSITION_PARAMETER_SPACE = MIME_CONTENT_DISPOSITION_SPACE + "/params"; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/AbstractMessage.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/AbstractMessage.java new file mode 100644 index 0000000..c40ec20 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/AbstractMessage.java @@ -0,0 +1,123 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; + +import javax.mail.Flags; + + + +/** + * Abstract base class for {@link Message} + * + */ +public abstract class AbstractMessage implements Message { + + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Message other) { + return (int) (getUid() - other.getUid()); + } + + + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#createFlags() + */ + public final Flags createFlags() { + final Flags flags = new Flags(); + + if (isAnswered()) { + flags.add(Flags.Flag.ANSWERED); + } + if (isDeleted()) { + flags.add(Flags.Flag.DELETED); + } + if (isDraft()) { + flags.add(Flags.Flag.DRAFT); + } + if (isFlagged()) { + flags.add(Flags.Flag.FLAGGED); + } + if (isRecent()) { + flags.add(Flags.Flag.RECENT); + } + if (isSeen()) { + flags.add(Flags.Flag.SEEN); + } + String[] userFlags = createUserFlags(); + if (userFlags != null && userFlags.length > 0) { + for (int i = 0; i < userFlags.length; i++) { + flags.add(userFlags[i]); + } + } + return flags; + } + + + /** + * Return all stored user flags or null if none are stored. By default this return null as no user flags are stored + * permanent. This method SHOULD get overridden, If the implementation supports to store user flags. + * + * @return userFlags + */ + protected String[] createUserFlags() { + return null; + } + + + /** + * The number of octets contained in the body of this part. + * + * @return number of octets + */ + public long getBodyOctets() { + return getFullContentOctets() - getBodyStartOctet(); + } + + /** + * Return the start octet of the body + * + * @return startOctet + */ + protected abstract int getBodyStartOctet(); + + + + + /** + * This implementation just concat {@link #getHeaderContent()} and {@link #getBodyContent()}. + * + * Implementation should override this if they can provide a more performant solution + * + * @return content + * @throws exception + */ + public InputStream getFullContent() throws IOException { + return new SequenceInputStream(getHeaderContent(), getBodyContent()); + } + + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Mailbox.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Mailbox.java new file mode 100644 index 0000000..de18d15 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Mailbox.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.store.mail.model; + +import org.apache.james.mailbox.model.MailboxACL; + +/** + * Models long term mailbox data. + */ +public interface Mailbox { + + /** + * Gets the unique mailbox ID. + * @return mailbox id + */ + Id getMailboxId(); + + /** + * Gets the current namespace for this mailbox. + * @return not null + */ + String getNamespace(); + + /** + * Sets the current namespace for this mailbox. + * @param namespace not null + */ + void setNamespace(String namespace); + + /** + * Gets the current user for this mailbox. + * @return not null + */ + String getUser(); + + /** + * Sets the current user for this mailbox. + * @param user not null + */ + void setUser(String user); + + /** + * Gets the current name for this mailbox. + * @return not null + */ + String getName(); + + /** + * Sets the current name for this mailbox. + * @param name not null + */ + void setName(String name); + + /** + * Gets the current UID VALIDITY for this mailbox. + * @return uid validity + */ + long getUidValidity(); + + + /** + * Gets the current ACL for this mailbox. + * + * @return ACL + */ + MailboxACL getACL(); + + /** + * Sets the current ACL for this mailbox. + * + * @param acl + */ + void setACL(MailboxACL acl); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java new file mode 100644 index 0000000..39aae5f --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java @@ -0,0 +1,210 @@ +/**************************************************************** + * 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 java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.List; + +import javax.mail.Flags; + +/** + * A MIME message, consisting of meta-data (including MIME headers) + * plus body content. In the case of multipart documents, this body content + * has internal structure described by the meta-data. + */ +public interface Message extends Comparable>{ + + Date getInternalDate(); + + /** + * Return the mailbox id of the linked mailbox + * + * @return mailboxId + */ + Id getMailboxId(); + + /** + * Return the uid + * + * @return uid + */ + long getUid(); + + /** + * Set the uid for the message. This must be called before the message is added to the store + * and must be unique / sequential. + * + * @param uid + */ + void setUid(long uid); + + + + /** + * Set the mod-sequence for the message. This must be called before the message is added to the store + * or any flags are changed. This must be unique / sequential. + * + * @param modSeq + */ + void setModSeq(long modSeq); + + /** + * Return the mod-sequence for the message + * + * @return message + */ + long getModSeq(); + + /** + * Return if it was marked as answered + * + * @return answered + */ + boolean isAnswered(); + + /** + * Return if it was mark as deleted + * + * @return deleted + */ + boolean isDeleted(); + + /** + * Return if it was mark as draft + * + * @return draft + */ + boolean isDraft(); + + /** + * Return if it was flagged + * + * @return flagged + */ + boolean isFlagged(); + + /** + * Return if it was marked as recent + * + * @return recent + */ + boolean isRecent(); + + /** + * Return if it was marked as seen + * + * @return seen + */ + boolean isSeen(); + + + /** + * Set the Flags + * + * @param flags + */ + void setFlags(Flags flags); + + /** + * Creates a new flags instance populated + * with the current flag data. + * + * @return new instance, not null + */ + Flags createFlags(); + + + /** + * Gets the body content of the document. Headers are excluded. + * + * Be aware that this method need to return a new fresh {@link InputStream} + * on every call, which basicly means it need to start at position 0 + * @return body, not null + */ + InputStream getBodyContent() throws IOException; + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + String getMediaType(); + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + String getSubType(); + + /** + * The number of octets contained in the body of this document. + * + * @return number of octets + */ + long getBodyOctets(); + + /** + * The number of octets contained in the full content of this document. + * + * @return number of octets + */ + long getFullContentOctets(); + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + public Long getTextualLineCount(); + + /** + * Gets the header as {@link InputStream}. This MUST INCLUDE the CRLF terminator + * + * Be aware that this method need to return a new fresh {@link InputStream} + * on every call + * + * @return header + * @throws IOException + */ + InputStream getHeaderContent() throws IOException; + + /** + *Returns the full raw content of the Message via an {@link InputStream}. + * + * Be aware that this method need to return a new fresh {@link InputStream} + * on every call + * + * @return content + * @throws IOException + */ + InputStream getFullContent() throws IOException; + + + /** + * Gets a read-only list of meta-data properties. + * For properties with multiple values, this list will contain + * several enteries with the same namespace and local name. + * + * @return unmodifiable list of meta-data, not null + */ + List getProperties(); +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Property.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Property.java new file mode 100644 index 0000000..742204e --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Property.java @@ -0,0 +1,61 @@ +/**************************************************************** + * 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; + + +/** + *

Values a namespaced property.

+ *

+ * The full name of a namespaced property consists of + * a local part ({@link #getLocalName()}) and a namespace ({@link #getNamespace()()}). + * This is similar - in concept - the local part and namespace of a QName + * in XML. + *

+ * Conventionally, the namespace + * is an URI + * and the name is simple, leading to a natural mapping into + * RDF. + * For example - namespace "http://james.apache.org/rfc2045", + * name "Content-Transfer-Encoding", value "BASE64" mapping to + * predicate "http://james.apache.org/rfc2045#Content-Transfer-Encoding", + * object "BASE64". + *

+ */ +public interface Property { + + /** + * Gets the namespace for the name. + * @return not null + */ + String getNamespace(); + + /** + * Gets the local part of the name of the property. + * @return not null + */ + String getLocalName(); + + /** + * Gets the value for this property. + * @return not null + */ + String getValue(); + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java new file mode 100644 index 0000000..a50eaf5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java @@ -0,0 +1,223 @@ +/**************************************************************** + * 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; + +/** + *

Conventional meta-data names for standard properties.

+ *

Conventions

+ *

+ * The domain containing the namespace should be controlled by the definer. + * So, all properties defined here begin with http://james.apache.org. + * This is a simple way to prevent namespace collisions with expansion terms. + *

+ * Local names are lower case. + *

+ * Namespaces derived from the RFC in which the term is + * defined are preferred. So, terms defined in + * RFC2045 + * would be based in the http://james.apache.org/rfc2045 space. + *

    + *
  • + * For unstructured fields this base is used directly as the namespace. + *
  • + * For structure fields, the capitalised name (as used in the RFC text) + * is suffixed to this base. + *
      + *
    • + * Elements are named from the grammar. + *
    • + *
    • + * Parameters of structured fields use a namespace rooted in this + * base suffixed with params. This namespace should contains only those + * parameters. The name of each property is the name of the parameter, converted + * to lower case. So, by iterating through all properties in the sufffixed namespace, + * every parameter can be named and value. + *
    • + *
    + *
  • + *
+ *

Examples

+ *
Content-Type
+ * Content-Type is defined in RFC2045. + * So, the namespaces are based in http://james.apache.org/rfc2045/. + * It is a structure field with parameters. It's direct properties are spaced in + * http://james.apache.org/rfc2045/Content-Type/. + * It's parameters are spaced in + * http://james.apache.org/rfc2045/Content-Type/params. + *

+ * So, for Content-Type: text/plain ; charset=us-ascii: + *

+ * + * + * + * + * + *
NamespaceNameValue
http://james.apache.org/rfc2045/Content-Type/typetext
http://james.apache.org/rfc2045/Content-Type/subtypeplain
http://james.apache.org/rfc2045/Content-Type/paramscharsetus-ascii
+ */ +public class StandardNames { + /** + * Namespace for MIME. + */ + public static final String NAMESPACE_RFC_2045 = "http://james.apache.org/rfc2045"; + + /** + * Namespace for MIME. + */ + public static final String MIME_CONTENT_TYPE_SPACE = NAMESPACE_RFC_2045 + "/Content-Type"; + + /** + * Namespace for MIME Content-Type + * parameter space. + */ + public static final String MIME_CONTENT_TYPE_PARAMETER_SPACE = MIME_CONTENT_TYPE_SPACE + "/params"; + + /** + * Namespace for MIME Content-Type + * "charset" parameter. + */ + public static final String MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME = "charset"; + + /** + * Namespace for MIME Content-Type + * "boundary" parameter. + */ + public static final String MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME = "boundary"; + + /** + * Namespace for MIME mime type properties. + * A distinct namespace is required to distinguish these properties from the Content-Type + * parameters. + * @see #NAMESPACE_RFC_2045 + */ + public static final String MIME_MIME_TYPE_SPACE = MIME_CONTENT_TYPE_SPACE; + + /** + * Local name for MIME media type property + */ + public static final String MIME_MEDIA_TYPE_NAME = "type"; + + /** + * Local name for MIME sub type property + */ + public static final String MIME_SUB_TYPE_NAME = "subtype"; + + /** + * Namespace for MIME Content-ID property. + * @see #MIME_CONTENT_TYPE_SPACE + */ + public static final String MIME_CONTENT_ID_SPACE = NAMESPACE_RFC_2045; + + /** + * Local name for MIME Content-ID property + */ + public static final String MIME_CONTENT_ID_NAME = "Content-ID"; + + /** + * Namespace for MIME Content-Description property. + * @see #NAMESPACE_RFC_2045 + */ + public static final String MIME_CONTENT_DESCRIPTION_SPACE = NAMESPACE_RFC_2045; + + /** + * Local name for MIME Content-Description property + */ + public static final String MIME_CONTENT_DESCRIPTION_NAME = "Content-Description"; + + /** + * Namespace for MIME Content-Transfer-Encoding property. + * @see #NAMESPACE_RFC_2045 + */ + public static final String MIME_CONTENT_TRANSFER_ENCODING_SPACE = NAMESPACE_RFC_2045; + + /** + * Local name for MIME Content-Transfer-Encoding property + */ + public static final String MIME_CONTENT_TRANSFER_ENCODING_NAME = "Content-Transfer-Encoding"; + + /** + * Namespace for MIME. + */ + public static final String NAMESPACE_RFC_2557 = "http://james.apache.org/rfc2557"; + + /** + * Namespace for RFC2557 Content-Location property. + * @see #NAMESPACE_RFC_2557 + */ + public static final String MIME_CONTENT_LOCATION_SPACE = NAMESPACE_RFC_2557; + + /** + * Local name for RFC2557 Content-Location property + */ + public static final String MIME_CONTENT_LOCATION_NAME = "Content-Location"; + + /** + * Namespace for RFC 1864 - The Content-MD5 Header Field. + */ + public static final String NAMESPACE_RFC_1864 = "http://james.apache.org/rfc1864"; + + /** + * Namespace for RFC1864 Content-MD5 property. + * @see #NAMESPACE_RFC_1864 + */ + public static final String MIME_CONTENT_MD5_SPACE = NAMESPACE_RFC_1864; + + /** + * Local name for RFC1864 Content-MD5 property + */ + public static final String MIME_CONTENT_MD5_NAME = "Content-MD5"; + + /** + * Namespace for RFC 1766 - Tags for the Identification of Languages. + */ + public static final String NAMESPACE_RFC_1766 = "http://james.apache.org/rfc1766"; + + /** + * Namespace for RFC1766 Content-Language property. + * @see #NAMESPACE_RFC_1766 + */ + public static final String MIME_CONTENT_LANGUAGE_SPACE = NAMESPACE_RFC_1766; + + /** + * Local name for RFC1864 Content-Language property + */ + public static final String MIME_CONTENT_LANGUAGE_NAME = "Content-Language"; + + /** + * Namespace for RFC 2183- + * Communicating Presentation Information in Internet Messages. + */ + public static final String NAMESPACE_RFC_2183 = "http://james.apache.org/rfc2183"; + + /** + * Namespace for RFC2183 Content-Disposition property. + * @see #NAMESPACE_RFC_2183 + */ + public static final String MIME_CONTENT_DISPOSITION_SPACE = NAMESPACE_RFC_2183 + "Content-Disposition"; + + /** + * Local name for RFC2183 Content-Disposition property + */ + public static final String MIME_CONTENT_DISPOSITION_TYPE_NAME = "disposition-type"; + + /** + * Namespace for RFC2183 Content-Disposition property. + * @see #NAMESPACE_RFC_2183 + */ + public static final String MIME_CONTENT_DISPOSITION_PARAMETER_SPACE = MIME_CONTENT_DISPOSITION_SPACE + "/params"; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops new file mode 100644 index 0000000..1dcb76c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1347537/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl +END +SimpleMailbox.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java +END +PropertyBuilder.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java +END +SimpleMessage.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1347537/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java +END +SimpleProperty.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleProperty.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries new file mode 100644 index 0000000..321b0e5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries @@ -0,0 +1,164 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl +http://svn.apache.org/repos/asf + + + +2012-06-07T08:50:19.204572Z +1347537 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SimpleMailbox.java +file + + + + +2013-09-02T02:54:39.000000Z +3d5d2c4e73e7229d54f054f0a9d30e45 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +5083 + +PropertyBuilder.java +file + + + + +2013-09-02T02:54:39.000000Z +8c96724204090a709d7ef5386b768462 +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +17133 + +SimpleMessage.java +file + + + + +2013-09-02T02:54:39.000000Z +ea427c849c163b13dbcfdef4f21881d0 +2012-06-07T08:50:19.204572Z +1347537 +eric + + + + + + + + + + + + + + + + + + + + + +7957 + +SimpleProperty.java +file + + + + +2013-09-02T02:54:39.000000Z +2e6780aa213556bbf0f6d9146fb89874 +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +3625 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/PropertyBuilder.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/PropertyBuilder.java.svn-base new file mode 100644 index 0000000..bc74499 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/PropertyBuilder.java.svn-base @@ -0,0 +1,478 @@ +/**************************************************************** + * 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.impl; + +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DESCRIPTION_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DESCRIPTION_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DISPOSITION_PARAMETER_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DISPOSITION_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DISPOSITION_TYPE_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_ID_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_ID_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LANGUAGE_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LANGUAGE_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LOCATION_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LOCATION_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_MD5_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_MD5_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MEDIA_TYPE_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MIME_TYPE_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_SUB_TYPE_NAME; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.james.mailbox.store.mail.model.Property; + +/** + * Builds properties + */ +public class PropertyBuilder { + + private static final int INITIAL_CAPACITY = 32; + + private Long textualLineCount; + private final List properties; + + public PropertyBuilder(final List props) { + textualLineCount = null; + properties = new ArrayList(props.size()); + for (final Property property:props) { + properties.add(new SimpleProperty(property)); + } + } + + public PropertyBuilder() { + textualLineCount = null; + properties = new ArrayList(INITIAL_CAPACITY); + } + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + public Long getTextualLineCount() { + return textualLineCount; + } + + /** + * Aets the number of CRLF in a textual document. + * @param textualLineCount count when document is textual, + * null otherwise + */ + public void setTextualLineCount(Long textualLineCount) { + this.textualLineCount = textualLineCount; + } + + /** + * Gets the first value with the given name. + * Used to retrieve values with a single value. + * @param namespace not null + * @param localName not null + * @return value, + * or null when no property has the given name and namespace + */ + public String getFirstValue(final String namespace, final String localName) { + String result = null; + for (SimpleProperty property: properties) { + if (property.isNamed(namespace, localName)) { + result = property.getValue(); + break; + } + } + return result; + } + + /** + * Lists all values for a property. + * @param namespace not null + * @param localName not null + * @return not null + */ + public List getValues(final String namespace, final String localName) { + List results = new ArrayList(); + for (SimpleProperty property: properties) { + if (property.isNamed(namespace, localName)) { + results.add(property.getValue()); + } + } + return results; + } + + /** + * Sets a property allowing only a single value. + * @param namespace not null + * @param localName not null + * @param value null to remove property + */ + public void setProperty(final String namespace, final String localName, final String value) + { + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isNamed(namespace, localName)) { + it.remove(); + } + } + + if (value != null) { + properties.add(new SimpleProperty(namespace, localName, value)); + } + } + + /** + * Sets a multiple valued property. + * @param namespace not null + * @param localName not null + * @param values null to remove property + */ + public void setProperty(final String namespace, final String localName, final List values) + { + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isNamed(namespace, localName)) { + it.remove(); + } + } + if (values !=null) { + for(final String value:values) { + properties.add(new SimpleProperty(namespace, localName, value)); + } + } + } + + /** + * Maps properties in the given namespace. + * @param namespace not null + * @return values indexed by local name + */ + public SortedMap getProperties(final String namespace) { + final SortedMap parameters = new TreeMap(); + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isInSpace(namespace)) { + parameters.put(property.getLocalName(), property.getValue()); + } + } + return parameters; + } + + /** + * Sets properties in the given namespace from the map. + * Existing properties in the namespace will be removed. + * All local names will be converted to lower case. + * @param namespace not null + * @param valuesByLocalName not null + */ + public void setProperties(final String namespace, final Map valuesByLocalName) { + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isInSpace(namespace)) { + it.remove(); + } + } + for (final Map.Entry valueByLocalName:valuesByLocalName.entrySet()) { + properties.add(new SimpleProperty(namespace, valueByLocalName.getKey().toLowerCase(), valueByLocalName.getValue())); + } + } + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + public String getMediaType() { + return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME); + } + + /** + * Sets the top level MIME content media type. + * + * @param value top level MIME content media type, + * or null to remove + */ + public void setMediaType(String value) { + setProperty(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME, value); + } + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + public String getSubType() { + return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_SUB_TYPE_NAME); + } + + /** + * Sets the MIME content subtype. + * + * @param value the MIME content subtype, + * or null to remove + */ + public void setSubType(String value) { + setProperty(MIME_MIME_TYPE_SPACE, MIME_SUB_TYPE_NAME, value); + } + + /** + * Gets the MIME Content-ID. + * + * @return the MIME content subtype, or null if default + */ + public String getContentID() { + return getFirstValue(MIME_CONTENT_ID_SPACE, MIME_CONTENT_ID_NAME); + } + + /** + * Sets MIME Content-ID. + * + * @param value the MIME content subtype, + * or null to remove + */ + public void setContentID(String value) { + setProperty(MIME_CONTENT_ID_SPACE, MIME_CONTENT_ID_NAME, value); + } + + /** + * Gets the MIME Content-Description. + * + * @return the MIME Content-Description, + * or null if this meta data is not present + */ + public String getContentDescription() { + return getFirstValue(MIME_CONTENT_DESCRIPTION_SPACE, MIME_CONTENT_DESCRIPTION_NAME); + } + + /** + * Sets MIME Content-Description. + * + * @param value the MIME Content-Description + * or null to remove + */ + public void setContentDescription(String value) { + setProperty(MIME_CONTENT_DESCRIPTION_SPACE, MIME_CONTENT_DESCRIPTION_NAME, value); + } + + /** + * Gets the MIME Content-Transfer-Encoding. + * + * @return the MIME Content-Transfer-Encoding, + * or null if this meta data is not present + */ + public String getContentTransferEncoding() { + return getFirstValue(MIME_CONTENT_TRANSFER_ENCODING_SPACE, MIME_CONTENT_TRANSFER_ENCODING_NAME); + } + + /** + * Sets MIME Content-Transfer-Encoding. + * + * @param value the MIME Content-Transfer-Encoding + * or null to remove + */ + public void setContentTransferEncoding(String value) { + setProperty(MIME_CONTENT_TRANSFER_ENCODING_SPACE, MIME_CONTENT_TRANSFER_ENCODING_NAME, value); + } + + /** + * Gets the RFC2557 Content-Location. + * + * @return the RFC2557 Content-Location, + * or null if this meta data is not present + */ + public String getContentLocation() { + return getFirstValue(MIME_CONTENT_LOCATION_SPACE, MIME_CONTENT_LOCATION_NAME); + } + + /** + * Sets RFC2557 Content-Location. + * + * @param value the RFC2557 Content-Location + * or null to remove + */ + public void setContentLocation(String value) { + setProperty(MIME_CONTENT_LOCATION_SPACE, MIME_CONTENT_LOCATION_NAME, value); + } + + /** + * Sets RFC2183 Content-Disposition disposition-type. + * + * @param value the RFC2183 Content-Disposition + * or null to remove + */ + public void setContentDispositionType(String value) { + setProperty(MIME_CONTENT_DISPOSITION_SPACE, MIME_CONTENT_DISPOSITION_TYPE_NAME, value); + } + + /** + * Gets the RFC2183 Content-Disposition disposition-type. + * + * @return the RFC2183 Content-Disposition, + * or null if this meta data is not present + */ + public String getContentDispositionType() { + return getFirstValue(MIME_CONTENT_DISPOSITION_SPACE, MIME_CONTENT_DISPOSITION_TYPE_NAME); + } + + /** + * Gets RFC2183 Content-Disposition parameters. + * @return parameter values indexed by lower case local names + */ + public Map getContentDispositionParameters() { + return getProperties(MIME_CONTENT_DISPOSITION_PARAMETER_SPACE); + } + + /** + * Sets Content-Disposition parameters. + * Parameter names will be normalised to lower case. + * @param valuesByParameterName values indexed by parameter name + */ + public void setContentDispositionParameters(final Map valuesByParameterName) { + setProperties(MIME_CONTENT_DISPOSITION_PARAMETER_SPACE, valuesByParameterName); + } + + /** + * Gets RFC2045 Content-Type parameters. + * @return parameter values indexed by lower case local names + */ + public Map getContentTypeParameters() { + return getProperties(MIME_CONTENT_TYPE_PARAMETER_SPACE); + } + + /** + * Sets Content-Type parameters. + * Parameter names will be normalised to lower case. + * @param valuesByParameterName values indexed by parameter name + */ + public void setContentTypeParameters(final Map valuesByParameterName) { + setProperties(MIME_CONTENT_TYPE_PARAMETER_SPACE, valuesByParameterName); + } + + /** + * Gets the RFC1864 Content-MD5. + * + * @return the RFC1864 Content-MD5, + * or null if this meta data is not present + */ + public String getContentMD5() { + return getFirstValue(MIME_CONTENT_MD5_SPACE, MIME_CONTENT_MD5_NAME); + } + + /** + * Sets RFC1864 Content-MD5. + * + * @param value the RFC1864 Content-MD5 + * or null to remove + */ + public void setContentMD5(String value) { + setProperty(MIME_CONTENT_MD5_SPACE, MIME_CONTENT_MD5_NAME, value); + } + + /** + * Gets the RFC2045 Content-Type "charset" parameter. + * + * @return the RFC2045 Content-Type "charset" parameter, + * or null if this meta data is not present + */ + public String getCharset() { + return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME); + } + + /** + * Sets RFC2045 Content-Type "charset" parameter. + * + * @param value the RFC2045 Content-Type "charset" parameter + * or null to remove + */ + public void setCharset(String value) { + setProperty(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME, value); + } + + /** + * Gets the RFC2045 Content-Type "boundary" parameter. + * + * @return the RFC2045 Content-Type "boundary" parameter, + * or null if this meta data is not present + */ + public String getBoundary() { + return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME); + } + + /** + * Sets RFC2045 Content-Type "boundary" parameter. + * + * @param value the RFC2045 Content-Type "boundary" parameter + * or null to remove + */ + public void setBoundary(String value) { + setProperty(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME, value); + } + + /** + * Gets the RFC1766 Content-Language. + * + * @return list of parsed langauge tags from the RFC1766 Content-Language, + * possibly empty + */ + public List getContentLanguage() { + return getValues(MIME_CONTENT_LANGUAGE_SPACE, MIME_CONTENT_LANGUAGE_NAME); + } + + /** + * Sets RFC1766 Content-Language. + * + * @param values list of parsed language tags from the RFC1766 Content-Language, + * possibly empty + */ + public void setContentLanguage(List values) { + setProperty(MIME_CONTENT_LANGUAGE_SPACE, MIME_CONTENT_LANGUAGE_NAME, values); + } + + /** + * Builds a list of properties. + * @return not null + */ + public List toProperties() { + final List results = new ArrayList(properties); + return results; + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() + { + return "PropertyBuilder ( " + + " textualLineCount = " + this.textualLineCount + + " properties = " + this.properties + + " )"; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMailbox.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMailbox.java.svn-base new file mode 100644 index 0000000..047e789 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMailbox.java.svn-base @@ -0,0 +1,170 @@ +/**************************************************************** + * 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.impl; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class SimpleMailbox implements Mailbox { + + private Id id = null; + private String namespace; + private String user; + private String name; + private long uidValidity; + private MailboxACL acl = SimpleMailboxACL.EMPTY; + + public SimpleMailbox(MailboxPath path, long uidValidity) { + this.namespace = path.getNamespace(); + this.user = path.getUser(); + this.name = path.getName(); + this.uidValidity = uidValidity; + } + + public SimpleMailbox(Mailbox mailbox) { + this.id = mailbox.getMailboxId(); + this.namespace = mailbox.getNamespace(); + this.user = mailbox.getUser(); + this.name = mailbox.getName(); + this.uidValidity = mailbox.getUidValidity(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + public Id getMailboxId() { + return id; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + public String getNamespace() { + return namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + public String getUser() { + return user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + public void setUser(String user) { + this.user = user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + public String getName() { + return name; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String + * ) + */ + public void setName(String name) { + this.name = name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + public long getUidValidity() { + return uidValidity; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @SuppressWarnings("unchecked") + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof SimpleMailbox) { + if (id != null) { + if (id.equals(((SimpleMailbox) obj).getMailboxId())) + return true; + } else { + if (((SimpleMailbox) obj).getMailboxId() == null) + return true; + } + } + return false; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + namespace.hashCode(); + result = PRIME * result + user.hashCode(); + result = PRIME * result + name.hashCode(); + return result; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return namespace + ":" + user + ":" + name; + } + + + public void setMailboxId(Id id) { + this.id = id; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + return acl; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + this.acl = acl; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessage.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessage.java.svn-base new file mode 100644 index 0000000..b78543c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessage.java.svn-base @@ -0,0 +1,250 @@ +/**************************************************************** + * 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.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; + +public class SimpleMessage extends AbstractMessage { + + private long uid; + private final Id mailboxId; + private long size; + private boolean answered; + private boolean deleted; + private boolean draft; + private boolean flagged; + private boolean recent; + private boolean seen; + private Date internalDate; + private final String subType; + private List properties; + private final String mediaType; + private Long lineCount; + private int bodyStartOctet; + private long modSeq; + private SharedInputStream content; + + public SimpleMessage(Date internalDate, int size, int bodyStartOctet, + SharedInputStream content, Flags flags, + PropertyBuilder propertyBuilder, final Id mailboxId) { + this.content = content; + + this.size = size; + this.bodyStartOctet = bodyStartOctet; + setFlags(flags); + lineCount = propertyBuilder.getTextualLineCount(); + this.internalDate = internalDate; + this.mailboxId = mailboxId; + this.properties = propertyBuilder.toProperties(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + } + + public SimpleMessage(Mailbox mailbox, Message original) + throws MailboxException { + this.internalDate = original.getInternalDate(); + this.size = original.getFullContentOctets(); + this.mailboxId = mailbox.getMailboxId(); + setFlags(original.createFlags()); + try { + this.content = new SharedByteArrayInputStream( + IOUtils.toByteArray(original.getFullContent())); + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } + + this.bodyStartOctet = (int) (original.getFullContentOctets() - original + .getBodyOctets()); + + PropertyBuilder pBuilder = new PropertyBuilder(original.getProperties()); + this.lineCount = original.getTextualLineCount(); + this.mediaType = original.getMediaType(); + this.subType = original.getSubType(); + final List properties = pBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + for (final Property property : properties) { + this.properties.add(new SimpleProperty(property)); + } + } + + public Date getInternalDate() { + return internalDate; + } + + public Id getMailboxId() { + return mailboxId; + } + + public long getUid() { + return uid; + } + + public boolean isAnswered() { + return answered; + } + + public boolean isDeleted() { + return deleted; + } + + public boolean isDraft() { + return draft; + } + + public boolean isFlagged() { + return flagged; + } + + public boolean isRecent() { + return recent; + } + + public boolean isSeen() { + return seen; + } + + public synchronized void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + } + + public InputStream getBodyContent() throws IOException { + return content.newStream(getBodyStartOctet(), -1); + } + + public long getFullContentOctets() { + return size; + } + + public String getMediaType() { + return mediaType; + } + + public List getProperties() { + return properties; + } + + public String getSubType() { + return subType; + } + + public Long getTextualLineCount() { + return lineCount; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SimpleMessage other = (SimpleMessage) obj; + if (uid != other.uid) + return false; + return true; + } + + /** + * Representation suitable for logging and debugging. + * + * @return a String representation of this object. + */ + public String toString() { + return super.toString() + "[" + "uid = " + this.uid + " " + + "mailboxId = " + this.mailboxId + " " + "size = " + this.size + + " " + "answered = " + this.answered + " " + "deleted = " + + this.deleted + " " + "draft = " + this.draft + " " + + "flagged = " + this.flagged + " " + "recent = " + this.recent + + " " + "seen = " + this.seen + " " + "internalDate = " + + this.internalDate + " " + "subType = " + this.subType + " " + + "mediaType = " + this.mediaType + " " + " ]"; + } + + @Override + protected int getBodyStartOctet() { + return bodyStartOctet; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + public long getModSeq() { + return modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setUid(long) + */ + public void setUid(long uid) { + this.uid = uid; + } + + @Override + public InputStream getHeaderContent() throws IOException { + long headerEnd = getBodyStartOctet(); + if (headerEnd < 0) { + headerEnd = 0; + } + return content.newStream(0, headerEnd); + } + + @Override + public InputStream getFullContent() throws IOException { + return content.newStream(0, -1); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleProperty.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleProperty.java.svn-base new file mode 100644 index 0000000..0160bf6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleProperty.java.svn-base @@ -0,0 +1,102 @@ +/**************************************************************** + * 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.impl; + +import org.apache.james.mailbox.store.mail.model.Property; + +public final class SimpleProperty implements Property { + private String namespace; + private String localName; + private String value; + + /** + * Construct a property. + * @param namespace not null + * @param localName not null + * @param value not null + */ + public SimpleProperty(String namespace, String localName, String value) { + super(); + this.namespace = namespace; + this.localName = localName; + this.value = value; + } + + public SimpleProperty(Property property) { + this(property.getNamespace(), property.getLocalName(), property.getValue()); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Property#getLocalName() + */ + public String getLocalName() { + return localName; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Property#getNamespace() + */ + public String getNamespace() { + return namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Property#getValue() + */ + public String getValue() { + return value; + } + + /** + * Does the full name of the property match that given? + * @param namespace not null + * @param localName not null + * @return true when namespaces and local names match, + * false otherwise + */ + public boolean isNamed(final String namespace, final String localName) { + return namespace.equals(this.namespace) && localName.equals(this.localName); + } + + /** + * Is this property in the given namespace? + * @param namespace not null + * @return true when this property is in the given namespace, + * false otherwise + */ + public boolean isInSpace(final String namespace) { + return this.namespace.equals(namespace); + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() + { + return "SimpleProperty(" + + "namespace='" + this.namespace + + "' localName='" + this.localName + + "' value='" + this.value + + "')"; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java new file mode 100644 index 0000000..bc74499 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java @@ -0,0 +1,478 @@ +/**************************************************************** + * 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.impl; + +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DESCRIPTION_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DESCRIPTION_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DISPOSITION_PARAMETER_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DISPOSITION_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_DISPOSITION_TYPE_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_ID_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_ID_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LANGUAGE_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LANGUAGE_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LOCATION_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_LOCATION_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_MD5_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_MD5_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MEDIA_TYPE_NAME; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MIME_TYPE_SPACE; +import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_SUB_TYPE_NAME; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.james.mailbox.store.mail.model.Property; + +/** + * Builds properties + */ +public class PropertyBuilder { + + private static final int INITIAL_CAPACITY = 32; + + private Long textualLineCount; + private final List properties; + + public PropertyBuilder(final List props) { + textualLineCount = null; + properties = new ArrayList(props.size()); + for (final Property property:props) { + properties.add(new SimpleProperty(property)); + } + } + + public PropertyBuilder() { + textualLineCount = null; + properties = new ArrayList(INITIAL_CAPACITY); + } + + /** + * Gets the number of CRLF in a textual document. + * @return CRLF count when document is textual, + * null otherwise + */ + public Long getTextualLineCount() { + return textualLineCount; + } + + /** + * Aets the number of CRLF in a textual document. + * @param textualLineCount count when document is textual, + * null otherwise + */ + public void setTextualLineCount(Long textualLineCount) { + this.textualLineCount = textualLineCount; + } + + /** + * Gets the first value with the given name. + * Used to retrieve values with a single value. + * @param namespace not null + * @param localName not null + * @return value, + * or null when no property has the given name and namespace + */ + public String getFirstValue(final String namespace, final String localName) { + String result = null; + for (SimpleProperty property: properties) { + if (property.isNamed(namespace, localName)) { + result = property.getValue(); + break; + } + } + return result; + } + + /** + * Lists all values for a property. + * @param namespace not null + * @param localName not null + * @return not null + */ + public List getValues(final String namespace, final String localName) { + List results = new ArrayList(); + for (SimpleProperty property: properties) { + if (property.isNamed(namespace, localName)) { + results.add(property.getValue()); + } + } + return results; + } + + /** + * Sets a property allowing only a single value. + * @param namespace not null + * @param localName not null + * @param value null to remove property + */ + public void setProperty(final String namespace, final String localName, final String value) + { + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isNamed(namespace, localName)) { + it.remove(); + } + } + + if (value != null) { + properties.add(new SimpleProperty(namespace, localName, value)); + } + } + + /** + * Sets a multiple valued property. + * @param namespace not null + * @param localName not null + * @param values null to remove property + */ + public void setProperty(final String namespace, final String localName, final List values) + { + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isNamed(namespace, localName)) { + it.remove(); + } + } + if (values !=null) { + for(final String value:values) { + properties.add(new SimpleProperty(namespace, localName, value)); + } + } + } + + /** + * Maps properties in the given namespace. + * @param namespace not null + * @return values indexed by local name + */ + public SortedMap getProperties(final String namespace) { + final SortedMap parameters = new TreeMap(); + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isInSpace(namespace)) { + parameters.put(property.getLocalName(), property.getValue()); + } + } + return parameters; + } + + /** + * Sets properties in the given namespace from the map. + * Existing properties in the namespace will be removed. + * All local names will be converted to lower case. + * @param namespace not null + * @param valuesByLocalName not null + */ + public void setProperties(final String namespace, final Map valuesByLocalName) { + for (Iterator it= properties.iterator();it.hasNext();) { + final SimpleProperty property = it.next(); + if (property.isInSpace(namespace)) { + it.remove(); + } + } + for (final Map.Entry valueByLocalName:valuesByLocalName.entrySet()) { + properties.add(new SimpleProperty(namespace, valueByLocalName.getKey().toLowerCase(), valueByLocalName.getValue())); + } + } + + /** + * Gets the top level MIME content media type. + * + * @return top level MIME content media type, or null if default + */ + public String getMediaType() { + return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME); + } + + /** + * Sets the top level MIME content media type. + * + * @param value top level MIME content media type, + * or null to remove + */ + public void setMediaType(String value) { + setProperty(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME, value); + } + + /** + * Gets the MIME content subtype. + * + * @return the MIME content subtype, or null if default + */ + public String getSubType() { + return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_SUB_TYPE_NAME); + } + + /** + * Sets the MIME content subtype. + * + * @param value the MIME content subtype, + * or null to remove + */ + public void setSubType(String value) { + setProperty(MIME_MIME_TYPE_SPACE, MIME_SUB_TYPE_NAME, value); + } + + /** + * Gets the MIME Content-ID. + * + * @return the MIME content subtype, or null if default + */ + public String getContentID() { + return getFirstValue(MIME_CONTENT_ID_SPACE, MIME_CONTENT_ID_NAME); + } + + /** + * Sets MIME Content-ID. + * + * @param value the MIME content subtype, + * or null to remove + */ + public void setContentID(String value) { + setProperty(MIME_CONTENT_ID_SPACE, MIME_CONTENT_ID_NAME, value); + } + + /** + * Gets the MIME Content-Description. + * + * @return the MIME Content-Description, + * or null if this meta data is not present + */ + public String getContentDescription() { + return getFirstValue(MIME_CONTENT_DESCRIPTION_SPACE, MIME_CONTENT_DESCRIPTION_NAME); + } + + /** + * Sets MIME Content-Description. + * + * @param value the MIME Content-Description + * or null to remove + */ + public void setContentDescription(String value) { + setProperty(MIME_CONTENT_DESCRIPTION_SPACE, MIME_CONTENT_DESCRIPTION_NAME, value); + } + + /** + * Gets the MIME Content-Transfer-Encoding. + * + * @return the MIME Content-Transfer-Encoding, + * or null if this meta data is not present + */ + public String getContentTransferEncoding() { + return getFirstValue(MIME_CONTENT_TRANSFER_ENCODING_SPACE, MIME_CONTENT_TRANSFER_ENCODING_NAME); + } + + /** + * Sets MIME Content-Transfer-Encoding. + * + * @param value the MIME Content-Transfer-Encoding + * or null to remove + */ + public void setContentTransferEncoding(String value) { + setProperty(MIME_CONTENT_TRANSFER_ENCODING_SPACE, MIME_CONTENT_TRANSFER_ENCODING_NAME, value); + } + + /** + * Gets the RFC2557 Content-Location. + * + * @return the RFC2557 Content-Location, + * or null if this meta data is not present + */ + public String getContentLocation() { + return getFirstValue(MIME_CONTENT_LOCATION_SPACE, MIME_CONTENT_LOCATION_NAME); + } + + /** + * Sets RFC2557 Content-Location. + * + * @param value the RFC2557 Content-Location + * or null to remove + */ + public void setContentLocation(String value) { + setProperty(MIME_CONTENT_LOCATION_SPACE, MIME_CONTENT_LOCATION_NAME, value); + } + + /** + * Sets RFC2183 Content-Disposition disposition-type. + * + * @param value the RFC2183 Content-Disposition + * or null to remove + */ + public void setContentDispositionType(String value) { + setProperty(MIME_CONTENT_DISPOSITION_SPACE, MIME_CONTENT_DISPOSITION_TYPE_NAME, value); + } + + /** + * Gets the RFC2183 Content-Disposition disposition-type. + * + * @return the RFC2183 Content-Disposition, + * or null if this meta data is not present + */ + public String getContentDispositionType() { + return getFirstValue(MIME_CONTENT_DISPOSITION_SPACE, MIME_CONTENT_DISPOSITION_TYPE_NAME); + } + + /** + * Gets RFC2183 Content-Disposition parameters. + * @return parameter values indexed by lower case local names + */ + public Map getContentDispositionParameters() { + return getProperties(MIME_CONTENT_DISPOSITION_PARAMETER_SPACE); + } + + /** + * Sets Content-Disposition parameters. + * Parameter names will be normalised to lower case. + * @param valuesByParameterName values indexed by parameter name + */ + public void setContentDispositionParameters(final Map valuesByParameterName) { + setProperties(MIME_CONTENT_DISPOSITION_PARAMETER_SPACE, valuesByParameterName); + } + + /** + * Gets RFC2045 Content-Type parameters. + * @return parameter values indexed by lower case local names + */ + public Map getContentTypeParameters() { + return getProperties(MIME_CONTENT_TYPE_PARAMETER_SPACE); + } + + /** + * Sets Content-Type parameters. + * Parameter names will be normalised to lower case. + * @param valuesByParameterName values indexed by parameter name + */ + public void setContentTypeParameters(final Map valuesByParameterName) { + setProperties(MIME_CONTENT_TYPE_PARAMETER_SPACE, valuesByParameterName); + } + + /** + * Gets the RFC1864 Content-MD5. + * + * @return the RFC1864 Content-MD5, + * or null if this meta data is not present + */ + public String getContentMD5() { + return getFirstValue(MIME_CONTENT_MD5_SPACE, MIME_CONTENT_MD5_NAME); + } + + /** + * Sets RFC1864 Content-MD5. + * + * @param value the RFC1864 Content-MD5 + * or null to remove + */ + public void setContentMD5(String value) { + setProperty(MIME_CONTENT_MD5_SPACE, MIME_CONTENT_MD5_NAME, value); + } + + /** + * Gets the RFC2045 Content-Type "charset" parameter. + * + * @return the RFC2045 Content-Type "charset" parameter, + * or null if this meta data is not present + */ + public String getCharset() { + return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME); + } + + /** + * Sets RFC2045 Content-Type "charset" parameter. + * + * @param value the RFC2045 Content-Type "charset" parameter + * or null to remove + */ + public void setCharset(String value) { + setProperty(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME, value); + } + + /** + * Gets the RFC2045 Content-Type "boundary" parameter. + * + * @return the RFC2045 Content-Type "boundary" parameter, + * or null if this meta data is not present + */ + public String getBoundary() { + return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME); + } + + /** + * Sets RFC2045 Content-Type "boundary" parameter. + * + * @param value the RFC2045 Content-Type "boundary" parameter + * or null to remove + */ + public void setBoundary(String value) { + setProperty(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME, value); + } + + /** + * Gets the RFC1766 Content-Language. + * + * @return list of parsed langauge tags from the RFC1766 Content-Language, + * possibly empty + */ + public List getContentLanguage() { + return getValues(MIME_CONTENT_LANGUAGE_SPACE, MIME_CONTENT_LANGUAGE_NAME); + } + + /** + * Sets RFC1766 Content-Language. + * + * @param values list of parsed language tags from the RFC1766 Content-Language, + * possibly empty + */ + public void setContentLanguage(List values) { + setProperty(MIME_CONTENT_LANGUAGE_SPACE, MIME_CONTENT_LANGUAGE_NAME, values); + } + + /** + * Builds a list of properties. + * @return not null + */ + public List toProperties() { + final List results = new ArrayList(properties); + return results; + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() + { + return "PropertyBuilder ( " + + " textualLineCount = " + this.textualLineCount + + " properties = " + this.properties + + " )"; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java new file mode 100644 index 0000000..047e789 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java @@ -0,0 +1,170 @@ +/**************************************************************** + * 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.impl; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.SimpleMailboxACL; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public class SimpleMailbox implements Mailbox { + + private Id id = null; + private String namespace; + private String user; + private String name; + private long uidValidity; + private MailboxACL acl = SimpleMailboxACL.EMPTY; + + public SimpleMailbox(MailboxPath path, long uidValidity) { + this.namespace = path.getNamespace(); + this.user = path.getUser(); + this.name = path.getName(); + this.uidValidity = uidValidity; + } + + public SimpleMailbox(Mailbox mailbox) { + this.id = mailbox.getMailboxId(); + this.namespace = mailbox.getNamespace(); + this.user = mailbox.getUser(); + this.name = mailbox.getName(); + this.uidValidity = mailbox.getUidValidity(); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getMailboxId() + */ + public Id getMailboxId() { + return id; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getNamespace() + */ + public String getNamespace() { + return namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setNamespace(java.lang.String) + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUser() + */ + public String getUser() { + return user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setUser(java.lang.String) + */ + public void setUser(String user) { + this.user = user; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getName() + */ + public String getName() { + return name; + } + + /** + * @see + * org.apache.james.mailbox.store.mail.model.Mailbox#setName(java.lang.String + * ) + */ + public void setName(String name) { + this.name = name; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getUidValidity() + */ + public long getUidValidity() { + return uidValidity; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @SuppressWarnings("unchecked") + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof SimpleMailbox) { + if (id != null) { + if (id.equals(((SimpleMailbox) obj).getMailboxId())) + return true; + } else { + if (((SimpleMailbox) obj).getMailboxId() == null) + return true; + } + } + return false; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + namespace.hashCode(); + result = PRIME * result + user.hashCode(); + result = PRIME * result + name.hashCode(); + return result; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return namespace + ":" + user + ":" + name; + } + + + public void setMailboxId(Id id) { + this.id = id; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#getACL() + */ + @Override + public MailboxACL getACL() { + return acl; + } + + /* (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.model.Mailbox#setACL(org.apache.james.mailbox.MailboxACL) + */ + @Override + public void setACL(MailboxACL acl) { + this.acl = acl; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java new file mode 100644 index 0000000..b78543c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java @@ -0,0 +1,250 @@ +/**************************************************************** + * 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.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.AbstractMessage; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; + +public class SimpleMessage extends AbstractMessage { + + private long uid; + private final Id mailboxId; + private long size; + private boolean answered; + private boolean deleted; + private boolean draft; + private boolean flagged; + private boolean recent; + private boolean seen; + private Date internalDate; + private final String subType; + private List properties; + private final String mediaType; + private Long lineCount; + private int bodyStartOctet; + private long modSeq; + private SharedInputStream content; + + public SimpleMessage(Date internalDate, int size, int bodyStartOctet, + SharedInputStream content, Flags flags, + PropertyBuilder propertyBuilder, final Id mailboxId) { + this.content = content; + + this.size = size; + this.bodyStartOctet = bodyStartOctet; + setFlags(flags); + lineCount = propertyBuilder.getTextualLineCount(); + this.internalDate = internalDate; + this.mailboxId = mailboxId; + this.properties = propertyBuilder.toProperties(); + this.mediaType = propertyBuilder.getMediaType(); + this.subType = propertyBuilder.getSubType(); + } + + public SimpleMessage(Mailbox mailbox, Message original) + throws MailboxException { + this.internalDate = original.getInternalDate(); + this.size = original.getFullContentOctets(); + this.mailboxId = mailbox.getMailboxId(); + setFlags(original.createFlags()); + try { + this.content = new SharedByteArrayInputStream( + IOUtils.toByteArray(original.getFullContent())); + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } + + this.bodyStartOctet = (int) (original.getFullContentOctets() - original + .getBodyOctets()); + + PropertyBuilder pBuilder = new PropertyBuilder(original.getProperties()); + this.lineCount = original.getTextualLineCount(); + this.mediaType = original.getMediaType(); + this.subType = original.getSubType(); + final List properties = pBuilder.toProperties(); + this.properties = new ArrayList(properties.size()); + for (final Property property : properties) { + this.properties.add(new SimpleProperty(property)); + } + } + + public Date getInternalDate() { + return internalDate; + } + + public Id getMailboxId() { + return mailboxId; + } + + public long getUid() { + return uid; + } + + public boolean isAnswered() { + return answered; + } + + public boolean isDeleted() { + return deleted; + } + + public boolean isDraft() { + return draft; + } + + public boolean isFlagged() { + return flagged; + } + + public boolean isRecent() { + return recent; + } + + public boolean isSeen() { + return seen; + } + + public synchronized void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + } + + public InputStream getBodyContent() throws IOException { + return content.newStream(getBodyStartOctet(), -1); + } + + public long getFullContentOctets() { + return size; + } + + public String getMediaType() { + return mediaType; + } + + public List getProperties() { + return properties; + } + + public String getSubType() { + return subType; + } + + public Long getTextualLineCount() { + return lineCount; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SimpleMessage other = (SimpleMessage) obj; + if (uid != other.uid) + return false; + return true; + } + + /** + * Representation suitable for logging and debugging. + * + * @return a String representation of this object. + */ + public String toString() { + return super.toString() + "[" + "uid = " + this.uid + " " + + "mailboxId = " + this.mailboxId + " " + "size = " + this.size + + " " + "answered = " + this.answered + " " + "deleted = " + + this.deleted + " " + "draft = " + this.draft + " " + + "flagged = " + this.flagged + " " + "recent = " + this.recent + + " " + "seen = " + this.seen + " " + "internalDate = " + + this.internalDate + " " + "subType = " + this.subType + " " + + "mediaType = " + this.mediaType + " " + " ]"; + } + + @Override + protected int getBodyStartOctet() { + return bodyStartOctet; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() + */ + public long getModSeq() { + return modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) + */ + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#setUid(long) + */ + public void setUid(long uid) { + this.uid = uid; + } + + @Override + public InputStream getHeaderContent() throws IOException { + long headerEnd = getBodyStartOctet(); + if (headerEnd < 0) { + headerEnd = 0; + } + return content.newStream(0, headerEnd); + } + + @Override + public InputStream getFullContent() throws IOException { + return content.newStream(0, -1); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleProperty.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleProperty.java new file mode 100644 index 0000000..0160bf6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleProperty.java @@ -0,0 +1,102 @@ +/**************************************************************** + * 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.impl; + +import org.apache.james.mailbox.store.mail.model.Property; + +public final class SimpleProperty implements Property { + private String namespace; + private String localName; + private String value; + + /** + * Construct a property. + * @param namespace not null + * @param localName not null + * @param value not null + */ + public SimpleProperty(String namespace, String localName, String value) { + super(); + this.namespace = namespace; + this.localName = localName; + this.value = value; + } + + public SimpleProperty(Property property) { + this(property.getNamespace(), property.getLocalName(), property.getValue()); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Property#getLocalName() + */ + public String getLocalName() { + return localName; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Property#getNamespace() + */ + public String getNamespace() { + return namespace; + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Property#getValue() + */ + public String getValue() { + return value; + } + + /** + * Does the full name of the property match that given? + * @param namespace not null + * @param localName not null + * @return true when namespaces and local names match, + * false otherwise + */ + public boolean isNamed(final String namespace, final String localName) { + return namespace.equals(this.namespace) && localName.equals(this.localName); + } + + /** + * Is this property in the given namespace? + * @param namespace not null + * @return true when this property is in the given namespace, + * false otherwise + */ + public boolean isInSpace(final String namespace) { + return this.namespace.equals(namespace); + } + + /** + * Constructs a String with all attributes + * in name = value format. + * + * @return a String representation + * of this object. + */ + public String toString() + { + return "SimpleProperty(" + + "namespace='" + this.namespace + + "' localName='" + this.localName + + "' value='" + this.value + + "')"; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/all-wcprops new file mode 100644 index 0000000..78e578b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/all-wcprops @@ -0,0 +1,29 @@ +K 25 +svn:wc:ra_dav:version-url +V 104 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/quota +END +ListeningQuotaManager.java +K 25 +svn:wc:ra_dav:version-url +V 131 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningQuotaManager.java +END +FixedQuotaManager.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/quota/FixedQuotaManager.java +END +PerUserQuotaManager.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/quota/PerUserQuotaManager.java +END +QuotaImpl.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/quota/QuotaImpl.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/entries new file mode 100644 index 0000000..2d7bbac --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/entries @@ -0,0 +1,164 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/quota +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +ListeningQuotaManager.java +file + + + + +2013-09-02T02:54:39.000000Z +526c486c6faab465f2abfc56d8139fb0 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +9153 + +FixedQuotaManager.java +file + + + + +2013-09-02T02:54:39.000000Z +50a9737b2621636c418bfcabb71b0cf1 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2343 + +PerUserQuotaManager.java +file + + + + +2013-09-02T02:54:39.000000Z +9b8e4ef01638269ea8495ffb01fbdcee +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2982 + +QuotaImpl.java +file + + + + +2013-09-02T02:54:39.000000Z +e319db77fc030704db050e0e9c51c434 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2096 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/FixedQuotaManager.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/FixedQuotaManager.java.svn-base new file mode 100644 index 0000000..de98922 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/FixedQuotaManager.java.svn-base @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.quota; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Quota; +import org.apache.james.mailbox.store.StoreMailboxManager; + +/** + * {@link ListeningQuotaManager} which use the same quota for all users. + * + * By default this means not quota at all + */ +public class FixedQuotaManager extends ListeningQuotaManager{ + + @SuppressWarnings("rawtypes") + public FixedQuotaManager(StoreMailboxManager manager) throws MailboxException { + super(manager); + } + + private long maxStorage = Quota.UNLIMITED; + private long maxMessage = Quota.UNLIMITED; + + public void setMaxStorage(long maxStorage) { + this.maxStorage = maxStorage; + } + + public void setMaxMessage(long maxMessage) { + this.maxMessage = maxMessage; + } + + @Override + protected long getMaxStorage(MailboxSession session) throws MailboxException { + return maxStorage; + } + + @Override + protected long getMaxMessage(MailboxSession session) throws MailboxException { + return maxMessage; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/ListeningQuotaManager.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/ListeningQuotaManager.java.svn-base new file mode 100644 index 0000000..58a1779 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/ListeningQuotaManager.java.svn-base @@ -0,0 +1,232 @@ +/**************************************************************** + * 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.quota; + +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.QuotaManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.Quota; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; + +/** + * {@link QuotaManager} which will keep track of quota by listing for {@link org.apache.james.mailbox.MailboxListener.Event}'s. + * + * The whole quota is keeped in memory after it was lazy-fetched on the first access + * * + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public abstract class ListeningQuotaManager implements QuotaManager, MailboxListener{ + + private MailboxSessionMapperFactory factory; + private ConcurrentHashMap counts = new ConcurrentHashMap(); + private ConcurrentHashMap sizes = new ConcurrentHashMap(); + private boolean calculateWhenUnlimited = false; + + public ListeningQuotaManager(StoreMailboxManager manager) throws MailboxException { + this.factory = manager.getMapperFactory(); + manager.addGlobalListener(this, null); + } + + protected MailboxSessionMapperFactory getFactory() { + return factory; + } + + public void setCalculateUsedWhenUnlimited(boolean calculateWhenUnlimited) { + this.calculateWhenUnlimited = calculateWhenUnlimited; + } + + + @Override + public Quota getMessageQuota(MailboxSession session) throws MailboxException { + long max = getMaxMessage(session); + if (max != Quota.UNLIMITED || calculateWhenUnlimited) { + + String id = session.getUser().getUserName(); + AtomicLong count = counts.get(id); + if (count == null) { + long mc = 0; + List mailboxes = factory.getMailboxMapper(session).findMailboxWithPathLike(new MailboxPath(session.getPersonalSpace(), id, "%")); + for (int i = 0; i < mailboxes.size(); i++) { + mc += factory.getMessageMapper(session).countMessagesInMailbox(mailboxes.get(i)); + } + AtomicLong c = counts.putIfAbsent(id, new AtomicLong(mc)); + if (c != null) { + count = c; + } + } + return QuotaImpl.quota(max, count != null ? count.get() : 0); + } else { + return QuotaImpl.unlimited(); + } + } + + @Override + public Quota getStorageQuota(MailboxSession session) throws MailboxException { + long max = getMaxStorage(session); + if (max != Quota.UNLIMITED || calculateWhenUnlimited) { + MessageMapper mapper = factory.getMessageMapper(session); + String id = session.getUser().getUserName(); + AtomicLong size = sizes.get(id); + + if (size == null) { + final AtomicLong mSizes = new AtomicLong(0); + List mailboxes = factory.getMailboxMapper(session).findMailboxWithPathLike(new MailboxPath(session.getPersonalSpace(), id, "%")); + for (int i = 0; i < mailboxes.size(); i++) { + long messageSizes = 0; + Iterator messages = mapper.findInMailbox(mailboxes.get(i), MessageRange.all(), FetchType.Metadata, -1); + + while(messages.hasNext()) { + messageSizes += messages.next().getFullContentOctets(); + } + mSizes.set(mSizes.get() + messageSizes); + } + + AtomicLong s = sizes.putIfAbsent(id, mSizes); + if (s != null) { + size = s; + } else { + size = mSizes; + } + } + return QuotaImpl.quota(max, size.get()); + } else { + return QuotaImpl.unlimited(); + } + } + + /** + * Return the maximum storage which is allowed for the given {@link MailboxSession} (in fact the user which the session is bound to) + * + * The returned valued must be in bytes + * + * @param session + * @return maxBytes + * @throws MailboxException + */ + protected abstract long getMaxStorage(MailboxSession session) throws MailboxException; + + + /** + * Return the maximum message count which is allowed for the given {@link MailboxSession} (in fact the user which the session is bound to) + * + * @param session + * @return maximum of allowed message count + * @throws MailboxException + */ + protected abstract long getMaxMessage(MailboxSession session) throws MailboxException; + + + @Override + public void event(Event event) { + String id = event.getSession().getUser().getUserName(); + if (event instanceof Added) { + Added added = (Added) event; + + long s = 0; + long c = 0; + Iterator uids = added.getUids().iterator();; + while(uids.hasNext()) { + long uid = uids.next(); + s += added.getMetaData(uid).getSize(); + c++; + } + + AtomicLong size = sizes.get(id); + if (size != null) { + while(true) { + long expected = size.get(); + long newValue = expected + s; + if (size.compareAndSet(expected, newValue)) { + break; + } + } + } + + AtomicLong count = counts.get(id); + if (count != null) { + while(true) { + long expected = count.get(); + long newValue = expected + c; + if (count.compareAndSet(expected, newValue)) { + break; + } + } + } + } else if (event instanceof Expunged) { + Expunged expunged = (Expunged) event; + long s = 0; + long c = 0; + Iterator uids = expunged.getUids().iterator();; + while(uids.hasNext()) { + long uid = uids.next(); + s += expunged.getMetaData(uid).getSize(); + c++; + } + + AtomicLong size = sizes.get(id); + if (size != null) { + while(true) { + long expected = size.get(); + long newValue = expected - s; + if (size.compareAndSet(expected, newValue)) { + break; + } + } + } + + AtomicLong count = counts.get(id); + if (count != null) { + while(true) { + long expected = count.get(); + long newValue = expected - c; + if (count.compareAndSet(expected, newValue)) { + break; + } + } + } + } else if (event instanceof MailboxAdded) { + counts.putIfAbsent(id, new AtomicLong(0)); + sizes.putIfAbsent(id, new AtomicLong(0)); + } + } + + /** + * Get never closed + * + * @return false + */ + public boolean isClosed() { + return false; + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/PerUserQuotaManager.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/PerUserQuotaManager.java.svn-base new file mode 100644 index 0000000..8d94818 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/PerUserQuotaManager.java.svn-base @@ -0,0 +1,80 @@ +/**************************************************************** + * 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.quota; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Quota; +import org.apache.james.mailbox.store.StoreMailboxManager; + +/** + * Allows to set a per Users quota + * + * + */ +public class PerUserQuotaManager extends ListeningQuotaManager{ + + public PerUserQuotaManager(StoreMailboxManager manager) throws MailboxException { + super(manager); + } + + private long maxMessage = Quota.UNLIMITED; + private long maxStorage = Quota.UNLIMITED; + + private Map userMaxStorage = new HashMap(); + private Map userMaxMessage = new HashMap(); + + public void setUserMaxStorage(Map userMaxStorage) { + this.userMaxStorage = userMaxStorage; + } + + public void setUserMaxMessage(Map userMaxMessage) { + this.userMaxMessage = userMaxMessage; + } + + public void setDefaultMaxStorage(long maxStorage) { + this.maxStorage = maxStorage; + } + + public void setDefaultMaxMessage(long maxMessage) { + this.maxMessage = maxMessage; + } + + @Override + protected long getMaxStorage(MailboxSession session) throws MailboxException { + Long max = userMaxStorage.get(session.getUser().getUserName()); + if (max == null) { + max = maxStorage; + } + return max; + } + + @Override + protected long getMaxMessage(MailboxSession session) throws MailboxException { + Long max = userMaxMessage.get(session.getUser().getUserName()); + if (max == null) { + max = maxMessage; + } + return max; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/QuotaImpl.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/QuotaImpl.java.svn-base new file mode 100644 index 0000000..b7a972c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/.svn/text-base/QuotaImpl.java.svn-base @@ -0,0 +1,64 @@ +/**************************************************************** + * 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.quota; + +import org.apache.james.mailbox.model.Quota; + +public final class QuotaImpl implements Quota{ + + private long max; + private long used; + + private final static Quota UNLIMITED_QUOTA = new QuotaImpl(UNKNOWN, UNLIMITED); + + private QuotaImpl(long used, long max) { + + } + + @Override + public long getMax() { + return max; + } + + @Override + public long getUsed() { + return used; + } + + /** + * Return a {@link Quota} which in fact is unlimited + * + * @return unlimited + */ + public static Quota unlimited() { + return UNLIMITED_QUOTA; + } + + /** + * Return a {@link Quota} for the given values + * + * @param max + * @param used + * @return quota + */ + public static Quota quota(long max , long used) { + return new QuotaImpl(used, max); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedQuotaManager.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedQuotaManager.java new file mode 100644 index 0000000..de98922 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/FixedQuotaManager.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.quota; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Quota; +import org.apache.james.mailbox.store.StoreMailboxManager; + +/** + * {@link ListeningQuotaManager} which use the same quota for all users. + * + * By default this means not quota at all + */ +public class FixedQuotaManager extends ListeningQuotaManager{ + + @SuppressWarnings("rawtypes") + public FixedQuotaManager(StoreMailboxManager manager) throws MailboxException { + super(manager); + } + + private long maxStorage = Quota.UNLIMITED; + private long maxMessage = Quota.UNLIMITED; + + public void setMaxStorage(long maxStorage) { + this.maxStorage = maxStorage; + } + + public void setMaxMessage(long maxMessage) { + this.maxMessage = maxMessage; + } + + @Override + protected long getMaxStorage(MailboxSession session) throws MailboxException { + return maxStorage; + } + + @Override + protected long getMaxMessage(MailboxSession session) throws MailboxException { + return maxMessage; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningQuotaManager.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningQuotaManager.java new file mode 100644 index 0000000..58a1779 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/ListeningQuotaManager.java @@ -0,0 +1,232 @@ +/**************************************************************** + * 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.quota; + +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.james.mailbox.MailboxListener; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.QuotaManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.Quota; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; + +/** + * {@link QuotaManager} which will keep track of quota by listing for {@link org.apache.james.mailbox.MailboxListener.Event}'s. + * + * The whole quota is keeped in memory after it was lazy-fetched on the first access + * * + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public abstract class ListeningQuotaManager implements QuotaManager, MailboxListener{ + + private MailboxSessionMapperFactory factory; + private ConcurrentHashMap counts = new ConcurrentHashMap(); + private ConcurrentHashMap sizes = new ConcurrentHashMap(); + private boolean calculateWhenUnlimited = false; + + public ListeningQuotaManager(StoreMailboxManager manager) throws MailboxException { + this.factory = manager.getMapperFactory(); + manager.addGlobalListener(this, null); + } + + protected MailboxSessionMapperFactory getFactory() { + return factory; + } + + public void setCalculateUsedWhenUnlimited(boolean calculateWhenUnlimited) { + this.calculateWhenUnlimited = calculateWhenUnlimited; + } + + + @Override + public Quota getMessageQuota(MailboxSession session) throws MailboxException { + long max = getMaxMessage(session); + if (max != Quota.UNLIMITED || calculateWhenUnlimited) { + + String id = session.getUser().getUserName(); + AtomicLong count = counts.get(id); + if (count == null) { + long mc = 0; + List mailboxes = factory.getMailboxMapper(session).findMailboxWithPathLike(new MailboxPath(session.getPersonalSpace(), id, "%")); + for (int i = 0; i < mailboxes.size(); i++) { + mc += factory.getMessageMapper(session).countMessagesInMailbox(mailboxes.get(i)); + } + AtomicLong c = counts.putIfAbsent(id, new AtomicLong(mc)); + if (c != null) { + count = c; + } + } + return QuotaImpl.quota(max, count != null ? count.get() : 0); + } else { + return QuotaImpl.unlimited(); + } + } + + @Override + public Quota getStorageQuota(MailboxSession session) throws MailboxException { + long max = getMaxStorage(session); + if (max != Quota.UNLIMITED || calculateWhenUnlimited) { + MessageMapper mapper = factory.getMessageMapper(session); + String id = session.getUser().getUserName(); + AtomicLong size = sizes.get(id); + + if (size == null) { + final AtomicLong mSizes = new AtomicLong(0); + List mailboxes = factory.getMailboxMapper(session).findMailboxWithPathLike(new MailboxPath(session.getPersonalSpace(), id, "%")); + for (int i = 0; i < mailboxes.size(); i++) { + long messageSizes = 0; + Iterator messages = mapper.findInMailbox(mailboxes.get(i), MessageRange.all(), FetchType.Metadata, -1); + + while(messages.hasNext()) { + messageSizes += messages.next().getFullContentOctets(); + } + mSizes.set(mSizes.get() + messageSizes); + } + + AtomicLong s = sizes.putIfAbsent(id, mSizes); + if (s != null) { + size = s; + } else { + size = mSizes; + } + } + return QuotaImpl.quota(max, size.get()); + } else { + return QuotaImpl.unlimited(); + } + } + + /** + * Return the maximum storage which is allowed for the given {@link MailboxSession} (in fact the user which the session is bound to) + * + * The returned valued must be in bytes + * + * @param session + * @return maxBytes + * @throws MailboxException + */ + protected abstract long getMaxStorage(MailboxSession session) throws MailboxException; + + + /** + * Return the maximum message count which is allowed for the given {@link MailboxSession} (in fact the user which the session is bound to) + * + * @param session + * @return maximum of allowed message count + * @throws MailboxException + */ + protected abstract long getMaxMessage(MailboxSession session) throws MailboxException; + + + @Override + public void event(Event event) { + String id = event.getSession().getUser().getUserName(); + if (event instanceof Added) { + Added added = (Added) event; + + long s = 0; + long c = 0; + Iterator uids = added.getUids().iterator();; + while(uids.hasNext()) { + long uid = uids.next(); + s += added.getMetaData(uid).getSize(); + c++; + } + + AtomicLong size = sizes.get(id); + if (size != null) { + while(true) { + long expected = size.get(); + long newValue = expected + s; + if (size.compareAndSet(expected, newValue)) { + break; + } + } + } + + AtomicLong count = counts.get(id); + if (count != null) { + while(true) { + long expected = count.get(); + long newValue = expected + c; + if (count.compareAndSet(expected, newValue)) { + break; + } + } + } + } else if (event instanceof Expunged) { + Expunged expunged = (Expunged) event; + long s = 0; + long c = 0; + Iterator uids = expunged.getUids().iterator();; + while(uids.hasNext()) { + long uid = uids.next(); + s += expunged.getMetaData(uid).getSize(); + c++; + } + + AtomicLong size = sizes.get(id); + if (size != null) { + while(true) { + long expected = size.get(); + long newValue = expected - s; + if (size.compareAndSet(expected, newValue)) { + break; + } + } + } + + AtomicLong count = counts.get(id); + if (count != null) { + while(true) { + long expected = count.get(); + long newValue = expected - c; + if (count.compareAndSet(expected, newValue)) { + break; + } + } + } + } else if (event instanceof MailboxAdded) { + counts.putIfAbsent(id, new AtomicLong(0)); + sizes.putIfAbsent(id, new AtomicLong(0)); + } + } + + /** + * Get never closed + * + * @return false + */ + public boolean isClosed() { + return false; + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/PerUserQuotaManager.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/PerUserQuotaManager.java new file mode 100644 index 0000000..8d94818 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/PerUserQuotaManager.java @@ -0,0 +1,80 @@ +/**************************************************************** + * 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.quota; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Quota; +import org.apache.james.mailbox.store.StoreMailboxManager; + +/** + * Allows to set a per Users quota + * + * + */ +public class PerUserQuotaManager extends ListeningQuotaManager{ + + public PerUserQuotaManager(StoreMailboxManager manager) throws MailboxException { + super(manager); + } + + private long maxMessage = Quota.UNLIMITED; + private long maxStorage = Quota.UNLIMITED; + + private Map userMaxStorage = new HashMap(); + private Map userMaxMessage = new HashMap(); + + public void setUserMaxStorage(Map userMaxStorage) { + this.userMaxStorage = userMaxStorage; + } + + public void setUserMaxMessage(Map userMaxMessage) { + this.userMaxMessage = userMaxMessage; + } + + public void setDefaultMaxStorage(long maxStorage) { + this.maxStorage = maxStorage; + } + + public void setDefaultMaxMessage(long maxMessage) { + this.maxMessage = maxMessage; + } + + @Override + protected long getMaxStorage(MailboxSession session) throws MailboxException { + Long max = userMaxStorage.get(session.getUser().getUserName()); + if (max == null) { + max = maxStorage; + } + return max; + } + + @Override + protected long getMaxMessage(MailboxSession session) throws MailboxException { + Long max = userMaxMessage.get(session.getUser().getUserName()); + if (max == null) { + max = maxMessage; + } + return max; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/QuotaImpl.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/QuotaImpl.java new file mode 100644 index 0000000..b7a972c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/quota/QuotaImpl.java @@ -0,0 +1,64 @@ +/**************************************************************** + * 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.quota; + +import org.apache.james.mailbox.model.Quota; + +public final class QuotaImpl implements Quota{ + + private long max; + private long used; + + private final static Quota UNLIMITED_QUOTA = new QuotaImpl(UNKNOWN, UNLIMITED); + + private QuotaImpl(long used, long max) { + + } + + @Override + public long getMax() { + return max; + } + + @Override + public long getUsed() { + return used; + } + + /** + * Return a {@link Quota} which in fact is unlimited + * + * @return unlimited + */ + public static Quota unlimited() { + return UNLIMITED_QUOTA; + } + + /** + * Return a {@link Quota} for the given values + * + * @param max + * @param used + * @return quota + */ + public static Quota quota(long max , long used) { + return new QuotaImpl(used, max); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/all-wcprops new file mode 100644 index 0000000..e855af8 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/all-wcprops @@ -0,0 +1,47 @@ +K 25 +svn:wc:ra_dav:version-url +V 105 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search +END +ListeningMessageSearchIndex.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java +END +MessageSearcher.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearcher.java +END +LazyMessageSearchIndex.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java +END +MessageSearchIndex.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearchIndex.java +END +MessageSearches.java +K 25 +svn:wc:ra_dav:version-url +V 126 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java +END +SearchUtil.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/SearchUtil.java +END +SimpleMessageSearchIndex.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1509309/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/entries new file mode 100644 index 0000000..1fcc91b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/entries @@ -0,0 +1,269 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search +http://svn.apache.org/repos/asf + + + +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +ListeningMessageSearchIndex.java +file + + + + +2013-09-02T02:54:39.000000Z +016e17ff88871355fac6a1c0eb39b438 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +7469 + +MessageSearcher.java +file + + + + +2013-09-02T02:54:39.000000Z +3304702253e19085890c39c2f6f7221c +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +9576 + +LazyMessageSearchIndex.java +file + + + + +2013-09-02T02:54:39.000000Z +99c2ed087dfbe5e424a532226a0d342b +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4770 + +MessageSearchIndex.java +file + + + + +2013-09-02T02:54:39.000000Z +ad17749b858e35133324f542c0ab67cc +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2091 + +MessageSearches.java +file + + + + +2013-09-02T02:54:39.000000Z +7de9f1ee7e8f3de4daade6c2aa531146 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +24675 + +SearchUtil.java +file + + + + +2013-09-02T02:54:39.000000Z +106d2c41db0bd073ed3732229a9bdcdf +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +17315 + +comparator +dir + +SimpleMessageSearchIndex.java +file + + + + +2013-09-02T02:54:39.000000Z +f148252f617d1d1f51384f3620c09bfc +2013-08-01T15:50:28.815636Z +1509309 +eric + + + + + + + + + + + + + + + + + + + + + +4881 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/LazyMessageSearchIndex.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/LazyMessageSearchIndex.java.svn-base new file mode 100644 index 0000000..e6edaf0 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/LazyMessageSearchIndex.java.svn-base @@ -0,0 +1,107 @@ +/**************************************************************** + * 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.search; + +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link ListeningMessageSearchIndex} implementation which wraps another {@link ListeningMessageSearchIndex} and will forward all calls to it. + * + * The only special thing about this is that it will index all the mails in the mailbox on the first call of {@link #search(MailboxSession, Mailbox, SearchQuery)} + * + * This class is mostly useful for in-memory indexes or for indexed that should be recreated on every server restart. + * + * + * @param + */ +public class LazyMessageSearchIndex extends ListeningMessageSearchIndex { + + private ListeningMessageSearchIndex index; + private final ConcurrentHashMap indexed = new ConcurrentHashMap(); + + + public LazyMessageSearchIndex(ListeningMessageSearchIndex index) { + super(index.getFactory()); + this.index = index; + } + + + + @Override + public void add(MailboxSession session, Mailbox mailbox, Message message) throws MailboxException { + index.add(session, mailbox, message); + } + + @Override + public void delete(MailboxSession session, Mailbox mailbox, MessageRange range) throws MailboxException { + index.delete(session, mailbox, range); + } + + /** + * Lazy index the mailbox on first search request if it was not indexed before. After indexing is done it delegate the search request to the wrapped + * {@link MessageSearchIndex}. Be aware that concurrent search requests are blocked on the same "not-yet-indexed" mailbox till it the index process was + * complete + * + */ + @Override + public Iterator search(final MailboxSession session, final Mailbox mailbox, SearchQuery searchQuery) throws MailboxException { + Id id = mailbox.getMailboxId(); + + Object done = indexed.get(id); + if (done == null) { + done = new Object(); + Object oldDone = indexed.putIfAbsent(id, done); + if (oldDone != null) { + done = oldDone; + } + synchronized (done) { + Iterator> messages = getFactory().getMessageMapper(session).findInMailbox(mailbox, MessageRange.all(), FetchType.Full, -1); + while(messages.hasNext()) { + final Message message = messages.next(); + try { + add(session, mailbox, message); + } catch (MailboxException e) { + session.getLog().debug("Unable to index message " + message.getUid() + " in mailbox " + mailbox.getName(), e); + } + + } + } + } + + return index.search(session, mailbox, searchQuery); + } + + + @Override + public void update(MailboxSession session, Mailbox mailbox, MessageRange range, Flags flags) throws MailboxException { + index.update(session, mailbox, range, flags); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/ListeningMessageSearchIndex.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/ListeningMessageSearchIndex.java.svn-base new file mode 100644 index 0000000..c49bb49 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/ListeningMessageSearchIndex.java.svn-base @@ -0,0 +1,170 @@ +/**************************************************************** + * 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.search; + +import java.util.Iterator; +import java.util.List; + +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; +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.mail.MessageMapper.FetchType; +import org.apache.james.mailbox.store.mail.MessageMapperFactory; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link MessageSearchIndex} which needs to get registered as global {@link MailboxListener} and so get + * notified about message changes. This will then allow to update the underlying index. + * + * + * @param + */ +public abstract class ListeningMessageSearchIndex implements MessageSearchIndex, MailboxListener{ + + private MessageMapperFactory factory; + + public ListeningMessageSearchIndex(MessageMapperFactory factory) { + this.factory = factory; + } + + + /** + * Return the {@link MessageMapperFactory} + * + * @return factory + */ + protected MessageMapperFactory getFactory() { + return factory; + } + + + /** + * Process the {@link org.apache.james.mailbox.MailboxListener.Event} and update the index if + * something relevant is received + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void event(Event event) { + final MailboxSession session = event.getSession(); + + try { + if (event instanceof MessageEvent) { + if (event instanceof AddedImpl) { + AddedImpl added = (AddedImpl) event; + final Mailbox mailbox = added.getMailbox(); + Iterator uids = added.getUids().iterator(); + + while (uids.hasNext()) { + long next = uids.next(); + Iterator> messages = factory.getMessageMapper(session).findInMailbox(mailbox, MessageRange.one(next), FetchType.Full, -1); + while(messages.hasNext()) { + Message message = messages.next(); + try { + add(session, mailbox, message); + } catch (MailboxException e) { + session.getLog().debug("Unable to index message " + message.getUid() + " for mailbox " + mailbox, e); + } + } + + } + } else if (event instanceof ExpungedImpl) { + ExpungedImpl expunged = (ExpungedImpl) event; + final Mailbox mailbox = expunged.getMailbox(); + List uids = expunged.getUids(); + List ranges = MessageRange.toRanges(uids); + for (int i = 0; i < ranges.size(); i++) { + MessageRange range = ranges.get(i); + try { + delete(session, mailbox, range); + } catch (MailboxException e) { + session.getLog().debug("Unable to deleted range " + range.toString() + " from index for mailbox " + mailbox, e); + } + } + } else if (event instanceof FlagsUpdatedImpl) { + FlagsUpdatedImpl flagsUpdated = (FlagsUpdatedImpl) event; + final Mailbox mailbox = flagsUpdated.getMailbox(); + + Iterator flags = flagsUpdated.getUpdatedFlags().iterator(); + while(flags.hasNext()) { + UpdatedFlags uFlags = flags.next(); + try { + update(session, mailbox, MessageRange.one(uFlags.getUid()), uFlags.getNewFlags()); + } catch (MailboxException e) { + session.getLog().debug("Unable to update flags for message " + uFlags.getUid() + " in index for mailbox " + mailbox, e); + } + } + } + } else if (event instanceof MailboxDeletionImpl) { + // delete all indexed messages for the mailbox + delete(session, ((MailboxDeletionImpl) event).getMailbox(), MessageRange.all()); + } + } catch (MailboxException e) { + session.getLog().debug("Unable to update index", e); + + } + } + + /** + * Never closed + */ + public boolean isClosed() { + return false; + } + + /** + * Add the {@link Message} for the given {@link Mailbox} to the index + * + * @param session + * @param mailbox + * @param message + * @throws MailboxException + */ + public abstract void add(MailboxSession session, Mailbox mailbox, Message message) throws MailboxException; + + /** + * Delete the {@link MessageRange} for the given {@link Mailbox} from the index + * + * @param session + * @param mailbox + * @param range + * @throws MailboxException + */ + public abstract void delete(MailboxSession session, Mailbox mailbox, MessageRange range) throws MailboxException; + + + /** + * Update the {@link MessageRange} for the given {@link Mailbox} with the new {@link Flags} in the index + * + * @param session + * @param mailbox + * @param range + * @param flags + * @throws MailboxException + */ + public abstract void update(MailboxSession session, Mailbox mailbox, MessageRange range, Flags flags) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearchIndex.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearchIndex.java.svn-base new file mode 100644 index 0000000..db7894a --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearchIndex.java.svn-base @@ -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.search; + +import java.util.Iterator; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.model.Mailbox; + + +/** + * An index which can be used to search for Message UID's that match a {@link SearchQuery}. + * + * A developer should think of building an inverse-index for that. + * + * + * @param + */ +public interface MessageSearchIndex { + + /** + * Return all uids of the previous indexed {@link Mailbox}'s which match the {@link SearchQuery} + * + * @param mailbox + * @param searchQuery + * @return Iterator on found uids + * @throws MailboxException + */ + Iterator search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearcher.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearcher.java.svn-base new file mode 100644 index 0000000..53b7974 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearcher.java.svn-base @@ -0,0 +1,279 @@ +/**************************************************************** + * 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.search; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.nio.CharBuffer; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Searches an email for content. This class should be safe for use by + * concurrent threads. + */ +public class MessageSearcher { + + private Logger logger; + + private CharSequence searchContent = null; + + private boolean isCaseInsensitive = false; + + private boolean includeHeaders = false; + + public MessageSearcher() { + } + + public MessageSearcher(CharSequence searchContent, + boolean isCaseInsensitive, boolean includeHeaders) { + super(); + this.searchContent = searchContent; + this.isCaseInsensitive = isCaseInsensitive; + this.includeHeaders = includeHeaders; + } + + /** + * Is the search to include headers? + * + * @return true if header values are included, false otherwise + */ + public boolean isIncludeHeaders() { + return includeHeaders; + } + + /** + * Sets whether the search should include headers. + * + * @param includesHeaders + * true if header values are included, false otherwise + */ + public synchronized void setIncludeHeaders(boolean includesHeaders) { + this.includeHeaders = includesHeaders; + } + + /** + * Is this search case insensitive? + * + * @return true if the search should be case insensitive, false otherwise + */ + public boolean isCaseInsensitive() { + return isCaseInsensitive; + } + + /** + * Sets whether the search should be case insensitive. + * + * @param isCaseInsensitive + * true for case insensitive searches, false otherwise + */ + public synchronized void setCaseInsensitive(boolean isCaseInsensitive) { + this.isCaseInsensitive = isCaseInsensitive; + } + + /** + * Gets the content to be searched for. + * + * @return search content, initially null + */ + public synchronized CharSequence getSearchContent() { + return searchContent; + } + + /** + * Sets the content sought. + * + * @param searchContent + * content sought + */ + public synchronized void setSearchContent(CharSequence searchContent) { + this.searchContent = searchContent; + } + + /** + * Is {@link #getSearchContent()} found in the given input? + * + * @param input + * InputStream containing an email + * @return true if the content exists and the stream contains the content, + * false otherwise + * @throws IOException + * @throws MimeException + */ + public boolean isFoundIn(final InputStream input) throws IOException, + MimeException { + final boolean includeHeaders; + final CharSequence searchContent; + final boolean isCaseInsensitive; + synchronized (this) { + includeHeaders = this.includeHeaders; + searchContent = this.searchContent; + isCaseInsensitive = this.isCaseInsensitive; + } + final boolean result; + if (searchContent == null || "".equals(searchContent)) { + final Logger logger = getLogger(); + logger.debug("Nothing to search for. "); + result = false; + } else { + final CharBuffer buffer = createBuffer(searchContent, + isCaseInsensitive); + result = parse(input, isCaseInsensitive, includeHeaders, buffer); + } + return result; + } + + private boolean parse(final InputStream input, + final boolean isCaseInsensitive, final boolean includeHeaders, + final CharBuffer buffer) throws IOException, MimeException { + try { + boolean result = false; + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + MimeTokenStream parser = new MimeTokenStream(config); parser.parse(input); + while (!result && parser.next() != EntityState.T_END_OF_STREAM) { + final EntityState state = parser.getState(); + switch (state) { + case T_BODY: + case T_PREAMBLE: + case T_EPILOGUE: + result = checkBody(isCaseInsensitive, buffer, result, + parser); + break; + case T_FIELD: + if (includeHeaders) { + result = checkHeader(isCaseInsensitive, buffer, + result, parser); + } + break; + } + } + return result; + } catch (IllegalCharsetNameException e) { + handle(e); + } catch (UnsupportedCharsetException e) { + handle(e); + } catch (IllegalStateException e) { + handle(e); + } + return false; + } + + private boolean checkHeader(final boolean isCaseInsensitive, + final CharBuffer buffer, boolean result, MimeTokenStream parser) + throws IOException { + final String value = parser.getField().getBody(); + final StringReader reader = new StringReader(value); + if (isFoundIn(reader, buffer, isCaseInsensitive)) { + result = true; + } + return result; + } + + private boolean checkBody(final boolean isCaseInsensitive, + final CharBuffer buffer, boolean result, MimeTokenStream parser) + throws IOException { + final Reader reader = parser.getReader(); + if (isFoundIn(reader, buffer, isCaseInsensitive)) { + result = true; + } + return result; + } + + private CharBuffer createBuffer(final CharSequence searchContent, + final boolean isCaseInsensitive) { + final CharBuffer buffer; + if (isCaseInsensitive) { + final int length = searchContent.length(); + buffer = CharBuffer.allocate(length); + for (int i = 0; i < length; i++) { + final char next = searchContent.charAt(i); + final char upperCase = Character.toUpperCase(next); + buffer.put(upperCase); + } + buffer.flip(); + } else { + buffer = CharBuffer.wrap(searchContent); + } + return buffer; + } + + protected void handle(Exception e) throws IOException, MimeException { + final Logger logger = getLogger(); + logger.warn("Cannot read MIME body."); + logger.debug("Failed to read body.", e); + } + + private boolean isFoundIn(final Reader reader, final CharBuffer buffer, + final boolean isCaseInsensitive) throws IOException { + boolean result = false; + int read; + while (!result && (read = reader.read()) != -1) { + final char next; + if (isCaseInsensitive) { + next = Character.toUpperCase((char) read); + } else { + next = (char) read; + } + result = matches(buffer, next); + } + return result; + } + + private boolean matches(final CharBuffer buffer, final char next) { + boolean result = false; + if (buffer.hasRemaining()) { + final boolean partialMatch = (buffer.position() > 0); + final char matching = buffer.get(); + if (next != matching) { + buffer.rewind(); + if (partialMatch) { + result = matches(buffer, next); + } + } + } else { + result = true; + } + return result; + } + + public final Logger getLogger() { + if (logger == null) { + logger = LoggerFactory.getLogger(MessageSearcher.class); + } + return logger; + } + + public final void setLogger(Logger logger) { + this.logger = logger; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearches.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearches.java.svn-base new file mode 100644 index 0000000..c73e4af --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/MessageSearches.java.svn-base @@ -0,0 +1,605 @@ +/**************************************************************** + * 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.search; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.UnsupportedSearchException; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SearchQuery.AddressType; +import org.apache.james.mailbox.model.SearchQuery.DateResolution; +import org.apache.james.mailbox.model.SearchQuery.NumericRange; +import org.apache.james.mailbox.store.ResultUtils; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.comparator.CombinedComparator; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.dom.datetime.DateTime; +import org.apache.james.mime4j.field.address.AddressFormatter; +import org.apache.james.mime4j.field.address.LenientAddressBuilder; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.field.datetime.parser.ParseException; +import org.slf4j.Logger; + +/** + * Utility methods to help perform search operations. + */ +public class MessageSearches implements Iterable { + + private Iterator> messages; + private SearchQuery query; + private Logger log; + + public MessageSearches(Iterator> messages, SearchQuery query) { + this(messages, query, null); + } + + public MessageSearches(Iterator> messages, SearchQuery query, Logger log) { + this.messages = messages; + this.query = query; + this.log = log; + } + + /** + * Empty constructor only for tests (which test isMatch()) + */ + public MessageSearches() { + } + + private Collection search() { + TreeSet> matched = new TreeSet>(CombinedComparator.create(query.getSorts())); + while (messages.hasNext()) { + Message m = messages.next(); + try { + if (isMatch(query, m, log)) { + matched.add(m); + } + } catch (MailboxException e) { + log.debug("Unable to search message " + m.getUid(), e); + } + } + Set uids = new HashSet(); + Iterator> matchedIt = matched.iterator(); + while (matchedIt.hasNext()) { + uids.add(matchedIt.next().getUid()); + } + return uids; + } + + /** + * Does the row match the given criteria? + * + * @param query + * SearchQuery, not null + * @param message + * Message, not null + * @param log + * the logger to use + * @return true if the row matches the given criteria, + * false otherwise + * @throws MailboxException + */ + protected boolean isMatch(final SearchQuery query, final Message message, Logger log) throws MailboxException { + final List criteria = query.getCriterias(); + final Collection recentMessageUids = query.getRecentMessageUids(); + boolean result = true; + if (criteria != null) { + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + if (!isMatch(criterion, message, recentMessageUids, log)) { + result = false; + break; + } + } + } + return result; + } + + /** + * Does the row match the given criterion? + * + * @param criterion + * the criterion to use + * @param message + * Message, not null + * @param recentMessageUids + * collection of recent message uids + * @param log + * the logger to use + * @return true if the row matches the given criterion, + * false otherwise + * @throws MailboxException + */ + public boolean isMatch(SearchQuery.Criterion criterion, Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + final boolean result; + if (criterion instanceof SearchQuery.InternalDateCriterion) { + result = matches((SearchQuery.InternalDateCriterion) criterion, message); + } else if (criterion instanceof SearchQuery.SizeCriterion) { + result = matches((SearchQuery.SizeCriterion) criterion, message); + } else if (criterion instanceof SearchQuery.HeaderCriterion) { + try { + result = matches((SearchQuery.HeaderCriterion) criterion, message, log); + } catch (IOException e) { + throw new MailboxException("Unable to search header", e); + } + } else if (criterion instanceof SearchQuery.UidCriterion) { + result = matches((SearchQuery.UidCriterion) criterion, message); + } else if (criterion instanceof SearchQuery.FlagCriterion) { + result = matches((SearchQuery.FlagCriterion) criterion, message, recentMessageUids); + } else if (criterion instanceof SearchQuery.CustomFlagCriterion) { + result = matches((SearchQuery.CustomFlagCriterion) criterion, message, recentMessageUids); + } else if (criterion instanceof SearchQuery.TextCriterion) { + result = matches((SearchQuery.TextCriterion) criterion, message, log); + } else if (criterion instanceof SearchQuery.AllCriterion) { + result = true; + } else if (criterion instanceof SearchQuery.ConjunctionCriterion) { + result = matches((SearchQuery.ConjunctionCriterion) criterion, message, recentMessageUids, log); + } else if (criterion instanceof SearchQuery.ModSeqCriterion) { + result = matches((SearchQuery.ModSeqCriterion) criterion, message); + } else { + throw new UnsupportedSearchException(); + } + return result; + } + + protected boolean matches(SearchQuery.TextCriterion criterion, Message message, Logger log) + throws MailboxException { + try { + final SearchQuery.ContainsOperator operator = criterion.getOperator(); + final String value = operator.getValue(); + switch (criterion.getType()) { + case BODY: + return bodyContains(value, message, log); + case FULL: + return messageContains(value, message, log); + default: + throw new UnsupportedSearchException(); + } + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } catch (MimeException e) { + throw new MailboxException("Unable to parse message", e); + } + } + + protected boolean bodyContains(String value, Message message, Logger log) throws IOException, MimeException { + final InputStream input = message.getFullContent(); + final boolean result = isInMessage(value, input, false, log); + return result; + } + + protected boolean isInMessage(String value, final InputStream input, boolean header, Logger log) + throws IOException, MimeException { + final MessageSearcher searcher = new MessageSearcher(value, true, header); + if (log != null) { + searcher.setLogger(log); + } + final boolean result = searcher.isFoundIn(input); + return result; + } + + protected boolean messageContains(String value, Message message, Logger log) throws IOException, MimeException { + final InputStream input = message.getFullContent(); + final boolean result = isInMessage(value, input, true, log); + return result; + } + + private boolean matches(SearchQuery.ConjunctionCriterion criterion, Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + final List criteria = criterion.getCriteria(); + switch (criterion.getType()) { + case NOR: + return nor(criteria, message, recentMessageUids, log); + case OR: + return or(criteria, message, recentMessageUids, log); + case AND: + return and(criteria, message, recentMessageUids, log); + default: + return false; + } + } + + private boolean and(final List criteria, final Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + boolean result = true; + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + final boolean matches = isMatch(criterion, message, recentMessageUids, log); + if (!matches) { + result = false; + break; + } + } + return result; + } + + private boolean or(final List criteria, final Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + boolean result = false; + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + final boolean matches = isMatch(criterion, message, recentMessageUids, log); + if (matches) { + result = true; + break; + } + } + return result; + } + + private boolean nor(final List criteria, final Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + boolean result = true; + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + final boolean matches = isMatch(criterion, message, recentMessageUids, log); + if (matches) { + result = false; + break; + } + } + return result; + } + + private boolean matches(SearchQuery.FlagCriterion criterion, Message message, + final Collection recentMessageUids) { + final SearchQuery.BooleanOperator operator = criterion.getOperator(); + final boolean isSet = operator.isSet(); + final Flags.Flag flag = criterion.getFlag(); + final boolean result; + if (flag == Flags.Flag.ANSWERED) { + result = isSet == message.isAnswered(); + } else if (flag == Flags.Flag.SEEN) { + result = isSet == message.isSeen(); + } else if (flag == Flags.Flag.DRAFT) { + result = isSet == message.isDraft(); + } else if (flag == Flags.Flag.FLAGGED) { + result = isSet == message.isFlagged(); + } else if (flag == Flags.Flag.RECENT) { + final long uid = message.getUid(); + result = isSet == recentMessageUids.contains(Long.valueOf(uid)); + } else if (flag == Flags.Flag.DELETED) { + result = isSet == message.isDeleted(); + } else { + result = false; + } + return result; + } + + private boolean matches(SearchQuery.CustomFlagCriterion criterion, Message message, + final Collection recentMessageUids) { + final SearchQuery.BooleanOperator operator = criterion.getOperator(); + final boolean isSet = operator.isSet(); + final String flag = criterion.getFlag(); + final boolean result = isSet == message.createFlags().contains(flag); + return result; + } + + private boolean matches(SearchQuery.UidCriterion criterion, Message message) { + final SearchQuery.InOperator operator = criterion.getOperator(); + final NumericRange[] ranges = operator.getRange(); + final long uid = message.getUid(); + final int length = ranges.length; + boolean result = false; + for (int i = 0; i < length; i++) { + final NumericRange numericRange = ranges[i]; + if (numericRange.isIn(uid)) { + result = true; + break; + } + } + return result; + } + + private boolean matches(SearchQuery.HeaderCriterion criterion, Message message, Logger log) + throws MailboxException, IOException { + final SearchQuery.HeaderOperator operator = criterion.getOperator(); + final String headerName = criterion.getHeaderName(); + final boolean result; + if (operator instanceof SearchQuery.DateOperator) { + result = matches((SearchQuery.DateOperator) operator, headerName, message); + } else if (operator instanceof SearchQuery.ContainsOperator) { + result = matches((SearchQuery.ContainsOperator) operator, headerName, message); + } else if (operator instanceof SearchQuery.ExistsOperator) { + result = exists(headerName, message); + } else if (operator instanceof SearchQuery.AddressOperator) { + result = matchesAddress((SearchQuery.AddressOperator) operator, headerName, message, log); + } else { + throw new UnsupportedSearchException(); + } + return result; + } + + /** + * Match against a {@link AddressType} header + * + * @param operator + * @param headerName + * @param message + * @return containsAddress + * @throws MailboxException + * @throws IOException + */ + private boolean matchesAddress(final SearchQuery.AddressOperator operator, final String headerName, + final Message message, Logger log) throws MailboxException, IOException { + final String text = operator.getAddress().toUpperCase(Locale.ENGLISH); + final List
headers = ResultUtils.createHeaders(message); + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + final String value = header.getValue(); + AddressList aList = LenientAddressBuilder.DEFAULT.parseAddressList(value); + for (int i = 0; i < aList.size(); i++) { + Address address = aList.get(i); + if (address instanceof Mailbox) { + if (AddressFormatter.DEFAULT.encode((Mailbox) address).toUpperCase(Locale.ENGLISH) + .contains(text)) { + return true; + } + } else if (address instanceof Group) { + MailboxList mList = ((Group) address).getMailboxes(); + for (int a = 0; a < mList.size(); a++) { + if (AddressFormatter.DEFAULT.encode(mList.get(a)).toUpperCase(Locale.ENGLISH) + .contains(text)) { + return true; + } + } + } + } + + // Also try to match against raw header now + return value.toUpperCase(Locale.ENGLISH).contains(text); + } + } + return false; + } + + private boolean exists(String headerName, Message message) throws MailboxException, IOException { + boolean result = false; + final List
headers = ResultUtils.createHeaders(message); + + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + result = true; + break; + } + } + return result; + } + + private boolean matches(final SearchQuery.ContainsOperator operator, final String headerName, + final Message message) throws MailboxException, IOException { + final String text = operator.getValue().toUpperCase(); + boolean result = false; + final List
headers = ResultUtils.createHeaders(message); + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + final String value = header.getValue(); + if (value != null) { + if (value.toUpperCase().indexOf(text) > -1) { + result = true; + break; + } + } + } + } + return result; + } + + private boolean matches(final SearchQuery.DateOperator operator, final String headerName, final Message message) + throws MailboxException { + + final Date date = operator.getDate(); + final DateResolution res = operator.getDateResultion(); + try { + final String value = headerValue(headerName, message); + if (value == null) { + return false; + } else { + try { + final Date isoFieldValue = toISODate(value); + final SearchQuery.DateComparator type = operator.getType(); + switch (type) { + case AFTER: + return after(isoFieldValue, date, res); + case BEFORE: + return before(isoFieldValue, date, res); + case ON: + return on(isoFieldValue, date, res); + default: + throw new UnsupportedSearchException(); + } + } catch (ParseException e) { + return false; + } + } + } catch (IOException e) { + return false; + } + } + + private String headerValue(final String headerName, final Message message) throws MailboxException, IOException { + final List
headers = ResultUtils.createHeaders(message); + String value = null; + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + value = header.getValue(); + break; + } + } + return value; + } + + private Date toISODate(String value) throws ParseException { + final StringReader reader = new StringReader(value); + final DateTime dateTime = new DateTimeParser(reader).parseAll(); + Calendar cal = getGMT(); + cal.set(dateTime.getYear(), dateTime.getMonth() - 1, dateTime.getDay(), dateTime.getHour(), + dateTime.getMinute(), dateTime.getSecond()); + return cal.getTime(); + } + + private boolean matches(SearchQuery.SizeCriterion criterion, Message message) throws UnsupportedSearchException { + final SearchQuery.NumericOperator operator = criterion.getOperator(); + final long size = message.getFullContentOctets(); + final long value = operator.getValue(); + switch (operator.getType()) { + case LESS_THAN: + return size < value; + case GREATER_THAN: + return size > value; + case EQUALS: + return size == value; + default: + throw new UnsupportedSearchException(); + } + } + + private boolean matches(SearchQuery.ModSeqCriterion criterion, Message message) + throws UnsupportedSearchException { + final SearchQuery.NumericOperator operator = criterion.getOperator(); + final long modSeq = message.getModSeq(); + final long value = operator.getValue(); + switch (operator.getType()) { + case LESS_THAN: + return modSeq < value; + case GREATER_THAN: + return modSeq > value; + case EQUALS: + return modSeq == value; + default: + throw new UnsupportedSearchException(); + } + } + + private boolean matches(SearchQuery.InternalDateCriterion criterion, Message message) + throws UnsupportedSearchException { + final SearchQuery.DateOperator operator = criterion.getOperator(); + final boolean result = matchesInternalDate(operator, message); + return result; + } + + private boolean matchesInternalDate(final SearchQuery.DateOperator operator, final Message message) + throws UnsupportedSearchException { + final Date date = operator.getDate(); + final DateResolution res = operator.getDateResultion(); + final Date internalDate = message.getInternalDate(); + final SearchQuery.DateComparator type = operator.getType(); + switch (type) { + case ON: + return on(internalDate, date, res); + case BEFORE: + return before(internalDate, date, res); + case AFTER: + return after(internalDate, date, res); + default: + throw new UnsupportedSearchException(); + } + } + + private boolean on(Date date1, final Date date2, DateResolution res) { + String d1 = createDateString(date1, res); + String d2 = createDateString(date2, res); + return d1.compareTo(d2) == 0; + } + + private boolean before(Date date1, final Date date2, DateResolution res) { + String d1 = createDateString(date1, res); + String d2 = createDateString(date2, res); + + return d1.compareTo(d2) < 0; + } + + private boolean after(Date date1, final Date date2, DateResolution res) { + String d1 = createDateString(date1, res); + String d2 = createDateString(date2, res); + + return d1.compareTo(d2) > 0; + } + + private String createDateString(Date date, DateResolution res) { + SimpleDateFormat format; + switch (res) { + case Year: + format = new SimpleDateFormat("yyyy"); + break; + case Month: + format = new SimpleDateFormat("yyyyMM"); + break; + case Day: + format = new SimpleDateFormat("yyyyMMdd"); + break; + case Hour: + format = new SimpleDateFormat("yyyyMMddhh"); + break; + case Minute: + format = new SimpleDateFormat("yyyyMMddhhmm"); + break; + case Second: + format = new SimpleDateFormat("yyyyMMddhhmmss"); + break; + default: + format = new SimpleDateFormat("yyyyMMddhhmmssSSS"); + + break; + } + format.setCalendar(getGMT()); + return format.format(date); + } + + private Calendar getGMT() { + return Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.ENGLISH); + } + + /** + * Return a {@link Iterator} which holds all uids which matched, sorted + * according to the SearchQuery + * + */ + public Iterator iterator() { + return search().iterator(); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtil.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtil.java.svn-base new file mode 100644 index 0000000..c5d176b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtil.java.svn-base @@ -0,0 +1,463 @@ +/**************************************************************** + * 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.search; + +import java.nio.charset.Charset; +import java.util.Locale; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.codec.DecoderUtil; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.field.address.LenientAddressBuilder; +import org.apache.james.mime4j.util.MimeUtil; + +/** + * Utility class which helps with extracting of data for searches + * + * + */ +public class SearchUtil { + + private final static String FWD_PARENS = "(fwd)"; + private final static String SUBJ_FWD_HDR = "[fwd:"; + private final static String SUBJ_FWD_TRL = "]"; + private final static String RE = "re"; + private final static String FWD = "fwd"; + private final static String FW = "fw"; + private final static char WS = ' '; + private final static char OPEN_SQUARE_BRACKED = '['; + private final static char CLOSE_SQUARE_BRACKED = ']'; + private final static char COLON = ':'; + + private final static Charset UTF8 = Charset.forName("UTF8"); + + + /** + * Return the DISPLAY ADDRESS for the given {@link Mailbox}. + * + * See rfc5957 3. DISPLAY Sort Value for an Address for the details + * + * For the purposes of the sort criteria defined in this document, the + * sort value for an [IMAP] address structure is defined as follows: + *

+ * o If the address structure's [IMAP] addr-name is non-NIL, apply the + * procedure from [RFC5255], Section 4.6. (That is, decode any + * RFC2047] encoded-words and convert the resulting character string + * into a charset valid for the currently active [RFC4790] collation, + * with a default of UTF-8.) If the resulting octet string is not + * the empty string, use it as the sort value for the address. + *

+ *

+ * o Otherwise, if the address structure's [IMAP] addr-mailbox and + * [IMAP] addr-host are both non-NIL, the sort value for the address + * is addr-mailbox@addr-host. + *

+ *

+ * o Otherwise, if the address structure's [IMAP] addr-mailbox is non- + * NIL, the sort value for the address is its addr-mailbox. + *

+ *

+ * o If none of the above conditions are met, the sort value for the + * address is the empty string. + *

+ * @param mailbox + * @return display + */ + public static String getDisplayAddress(Mailbox mailbox) { + String display = mailbox.getName(); + if (display == null || display.length() < 1) { + String localPart = mailbox.getLocalPart(); + String domainPart = mailbox.getDomain(); + if (domainPart != null && domainPart.length() > 0 ) { + return localPart + "@" + domainPart; + } else { + return localPart; + } + } + return display; + } + + + /** + * Parse the headerValue and delegate to {@link #getDisplayAddress(Mailbox)} + * + * If no display address is found an empty String is returned + * + * @param headerValue + * @return display + */ + public static String getDisplayAddress(String headerValue) { + AddressList addressList = LenientAddressBuilder.DEFAULT.parseAddressList(MimeUtil.unfold(headerValue)); + if (addressList != null && addressList.isEmpty() == false) { + Address address = addressList.get(0); + if (address instanceof Mailbox) { + return getDisplayAddress((Mailbox) address); + } else if (address instanceof Group) { + Group group = (Group) address; + if (group != null) { + MailboxList mList = group.getMailboxes(); + if (mList != null && mList.isEmpty() == false) { + return getDisplayAddress(mList.get(0)); + } + } + } + } + + return ""; + } + + /** + * Return addr-mailbox of the first "From" address. + * + * See RFC5256 and RFC3501 + * + * Which is in fact the LocalPart + * @param mailbox + * @return addrMailbox + */ + public static String getMailboxAddress(Mailbox mailbox) { + return mailbox.getLocalPart(); + } + + + /** + * Parse the headerValue and delegate to {@link #getMailboxAddress(Mailbox)} + * + * If no mailbox name is found an empty String is returned + * + * @param headerValue + * @return mailbox + */ + public static String getMailboxAddress(String headerValue) { + AddressList aList = LenientAddressBuilder.DEFAULT.parseAddressList(headerValue); + for (int i = 0; i < aList.size(); i++) { + Address address = aList.get(i); + if (address instanceof Mailbox) { + Mailbox m = (Mailbox) address; + String mailboxName = m.getLocalPart(); + if (mailboxName == null) { + mailboxName = ""; + } + return mailboxName; + } else if (address instanceof Group) { + MailboxList mList = ((Group) address).getMailboxes(); + for (int a = 0; a < mList.size();) { + String mailboxName = mList.get(a).getLocalPart(); + if (mailboxName == null) { + mailboxName = ""; + } + return mailboxName; + } + } + } + return ""; + } + + + /** + * Extract the base subject from the given subject. + * + * See rfc5256 2.1 Base Subject + * + * Subject sorting and threading use the "base subject", which has + * specific subject artifacts removed. Due to the complexity of these + * artifacts, the formal syntax for the subject extraction rules is + * ambiguous. The following procedure is followed to determine the + * "base subject", using the [ABNF] formal syntax rules described in + * section 5: + *

+ * (1) Convert any RFC 2047 encoded-words in the subject to [UTF-8] + * as described in "Internationalization Considerations". + * Convert all tabs and continuations to space. Convert all + * multiple spaces to a single space. + *

+ *

+ * (2) Remove all trailing text of the subject that matches the + * subj-trailer ABNF; repeat until no more matches are possible. + *

+ *

+ * (3) Remove all prefix text of the subject that matches the subj- + * leader ABNF. + *

+ *

+ * (4) If there is prefix text of the subject that matches the subj- + * blob ABNF, and removing that prefix leaves a non-empty subj- + * base, then remove the prefix text. + *

+ *

+ * (5) Repeat (3) and (4) until no matches remain. + *

+ * Note: It is possible to defer step (2) until step (6), but this + * requires checking for subj-trailer in step (4). + *
+ *

+ * (6) If the resulting text begins with the subj-fwd-hdr ABNF and + * ends with the subj-fwd-trl ABNF, remove the subj-fwd-hdr and + * subj-fwd-trl and repeat from step (2). + *

+ *

+ * (7) The resulting text is the "base subject" used in the SORT. + *

+ * + * + * @param subject + * @return baseSubject + */ + public static String getBaseSubject(String subject) { + + // (1) Convert any RFC 2047 encoded-words in the subject to [UTF-8] + // as described in "Internationalization Considerations". + // Convert all tabs and continuations to space. Convert all + // multiple spaces to a single space. + String decodedSubject = MimeUtil.unfold(DecoderUtil.decodeEncodedWords(subject, DecodeMonitor.SILENT)); + decodedSubject = new String(decodedSubject.getBytes(UTF8), UTF8); + + // replace all tabs with spaces and replace multiple spaces with one space + decodedSubject = decodedSubject.replaceAll("\t", " ").replaceAll("( ){2,}", " "); + + + while (true) { + int decodedSubjectLength = decodedSubject.length(); + while (true) { + // (2) Remove all trailing text of the subject that matches the + // subj-trailer ABNF; repeat until no more matches are possible. + String subj = removeSubTrailers(decodedSubject); + if (decodedSubjectLength > subj.length()) { + decodedSubject = subj; + decodedSubjectLength = decodedSubject.length(); + } else { + break; + } + + } + + while (true) { + boolean matchedInner = false; + + // (3) Remove all prefix text of the subject that matches the subj- + // leader ABNF. + decodedSubjectLength = decodedSubject.length(); + decodedSubject = removeSubjLeaders(decodedSubject); + if (decodedSubjectLength > decodedSubject.length()) { + matchedInner = true; + decodedSubjectLength = decodedSubject.length(); + + } + + // (4) If there is prefix text of the subject that matches the subj- + // blob ABNF, and removing that prefix leaves a non-empty subj- + // base, then remove the prefix text. + decodedSubjectLength = decodedSubject.length(); + String subj = removeBlob(decodedSubject); + + // check if it will leave a non-empty subject + if (subj.length() > 0) { + decodedSubject = subj; + if (decodedSubjectLength > decodedSubject.length()) { + matchedInner = true; + decodedSubjectLength = decodedSubject.length(); + + } + + } + // (5) Repeat (3) and (4) until no matches remain. + if (!matchedInner) { + // no more matches so break the loop + break; + } + } + String lowcaseSubject = decodedSubject.toLowerCase(Locale.US); + + if (lowcaseSubject.startsWith(SUBJ_FWD_HDR) && lowcaseSubject.endsWith(SUBJ_FWD_TRL)) { + // (6) If the resulting text begins with the subj-fwd-hdr ABNF and + // ends with the subj-fwd-trl ABNF, remove the subj-fwd-hdr and + // subj-fwd-trl and repeat from step (2). + decodedSubject = decodedSubject.substring(SUBJ_FWD_HDR.length(), decodedSubject.length() - SUBJ_FWD_TRL.length()); + decodedSubjectLength = decodedSubject.length(); + } else { + break; + } + + } + // (7) The resulting text is the "base subject" used in the SORT. + return decodedSubject; + } + + /** + * Remove the subj-blob + * + * subj-blob = "[" *BLOBCHAR "]" *WSP + * subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":" + * + * BLOBCHAR = %x01-5a / %x5c / %x5e-7f + * ; any CHAR except '[' and ']' + * + * + * @param subject + * @return sub + */ + private static String removeSubjectBlob(String subject) { + String subj = subject; + while(subj.charAt(0) == OPEN_SQUARE_BRACKED) { + int length = subj.length(); + subj = removeBlob(subject); + int i = 0; + if (subj.length() > 0 && subj.charAt(i) == CLOSE_SQUARE_BRACKED) { + i++; + } else { + return subject; + } + while (subj.charAt(i) == WS) { + i++; + } + subj = subj.substring(i); + if (length == subj.length()) { + return subj; + } + } + return subj; + } + + /** + * Remove the subj-leader + * + * subj-leader = (*subj-blob subj-refwd) / WSP + * subj-blob = "[" *BLOBCHAR "]" *WSP + * subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":" + * + * BLOBCHAR = %x01-5a / %x5c / %x5e-7f + * ; any CHAR except '[' and ']' + * + * + * @param subject + * @return sub + */ + private static String removeSubjLeaders(String subject) { + int subString = 0; + while (subject.charAt(subString) == WS) { + subString++; + } + if (subString > 0) { + // check if we have matched WSP + return subject.substring(subString); + } else { + + String subj = removeSubjectBlob(subject); + + String lowCaseSubj = subj.toLowerCase(Locale.US); + if (lowCaseSubj.startsWith(RE)) { + subString = RE.length(); + } else if (lowCaseSubj.startsWith(FWD)) { + subString = FWD.length(); + } else if (lowCaseSubj.startsWith(FW)) { + subString = FW.length(); + } else { + return subject; + } + while (subj.charAt(subString) == WS) { + subString++; + } + + /* + * subj = removeSubjectBlob(subj.substring(subString)); if + * (subj.endsWith(String.valueOf(CLOSE_SQUARE_BRACKED))) { subString + * = 1; } else { subString = 0; } + */ + + if (subj.charAt(subString) == COLON) { + subString++; + } else { + return subject; + } + + while (subj.charAt(subString) == WS) { + subString++; + } + return subj.substring(subString); + } + } + + + /** + * remove the remove_subj_trailers + * + * subj-trailer = "(fwd)" / WSP + * + * + * @param decodedSubject + * * @return sub + */ + private static String removeSubTrailers(String decodedSubject) { + int subStringStart = 0; + int subStringEnd = decodedSubject.length(); + + int originalSize = decodedSubject.length(); + int curPos = originalSize -1; + while(true) { + char c = decodedSubject.charAt(curPos--); + if (c == WS) { + subStringEnd--; + } else { + if (subStringEnd > FWD_PARENS.length() && decodedSubject.endsWith(FWD_PARENS)) { + subStringEnd -= FWD_PARENS.length(); + } + break; + } + } + decodedSubject = decodedSubject.substring(subStringStart, subStringEnd); + return decodedSubject; + } + + /** + * Remove all blobchars + * + * BLOBCHAR = %x01-5a / %x5c / %x5e-7f + * ; any CHAR except '[' and ']' + * + * @param subject + * @return subj + */ + private static String removeBlob(String subject) { + int i = 0; + char lastChar = Character.UNASSIGNED; + for (int a = 0; a < subject.length(); a++) { + char c = subject.charAt(a); + lastChar = c; + if (( a != 0 && c == OPEN_SQUARE_BRACKED) || c == CLOSE_SQUARE_BRACKED) { + break; + } + i++; + } + + if (lastChar != CLOSE_SQUARE_BRACKED) { + return subject; + } else { + // the lastChar was a ] so increase the count before substring + i++; + return subject.substring(i); + } + + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SimpleMessageSearchIndex.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SimpleMessageSearchIndex.java.svn-base new file mode 100644 index 0000000..8f02e71 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/.svn/text-base/SimpleMessageSearchIndex.java.svn-base @@ -0,0 +1,110 @@ +/**************************************************************** + * 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.search; + +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SearchQuery.ConjunctionCriterion; +import org.apache.james.mailbox.model.SearchQuery.Criterion; +import org.apache.james.mailbox.model.SearchQuery.NumericRange; +import org.apache.james.mailbox.model.SearchQuery.UidCriterion; +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; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link MessageSearchIndex} which just fetch {@link Message}'s from the {@link MessageMapper} and use {@link MessageSearcher} + * to match them against the {@link SearchQuery}. + * + * This works with every implementation but is SLOW. + * + * + * @param + */ +public class SimpleMessageSearchIndex implements MessageSearchIndex{ + + private final MessageMapperFactory factory; + public SimpleMessageSearchIndex(MessageMapperFactory factory) { + this.factory = factory; + } + + /** + * Walks down the query tree's conjunctions to find a UidCriterion + * @param crits - list of Criterion to search from + * @return + * first UidCriterion found + * null - if not found + */ + private static UidCriterion findConjugatedUidCriterion(List crits) { + for (Criterion crit : crits) { + if (crit instanceof UidCriterion) { + return (UidCriterion) crit; + } else if (crit instanceof ConjunctionCriterion) { + return findConjugatedUidCriterion(((ConjunctionCriterion) crit) + .getCriteria()); + } + } + return null; + } + + @Override + public Iterator search(MailboxSession session, Mailbox mailbox, SearchQuery query) throws MailboxException { + MessageMapper mapper = factory.getMessageMapper(session); + + final SortedSet> hitSet = new TreeSet>(); + + UidCriterion uidCrit = findConjugatedUidCriterion(query.getCriterias()); + if (uidCrit != null) { + // if there is a conjugated uid range criterion in the query tree we can optimize by + // only fetching this uid range + NumericRange[] ranges = uidCrit.getOperator().getRange(); + for (int i = 0; i < ranges.length; i++) { + NumericRange r = ranges[i]; + Iterator> it = mapper.findInMailbox(mailbox, MessageRange.range(r.getLowValue(), r.getHighValue()), FetchType.Metadata, -1); + while(it.hasNext()) { + hitSet.add(it.next()); + } + } + } else { + // we have to fetch all messages + Iterator> messages = mapper.findInMailbox(mailbox, MessageRange.all(), FetchType.Full, -1); + while(messages.hasNext()) { + Message m = messages.next(); + hitSet.add(m); + } + } + + // MessageSearches does the filtering for us + if (session == null) { + return new MessageSearches(hitSet.iterator(), query).iterator(); + } else { + return new MessageSearches(hitSet.iterator(), query, session.getLog()).iterator(); + } + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java new file mode 100644 index 0000000..e6edaf0 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/LazyMessageSearchIndex.java @@ -0,0 +1,107 @@ +/**************************************************************** + * 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.search; + +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + +import javax.mail.Flags; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link ListeningMessageSearchIndex} implementation which wraps another {@link ListeningMessageSearchIndex} and will forward all calls to it. + * + * The only special thing about this is that it will index all the mails in the mailbox on the first call of {@link #search(MailboxSession, Mailbox, SearchQuery)} + * + * This class is mostly useful for in-memory indexes or for indexed that should be recreated on every server restart. + * + * + * @param + */ +public class LazyMessageSearchIndex extends ListeningMessageSearchIndex { + + private ListeningMessageSearchIndex index; + private final ConcurrentHashMap indexed = new ConcurrentHashMap(); + + + public LazyMessageSearchIndex(ListeningMessageSearchIndex index) { + super(index.getFactory()); + this.index = index; + } + + + + @Override + public void add(MailboxSession session, Mailbox mailbox, Message message) throws MailboxException { + index.add(session, mailbox, message); + } + + @Override + public void delete(MailboxSession session, Mailbox mailbox, MessageRange range) throws MailboxException { + index.delete(session, mailbox, range); + } + + /** + * Lazy index the mailbox on first search request if it was not indexed before. After indexing is done it delegate the search request to the wrapped + * {@link MessageSearchIndex}. Be aware that concurrent search requests are blocked on the same "not-yet-indexed" mailbox till it the index process was + * complete + * + */ + @Override + public Iterator search(final MailboxSession session, final Mailbox mailbox, SearchQuery searchQuery) throws MailboxException { + Id id = mailbox.getMailboxId(); + + Object done = indexed.get(id); + if (done == null) { + done = new Object(); + Object oldDone = indexed.putIfAbsent(id, done); + if (oldDone != null) { + done = oldDone; + } + synchronized (done) { + Iterator> messages = getFactory().getMessageMapper(session).findInMailbox(mailbox, MessageRange.all(), FetchType.Full, -1); + while(messages.hasNext()) { + final Message message = messages.next(); + try { + add(session, mailbox, message); + } catch (MailboxException e) { + session.getLog().debug("Unable to index message " + message.getUid() + " in mailbox " + mailbox.getName(), e); + } + + } + } + } + + return index.search(session, mailbox, searchQuery); + } + + + @Override + public void update(MailboxSession session, Mailbox mailbox, MessageRange range, Flags flags) throws MailboxException { + index.update(session, mailbox, range, flags); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java new file mode 100644 index 0000000..c49bb49 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndex.java @@ -0,0 +1,170 @@ +/**************************************************************** + * 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.search; + +import java.util.Iterator; +import java.util.List; + +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; +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.mail.MessageMapper.FetchType; +import org.apache.james.mailbox.store.mail.MessageMapperFactory; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link MessageSearchIndex} which needs to get registered as global {@link MailboxListener} and so get + * notified about message changes. This will then allow to update the underlying index. + * + * + * @param + */ +public abstract class ListeningMessageSearchIndex implements MessageSearchIndex, MailboxListener{ + + private MessageMapperFactory factory; + + public ListeningMessageSearchIndex(MessageMapperFactory factory) { + this.factory = factory; + } + + + /** + * Return the {@link MessageMapperFactory} + * + * @return factory + */ + protected MessageMapperFactory getFactory() { + return factory; + } + + + /** + * Process the {@link org.apache.james.mailbox.MailboxListener.Event} and update the index if + * something relevant is received + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void event(Event event) { + final MailboxSession session = event.getSession(); + + try { + if (event instanceof MessageEvent) { + if (event instanceof AddedImpl) { + AddedImpl added = (AddedImpl) event; + final Mailbox mailbox = added.getMailbox(); + Iterator uids = added.getUids().iterator(); + + while (uids.hasNext()) { + long next = uids.next(); + Iterator> messages = factory.getMessageMapper(session).findInMailbox(mailbox, MessageRange.one(next), FetchType.Full, -1); + while(messages.hasNext()) { + Message message = messages.next(); + try { + add(session, mailbox, message); + } catch (MailboxException e) { + session.getLog().debug("Unable to index message " + message.getUid() + " for mailbox " + mailbox, e); + } + } + + } + } else if (event instanceof ExpungedImpl) { + ExpungedImpl expunged = (ExpungedImpl) event; + final Mailbox mailbox = expunged.getMailbox(); + List uids = expunged.getUids(); + List ranges = MessageRange.toRanges(uids); + for (int i = 0; i < ranges.size(); i++) { + MessageRange range = ranges.get(i); + try { + delete(session, mailbox, range); + } catch (MailboxException e) { + session.getLog().debug("Unable to deleted range " + range.toString() + " from index for mailbox " + mailbox, e); + } + } + } else if (event instanceof FlagsUpdatedImpl) { + FlagsUpdatedImpl flagsUpdated = (FlagsUpdatedImpl) event; + final Mailbox mailbox = flagsUpdated.getMailbox(); + + Iterator flags = flagsUpdated.getUpdatedFlags().iterator(); + while(flags.hasNext()) { + UpdatedFlags uFlags = flags.next(); + try { + update(session, mailbox, MessageRange.one(uFlags.getUid()), uFlags.getNewFlags()); + } catch (MailboxException e) { + session.getLog().debug("Unable to update flags for message " + uFlags.getUid() + " in index for mailbox " + mailbox, e); + } + } + } + } else if (event instanceof MailboxDeletionImpl) { + // delete all indexed messages for the mailbox + delete(session, ((MailboxDeletionImpl) event).getMailbox(), MessageRange.all()); + } + } catch (MailboxException e) { + session.getLog().debug("Unable to update index", e); + + } + } + + /** + * Never closed + */ + public boolean isClosed() { + return false; + } + + /** + * Add the {@link Message} for the given {@link Mailbox} to the index + * + * @param session + * @param mailbox + * @param message + * @throws MailboxException + */ + public abstract void add(MailboxSession session, Mailbox mailbox, Message message) throws MailboxException; + + /** + * Delete the {@link MessageRange} for the given {@link Mailbox} from the index + * + * @param session + * @param mailbox + * @param range + * @throws MailboxException + */ + public abstract void delete(MailboxSession session, Mailbox mailbox, MessageRange range) throws MailboxException; + + + /** + * Update the {@link MessageRange} for the given {@link Mailbox} with the new {@link Flags} in the index + * + * @param session + * @param mailbox + * @param range + * @param flags + * @throws MailboxException + */ + public abstract void update(MailboxSession session, Mailbox mailbox, MessageRange range, Flags flags) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearchIndex.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearchIndex.java new file mode 100644 index 0000000..db7894a --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearchIndex.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.search; + +import java.util.Iterator; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.model.Mailbox; + + +/** + * An index which can be used to search for Message UID's that match a {@link SearchQuery}. + * + * A developer should think of building an inverse-index for that. + * + * + * @param + */ +public interface MessageSearchIndex { + + /** + * Return all uids of the previous indexed {@link Mailbox}'s which match the {@link SearchQuery} + * + * @param mailbox + * @param searchQuery + * @return Iterator on found uids + * @throws MailboxException + */ + Iterator search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) throws MailboxException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearcher.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearcher.java new file mode 100644 index 0000000..53b7974 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearcher.java @@ -0,0 +1,279 @@ +/**************************************************************** + * 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.search; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.nio.CharBuffer; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Searches an email for content. This class should be safe for use by + * concurrent threads. + */ +public class MessageSearcher { + + private Logger logger; + + private CharSequence searchContent = null; + + private boolean isCaseInsensitive = false; + + private boolean includeHeaders = false; + + public MessageSearcher() { + } + + public MessageSearcher(CharSequence searchContent, + boolean isCaseInsensitive, boolean includeHeaders) { + super(); + this.searchContent = searchContent; + this.isCaseInsensitive = isCaseInsensitive; + this.includeHeaders = includeHeaders; + } + + /** + * Is the search to include headers? + * + * @return true if header values are included, false otherwise + */ + public boolean isIncludeHeaders() { + return includeHeaders; + } + + /** + * Sets whether the search should include headers. + * + * @param includesHeaders + * true if header values are included, false otherwise + */ + public synchronized void setIncludeHeaders(boolean includesHeaders) { + this.includeHeaders = includesHeaders; + } + + /** + * Is this search case insensitive? + * + * @return true if the search should be case insensitive, false otherwise + */ + public boolean isCaseInsensitive() { + return isCaseInsensitive; + } + + /** + * Sets whether the search should be case insensitive. + * + * @param isCaseInsensitive + * true for case insensitive searches, false otherwise + */ + public synchronized void setCaseInsensitive(boolean isCaseInsensitive) { + this.isCaseInsensitive = isCaseInsensitive; + } + + /** + * Gets the content to be searched for. + * + * @return search content, initially null + */ + public synchronized CharSequence getSearchContent() { + return searchContent; + } + + /** + * Sets the content sought. + * + * @param searchContent + * content sought + */ + public synchronized void setSearchContent(CharSequence searchContent) { + this.searchContent = searchContent; + } + + /** + * Is {@link #getSearchContent()} found in the given input? + * + * @param input + * InputStream containing an email + * @return true if the content exists and the stream contains the content, + * false otherwise + * @throws IOException + * @throws MimeException + */ + public boolean isFoundIn(final InputStream input) throws IOException, + MimeException { + final boolean includeHeaders; + final CharSequence searchContent; + final boolean isCaseInsensitive; + synchronized (this) { + includeHeaders = this.includeHeaders; + searchContent = this.searchContent; + isCaseInsensitive = this.isCaseInsensitive; + } + final boolean result; + if (searchContent == null || "".equals(searchContent)) { + final Logger logger = getLogger(); + logger.debug("Nothing to search for. "); + result = false; + } else { + final CharBuffer buffer = createBuffer(searchContent, + isCaseInsensitive); + result = parse(input, isCaseInsensitive, includeHeaders, buffer); + } + return result; + } + + private boolean parse(final InputStream input, + final boolean isCaseInsensitive, final boolean includeHeaders, + final CharBuffer buffer) throws IOException, MimeException { + try { + boolean result = false; + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + MimeTokenStream parser = new MimeTokenStream(config); parser.parse(input); + while (!result && parser.next() != EntityState.T_END_OF_STREAM) { + final EntityState state = parser.getState(); + switch (state) { + case T_BODY: + case T_PREAMBLE: + case T_EPILOGUE: + result = checkBody(isCaseInsensitive, buffer, result, + parser); + break; + case T_FIELD: + if (includeHeaders) { + result = checkHeader(isCaseInsensitive, buffer, + result, parser); + } + break; + } + } + return result; + } catch (IllegalCharsetNameException e) { + handle(e); + } catch (UnsupportedCharsetException e) { + handle(e); + } catch (IllegalStateException e) { + handle(e); + } + return false; + } + + private boolean checkHeader(final boolean isCaseInsensitive, + final CharBuffer buffer, boolean result, MimeTokenStream parser) + throws IOException { + final String value = parser.getField().getBody(); + final StringReader reader = new StringReader(value); + if (isFoundIn(reader, buffer, isCaseInsensitive)) { + result = true; + } + return result; + } + + private boolean checkBody(final boolean isCaseInsensitive, + final CharBuffer buffer, boolean result, MimeTokenStream parser) + throws IOException { + final Reader reader = parser.getReader(); + if (isFoundIn(reader, buffer, isCaseInsensitive)) { + result = true; + } + return result; + } + + private CharBuffer createBuffer(final CharSequence searchContent, + final boolean isCaseInsensitive) { + final CharBuffer buffer; + if (isCaseInsensitive) { + final int length = searchContent.length(); + buffer = CharBuffer.allocate(length); + for (int i = 0; i < length; i++) { + final char next = searchContent.charAt(i); + final char upperCase = Character.toUpperCase(next); + buffer.put(upperCase); + } + buffer.flip(); + } else { + buffer = CharBuffer.wrap(searchContent); + } + return buffer; + } + + protected void handle(Exception e) throws IOException, MimeException { + final Logger logger = getLogger(); + logger.warn("Cannot read MIME body."); + logger.debug("Failed to read body.", e); + } + + private boolean isFoundIn(final Reader reader, final CharBuffer buffer, + final boolean isCaseInsensitive) throws IOException { + boolean result = false; + int read; + while (!result && (read = reader.read()) != -1) { + final char next; + if (isCaseInsensitive) { + next = Character.toUpperCase((char) read); + } else { + next = (char) read; + } + result = matches(buffer, next); + } + return result; + } + + private boolean matches(final CharBuffer buffer, final char next) { + boolean result = false; + if (buffer.hasRemaining()) { + final boolean partialMatch = (buffer.position() > 0); + final char matching = buffer.get(); + if (next != matching) { + buffer.rewind(); + if (partialMatch) { + result = matches(buffer, next); + } + } + } else { + result = true; + } + return result; + } + + public final Logger getLogger() { + if (logger == null) { + logger = LoggerFactory.getLogger(MessageSearcher.class); + } + return logger; + } + + public final void setLogger(Logger logger) { + this.logger = logger; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java new file mode 100644 index 0000000..c73e4af --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java @@ -0,0 +1,605 @@ +/**************************************************************** + * 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.search; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import javax.mail.Flags; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.UnsupportedSearchException; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SearchQuery.AddressType; +import org.apache.james.mailbox.model.SearchQuery.DateResolution; +import org.apache.james.mailbox.model.SearchQuery.NumericRange; +import org.apache.james.mailbox.store.ResultUtils; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.comparator.CombinedComparator; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.dom.datetime.DateTime; +import org.apache.james.mime4j.field.address.AddressFormatter; +import org.apache.james.mime4j.field.address.LenientAddressBuilder; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.field.datetime.parser.ParseException; +import org.slf4j.Logger; + +/** + * Utility methods to help perform search operations. + */ +public class MessageSearches implements Iterable { + + private Iterator> messages; + private SearchQuery query; + private Logger log; + + public MessageSearches(Iterator> messages, SearchQuery query) { + this(messages, query, null); + } + + public MessageSearches(Iterator> messages, SearchQuery query, Logger log) { + this.messages = messages; + this.query = query; + this.log = log; + } + + /** + * Empty constructor only for tests (which test isMatch()) + */ + public MessageSearches() { + } + + private Collection search() { + TreeSet> matched = new TreeSet>(CombinedComparator.create(query.getSorts())); + while (messages.hasNext()) { + Message m = messages.next(); + try { + if (isMatch(query, m, log)) { + matched.add(m); + } + } catch (MailboxException e) { + log.debug("Unable to search message " + m.getUid(), e); + } + } + Set uids = new HashSet(); + Iterator> matchedIt = matched.iterator(); + while (matchedIt.hasNext()) { + uids.add(matchedIt.next().getUid()); + } + return uids; + } + + /** + * Does the row match the given criteria? + * + * @param query + * SearchQuery, not null + * @param message + * Message, not null + * @param log + * the logger to use + * @return true if the row matches the given criteria, + * false otherwise + * @throws MailboxException + */ + protected boolean isMatch(final SearchQuery query, final Message message, Logger log) throws MailboxException { + final List criteria = query.getCriterias(); + final Collection recentMessageUids = query.getRecentMessageUids(); + boolean result = true; + if (criteria != null) { + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + if (!isMatch(criterion, message, recentMessageUids, log)) { + result = false; + break; + } + } + } + return result; + } + + /** + * Does the row match the given criterion? + * + * @param criterion + * the criterion to use + * @param message + * Message, not null + * @param recentMessageUids + * collection of recent message uids + * @param log + * the logger to use + * @return true if the row matches the given criterion, + * false otherwise + * @throws MailboxException + */ + public boolean isMatch(SearchQuery.Criterion criterion, Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + final boolean result; + if (criterion instanceof SearchQuery.InternalDateCriterion) { + result = matches((SearchQuery.InternalDateCriterion) criterion, message); + } else if (criterion instanceof SearchQuery.SizeCriterion) { + result = matches((SearchQuery.SizeCriterion) criterion, message); + } else if (criterion instanceof SearchQuery.HeaderCriterion) { + try { + result = matches((SearchQuery.HeaderCriterion) criterion, message, log); + } catch (IOException e) { + throw new MailboxException("Unable to search header", e); + } + } else if (criterion instanceof SearchQuery.UidCriterion) { + result = matches((SearchQuery.UidCriterion) criterion, message); + } else if (criterion instanceof SearchQuery.FlagCriterion) { + result = matches((SearchQuery.FlagCriterion) criterion, message, recentMessageUids); + } else if (criterion instanceof SearchQuery.CustomFlagCriterion) { + result = matches((SearchQuery.CustomFlagCriterion) criterion, message, recentMessageUids); + } else if (criterion instanceof SearchQuery.TextCriterion) { + result = matches((SearchQuery.TextCriterion) criterion, message, log); + } else if (criterion instanceof SearchQuery.AllCriterion) { + result = true; + } else if (criterion instanceof SearchQuery.ConjunctionCriterion) { + result = matches((SearchQuery.ConjunctionCriterion) criterion, message, recentMessageUids, log); + } else if (criterion instanceof SearchQuery.ModSeqCriterion) { + result = matches((SearchQuery.ModSeqCriterion) criterion, message); + } else { + throw new UnsupportedSearchException(); + } + return result; + } + + protected boolean matches(SearchQuery.TextCriterion criterion, Message message, Logger log) + throws MailboxException { + try { + final SearchQuery.ContainsOperator operator = criterion.getOperator(); + final String value = operator.getValue(); + switch (criterion.getType()) { + case BODY: + return bodyContains(value, message, log); + case FULL: + return messageContains(value, message, log); + default: + throw new UnsupportedSearchException(); + } + } catch (IOException e) { + throw new MailboxException("Unable to parse message", e); + } catch (MimeException e) { + throw new MailboxException("Unable to parse message", e); + } + } + + protected boolean bodyContains(String value, Message message, Logger log) throws IOException, MimeException { + final InputStream input = message.getFullContent(); + final boolean result = isInMessage(value, input, false, log); + return result; + } + + protected boolean isInMessage(String value, final InputStream input, boolean header, Logger log) + throws IOException, MimeException { + final MessageSearcher searcher = new MessageSearcher(value, true, header); + if (log != null) { + searcher.setLogger(log); + } + final boolean result = searcher.isFoundIn(input); + return result; + } + + protected boolean messageContains(String value, Message message, Logger log) throws IOException, MimeException { + final InputStream input = message.getFullContent(); + final boolean result = isInMessage(value, input, true, log); + return result; + } + + private boolean matches(SearchQuery.ConjunctionCriterion criterion, Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + final List criteria = criterion.getCriteria(); + switch (criterion.getType()) { + case NOR: + return nor(criteria, message, recentMessageUids, log); + case OR: + return or(criteria, message, recentMessageUids, log); + case AND: + return and(criteria, message, recentMessageUids, log); + default: + return false; + } + } + + private boolean and(final List criteria, final Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + boolean result = true; + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + final boolean matches = isMatch(criterion, message, recentMessageUids, log); + if (!matches) { + result = false; + break; + } + } + return result; + } + + private boolean or(final List criteria, final Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + boolean result = false; + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + final boolean matches = isMatch(criterion, message, recentMessageUids, log); + if (matches) { + result = true; + break; + } + } + return result; + } + + private boolean nor(final List criteria, final Message message, + final Collection recentMessageUids, Logger log) throws MailboxException { + boolean result = true; + for (Iterator it = criteria.iterator(); it.hasNext();) { + final SearchQuery.Criterion criterion = it.next(); + final boolean matches = isMatch(criterion, message, recentMessageUids, log); + if (matches) { + result = false; + break; + } + } + return result; + } + + private boolean matches(SearchQuery.FlagCriterion criterion, Message message, + final Collection recentMessageUids) { + final SearchQuery.BooleanOperator operator = criterion.getOperator(); + final boolean isSet = operator.isSet(); + final Flags.Flag flag = criterion.getFlag(); + final boolean result; + if (flag == Flags.Flag.ANSWERED) { + result = isSet == message.isAnswered(); + } else if (flag == Flags.Flag.SEEN) { + result = isSet == message.isSeen(); + } else if (flag == Flags.Flag.DRAFT) { + result = isSet == message.isDraft(); + } else if (flag == Flags.Flag.FLAGGED) { + result = isSet == message.isFlagged(); + } else if (flag == Flags.Flag.RECENT) { + final long uid = message.getUid(); + result = isSet == recentMessageUids.contains(Long.valueOf(uid)); + } else if (flag == Flags.Flag.DELETED) { + result = isSet == message.isDeleted(); + } else { + result = false; + } + return result; + } + + private boolean matches(SearchQuery.CustomFlagCriterion criterion, Message message, + final Collection recentMessageUids) { + final SearchQuery.BooleanOperator operator = criterion.getOperator(); + final boolean isSet = operator.isSet(); + final String flag = criterion.getFlag(); + final boolean result = isSet == message.createFlags().contains(flag); + return result; + } + + private boolean matches(SearchQuery.UidCriterion criterion, Message message) { + final SearchQuery.InOperator operator = criterion.getOperator(); + final NumericRange[] ranges = operator.getRange(); + final long uid = message.getUid(); + final int length = ranges.length; + boolean result = false; + for (int i = 0; i < length; i++) { + final NumericRange numericRange = ranges[i]; + if (numericRange.isIn(uid)) { + result = true; + break; + } + } + return result; + } + + private boolean matches(SearchQuery.HeaderCriterion criterion, Message message, Logger log) + throws MailboxException, IOException { + final SearchQuery.HeaderOperator operator = criterion.getOperator(); + final String headerName = criterion.getHeaderName(); + final boolean result; + if (operator instanceof SearchQuery.DateOperator) { + result = matches((SearchQuery.DateOperator) operator, headerName, message); + } else if (operator instanceof SearchQuery.ContainsOperator) { + result = matches((SearchQuery.ContainsOperator) operator, headerName, message); + } else if (operator instanceof SearchQuery.ExistsOperator) { + result = exists(headerName, message); + } else if (operator instanceof SearchQuery.AddressOperator) { + result = matchesAddress((SearchQuery.AddressOperator) operator, headerName, message, log); + } else { + throw new UnsupportedSearchException(); + } + return result; + } + + /** + * Match against a {@link AddressType} header + * + * @param operator + * @param headerName + * @param message + * @return containsAddress + * @throws MailboxException + * @throws IOException + */ + private boolean matchesAddress(final SearchQuery.AddressOperator operator, final String headerName, + final Message message, Logger log) throws MailboxException, IOException { + final String text = operator.getAddress().toUpperCase(Locale.ENGLISH); + final List
headers = ResultUtils.createHeaders(message); + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + final String value = header.getValue(); + AddressList aList = LenientAddressBuilder.DEFAULT.parseAddressList(value); + for (int i = 0; i < aList.size(); i++) { + Address address = aList.get(i); + if (address instanceof Mailbox) { + if (AddressFormatter.DEFAULT.encode((Mailbox) address).toUpperCase(Locale.ENGLISH) + .contains(text)) { + return true; + } + } else if (address instanceof Group) { + MailboxList mList = ((Group) address).getMailboxes(); + for (int a = 0; a < mList.size(); a++) { + if (AddressFormatter.DEFAULT.encode(mList.get(a)).toUpperCase(Locale.ENGLISH) + .contains(text)) { + return true; + } + } + } + } + + // Also try to match against raw header now + return value.toUpperCase(Locale.ENGLISH).contains(text); + } + } + return false; + } + + private boolean exists(String headerName, Message message) throws MailboxException, IOException { + boolean result = false; + final List
headers = ResultUtils.createHeaders(message); + + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + result = true; + break; + } + } + return result; + } + + private boolean matches(final SearchQuery.ContainsOperator operator, final String headerName, + final Message message) throws MailboxException, IOException { + final String text = operator.getValue().toUpperCase(); + boolean result = false; + final List
headers = ResultUtils.createHeaders(message); + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + final String value = header.getValue(); + if (value != null) { + if (value.toUpperCase().indexOf(text) > -1) { + result = true; + break; + } + } + } + } + return result; + } + + private boolean matches(final SearchQuery.DateOperator operator, final String headerName, final Message message) + throws MailboxException { + + final Date date = operator.getDate(); + final DateResolution res = operator.getDateResultion(); + try { + final String value = headerValue(headerName, message); + if (value == null) { + return false; + } else { + try { + final Date isoFieldValue = toISODate(value); + final SearchQuery.DateComparator type = operator.getType(); + switch (type) { + case AFTER: + return after(isoFieldValue, date, res); + case BEFORE: + return before(isoFieldValue, date, res); + case ON: + return on(isoFieldValue, date, res); + default: + throw new UnsupportedSearchException(); + } + } catch (ParseException e) { + return false; + } + } + } catch (IOException e) { + return false; + } + } + + private String headerValue(final String headerName, final Message message) throws MailboxException, IOException { + final List
headers = ResultUtils.createHeaders(message); + String value = null; + for (Header header : headers) { + final String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + value = header.getValue(); + break; + } + } + return value; + } + + private Date toISODate(String value) throws ParseException { + final StringReader reader = new StringReader(value); + final DateTime dateTime = new DateTimeParser(reader).parseAll(); + Calendar cal = getGMT(); + cal.set(dateTime.getYear(), dateTime.getMonth() - 1, dateTime.getDay(), dateTime.getHour(), + dateTime.getMinute(), dateTime.getSecond()); + return cal.getTime(); + } + + private boolean matches(SearchQuery.SizeCriterion criterion, Message message) throws UnsupportedSearchException { + final SearchQuery.NumericOperator operator = criterion.getOperator(); + final long size = message.getFullContentOctets(); + final long value = operator.getValue(); + switch (operator.getType()) { + case LESS_THAN: + return size < value; + case GREATER_THAN: + return size > value; + case EQUALS: + return size == value; + default: + throw new UnsupportedSearchException(); + } + } + + private boolean matches(SearchQuery.ModSeqCriterion criterion, Message message) + throws UnsupportedSearchException { + final SearchQuery.NumericOperator operator = criterion.getOperator(); + final long modSeq = message.getModSeq(); + final long value = operator.getValue(); + switch (operator.getType()) { + case LESS_THAN: + return modSeq < value; + case GREATER_THAN: + return modSeq > value; + case EQUALS: + return modSeq == value; + default: + throw new UnsupportedSearchException(); + } + } + + private boolean matches(SearchQuery.InternalDateCriterion criterion, Message message) + throws UnsupportedSearchException { + final SearchQuery.DateOperator operator = criterion.getOperator(); + final boolean result = matchesInternalDate(operator, message); + return result; + } + + private boolean matchesInternalDate(final SearchQuery.DateOperator operator, final Message message) + throws UnsupportedSearchException { + final Date date = operator.getDate(); + final DateResolution res = operator.getDateResultion(); + final Date internalDate = message.getInternalDate(); + final SearchQuery.DateComparator type = operator.getType(); + switch (type) { + case ON: + return on(internalDate, date, res); + case BEFORE: + return before(internalDate, date, res); + case AFTER: + return after(internalDate, date, res); + default: + throw new UnsupportedSearchException(); + } + } + + private boolean on(Date date1, final Date date2, DateResolution res) { + String d1 = createDateString(date1, res); + String d2 = createDateString(date2, res); + return d1.compareTo(d2) == 0; + } + + private boolean before(Date date1, final Date date2, DateResolution res) { + String d1 = createDateString(date1, res); + String d2 = createDateString(date2, res); + + return d1.compareTo(d2) < 0; + } + + private boolean after(Date date1, final Date date2, DateResolution res) { + String d1 = createDateString(date1, res); + String d2 = createDateString(date2, res); + + return d1.compareTo(d2) > 0; + } + + private String createDateString(Date date, DateResolution res) { + SimpleDateFormat format; + switch (res) { + case Year: + format = new SimpleDateFormat("yyyy"); + break; + case Month: + format = new SimpleDateFormat("yyyyMM"); + break; + case Day: + format = new SimpleDateFormat("yyyyMMdd"); + break; + case Hour: + format = new SimpleDateFormat("yyyyMMddhh"); + break; + case Minute: + format = new SimpleDateFormat("yyyyMMddhhmm"); + break; + case Second: + format = new SimpleDateFormat("yyyyMMddhhmmss"); + break; + default: + format = new SimpleDateFormat("yyyyMMddhhmmssSSS"); + + break; + } + format.setCalendar(getGMT()); + return format.format(date); + } + + private Calendar getGMT() { + return Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.ENGLISH); + } + + /** + * Return a {@link Iterator} which holds all uids which matched, sorted + * according to the SearchQuery + * + */ + public Iterator iterator() { + return search().iterator(); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SearchUtil.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SearchUtil.java new file mode 100644 index 0000000..c5d176b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SearchUtil.java @@ -0,0 +1,463 @@ +/**************************************************************** + * 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.search; + +import java.nio.charset.Charset; +import java.util.Locale; + +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.codec.DecoderUtil; +import org.apache.james.mime4j.dom.address.Address; +import org.apache.james.mime4j.dom.address.AddressList; +import org.apache.james.mime4j.dom.address.Group; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import org.apache.james.mime4j.field.address.LenientAddressBuilder; +import org.apache.james.mime4j.util.MimeUtil; + +/** + * Utility class which helps with extracting of data for searches + * + * + */ +public class SearchUtil { + + private final static String FWD_PARENS = "(fwd)"; + private final static String SUBJ_FWD_HDR = "[fwd:"; + private final static String SUBJ_FWD_TRL = "]"; + private final static String RE = "re"; + private final static String FWD = "fwd"; + private final static String FW = "fw"; + private final static char WS = ' '; + private final static char OPEN_SQUARE_BRACKED = '['; + private final static char CLOSE_SQUARE_BRACKED = ']'; + private final static char COLON = ':'; + + private final static Charset UTF8 = Charset.forName("UTF8"); + + + /** + * Return the DISPLAY ADDRESS for the given {@link Mailbox}. + * + * See rfc5957 3. DISPLAY Sort Value for an Address for the details + * + * For the purposes of the sort criteria defined in this document, the + * sort value for an [IMAP] address structure is defined as follows: + *

+ * o If the address structure's [IMAP] addr-name is non-NIL, apply the + * procedure from [RFC5255], Section 4.6. (That is, decode any + * RFC2047] encoded-words and convert the resulting character string + * into a charset valid for the currently active [RFC4790] collation, + * with a default of UTF-8.) If the resulting octet string is not + * the empty string, use it as the sort value for the address. + *

+ *

+ * o Otherwise, if the address structure's [IMAP] addr-mailbox and + * [IMAP] addr-host are both non-NIL, the sort value for the address + * is addr-mailbox@addr-host. + *

+ *

+ * o Otherwise, if the address structure's [IMAP] addr-mailbox is non- + * NIL, the sort value for the address is its addr-mailbox. + *

+ *

+ * o If none of the above conditions are met, the sort value for the + * address is the empty string. + *

+ * @param mailbox + * @return display + */ + public static String getDisplayAddress(Mailbox mailbox) { + String display = mailbox.getName(); + if (display == null || display.length() < 1) { + String localPart = mailbox.getLocalPart(); + String domainPart = mailbox.getDomain(); + if (domainPart != null && domainPart.length() > 0 ) { + return localPart + "@" + domainPart; + } else { + return localPart; + } + } + return display; + } + + + /** + * Parse the headerValue and delegate to {@link #getDisplayAddress(Mailbox)} + * + * If no display address is found an empty String is returned + * + * @param headerValue + * @return display + */ + public static String getDisplayAddress(String headerValue) { + AddressList addressList = LenientAddressBuilder.DEFAULT.parseAddressList(MimeUtil.unfold(headerValue)); + if (addressList != null && addressList.isEmpty() == false) { + Address address = addressList.get(0); + if (address instanceof Mailbox) { + return getDisplayAddress((Mailbox) address); + } else if (address instanceof Group) { + Group group = (Group) address; + if (group != null) { + MailboxList mList = group.getMailboxes(); + if (mList != null && mList.isEmpty() == false) { + return getDisplayAddress(mList.get(0)); + } + } + } + } + + return ""; + } + + /** + * Return addr-mailbox of the first "From" address. + * + * See RFC5256 and RFC3501 + * + * Which is in fact the LocalPart + * @param mailbox + * @return addrMailbox + */ + public static String getMailboxAddress(Mailbox mailbox) { + return mailbox.getLocalPart(); + } + + + /** + * Parse the headerValue and delegate to {@link #getMailboxAddress(Mailbox)} + * + * If no mailbox name is found an empty String is returned + * + * @param headerValue + * @return mailbox + */ + public static String getMailboxAddress(String headerValue) { + AddressList aList = LenientAddressBuilder.DEFAULT.parseAddressList(headerValue); + for (int i = 0; i < aList.size(); i++) { + Address address = aList.get(i); + if (address instanceof Mailbox) { + Mailbox m = (Mailbox) address; + String mailboxName = m.getLocalPart(); + if (mailboxName == null) { + mailboxName = ""; + } + return mailboxName; + } else if (address instanceof Group) { + MailboxList mList = ((Group) address).getMailboxes(); + for (int a = 0; a < mList.size();) { + String mailboxName = mList.get(a).getLocalPart(); + if (mailboxName == null) { + mailboxName = ""; + } + return mailboxName; + } + } + } + return ""; + } + + + /** + * Extract the base subject from the given subject. + * + * See rfc5256 2.1 Base Subject + * + * Subject sorting and threading use the "base subject", which has + * specific subject artifacts removed. Due to the complexity of these + * artifacts, the formal syntax for the subject extraction rules is + * ambiguous. The following procedure is followed to determine the + * "base subject", using the [ABNF] formal syntax rules described in + * section 5: + *

+ * (1) Convert any RFC 2047 encoded-words in the subject to [UTF-8] + * as described in "Internationalization Considerations". + * Convert all tabs and continuations to space. Convert all + * multiple spaces to a single space. + *

+ *

+ * (2) Remove all trailing text of the subject that matches the + * subj-trailer ABNF; repeat until no more matches are possible. + *

+ *

+ * (3) Remove all prefix text of the subject that matches the subj- + * leader ABNF. + *

+ *

+ * (4) If there is prefix text of the subject that matches the subj- + * blob ABNF, and removing that prefix leaves a non-empty subj- + * base, then remove the prefix text. + *

+ *

+ * (5) Repeat (3) and (4) until no matches remain. + *

+ * Note: It is possible to defer step (2) until step (6), but this + * requires checking for subj-trailer in step (4). + *
+ *

+ * (6) If the resulting text begins with the subj-fwd-hdr ABNF and + * ends with the subj-fwd-trl ABNF, remove the subj-fwd-hdr and + * subj-fwd-trl and repeat from step (2). + *

+ *

+ * (7) The resulting text is the "base subject" used in the SORT. + *

+ * + * + * @param subject + * @return baseSubject + */ + public static String getBaseSubject(String subject) { + + // (1) Convert any RFC 2047 encoded-words in the subject to [UTF-8] + // as described in "Internationalization Considerations". + // Convert all tabs and continuations to space. Convert all + // multiple spaces to a single space. + String decodedSubject = MimeUtil.unfold(DecoderUtil.decodeEncodedWords(subject, DecodeMonitor.SILENT)); + decodedSubject = new String(decodedSubject.getBytes(UTF8), UTF8); + + // replace all tabs with spaces and replace multiple spaces with one space + decodedSubject = decodedSubject.replaceAll("\t", " ").replaceAll("( ){2,}", " "); + + + while (true) { + int decodedSubjectLength = decodedSubject.length(); + while (true) { + // (2) Remove all trailing text of the subject that matches the + // subj-trailer ABNF; repeat until no more matches are possible. + String subj = removeSubTrailers(decodedSubject); + if (decodedSubjectLength > subj.length()) { + decodedSubject = subj; + decodedSubjectLength = decodedSubject.length(); + } else { + break; + } + + } + + while (true) { + boolean matchedInner = false; + + // (3) Remove all prefix text of the subject that matches the subj- + // leader ABNF. + decodedSubjectLength = decodedSubject.length(); + decodedSubject = removeSubjLeaders(decodedSubject); + if (decodedSubjectLength > decodedSubject.length()) { + matchedInner = true; + decodedSubjectLength = decodedSubject.length(); + + } + + // (4) If there is prefix text of the subject that matches the subj- + // blob ABNF, and removing that prefix leaves a non-empty subj- + // base, then remove the prefix text. + decodedSubjectLength = decodedSubject.length(); + String subj = removeBlob(decodedSubject); + + // check if it will leave a non-empty subject + if (subj.length() > 0) { + decodedSubject = subj; + if (decodedSubjectLength > decodedSubject.length()) { + matchedInner = true; + decodedSubjectLength = decodedSubject.length(); + + } + + } + // (5) Repeat (3) and (4) until no matches remain. + if (!matchedInner) { + // no more matches so break the loop + break; + } + } + String lowcaseSubject = decodedSubject.toLowerCase(Locale.US); + + if (lowcaseSubject.startsWith(SUBJ_FWD_HDR) && lowcaseSubject.endsWith(SUBJ_FWD_TRL)) { + // (6) If the resulting text begins with the subj-fwd-hdr ABNF and + // ends with the subj-fwd-trl ABNF, remove the subj-fwd-hdr and + // subj-fwd-trl and repeat from step (2). + decodedSubject = decodedSubject.substring(SUBJ_FWD_HDR.length(), decodedSubject.length() - SUBJ_FWD_TRL.length()); + decodedSubjectLength = decodedSubject.length(); + } else { + break; + } + + } + // (7) The resulting text is the "base subject" used in the SORT. + return decodedSubject; + } + + /** + * Remove the subj-blob + * + * subj-blob = "[" *BLOBCHAR "]" *WSP + * subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":" + * + * BLOBCHAR = %x01-5a / %x5c / %x5e-7f + * ; any CHAR except '[' and ']' + * + * + * @param subject + * @return sub + */ + private static String removeSubjectBlob(String subject) { + String subj = subject; + while(subj.charAt(0) == OPEN_SQUARE_BRACKED) { + int length = subj.length(); + subj = removeBlob(subject); + int i = 0; + if (subj.length() > 0 && subj.charAt(i) == CLOSE_SQUARE_BRACKED) { + i++; + } else { + return subject; + } + while (subj.charAt(i) == WS) { + i++; + } + subj = subj.substring(i); + if (length == subj.length()) { + return subj; + } + } + return subj; + } + + /** + * Remove the subj-leader + * + * subj-leader = (*subj-blob subj-refwd) / WSP + * subj-blob = "[" *BLOBCHAR "]" *WSP + * subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":" + * + * BLOBCHAR = %x01-5a / %x5c / %x5e-7f + * ; any CHAR except '[' and ']' + * + * + * @param subject + * @return sub + */ + private static String removeSubjLeaders(String subject) { + int subString = 0; + while (subject.charAt(subString) == WS) { + subString++; + } + if (subString > 0) { + // check if we have matched WSP + return subject.substring(subString); + } else { + + String subj = removeSubjectBlob(subject); + + String lowCaseSubj = subj.toLowerCase(Locale.US); + if (lowCaseSubj.startsWith(RE)) { + subString = RE.length(); + } else if (lowCaseSubj.startsWith(FWD)) { + subString = FWD.length(); + } else if (lowCaseSubj.startsWith(FW)) { + subString = FW.length(); + } else { + return subject; + } + while (subj.charAt(subString) == WS) { + subString++; + } + + /* + * subj = removeSubjectBlob(subj.substring(subString)); if + * (subj.endsWith(String.valueOf(CLOSE_SQUARE_BRACKED))) { subString + * = 1; } else { subString = 0; } + */ + + if (subj.charAt(subString) == COLON) { + subString++; + } else { + return subject; + } + + while (subj.charAt(subString) == WS) { + subString++; + } + return subj.substring(subString); + } + } + + + /** + * remove the remove_subj_trailers + * + * subj-trailer = "(fwd)" / WSP + * + * + * @param decodedSubject + * * @return sub + */ + private static String removeSubTrailers(String decodedSubject) { + int subStringStart = 0; + int subStringEnd = decodedSubject.length(); + + int originalSize = decodedSubject.length(); + int curPos = originalSize -1; + while(true) { + char c = decodedSubject.charAt(curPos--); + if (c == WS) { + subStringEnd--; + } else { + if (subStringEnd > FWD_PARENS.length() && decodedSubject.endsWith(FWD_PARENS)) { + subStringEnd -= FWD_PARENS.length(); + } + break; + } + } + decodedSubject = decodedSubject.substring(subStringStart, subStringEnd); + return decodedSubject; + } + + /** + * Remove all blobchars + * + * BLOBCHAR = %x01-5a / %x5c / %x5e-7f + * ; any CHAR except '[' and ']' + * + * @param subject + * @return subj + */ + private static String removeBlob(String subject) { + int i = 0; + char lastChar = Character.UNASSIGNED; + for (int a = 0; a < subject.length(); a++) { + char c = subject.charAt(a); + lastChar = c; + if (( a != 0 && c == OPEN_SQUARE_BRACKED) || c == CLOSE_SQUARE_BRACKED) { + break; + } + i++; + } + + if (lastChar != CLOSE_SQUARE_BRACKED) { + return subject; + } else { + // the lastChar was a ] so increase the count before substring + i++; + return subject.substring(i); + } + + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java new file mode 100644 index 0000000..8f02e71 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java @@ -0,0 +1,110 @@ +/**************************************************************** + * 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.search; + +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.model.SearchQuery.ConjunctionCriterion; +import org.apache.james.mailbox.model.SearchQuery.Criterion; +import org.apache.james.mailbox.model.SearchQuery.NumericRange; +import org.apache.james.mailbox.model.SearchQuery.UidCriterion; +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; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link MessageSearchIndex} which just fetch {@link Message}'s from the {@link MessageMapper} and use {@link MessageSearcher} + * to match them against the {@link SearchQuery}. + * + * This works with every implementation but is SLOW. + * + * + * @param + */ +public class SimpleMessageSearchIndex implements MessageSearchIndex{ + + private final MessageMapperFactory factory; + public SimpleMessageSearchIndex(MessageMapperFactory factory) { + this.factory = factory; + } + + /** + * Walks down the query tree's conjunctions to find a UidCriterion + * @param crits - list of Criterion to search from + * @return + * first UidCriterion found + * null - if not found + */ + private static UidCriterion findConjugatedUidCriterion(List crits) { + for (Criterion crit : crits) { + if (crit instanceof UidCriterion) { + return (UidCriterion) crit; + } else if (crit instanceof ConjunctionCriterion) { + return findConjugatedUidCriterion(((ConjunctionCriterion) crit) + .getCriteria()); + } + } + return null; + } + + @Override + public Iterator search(MailboxSession session, Mailbox mailbox, SearchQuery query) throws MailboxException { + MessageMapper mapper = factory.getMessageMapper(session); + + final SortedSet> hitSet = new TreeSet>(); + + UidCriterion uidCrit = findConjugatedUidCriterion(query.getCriterias()); + if (uidCrit != null) { + // if there is a conjugated uid range criterion in the query tree we can optimize by + // only fetching this uid range + NumericRange[] ranges = uidCrit.getOperator().getRange(); + for (int i = 0; i < ranges.length; i++) { + NumericRange r = ranges[i]; + Iterator> it = mapper.findInMailbox(mailbox, MessageRange.range(r.getLowValue(), r.getHighValue()), FetchType.Metadata, -1); + while(it.hasNext()) { + hitSet.add(it.next()); + } + } + } else { + // we have to fetch all messages + Iterator> messages = mapper.findInMailbox(mailbox, MessageRange.all(), FetchType.Full, -1); + while(messages.hasNext()) { + Message m = messages.next(); + hitSet.add(m); + } + } + + // MessageSearches does the filtering for us + if (session == null) { + return new MessageSearches(hitSet.iterator(), query).iterator(); + } else { + return new MessageSearches(hitSet.iterator(), query, session.getLog()).iterator(); + } + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/all-wcprops new file mode 100644 index 0000000..830b317 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/all-wcprops @@ -0,0 +1,65 @@ +K 25 +svn:wc:ra_dav:version-url +V 116 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator +END +SentDateComparator.java +K 25 +svn:wc:ra_dav:version-url +V 140 +/repos/asf/!svn/ver/1151980/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java +END +BaseSubjectComparator.java +K 25 +svn:wc:ra_dav:version-url +V 143 +/repos/asf/!svn/ver/1132468/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/BaseSubjectComparator.java +END +InternalDateComparator.java +K 25 +svn:wc:ra_dav:version-url +V 144 +/repos/asf/!svn/ver/1131138/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/InternalDateComparator.java +END +SizeComparator.java +K 25 +svn:wc:ra_dav:version-url +V 136 +/repos/asf/!svn/ver/1131145/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SizeComparator.java +END +ReverseComparator.java +K 25 +svn:wc:ra_dav:version-url +V 139 +/repos/asf/!svn/ver/1131138/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/ReverseComparator.java +END +AbstractHeaderComparator.java +K 25 +svn:wc:ra_dav:version-url +V 146 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/AbstractHeaderComparator.java +END +HeaderDisplayComparator.java +K 25 +svn:wc:ra_dav:version-url +V 145 +/repos/asf/!svn/ver/1133447/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderDisplayComparator.java +END +CombinedComparator.java +K 25 +svn:wc:ra_dav:version-url +V 140 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/CombinedComparator.java +END +UidComparator.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1131138/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/UidComparator.java +END +HeaderMailboxComparator.java +K 25 +svn:wc:ra_dav:version-url +V 145 +/repos/asf/!svn/ver/1133447/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderMailboxComparator.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/entries new file mode 100644 index 0000000..efe7f14 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/entries @@ -0,0 +1,368 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/search/comparator +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SentDateComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +c683781abcce04a10fbb37d29d795cb8 +2011-07-28T19:32:31.300573Z +1151980 +norman + + + + + + + + + + + + + + + + + + + + + +3147 + +BaseSubjectComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +0d2346765ae504402e85036742115237 +2011-06-05T18:13:34.191359Z +1132468 +norman + + + + + + + + + + + + + + + + + + + + + +2247 + +InternalDateComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +d1677c00bbd9bcae7eaf9331d79bb435 +2011-06-03T18:54:59.335893Z +1131138 +norman + + + + + + + + + + + + + + + + + + + + + +2086 + +SizeComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +dcabb350be753b99ad4b3c3f5a3dfe71 +2011-06-03T19:07:35.326109Z +1131145 +norman + + + + + + + + + + + + + + + + + + + + + +2034 + +ReverseComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +187c959c2e7d0952117155c7a921aa09 +2011-06-03T18:54:59.335893Z +1131138 +norman + + + + + + + + + + + + + + + + + + + + + +1776 + +AbstractHeaderComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +0bd61bd0d7051d9a015829c6dbfc8aa2 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2521 + +HeaderDisplayComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +9defc0e1fb29e6bb3c1d100266b56859 +2011-06-08T15:53:54.641154Z +1133447 +norman + + + + + + + + + + + + + + + + + + + + + +2750 + +CombinedComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +34fc44c294e7a334bc9c704c8c6cba44 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +4070 + +UidComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +aa6cbe4c06a06d1574688ff7fe0f4d77 +2011-06-03T18:54:59.335893Z +1131138 +norman + + + + + + + + + + + + + + + + + + + + + +1984 + +HeaderMailboxComparator.java +file + + + + +2013-09-02T02:54:39.000000Z +7d6b56e655bee8b3dab8efdd6b6ca0b0 +2011-06-08T15:53:54.641154Z +1133447 +norman + + + + + + + + + + + + + + + + + + + + + +3170 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/AbstractHeaderComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/AbstractHeaderComparator.java.svn-base new file mode 100644 index 0000000..02c8d97 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/AbstractHeaderComparator.java.svn-base @@ -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.search.comparator; + +import java.io.IOException; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultUtils; +import org.apache.james.mailbox.store.mail.model.Message; + + +public abstract class AbstractHeaderComparator implements Comparator>{ + + public final static String FROM ="from"; + public final static String TO ="to"; + public final static String CC ="cc"; + + protected String getHeaderValue(String headerName, Message message) { + try { + final List
headers = ResultUtils.createHeaders(message); + for (Header header : headers) { + try { + String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + final String value = header.getValue(); + return value.toUpperCase(Locale.ENGLISH); + } + } catch (MailboxException e) { + // skip the header line + } + + } + } catch (IOException e) { + // skip the header + } + return ""; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/BaseSubjectComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/BaseSubjectComparator.java.svn-base new file mode 100644 index 0000000..9ac19c4 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/BaseSubjectComparator.java.svn-base @@ -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.store.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.SearchUtil; + +public class BaseSubjectComparator extends AbstractHeaderComparator{ + + + + private final static Comparator> BASESUBJECT = new BaseSubjectComparator();; + private final static Comparator> REVERSE_BASESUBJECT = new ReverseComparator(BASESUBJECT); + + + + private final static String SUBJECT = "subject"; + + @Override + public int compare(Message o1, Message o2) { + String baseSubject1 = SearchUtil.getBaseSubject(getHeaderValue(SUBJECT, o1)); + String baseSubject2 = SearchUtil.getBaseSubject(getHeaderValue(SUBJECT, o2)); + + return baseSubject1.compareToIgnoreCase(baseSubject2); + } + + + public static Comparator> baseSubject(boolean reverse){ + if (reverse) { + return REVERSE_BASESUBJECT; + } else { + return BASESUBJECT; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/CombinedComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/CombinedComparator.java.svn-base new file mode 100644 index 0000000..7fee69c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/CombinedComparator.java.svn-base @@ -0,0 +1,102 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.apache.james.mailbox.model.SearchQuery.Sort; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which takes a Array of other {@link Comparator}'s and use them to compare two {@link Message} instances till one of them + * return <> 0 + * + */ +public class CombinedComparator implements Comparator>{ + + private final Comparator>[] comparators; + public CombinedComparator(Comparator>[] comparators) { + if(comparators == null || comparators.length < 1) { + throw new IllegalArgumentException(); + } + this.comparators = comparators; + } + @Override + public int compare(Message o1, Message o2) { + int i = 0; + for (int a = 0; a < comparators.length; a++) { + i = comparators[a].compare(o1, o2); + if (i != 0) { + break; + } + } + return i; + } + + @SuppressWarnings("unchecked") + public static Comparator> create(List sorts) { + List> comps = new ArrayList>(); + for (int i = 0; i < sorts.size(); i++) { + Sort sort = sorts.get(i); + boolean reverse = sort.isReverse(); + Comparator> comparator = null; + + switch (sort.getSortClause()) { + case Arrival: + comparator = InternalDateComparator.internalDate(reverse); + break; + case MailboxCc: + comparator = HeaderMailboxComparator.cc(reverse); + break; + case MailboxFrom: + comparator = HeaderMailboxComparator.from(reverse); + break; + case Size: + comparator = SizeComparator.size(reverse); + break; + case BaseSubject: + comparator = BaseSubjectComparator.baseSubject(reverse); + break; + case MailboxTo: + comparator = HeaderMailboxComparator.to(reverse); + break; + case Uid: + comparator = UidComparator.uid(reverse); + break; + case SentDate: + comparator = SentDateComparator.sentDate(reverse); + case DisplayFrom: + comparator = HeaderDisplayComparator.from(reverse); + break; + case DisplayTo: + comparator = HeaderDisplayComparator.to(reverse); + break; + default: + break; + } + if (comparator != null) { + comps.add(comparator); + } + } + return new CombinedComparator(comps.toArray(new Comparator[0])); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderDisplayComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderDisplayComparator.java.svn-base new file mode 100644 index 0000000..0830cec --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderDisplayComparator.java.svn-base @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.SearchUtil; + +public class HeaderDisplayComparator extends AbstractHeaderComparator{ + + + private final static Comparator> FROM_COMPARATOR = new HeaderDisplayComparator(FROM); + private final static Comparator> REVERSE_FROM_COMPARATOR = new ReverseComparator(FROM_COMPARATOR); + + + private final static Comparator> TO_COMPARATOR = new HeaderDisplayComparator(TO); + private final static Comparator> REVERSE_TO_COMPARATOR = new ReverseComparator(TO_COMPARATOR); + + + private String headerName; + + public HeaderDisplayComparator(String headerName) { + this.headerName = headerName; + } + + @Override + public int compare(Message o1, Message o2) { + String display1 = SearchUtil.getDisplayAddress(getHeaderValue(headerName, o1)); + String display2 = SearchUtil.getDisplayAddress(getHeaderValue(headerName, o2)); + return display1.compareToIgnoreCase(display2); + } + + + public static Comparator> from(boolean reverse) { + if (reverse) { + return REVERSE_FROM_COMPARATOR; + } else { + return FROM_COMPARATOR; + } + } + + + public static Comparator> to(boolean reverse) { + if (reverse) { + return REVERSE_TO_COMPARATOR; + } else { + return TO_COMPARATOR; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderMailboxComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderMailboxComparator.java.svn-base new file mode 100644 index 0000000..440be21 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/HeaderMailboxComparator.java.svn-base @@ -0,0 +1,80 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.SearchUtil; + +public class HeaderMailboxComparator extends AbstractHeaderComparator{ + + private final String headerName; + + private final static Comparator> FROM_COMPARATOR = new HeaderMailboxComparator(FROM); + private final static Comparator> REVERSE_FROM_COMPARATOR = new ReverseComparator(FROM_COMPARATOR); + + + private final static Comparator> TO_COMPARATOR = new HeaderMailboxComparator(TO); + private final static Comparator> REVERSE_TO_COMPARATOR = new ReverseComparator(TO_COMPARATOR); + + + private final static Comparator> CC_COMPARATOR = new HeaderMailboxComparator(CC); + private final static Comparator> REVERSE_CC_COMPARATOR = new ReverseComparator(CC_COMPARATOR); + + + public HeaderMailboxComparator(String headerName) { + this.headerName = headerName; + } + + @Override + public int compare(Message o1, Message o2) { + String mailbox1 = SearchUtil.getMailboxAddress(getHeaderValue(headerName, o1)); + String mailbox2 = SearchUtil.getMailboxAddress(getHeaderValue(headerName, o2)); + + return mailbox1.compareToIgnoreCase(mailbox2); + } + + + + public static Comparator> from(boolean reverse) { + if (reverse) { + return REVERSE_FROM_COMPARATOR; + } else { + return FROM_COMPARATOR; + } + } + + public static Comparator> cc(boolean reverse) { + if (reverse) { + return REVERSE_CC_COMPARATOR; + } else { + return CC_COMPARATOR; + } + } + + public static Comparator> to(boolean reverse) { + if (reverse) { + return REVERSE_TO_COMPARATOR; + } else { + return TO_COMPARATOR; + } + } +} + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/InternalDateComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/InternalDateComparator.java.svn-base new file mode 100644 index 0000000..ed63537 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/InternalDateComparator.java.svn-base @@ -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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which compares {@link Message}'s with their {@link Message#getInternalDate()} value + * + */ +public class InternalDateComparator implements Comparator>{ + + + private final static Comparator> INTERNALDATE = new InternalDateComparator();; + private final static Comparator> REVERSE_INTERNALDATE = new ReverseComparator(INTERNALDATE); + + + @Override + public int compare(Message o1, Message o2) { + return (o1.getInternalDate().compareTo(o2.getInternalDate())); + } + + public static Comparator> internalDate(boolean reverse){ + if (reverse) { + return REVERSE_INTERNALDATE; + } else { + return INTERNALDATE; + } + } +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/ReverseComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/ReverseComparator.java.svn-base new file mode 100644 index 0000000..2da8e6b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/ReverseComparator.java.svn-base @@ -0,0 +1,40 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which wraps an other {@link Comparator} and reverse it + * + */ +public class ReverseComparator implements Comparator>{ + + private final Comparator> comparator; + public ReverseComparator(Comparator> comparator) { + this.comparator = comparator; + } + @Override + public int compare(Message o1, Message o2) { + return comparator.compare(o2, o1); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SentDateComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SentDateComparator.java.svn-base new file mode 100644 index 0000000..e7cd330 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SentDateComparator.java.svn-base @@ -0,0 +1,80 @@ +/**************************************************************** + * 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.search.comparator; + +import java.io.StringReader; +import java.util.Comparator; +import java.util.Date; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mime4j.dom.datetime.DateTime; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.field.datetime.parser.ParseException; + +/** + * {@link Comparator} which works like stated in RFC5256 2.2 Sent Date + * + */ +public class SentDateComparator extends AbstractHeaderComparator { + + + + private final static Comparator> SENTDATE = new SentDateComparator(false); + private final static Comparator> REVERSE_SENTDATE = new ReverseComparator(new SentDateComparator(true)); + + private final boolean reverse; + + public SentDateComparator(boolean reverse) { + this.reverse = reverse; + } + + @Override + public int compare(Message o1, Message o2) { + Date date1 = getSentDate(o1); + Date date2 = getSentDate(o2); + int i = date1.compareTo(date2); + + // sent date was the same so use the uid as tie-breaker + if (i == 0) { + return UidComparator.uid(reverse).compare(o1, o2); + } + return 0; + } + + private Date getSentDate(Message message) { + final String value = getHeaderValue("Date", message); + final StringReader reader = new StringReader(value); + try { + DateTime dateTime = new DateTimeParser(reader).parseAll(); + return dateTime.getDate(); + } catch (ParseException e) { + // if we can not parse the date header we should use the internaldate as fallback + return message.getInternalDate(); + } + } + + public static Comparator> sentDate(boolean reverse){ + if (reverse) { + return REVERSE_SENTDATE; + } else { + return SENTDATE; + } + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SizeComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SizeComparator.java.svn-base new file mode 100644 index 0000000..b354727 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/SizeComparator.java.svn-base @@ -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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which compares {@link Message}'s with their {@link Message#getFullContentOctets()} value + * + */ +public class SizeComparator implements Comparator>{ + + + private final static Comparator> SIZE = new SizeComparator(); + private final static Comparator> REVERSE_SIZE = new ReverseComparator(SIZE); + + + @Override + public int compare(Message o1, Message o2) { + return (int) (o1.getFullContentOctets() - o2.getFullContentOctets()); + } + + public static Comparator> size(boolean reverse) { + if (reverse) { + return REVERSE_SIZE; + } else { + return SIZE; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/UidComparator.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/UidComparator.java.svn-base new file mode 100644 index 0000000..ef29c60 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/.svn/text-base/UidComparator.java.svn-base @@ -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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which compares {@link Message}'s with their {@link Message#getUid()} value + * + */ +public class UidComparator implements Comparator>{ + + + private final static Comparator> UID = new UidComparator();; + private final static Comparator> REVERSE_UID = new ReverseComparator(UID); + + + @Override + public int compare(Message o1, Message o2) { + return (int) (o1.getUid() - o2.getUid()); + } + + public static Comparator> uid(boolean reverse){ + if (reverse) { + return REVERSE_UID; + } else { + return UID; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/AbstractHeaderComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/AbstractHeaderComparator.java new file mode 100644 index 0000000..02c8d97 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/AbstractHeaderComparator.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.search.comparator; + +import java.io.IOException; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultUtils; +import org.apache.james.mailbox.store.mail.model.Message; + + +public abstract class AbstractHeaderComparator implements Comparator>{ + + public final static String FROM ="from"; + public final static String TO ="to"; + public final static String CC ="cc"; + + protected String getHeaderValue(String headerName, Message message) { + try { + final List
headers = ResultUtils.createHeaders(message); + for (Header header : headers) { + try { + String name = header.getName(); + if (headerName.equalsIgnoreCase(name)) { + final String value = header.getValue(); + return value.toUpperCase(Locale.ENGLISH); + } + } catch (MailboxException e) { + // skip the header line + } + + } + } catch (IOException e) { + // skip the header + } + return ""; + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/BaseSubjectComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/BaseSubjectComparator.java new file mode 100644 index 0000000..9ac19c4 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/BaseSubjectComparator.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.store.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.SearchUtil; + +public class BaseSubjectComparator extends AbstractHeaderComparator{ + + + + private final static Comparator> BASESUBJECT = new BaseSubjectComparator();; + private final static Comparator> REVERSE_BASESUBJECT = new ReverseComparator(BASESUBJECT); + + + + private final static String SUBJECT = "subject"; + + @Override + public int compare(Message o1, Message o2) { + String baseSubject1 = SearchUtil.getBaseSubject(getHeaderValue(SUBJECT, o1)); + String baseSubject2 = SearchUtil.getBaseSubject(getHeaderValue(SUBJECT, o2)); + + return baseSubject1.compareToIgnoreCase(baseSubject2); + } + + + public static Comparator> baseSubject(boolean reverse){ + if (reverse) { + return REVERSE_BASESUBJECT; + } else { + return BASESUBJECT; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/CombinedComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/CombinedComparator.java new file mode 100644 index 0000000..7fee69c --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/CombinedComparator.java @@ -0,0 +1,102 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.apache.james.mailbox.model.SearchQuery.Sort; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which takes a Array of other {@link Comparator}'s and use them to compare two {@link Message} instances till one of them + * return <> 0 + * + */ +public class CombinedComparator implements Comparator>{ + + private final Comparator>[] comparators; + public CombinedComparator(Comparator>[] comparators) { + if(comparators == null || comparators.length < 1) { + throw new IllegalArgumentException(); + } + this.comparators = comparators; + } + @Override + public int compare(Message o1, Message o2) { + int i = 0; + for (int a = 0; a < comparators.length; a++) { + i = comparators[a].compare(o1, o2); + if (i != 0) { + break; + } + } + return i; + } + + @SuppressWarnings("unchecked") + public static Comparator> create(List sorts) { + List> comps = new ArrayList>(); + for (int i = 0; i < sorts.size(); i++) { + Sort sort = sorts.get(i); + boolean reverse = sort.isReverse(); + Comparator> comparator = null; + + switch (sort.getSortClause()) { + case Arrival: + comparator = InternalDateComparator.internalDate(reverse); + break; + case MailboxCc: + comparator = HeaderMailboxComparator.cc(reverse); + break; + case MailboxFrom: + comparator = HeaderMailboxComparator.from(reverse); + break; + case Size: + comparator = SizeComparator.size(reverse); + break; + case BaseSubject: + comparator = BaseSubjectComparator.baseSubject(reverse); + break; + case MailboxTo: + comparator = HeaderMailboxComparator.to(reverse); + break; + case Uid: + comparator = UidComparator.uid(reverse); + break; + case SentDate: + comparator = SentDateComparator.sentDate(reverse); + case DisplayFrom: + comparator = HeaderDisplayComparator.from(reverse); + break; + case DisplayTo: + comparator = HeaderDisplayComparator.to(reverse); + break; + default: + break; + } + if (comparator != null) { + comps.add(comparator); + } + } + return new CombinedComparator(comps.toArray(new Comparator[0])); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderDisplayComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderDisplayComparator.java new file mode 100644 index 0000000..0830cec --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderDisplayComparator.java @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.SearchUtil; + +public class HeaderDisplayComparator extends AbstractHeaderComparator{ + + + private final static Comparator> FROM_COMPARATOR = new HeaderDisplayComparator(FROM); + private final static Comparator> REVERSE_FROM_COMPARATOR = new ReverseComparator(FROM_COMPARATOR); + + + private final static Comparator> TO_COMPARATOR = new HeaderDisplayComparator(TO); + private final static Comparator> REVERSE_TO_COMPARATOR = new ReverseComparator(TO_COMPARATOR); + + + private String headerName; + + public HeaderDisplayComparator(String headerName) { + this.headerName = headerName; + } + + @Override + public int compare(Message o1, Message o2) { + String display1 = SearchUtil.getDisplayAddress(getHeaderValue(headerName, o1)); + String display2 = SearchUtil.getDisplayAddress(getHeaderValue(headerName, o2)); + return display1.compareToIgnoreCase(display2); + } + + + public static Comparator> from(boolean reverse) { + if (reverse) { + return REVERSE_FROM_COMPARATOR; + } else { + return FROM_COMPARATOR; + } + } + + + public static Comparator> to(boolean reverse) { + if (reverse) { + return REVERSE_TO_COMPARATOR; + } else { + return TO_COMPARATOR; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderMailboxComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderMailboxComparator.java new file mode 100644 index 0000000..440be21 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/HeaderMailboxComparator.java @@ -0,0 +1,80 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.SearchUtil; + +public class HeaderMailboxComparator extends AbstractHeaderComparator{ + + private final String headerName; + + private final static Comparator> FROM_COMPARATOR = new HeaderMailboxComparator(FROM); + private final static Comparator> REVERSE_FROM_COMPARATOR = new ReverseComparator(FROM_COMPARATOR); + + + private final static Comparator> TO_COMPARATOR = new HeaderMailboxComparator(TO); + private final static Comparator> REVERSE_TO_COMPARATOR = new ReverseComparator(TO_COMPARATOR); + + + private final static Comparator> CC_COMPARATOR = new HeaderMailboxComparator(CC); + private final static Comparator> REVERSE_CC_COMPARATOR = new ReverseComparator(CC_COMPARATOR); + + + public HeaderMailboxComparator(String headerName) { + this.headerName = headerName; + } + + @Override + public int compare(Message o1, Message o2) { + String mailbox1 = SearchUtil.getMailboxAddress(getHeaderValue(headerName, o1)); + String mailbox2 = SearchUtil.getMailboxAddress(getHeaderValue(headerName, o2)); + + return mailbox1.compareToIgnoreCase(mailbox2); + } + + + + public static Comparator> from(boolean reverse) { + if (reverse) { + return REVERSE_FROM_COMPARATOR; + } else { + return FROM_COMPARATOR; + } + } + + public static Comparator> cc(boolean reverse) { + if (reverse) { + return REVERSE_CC_COMPARATOR; + } else { + return CC_COMPARATOR; + } + } + + public static Comparator> to(boolean reverse) { + if (reverse) { + return REVERSE_TO_COMPARATOR; + } else { + return TO_COMPARATOR; + } + } +} + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/InternalDateComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/InternalDateComparator.java new file mode 100644 index 0000000..ed63537 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/InternalDateComparator.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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which compares {@link Message}'s with their {@link Message#getInternalDate()} value + * + */ +public class InternalDateComparator implements Comparator>{ + + + private final static Comparator> INTERNALDATE = new InternalDateComparator();; + private final static Comparator> REVERSE_INTERNALDATE = new ReverseComparator(INTERNALDATE); + + + @Override + public int compare(Message o1, Message o2) { + return (o1.getInternalDate().compareTo(o2.getInternalDate())); + } + + public static Comparator> internalDate(boolean reverse){ + if (reverse) { + return REVERSE_INTERNALDATE; + } else { + return INTERNALDATE; + } + } +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/ReverseComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/ReverseComparator.java new file mode 100644 index 0000000..2da8e6b --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/ReverseComparator.java @@ -0,0 +1,40 @@ +/**************************************************************** + * 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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which wraps an other {@link Comparator} and reverse it + * + */ +public class ReverseComparator implements Comparator>{ + + private final Comparator> comparator; + public ReverseComparator(Comparator> comparator) { + this.comparator = comparator; + } + @Override + public int compare(Message o1, Message o2) { + return comparator.compare(o2, o1); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java new file mode 100644 index 0000000..e7cd330 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java @@ -0,0 +1,80 @@ +/**************************************************************** + * 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.search.comparator; + +import java.io.StringReader; +import java.util.Comparator; +import java.util.Date; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mime4j.dom.datetime.DateTime; +import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; +import org.apache.james.mime4j.field.datetime.parser.ParseException; + +/** + * {@link Comparator} which works like stated in RFC5256 2.2 Sent Date + * + */ +public class SentDateComparator extends AbstractHeaderComparator { + + + + private final static Comparator> SENTDATE = new SentDateComparator(false); + private final static Comparator> REVERSE_SENTDATE = new ReverseComparator(new SentDateComparator(true)); + + private final boolean reverse; + + public SentDateComparator(boolean reverse) { + this.reverse = reverse; + } + + @Override + public int compare(Message o1, Message o2) { + Date date1 = getSentDate(o1); + Date date2 = getSentDate(o2); + int i = date1.compareTo(date2); + + // sent date was the same so use the uid as tie-breaker + if (i == 0) { + return UidComparator.uid(reverse).compare(o1, o2); + } + return 0; + } + + private Date getSentDate(Message message) { + final String value = getHeaderValue("Date", message); + final StringReader reader = new StringReader(value); + try { + DateTime dateTime = new DateTimeParser(reader).parseAll(); + return dateTime.getDate(); + } catch (ParseException e) { + // if we can not parse the date header we should use the internaldate as fallback + return message.getInternalDate(); + } + } + + public static Comparator> sentDate(boolean reverse){ + if (reverse) { + return REVERSE_SENTDATE; + } else { + return SENTDATE; + } + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SizeComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SizeComparator.java new file mode 100644 index 0000000..b354727 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SizeComparator.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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which compares {@link Message}'s with their {@link Message#getFullContentOctets()} value + * + */ +public class SizeComparator implements Comparator>{ + + + private final static Comparator> SIZE = new SizeComparator(); + private final static Comparator> REVERSE_SIZE = new ReverseComparator(SIZE); + + + @Override + public int compare(Message o1, Message o2) { + return (int) (o1.getFullContentOctets() - o2.getFullContentOctets()); + } + + public static Comparator> size(boolean reverse) { + if (reverse) { + return REVERSE_SIZE; + } else { + return SIZE; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/UidComparator.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/UidComparator.java new file mode 100644 index 0000000..ef29c60 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/UidComparator.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.search.comparator; + +import java.util.Comparator; + +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Comparator} which compares {@link Message}'s with their {@link Message#getUid()} value + * + */ +public class UidComparator implements Comparator>{ + + + private final static Comparator> UID = new UidComparator();; + private final static Comparator> REVERSE_UID = new ReverseComparator(UID); + + + @Override + public int compare(Message o1, Message o2) { + return (int) (o1.getUid() - o2.getUid()); + } + + public static Comparator> uid(boolean reverse){ + if (reverse) { + return REVERSE_UID; + } else { + return UID; + } + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops new file mode 100644 index 0000000..cdb583a --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops @@ -0,0 +1,47 @@ +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming +END +BodyOffsetInputStream.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStream.java +END +FullByteContent.java +K 25 +svn:wc:ra_dav:version-url +V 129 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming/FullByteContent.java +END +CountingInputStream.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming/CountingInputStream.java +END +InputStreamContent.java +K 25 +svn:wc:ra_dav:version-url +V 132 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming/InputStreamContent.java +END +LimitingFileInputStream.java +K 25 +svn:wc:ra_dav:version-url +V 137 +/repos/asf/!svn/ver/1161215/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming/LimitingFileInputStream.java +END +PartContentBuilder.java +K 25 +svn:wc:ra_dav:version-url +V 132 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming/PartContentBuilder.java +END +ByteContent.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming/ByteContent.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/entries new file mode 100644 index 0000000..5c42c12 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/entries @@ -0,0 +1,266 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/streaming +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +BodyOffsetInputStream.java +file + + + + +2013-09-02T02:54:39.000000Z +e294930352c82ce9d2ea62da6af29f06 +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +4661 + +FullByteContent.java +file + + + + +2013-09-02T02:54:39.000000Z +429627a2dcb8389384cfcb66e8710445 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +3557 + +CountingInputStream.java +file + + + + +2013-09-02T02:54:39.000000Z +1dc3b8509a1ac4855e372435b7eee418 +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +2498 + +InputStreamContent.java +file + + + + +2013-09-02T02:54:39.000000Z +7b4c8f34c03cc765021e79ec44a8ce29 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2538 + +LimitingFileInputStream.java +file + + + + +2013-09-02T02:54:39.000000Z +c747d6a0bc1d7de475cedcac924ca3e6 +2011-08-24T18:12:17.839271Z +1161215 +norman +has-props + + + + + + + + + + + + + + + + + + + + +8085 + +PartContentBuilder.java +file + + + + +2013-09-02T02:54:39.000000Z +e13f0c8704e33c4d64e0e8dd371bddb8 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +10806 + +ByteContent.java +file + + + + +2013-09-02T02:54:39.000000Z +00abb0e055d2fc35c05c566161c76324 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1898 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/prop-base/LimitingFileInputStream.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/prop-base/LimitingFileInputStream.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/prop-base/LimitingFileInputStream.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStream.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStream.java.svn-base new file mode 100644 index 0000000..706fea6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStream.java.svn-base @@ -0,0 +1,159 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; + +/** + * {@link InputStream} which helps to keep track of the BodyOffset of the wrapped + * {@link InputStream} + * + * IMPORTANT: This class is not thread-safe! + * + */ +public class BodyOffsetInputStream extends InputStream{ + private long count = 0; + private long bodyStartOctet = -1; + private PushbackInputStream in; + private long readBytes = 0; + + public BodyOffsetInputStream(InputStream in) { + // we need to pushback at max 3 bytes + this.in = new PushbackInputStream(in, 3); + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + int i = in.read(); + if (i != -1) { + readBytes++; + if (bodyStartOctet == -1 && i == 0x0D) { + int a = in.read(); + if (a == 0x0A) { + int b = in.read(); + + if (b == 0x0D) { + int c = in.read(); + + if (c == 0x0A) { + bodyStartOctet = count+4; + } + in.unread(c); + } + in.unread(b); + } + in.unread(a); + } + count++; + } + return i; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (bodyStartOctet == -1) { + return super.read(b, off, len); + } else { + int r = in.read(b, off, len); + if (r != -1) { + readBytes += r; + } + return r; + } + } + + @Override + public int read(byte[] b) throws IOException { + if (bodyStartOctet == -1) { + return super.read(b); + } else { + int r = in.read(b); + if (r != -1) { + readBytes += r; + } + return r; + } + } + + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public void mark(int readlimit) { + + } + + /** + * Mark is not supported by this implementation + */ + public boolean markSupported() { + return false; + } + + /** + * Throws {@link IOException} + */ + public void reset() throws IOException { + throw new IOException("Not supported"); + } + + @Override + public long skip(long n) throws IOException { + long i = 0; + while(i++ < n) { + if (read() == -1) { + break; + } + } + return i; + } + + /** + * Return the bodyStartOffset or -1 if it could not be found. + * Be aware you can only expect some valid result from the method + * if you have consumed the whole InputStream or if you are + * sure that you reached the body + * + * @return offset + */ + public long getBodyStartOffset() { + return bodyStartOctet; + } + + /** + * Return the read bytes so far + * + * @return readBytes + */ + public long getReadBytes() { + return readBytes; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/ByteContent.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/ByteContent.java.svn-base new file mode 100644 index 0000000..ab11da7 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/ByteContent.java.svn-base @@ -0,0 +1,55 @@ +/**************************************************************** + * 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.streaming; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mailbox.model.Content; + +public final class ByteContent implements Content { + + private final byte[] contents; + + private final long size; + + public ByteContent(final byte[] contents) { + this.contents = contents; + size = contents.length; + } + + /** + * @see org.apache.james.mailbox.model.Content#size() + */ + public long size() { + return size; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(contents); + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/CountingInputStream.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/CountingInputStream.java.svn-base new file mode 100644 index 0000000..d7c291f --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/CountingInputStream.java.svn-base @@ -0,0 +1,85 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; + +/** + * {@link InputStream} implementation which just consume the the wrapped {@link InputStream} and count + * the lines which are contained within the wrapped stream + * + * + */ +final public class CountingInputStream extends InputStream { + + private final InputStream in; + + private int lineCount; + + private int octetCount; + + public CountingInputStream(InputStream in) { + super(); + this.in = in; + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + int next = in.read(); + if (next > 0) { + octetCount++; + if (next == '\r') { + lineCount++; + } + } + return next; + } + + /** + * Return the line count + * + * @return lineCount + */ + public int getLineCount() { + return lineCount; + } + + /** + * Return the octet count + * + * @return octetCount + */ + public int getOctetCount() { + return octetCount; + } + + /** + * Reads - and discards - the rest of the stream + * @throws IOException + */ + public void readAll() throws IOException { + while (read()>0); + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/FullByteContent.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/FullByteContent.java.svn-base new file mode 100644 index 0000000..7bfb5cf --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/FullByteContent.java.svn-base @@ -0,0 +1,90 @@ +/**************************************************************** + * 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.streaming; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.util.Iterator; +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.Header; + +/** + * Abstract base class for {@link Content} implementations which hold the headers and + * the body a email + * + */ +public class FullByteContent implements Content { + + + private List
headers; + private byte[] body; + private long size; + + public FullByteContent(final byte[] body, final List headers) throws MailboxException { + this.headers = headers; + this.body = body; + this.size = caculateSize(); + } + + protected long caculateSize() throws MailboxException{ + long result = body.length; + result += 2; + for (final Iterator it = headers.iterator(); it.hasNext();) { + final MessageResult.Header header = it.next(); + if (header != null) { + result += header.size(); + result += 2; + } + } + return result; + } + + @Override + public InputStream getInputStream() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (final Iterator it = headers.iterator(); it.hasNext();) { + final MessageResult.Header header = it.next(); + if (header != null) { + try { + out.write((header.getName() + ": " + header.getValue() + "\r\n").getBytes("US-ASCII")); + } catch (MailboxException e) { + throw new IOException("Unable to read headers", e); + } + } + } + out.write("\r\n".getBytes("US-ASCII")); + out.flush(); + return new SequenceInputStream(new ByteArrayInputStream(out.toByteArray()), new ByteArrayInputStream(body)); + } + + @Override + public long size() { + return size; + } + + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/InputStreamContent.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/InputStreamContent.java.svn-base new file mode 100644 index 0000000..53e532e --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/InputStreamContent.java.svn-base @@ -0,0 +1,72 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Content} which is stored in a {@link InputStream} + * + */ +public final class InputStreamContent implements Content{ + private Message m; + private Type type; + + public static enum Type { + Full, + Body + } + + public InputStreamContent(Message m, Type type) throws IOException{ + this.m = m; + this.type = type; + } + + /** + * @see org.apache.james.mailbox.model.Content#size() + */ + public long size() { + switch (type) { + case Full: + return m.getFullContentOctets(); + + default: + return m.getBodyOctets(); + } + } + + /** + * @see org.apache.james.mailbox.model.InputStreamContent#getInputStream() + */ + public InputStream getInputStream() throws IOException { + // wrap the streams in a BoundedInputStream to make sure it really match with the stored size. + switch (type) { + case Full: + return m.getFullContent(); + default: + return m.getBodyContent(); + } + + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/LimitingFileInputStream.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/LimitingFileInputStream.java.svn-base new file mode 100644 index 0000000..59784e6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/LimitingFileInputStream.java.svn-base @@ -0,0 +1,276 @@ +/**************************************************************** + * 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.streaming; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +public class LimitingFileInputStream extends FileInputStream{ + private long pos = 0; + private final long limit; + + public LimitingFileInputStream(File file, long limit) throws FileNotFoundException { + super(file); + this.limit = limit; + } + + public LimitingFileInputStream(FileDescriptor fdObj, long limit) { + super(fdObj); + this.limit = limit; + + } + + public LimitingFileInputStream(String name, long limit) throws FileNotFoundException { + super(name); + this.limit = limit; + + } + + @Override + public int read() throws IOException { + if (pos >= limit) { + return -1; + } + pos++; + return super.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (pos >= limit) { + return -1; + } + int readLimit; + if (pos + len >= limit) { + readLimit = (int) limit - (int) pos; + } else { + readLimit = len; + } + + int i = super.read(b, off, readLimit); + pos += i; + return i; + } + + @Override + public long skip(long n) throws IOException { + long currentPos = pos; + long i = super.skip(n); + if (currentPos == pos) { + pos += i; + } + return i; + } + + @Override + public int available() throws IOException { + int i = super.available(); + if (i == -1) { + return -1; + } else { + if (i >= limit) { + return (int) limit - (int) pos; + } else { + return i; + } + } + } + + + /** + * Return the limit + * + * @return limit + */ + public long getLimit() { + return limit; + } + + /** + * Return a READ-ONLY {@link FileChannel} which is limited also in the size + * + * @return channel + */ + @Override + public FileChannel getChannel() { + return new LimitingFileChannel(super.getChannel()); + } + + + /** + * A {@link FileChannel} implementation which wrapps another {@link FileChannel} and limit the access to it + * + * + */ + private final class LimitingFileChannel extends FileChannel { + + private final FileChannel channel; + + public LimitingFileChannel(FileChannel channel) { + this.channel = channel; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + int bufLimit = dst.limit(); + int left = (int) bytesLeft(); + int r; + if (bufLimit > left) { + dst.limit(left); + r = channel.read(dst); + dst.limit(bufLimit); + } else { + r = channel.read(dst); + } + return r; + + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + long r = 0; + for (int a = offset; a < length; a++) { + r += read(dsts[a]); + } + + return r; + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public long position() throws IOException { + return channel.position(); + } + + @Override + public FileChannel position(long newPosition) throws IOException { + if (newPosition <= limit) { + channel.position(newPosition); + } + return LimitingFileChannel.this ; + } + + @Override + public long size() throws IOException { + return limit; + } + + @Override + public FileChannel truncate(long size) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public void force(boolean metaData) throws IOException { + channel.force(metaData); + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + if (position > limit) { + return 0; + } else { + long left = bytesLeft(); + + if (count > left) { + count = left; + } + return channel.transferTo(position, count, target); + } + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + if (position > size()) { + return 0; + } + int bufLimit = dst.limit(); + int left = (int) bytesLeft(); + int r; + if (bufLimit > left) { + dst.limit(left); + r = channel.read(dst, position); + dst.limit(bufLimit); + } else { + r = channel.read(dst, position); + } + return r; + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + throw new IOException("Read-Only FileChannel"); + + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + return channel.map(mode, position, size); + } + + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + return channel.lock(position, size, shared); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + return channel.tryLock(position, size, shared); + } + + @Override + protected void implCloseChannel() throws IOException { + channel.close(); + } + + private long bytesLeft() throws IOException { + return limit - position(); + } + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/PartContentBuilder.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/PartContentBuilder.java.svn-base new file mode 100644 index 0000000..da89da3 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/.svn/text-base/PartContentBuilder.java.svn-base @@ -0,0 +1,301 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultHeader; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + + +public class PartContentBuilder { + + private static final byte[] EMPTY = {}; + + private MimeTokenStream parser; + + private boolean empty = false; + + private boolean topLevel = true; + + public PartContentBuilder() { + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + parser = new MimeTokenStream(config); + } + + public void markEmpty() { + empty = true; + } + + public void parse(final InputStream in) { + + parser.setRecursionMode(RecursionMode.M_RECURSE); + parser.parse(in); + topLevel = true; + } + + private void skipToStartOfInner(int position) throws IOException, MimeException { + final EntityState state = parser.next(); + switch (state) { + case T_START_MULTIPART: + break; + case T_START_MESSAGE: + break; + case T_END_OF_STREAM: + throw new PartNotFoundException(position); + case T_END_BODYPART: + throw new PartNotFoundException(position); + default: + skipToStartOfInner(position); + } + } + + public void to(int position) throws IOException, MimeException { + try { + if (topLevel) { + topLevel = false; + } else { + skipToStartOfInner(position); + } + for (int count = 0; count < position;) { + final EntityState state = parser.next(); + switch (state) { + case T_BODY: + if (position == 1) { + count++; + } + break; + case T_START_BODYPART: + count++; + break; + case T_START_MULTIPART: + if (count > 0 && count < position) { + ignoreInnerMessage(); + } + break; + case T_END_OF_STREAM: + throw new PartNotFoundException(position); + } + } + } catch (IllegalStateException e) { + throw new PartNotFoundException(position, e); + } + } + + private void ignoreInnerMessage() throws IOException, UnexpectedEOFException, MimeException { + for (EntityState state = parser.next(); state != EntityState.T_END_MULTIPART; state = parser + .next()) { + switch (state) { + case T_END_OF_STREAM: + throw new UnexpectedEOFException(); + + case T_START_MULTIPART: + ignoreInnerMessage(); + break; + } + } + } + + public Content getFullContent() throws IOException, UnexpectedEOFException, MimeException, MailboxException { + final List
headers = getMimeHeaders(); + final byte[] content = mimeBodyContent(); + return new FullByteContent(content, headers); + } + + public Content getMessageBodyContent() throws IOException, MimeException { + final byte[] content = messageBodyContent(); + return new ByteContent(content); + } + + private byte[] messageBodyContent() throws IOException, MimeException { + final byte[] content; + if (empty) { + content = EMPTY; + } else { + boolean valid; + try { + advancedToMessage(); + valid = true; + } catch (UnexpectedEOFException e) { + // No TEXT part + valid = false; + } + if (valid) { + parser.setRecursionMode(RecursionMode.M_FLAT); + for (EntityState state = parser.getState(); state != EntityState.T_BODY + && state != EntityState.T_START_MULTIPART; state = parser + .next()) { + if (state == EntityState.T_END_OF_STREAM) { + valid = false; + break; + } + } + if (valid) { + content = IOUtils.toByteArray(parser.getInputStream()); + } else { + content = EMPTY; + } + } else { + content = EMPTY; + } + } + return content; + } + + public Content getMimeBodyContent() throws IOException, MimeException { + final byte[] content = mimeBodyContent(); + return new ByteContent(content); + } + + private byte[] mimeBodyContent() throws IOException, MimeException { + final byte[] content; + if (empty) { + content = EMPTY; + } else { + parser.setRecursionMode(RecursionMode.M_FLAT); + boolean valid = true; + for (EntityState state = parser.getState(); state != EntityState.T_BODY + && state != EntityState.T_START_MULTIPART; state = parser + .next()) { + if (state == EntityState.T_END_OF_STREAM) { + valid = false; + break; + } + } + if (valid) { + content = IOUtils.toByteArray(parser.getInputStream()); + } else { + content = EMPTY; + } + } + return content; + } + + @SuppressWarnings("unchecked") + public List getMimeHeaders() throws IOException, UnexpectedEOFException, MimeException { + final List results; + if (empty) { + results = Collections.EMPTY_LIST; + } else { + results = new ArrayList(); + for (EntityState state = parser.getState(); state != EntityState.T_END_HEADER; state = parser + .next()) { + switch (state) { + case T_END_OF_STREAM: + throw new UnexpectedEOFException(); + + case T_FIELD: + final String fieldValue = parser.getField().getBody().trim(); + final String fieldName = parser.getField().getName(); + ResultHeader header = new ResultHeader(fieldName, fieldValue); + results.add(header); + break; + } + } + } + return results; + } + + @SuppressWarnings("unchecked") + public List getMessageHeaders() throws IOException, MimeException { + final List results; + if (empty) { + results = Collections.EMPTY_LIST; + } else { + results = new ArrayList(); + try { + advancedToMessage(); + + for (EntityState state = parser.getState(); state != EntityState.T_END_HEADER; state = parser + .next()) { + switch (state) { + case T_END_OF_STREAM: + throw new IOException("Unexpected EOF"); + + case T_FIELD: + final String fieldValue = parser.getField().getBody().trim(); + final String fieldName = parser.getField().getName(); + ResultHeader header = new ResultHeader(fieldName, fieldValue); + results.add(header); + break; + } + } + } catch (UnexpectedEOFException e) { + // No headers found + } + } + return results; + } + + private void advancedToMessage() throws IOException, UnexpectedEOFException, MimeException { + for (EntityState state = parser.getState(); state != EntityState.T_START_MESSAGE; state = parser + .next()) { + if (state == EntityState.T_END_OF_STREAM) { + throw new UnexpectedEOFException(); + } + } + } + + public static final class UnexpectedEOFException extends MimeException { + + private static final long serialVersionUID = -3755637466593055796L; + + public UnexpectedEOFException() { + super("Unexpected EOF"); + } + } + + public static final class PartNotFoundException extends MimeException { + + private static final long serialVersionUID = 7519976990944851574L; + + private final int position; + + public PartNotFoundException(int position) { + this(position, null); + } + + public PartNotFoundException(int position, Exception e) { + super("Part " + position + " not found.", e); + this.position = position; + } + + public int getPosition() { + return position; + } + + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStream.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStream.java new file mode 100644 index 0000000..706fea6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStream.java @@ -0,0 +1,159 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; + +/** + * {@link InputStream} which helps to keep track of the BodyOffset of the wrapped + * {@link InputStream} + * + * IMPORTANT: This class is not thread-safe! + * + */ +public class BodyOffsetInputStream extends InputStream{ + private long count = 0; + private long bodyStartOctet = -1; + private PushbackInputStream in; + private long readBytes = 0; + + public BodyOffsetInputStream(InputStream in) { + // we need to pushback at max 3 bytes + this.in = new PushbackInputStream(in, 3); + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + int i = in.read(); + if (i != -1) { + readBytes++; + if (bodyStartOctet == -1 && i == 0x0D) { + int a = in.read(); + if (a == 0x0A) { + int b = in.read(); + + if (b == 0x0D) { + int c = in.read(); + + if (c == 0x0A) { + bodyStartOctet = count+4; + } + in.unread(c); + } + in.unread(b); + } + in.unread(a); + } + count++; + } + return i; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (bodyStartOctet == -1) { + return super.read(b, off, len); + } else { + int r = in.read(b, off, len); + if (r != -1) { + readBytes += r; + } + return r; + } + } + + @Override + public int read(byte[] b) throws IOException { + if (bodyStartOctet == -1) { + return super.read(b); + } else { + int r = in.read(b); + if (r != -1) { + readBytes += r; + } + return r; + } + } + + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public void mark(int readlimit) { + + } + + /** + * Mark is not supported by this implementation + */ + public boolean markSupported() { + return false; + } + + /** + * Throws {@link IOException} + */ + public void reset() throws IOException { + throw new IOException("Not supported"); + } + + @Override + public long skip(long n) throws IOException { + long i = 0; + while(i++ < n) { + if (read() == -1) { + break; + } + } + return i; + } + + /** + * Return the bodyStartOffset or -1 if it could not be found. + * Be aware you can only expect some valid result from the method + * if you have consumed the whole InputStream or if you are + * sure that you reached the body + * + * @return offset + */ + public long getBodyStartOffset() { + return bodyStartOctet; + } + + /** + * Return the read bytes so far + * + * @return readBytes + */ + public long getReadBytes() { + return readBytes; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/ByteContent.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/ByteContent.java new file mode 100644 index 0000000..ab11da7 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/ByteContent.java @@ -0,0 +1,55 @@ +/**************************************************************** + * 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.streaming; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mailbox.model.Content; + +public final class ByteContent implements Content { + + private final byte[] contents; + + private final long size; + + public ByteContent(final byte[] contents) { + this.contents = contents; + size = contents.length; + } + + /** + * @see org.apache.james.mailbox.model.Content#size() + */ + public long size() { + return size; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(contents); + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/CountingInputStream.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/CountingInputStream.java new file mode 100644 index 0000000..d7c291f --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/CountingInputStream.java @@ -0,0 +1,85 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; + +/** + * {@link InputStream} implementation which just consume the the wrapped {@link InputStream} and count + * the lines which are contained within the wrapped stream + * + * + */ +final public class CountingInputStream extends InputStream { + + private final InputStream in; + + private int lineCount; + + private int octetCount; + + public CountingInputStream(InputStream in) { + super(); + this.in = in; + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + int next = in.read(); + if (next > 0) { + octetCount++; + if (next == '\r') { + lineCount++; + } + } + return next; + } + + /** + * Return the line count + * + * @return lineCount + */ + public int getLineCount() { + return lineCount; + } + + /** + * Return the octet count + * + * @return octetCount + */ + public int getOctetCount() { + return octetCount; + } + + /** + * Reads - and discards - the rest of the stream + * @throws IOException + */ + public void readAll() throws IOException { + while (read()>0); + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/FullByteContent.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/FullByteContent.java new file mode 100644 index 0000000..7bfb5cf --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/FullByteContent.java @@ -0,0 +1,90 @@ +/**************************************************************** + * 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.streaming; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.util.Iterator; +import java.util.List; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.Header; + +/** + * Abstract base class for {@link Content} implementations which hold the headers and + * the body a email + * + */ +public class FullByteContent implements Content { + + + private List
headers; + private byte[] body; + private long size; + + public FullByteContent(final byte[] body, final List headers) throws MailboxException { + this.headers = headers; + this.body = body; + this.size = caculateSize(); + } + + protected long caculateSize() throws MailboxException{ + long result = body.length; + result += 2; + for (final Iterator it = headers.iterator(); it.hasNext();) { + final MessageResult.Header header = it.next(); + if (header != null) { + result += header.size(); + result += 2; + } + } + return result; + } + + @Override + public InputStream getInputStream() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (final Iterator it = headers.iterator(); it.hasNext();) { + final MessageResult.Header header = it.next(); + if (header != null) { + try { + out.write((header.getName() + ": " + header.getValue() + "\r\n").getBytes("US-ASCII")); + } catch (MailboxException e) { + throw new IOException("Unable to read headers", e); + } + } + } + out.write("\r\n".getBytes("US-ASCII")); + out.flush(); + return new SequenceInputStream(new ByteArrayInputStream(out.toByteArray()), new ByteArrayInputStream(body)); + } + + @Override + public long size() { + return size; + } + + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/InputStreamContent.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/InputStreamContent.java new file mode 100644 index 0000000..53e532e --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/InputStreamContent.java @@ -0,0 +1,72 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.store.mail.model.Message; + +/** + * {@link Content} which is stored in a {@link InputStream} + * + */ +public final class InputStreamContent implements Content{ + private Message m; + private Type type; + + public static enum Type { + Full, + Body + } + + public InputStreamContent(Message m, Type type) throws IOException{ + this.m = m; + this.type = type; + } + + /** + * @see org.apache.james.mailbox.model.Content#size() + */ + public long size() { + switch (type) { + case Full: + return m.getFullContentOctets(); + + default: + return m.getBodyOctets(); + } + } + + /** + * @see org.apache.james.mailbox.model.InputStreamContent#getInputStream() + */ + public InputStream getInputStream() throws IOException { + // wrap the streams in a BoundedInputStream to make sure it really match with the stored size. + switch (type) { + case Full: + return m.getFullContent(); + default: + return m.getBodyContent(); + } + + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/LimitingFileInputStream.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/LimitingFileInputStream.java new file mode 100644 index 0000000..59784e6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/LimitingFileInputStream.java @@ -0,0 +1,276 @@ +/**************************************************************** + * 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.streaming; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +public class LimitingFileInputStream extends FileInputStream{ + private long pos = 0; + private final long limit; + + public LimitingFileInputStream(File file, long limit) throws FileNotFoundException { + super(file); + this.limit = limit; + } + + public LimitingFileInputStream(FileDescriptor fdObj, long limit) { + super(fdObj); + this.limit = limit; + + } + + public LimitingFileInputStream(String name, long limit) throws FileNotFoundException { + super(name); + this.limit = limit; + + } + + @Override + public int read() throws IOException { + if (pos >= limit) { + return -1; + } + pos++; + return super.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (pos >= limit) { + return -1; + } + int readLimit; + if (pos + len >= limit) { + readLimit = (int) limit - (int) pos; + } else { + readLimit = len; + } + + int i = super.read(b, off, readLimit); + pos += i; + return i; + } + + @Override + public long skip(long n) throws IOException { + long currentPos = pos; + long i = super.skip(n); + if (currentPos == pos) { + pos += i; + } + return i; + } + + @Override + public int available() throws IOException { + int i = super.available(); + if (i == -1) { + return -1; + } else { + if (i >= limit) { + return (int) limit - (int) pos; + } else { + return i; + } + } + } + + + /** + * Return the limit + * + * @return limit + */ + public long getLimit() { + return limit; + } + + /** + * Return a READ-ONLY {@link FileChannel} which is limited also in the size + * + * @return channel + */ + @Override + public FileChannel getChannel() { + return new LimitingFileChannel(super.getChannel()); + } + + + /** + * A {@link FileChannel} implementation which wrapps another {@link FileChannel} and limit the access to it + * + * + */ + private final class LimitingFileChannel extends FileChannel { + + private final FileChannel channel; + + public LimitingFileChannel(FileChannel channel) { + this.channel = channel; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + int bufLimit = dst.limit(); + int left = (int) bytesLeft(); + int r; + if (bufLimit > left) { + dst.limit(left); + r = channel.read(dst); + dst.limit(bufLimit); + } else { + r = channel.read(dst); + } + return r; + + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + long r = 0; + for (int a = offset; a < length; a++) { + r += read(dsts[a]); + } + + return r; + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public long position() throws IOException { + return channel.position(); + } + + @Override + public FileChannel position(long newPosition) throws IOException { + if (newPosition <= limit) { + channel.position(newPosition); + } + return LimitingFileChannel.this ; + } + + @Override + public long size() throws IOException { + return limit; + } + + @Override + public FileChannel truncate(long size) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public void force(boolean metaData) throws IOException { + channel.force(metaData); + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + if (position > limit) { + return 0; + } else { + long left = bytesLeft(); + + if (count > left) { + count = left; + } + return channel.transferTo(position, count, target); + } + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + throw new IOException("Read-Only FileChannel"); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + if (position > size()) { + return 0; + } + int bufLimit = dst.limit(); + int left = (int) bytesLeft(); + int r; + if (bufLimit > left) { + dst.limit(left); + r = channel.read(dst, position); + dst.limit(bufLimit); + } else { + r = channel.read(dst, position); + } + return r; + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + throw new IOException("Read-Only FileChannel"); + + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + return channel.map(mode, position, size); + } + + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + return channel.lock(position, size, shared); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + return channel.tryLock(position, size, shared); + } + + @Override + protected void implCloseChannel() throws IOException { + channel.close(); + } + + private long bytesLeft() throws IOException { + return limit - position(); + } + } + + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/PartContentBuilder.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/PartContentBuilder.java new file mode 100644 index 0000000..da89da3 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/streaming/PartContentBuilder.java @@ -0,0 +1,301 @@ +/**************************************************************** + * 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.streaming; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.Content; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultHeader; +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.stream.EntityState; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.james.mime4j.stream.MimeTokenStream; +import org.apache.james.mime4j.stream.RecursionMode; + + +public class PartContentBuilder { + + private static final byte[] EMPTY = {}; + + private MimeTokenStream parser; + + private boolean empty = false; + + private boolean topLevel = true; + + public PartContentBuilder() { + MimeConfig config = new MimeConfig(); + config.setMaxLineLen(-1); + config.setMaxHeaderLen(-1); + + parser = new MimeTokenStream(config); + } + + public void markEmpty() { + empty = true; + } + + public void parse(final InputStream in) { + + parser.setRecursionMode(RecursionMode.M_RECURSE); + parser.parse(in); + topLevel = true; + } + + private void skipToStartOfInner(int position) throws IOException, MimeException { + final EntityState state = parser.next(); + switch (state) { + case T_START_MULTIPART: + break; + case T_START_MESSAGE: + break; + case T_END_OF_STREAM: + throw new PartNotFoundException(position); + case T_END_BODYPART: + throw new PartNotFoundException(position); + default: + skipToStartOfInner(position); + } + } + + public void to(int position) throws IOException, MimeException { + try { + if (topLevel) { + topLevel = false; + } else { + skipToStartOfInner(position); + } + for (int count = 0; count < position;) { + final EntityState state = parser.next(); + switch (state) { + case T_BODY: + if (position == 1) { + count++; + } + break; + case T_START_BODYPART: + count++; + break; + case T_START_MULTIPART: + if (count > 0 && count < position) { + ignoreInnerMessage(); + } + break; + case T_END_OF_STREAM: + throw new PartNotFoundException(position); + } + } + } catch (IllegalStateException e) { + throw new PartNotFoundException(position, e); + } + } + + private void ignoreInnerMessage() throws IOException, UnexpectedEOFException, MimeException { + for (EntityState state = parser.next(); state != EntityState.T_END_MULTIPART; state = parser + .next()) { + switch (state) { + case T_END_OF_STREAM: + throw new UnexpectedEOFException(); + + case T_START_MULTIPART: + ignoreInnerMessage(); + break; + } + } + } + + public Content getFullContent() throws IOException, UnexpectedEOFException, MimeException, MailboxException { + final List
headers = getMimeHeaders(); + final byte[] content = mimeBodyContent(); + return new FullByteContent(content, headers); + } + + public Content getMessageBodyContent() throws IOException, MimeException { + final byte[] content = messageBodyContent(); + return new ByteContent(content); + } + + private byte[] messageBodyContent() throws IOException, MimeException { + final byte[] content; + if (empty) { + content = EMPTY; + } else { + boolean valid; + try { + advancedToMessage(); + valid = true; + } catch (UnexpectedEOFException e) { + // No TEXT part + valid = false; + } + if (valid) { + parser.setRecursionMode(RecursionMode.M_FLAT); + for (EntityState state = parser.getState(); state != EntityState.T_BODY + && state != EntityState.T_START_MULTIPART; state = parser + .next()) { + if (state == EntityState.T_END_OF_STREAM) { + valid = false; + break; + } + } + if (valid) { + content = IOUtils.toByteArray(parser.getInputStream()); + } else { + content = EMPTY; + } + } else { + content = EMPTY; + } + } + return content; + } + + public Content getMimeBodyContent() throws IOException, MimeException { + final byte[] content = mimeBodyContent(); + return new ByteContent(content); + } + + private byte[] mimeBodyContent() throws IOException, MimeException { + final byte[] content; + if (empty) { + content = EMPTY; + } else { + parser.setRecursionMode(RecursionMode.M_FLAT); + boolean valid = true; + for (EntityState state = parser.getState(); state != EntityState.T_BODY + && state != EntityState.T_START_MULTIPART; state = parser + .next()) { + if (state == EntityState.T_END_OF_STREAM) { + valid = false; + break; + } + } + if (valid) { + content = IOUtils.toByteArray(parser.getInputStream()); + } else { + content = EMPTY; + } + } + return content; + } + + @SuppressWarnings("unchecked") + public List getMimeHeaders() throws IOException, UnexpectedEOFException, MimeException { + final List results; + if (empty) { + results = Collections.EMPTY_LIST; + } else { + results = new ArrayList(); + for (EntityState state = parser.getState(); state != EntityState.T_END_HEADER; state = parser + .next()) { + switch (state) { + case T_END_OF_STREAM: + throw new UnexpectedEOFException(); + + case T_FIELD: + final String fieldValue = parser.getField().getBody().trim(); + final String fieldName = parser.getField().getName(); + ResultHeader header = new ResultHeader(fieldName, fieldValue); + results.add(header); + break; + } + } + } + return results; + } + + @SuppressWarnings("unchecked") + public List getMessageHeaders() throws IOException, MimeException { + final List results; + if (empty) { + results = Collections.EMPTY_LIST; + } else { + results = new ArrayList(); + try { + advancedToMessage(); + + for (EntityState state = parser.getState(); state != EntityState.T_END_HEADER; state = parser + .next()) { + switch (state) { + case T_END_OF_STREAM: + throw new IOException("Unexpected EOF"); + + case T_FIELD: + final String fieldValue = parser.getField().getBody().trim(); + final String fieldName = parser.getField().getName(); + ResultHeader header = new ResultHeader(fieldName, fieldValue); + results.add(header); + break; + } + } + } catch (UnexpectedEOFException e) { + // No headers found + } + } + return results; + } + + private void advancedToMessage() throws IOException, UnexpectedEOFException, MimeException { + for (EntityState state = parser.getState(); state != EntityState.T_START_MESSAGE; state = parser + .next()) { + if (state == EntityState.T_END_OF_STREAM) { + throw new UnexpectedEOFException(); + } + } + } + + public static final class UnexpectedEOFException extends MimeException { + + private static final long serialVersionUID = -3755637466593055796L; + + public UnexpectedEOFException() { + super("Unexpected EOF"); + } + } + + public static final class PartNotFoundException extends MimeException { + + private static final long serialVersionUID = 7519976990944851574L; + + private final int position; + + public PartNotFoundException(int position) { + this(position, null); + } + + public PartNotFoundException(int position, Exception e) { + super("Part " + position + " not found.", e); + this.position = position; + } + + public int getPosition() { + return position; + } + + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/all-wcprops new file mode 100644 index 0000000..31528b3 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 110 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/transaction +END +NonTransactionalMapper.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/transaction/NonTransactionalMapper.java +END +Mapper.java +K 25 +svn:wc:ra_dav:version-url +V 122 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/transaction/Mapper.java +END +TransactionalMapper.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/transaction/TransactionalMapper.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/entries new file mode 100644 index 0000000..521a483 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/transaction +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +NonTransactionalMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +708a08b9d9e2d3c09a10c68c1ffef3fa +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1887 + +Mapper.java +file + + + + +2013-09-02T02:54:39.000000Z +b4efd0d412310ffe6eacc579e8c56a4b +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2321 + +TransactionalMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +defcd21f35279b1ec02a9019deaed017 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +2359 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/Mapper.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/Mapper.java.svn-base new file mode 100644 index 0000000..ed0d64f --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/Mapper.java.svn-base @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.transaction; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * Mapper which execute units of work in a {@link Transaction} + * + */ +public interface Mapper { + + /** + * IMAP Request was complete. Cleanup all Request scoped stuff + */ + public void endRequest(); + + /** + * Execute the given Transaction + * + * @param transaction + * @throws MailboxException + */ + public T execute(Transaction transaction) throws MailboxException; + + /** + * Unit of work executed in a Transaction + * + */ + public interface Transaction { + + /** + * Run unit of work in a Transaction and return a value + * + * @throws MailboxException + */ + public T run() throws MailboxException; + } + + + public abstract class VoidTransaction implements Transaction { + + public final Void run() throws MailboxException { + runVoid(); + return null; + } + public abstract void runVoid() throws MailboxException; + + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/NonTransactionalMapper.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/NonTransactionalMapper.java.svn-base new file mode 100644 index 0000000..4b92e23 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/NonTransactionalMapper.java.svn-base @@ -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.store.transaction; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * A Mapper which does no transaction handling. It just executes the execute() method + * of the Transaction object without any special handling. + * + * This class is mostly useful for Mapper implementations which do not support Transactions + */ +public abstract class NonTransactionalMapper implements Mapper { + + /** + * @see org.apache.james.mailbox.store.transaction.Mapper#execute(org.apache.james.mailbox.store.transaction.Mapper.Transaction) + */ + public final T execute(Transaction transaction) throws MailboxException { + return transaction.run(); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/TransactionalMapper.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/TransactionalMapper.java.svn-base new file mode 100644 index 0000000..a159673 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/.svn/text-base/TransactionalMapper.java.svn-base @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.transaction; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * + * Run Transaction and handle begin, commit and rollback in the right order + * + */ +public abstract class TransactionalMapper implements Mapper { + + /** + * @see org.apache.james.mailbox.store.transaction.Mapper#execute(org.apache.james.mailbox.store.transaction.Mapper.Transaction) + */ + public final T execute(Transaction transaction) throws MailboxException { + begin(); + try { + T value = transaction.run(); + commit(); + return value; + } catch (MailboxException e) { + rollback(); + throw e; + } + } + + /** + * Begin transaction + * + * @throws StorageException + */ + protected abstract void begin() throws MailboxException; + + /** + * Commit transaction + * + * @throws StorageException + */ + protected abstract void commit() throws MailboxException; + + /** + * Rollback transaction + * + * @throws StorageException + */ + protected abstract void rollback() throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/Mapper.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/Mapper.java new file mode 100644 index 0000000..ed0d64f --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/Mapper.java @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.transaction; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * Mapper which execute units of work in a {@link Transaction} + * + */ +public interface Mapper { + + /** + * IMAP Request was complete. Cleanup all Request scoped stuff + */ + public void endRequest(); + + /** + * Execute the given Transaction + * + * @param transaction + * @throws MailboxException + */ + public T execute(Transaction transaction) throws MailboxException; + + /** + * Unit of work executed in a Transaction + * + */ + public interface Transaction { + + /** + * Run unit of work in a Transaction and return a value + * + * @throws MailboxException + */ + public T run() throws MailboxException; + } + + + public abstract class VoidTransaction implements Transaction { + + public final Void run() throws MailboxException { + runVoid(); + return null; + } + public abstract void runVoid() throws MailboxException; + + } +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/NonTransactionalMapper.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/NonTransactionalMapper.java new file mode 100644 index 0000000..4b92e23 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/NonTransactionalMapper.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.store.transaction; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * A Mapper which does no transaction handling. It just executes the execute() method + * of the Transaction object without any special handling. + * + * This class is mostly useful for Mapper implementations which do not support Transactions + */ +public abstract class NonTransactionalMapper implements Mapper { + + /** + * @see org.apache.james.mailbox.store.transaction.Mapper#execute(org.apache.james.mailbox.store.transaction.Mapper.Transaction) + */ + public final T execute(Transaction transaction) throws MailboxException { + return transaction.run(); + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/TransactionalMapper.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/TransactionalMapper.java new file mode 100644 index 0000000..a159673 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/transaction/TransactionalMapper.java @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.transaction; + +import org.apache.james.mailbox.exception.MailboxException; + +/** + * + * Run Transaction and handle begin, commit and rollback in the right order + * + */ +public abstract class TransactionalMapper implements Mapper { + + /** + * @see org.apache.james.mailbox.store.transaction.Mapper#execute(org.apache.james.mailbox.store.transaction.Mapper.Transaction) + */ + public final T execute(Transaction transaction) throws MailboxException { + begin(); + try { + T value = transaction.run(); + commit(); + return value; + } catch (MailboxException e) { + rollback(); + throw e; + } + } + + /** + * Begin transaction + * + * @throws StorageException + */ + protected abstract void begin() throws MailboxException; + + /** + * Commit transaction + * + * @throws StorageException + */ + protected abstract void commit() throws MailboxException; + + /** + * Rollback transaction + * + * @throws StorageException + */ + protected abstract void rollback() throws MailboxException; + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/all-wcprops new file mode 100644 index 0000000..b35dda2 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user +END +SubscriptionMapperFactory.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapperFactory.java +END +SubscriptionMapper.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapper.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/entries new file mode 100644 index 0000000..fe2ce44 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/entries @@ -0,0 +1,99 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + +SubscriptionMapperFactory.java +file + + + + +2013-09-02T02:54:39.000000Z +2b5dde75cb97dfe1e5a732fbd18ce011 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1693 + +SubscriptionMapper.java +file + + + + +2013-09-02T02:54:39.000000Z +4bfb7b3035b4c3862d42c9b30d7fe896 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +2534 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapper.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapper.java.svn-base new file mode 100644 index 0000000..7b70013 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapper.java.svn-base @@ -0,0 +1,62 @@ +/**************************************************************** + * 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.user; + +import java.util.List; + +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * Mapper for {@link Subscription} + * + */ +public interface SubscriptionMapper extends Mapper { + + + /** + * Finds any subscriptions for a given user to the given mailbox. + * @param user not null + * @param mailbox not null + * @return Subscription, + * or null when the user is not subscribed to the given mailbox + */ + public abstract Subscription findMailboxSubscriptionForUser( + final String user, final String mailbox) throws SubscriptionException; + + /** + * Saves the given subscription. + * @param subscription not null + */ + public abstract void save(Subscription subscription) throws SubscriptionException; + + /** + * Finds subscriptions for the given user. + * @param user not null + * @return not null + */ + public abstract List findSubscriptionsForUser(String user) throws SubscriptionException; + + /** + * Deletes the given subscription. + * @param subscription not null + */ + public abstract void delete(Subscription subscription) throws SubscriptionException; +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapperFactory.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapperFactory.java.svn-base new file mode 100644 index 0000000..10908bc --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/.svn/text-base/SubscriptionMapperFactory.java.svn-base @@ -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.user; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.SubscriptionException; + +public interface SubscriptionMapperFactory { + + /** + * Create a {@link SubscriptionMapper} instance or return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + public SubscriptionMapper getSubscriptionMapper(MailboxSession session) throws SubscriptionException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapper.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapper.java new file mode 100644 index 0000000..7b70013 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapper.java @@ -0,0 +1,62 @@ +/**************************************************************** + * 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.user; + +import java.util.List; + +import org.apache.james.mailbox.exception.SubscriptionException; +import org.apache.james.mailbox.store.transaction.Mapper; +import org.apache.james.mailbox.store.user.model.Subscription; + +/** + * Mapper for {@link Subscription} + * + */ +public interface SubscriptionMapper extends Mapper { + + + /** + * Finds any subscriptions for a given user to the given mailbox. + * @param user not null + * @param mailbox not null + * @return Subscription, + * or null when the user is not subscribed to the given mailbox + */ + public abstract Subscription findMailboxSubscriptionForUser( + final String user, final String mailbox) throws SubscriptionException; + + /** + * Saves the given subscription. + * @param subscription not null + */ + public abstract void save(Subscription subscription) throws SubscriptionException; + + /** + * Finds subscriptions for the given user. + * @param user not null + * @return not null + */ + public abstract List findSubscriptionsForUser(String user) throws SubscriptionException; + + /** + * Deletes the given subscription. + * @param subscription not null + */ + public abstract void delete(Subscription subscription) throws SubscriptionException; +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapperFactory.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapperFactory.java new file mode 100644 index 0000000..10908bc --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/SubscriptionMapperFactory.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.user; + +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.SubscriptionException; + +public interface SubscriptionMapperFactory { + + /** + * Create a {@link SubscriptionMapper} instance or return the one which exists for the {@link MailboxSession} already + * + * @param session + * @return mapper + */ + public SubscriptionMapper getSubscriptionMapper(MailboxSession session) throws SubscriptionException; +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/all-wcprops new file mode 100644 index 0000000..725fcd2 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/model +END +Subscription.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/model/Subscription.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/entries new file mode 100644 index 0000000..b2b9bf4 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/model +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +impl +dir + +Subscription.java +file + + + + +2013-09-02T02:54:39.000000Z +d9d4e9ea21c277e92c757aff305e5af3 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +1716 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/text-base/Subscription.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/text-base/Subscription.java.svn-base new file mode 100644 index 0000000..a967b58 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/.svn/text-base/Subscription.java.svn-base @@ -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.store.user.model; + +/** + * + * Subscription of a mailbox to a user + */ +public interface Subscription { + + /** + * Gets the name of the mailbox to which + * the user is subscribed. + * Note that subscriptions must be maintained + * beyond the lifetime of a particular instance + * of a mailbox. + * + * @return not null + */ + String getMailbox(); + + /** + * Gets the name of the subscribed user. + * + * @return not null + */ + String getUser(); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/Subscription.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/Subscription.java new file mode 100644 index 0000000..a967b58 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/Subscription.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.store.user.model; + +/** + * + * Subscription of a mailbox to a user + */ +public interface Subscription { + + /** + * Gets the name of the mailbox to which + * the user is subscribed. + * Note that subscriptions must be maintained + * beyond the lifetime of a particular instance + * of a mailbox. + * + * @return not null + */ + String getMailbox(); + + /** + * Gets the name of the subscribed user. + * + * @return not null + */ + String getUser(); + +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/all-wcprops b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/all-wcprops new file mode 100644 index 0000000..d7068d9 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/model/impl +END +SimpleSubscription.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/SimpleSubscription.java +END diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/entries b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/entries new file mode 100644 index 0000000..b9460ba --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/user/model/impl +http://svn.apache.org/repos/asf + + + +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SimpleSubscription.java +file + + + + +2013-09-02T02:54:39.000000Z +52c6671e2c3d2c122d75cd4d67ff8ad3 +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +1878 + diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/text-base/SimpleSubscription.java.svn-base b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/text-base/SimpleSubscription.java.svn-base new file mode 100644 index 0000000..beb5af6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/.svn/text-base/SimpleSubscription.java.svn-base @@ -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.user.model.impl; + +import org.apache.james.mailbox.store.user.model.Subscription; + +public class SimpleSubscription implements Subscription { + + private final String user; + private final String mailbox; + + public SimpleSubscription(String user, String mailbox) { + this.user = user; + this.mailbox = mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getMailbox() + */ + public String getMailbox() { + return mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getUser() + */ + public String getUser() { + return user; + } + +} diff --git a/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/SimpleSubscription.java b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/SimpleSubscription.java new file mode 100644 index 0000000..beb5af6 --- /dev/null +++ b/james/apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/user/model/impl/SimpleSubscription.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.user.model.impl; + +import org.apache.james.mailbox.store.user.model.Subscription; + +public class SimpleSubscription implements Subscription { + + private final String user; + private final String mailbox; + + public SimpleSubscription(String user, String mailbox) { + this.user = user; + this.mailbox = mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getMailbox() + */ + public String getMailbox() { + return mailbox; + } + + /** + * @see org.apache.james.mailbox.store.user.model.Subscription#getUser() + */ + public String getUser() { + return user; + } + +} diff --git a/james/apache-james-mailbox/store/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/store/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..9cb744b --- /dev/null +++ b/james/apache-james-mailbox/store/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/store/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 81 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/store/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/store/src/reporting-site/.svn/entries b/james/apache-james-mailbox/store/src/reporting-site/.svn/entries new file mode 100644 index 0000000..79537a9 --- /dev/null +++ b/james/apache-james-mailbox/store/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-03-05T11:38:38.771020Z +1078275 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:39.000000Z +5bde8261948ff1881960902a322aa196 +2011-03-05T11:38:38.771020Z +1078275 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/store/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/store/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/store/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/store/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/store/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/store/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/store/src/reporting-site/site.xml b/james/apache-james-mailbox/store/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/store/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/store/src/test/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/.svn/all-wcprops new file mode 100644 index 0000000..508a85d --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 62 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test +END diff --git a/james/apache-james-mailbox/store/src/test/.svn/entries b/james/apache-james-mailbox/store/src/test/.svn/entries new file mode 100644 index 0000000..d476286 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/store/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..d214e64 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 67 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java +END diff --git a/james/apache-james-mailbox/store/src/test/java/.svn/entries b/james/apache-james-mailbox/store/src/test/java/.svn/entries new file mode 100644 index 0000000..c2dc237 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/store/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..12e3647 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 71 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/.svn/entries new file mode 100644 index 0000000..8d69847 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..a880575 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 78 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..d305e9d --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..0008611 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 84 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..1d692ef --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..0266154 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 92 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..889c3be --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +store +dir + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops new file mode 100644 index 0000000..cc3c727 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops @@ -0,0 +1,83 @@ +K 25 +svn:wc:ra_dav:version-url +V 98 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store +END +SearchUtilsMultipartMixedTest.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsMultipartMixedTest.java +END +MailboxEventDispatcherFlagsTest.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java +END +SimpleMailboxMembership.java +K 25 +svn:wc:ra_dav:version-url +V 127 +/repos/asf/!svn/ver/1179640/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/SimpleMailboxMembership.java +END +PartContentBuilderComplexMultipartTest.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderComplexMultipartTest.java +END +MockAuthenticator.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1056505/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/MockAuthenticator.java +END +MessageResultImplTest.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1176953/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/MessageResultImplTest.java +END +SearchUtilsTest.java +K 25 +svn:wc:ra_dav:version-url +V 119 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsTest.java +END +StringBuilderChannel.java +K 25 +svn:wc:ra_dav:version-url +V 124 +/repos/asf/!svn/ver/1041812/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/StringBuilderChannel.java +END +PartContentBuilderMultipartAlternativeTest.java +K 25 +svn:wc:ra_dav:version-url +V 146 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderMultipartAlternativeTest.java +END +StoreMessageResultIteratorTest.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1418609/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java +END +MessageBuilder.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java +END +SearchUtilsRFC822Test.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsRFC822Test.java +END +SimpleProperty.java +K 25 +svn:wc:ra_dav:version-url +V 118 +/repos/asf/!svn/ver/1041812/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/SimpleProperty.java +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/entries new file mode 100644 index 0000000..90ce51b --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/entries @@ -0,0 +1,479 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +PartContentBuilderComplexMultipartTest.java +file + + + + +2013-09-02T02:54:39.000000Z +ce956b766b0d163d82c0782fa040d396 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +8510 + +mail +dir + +MessageResultImplTest.java +file + + + + +2013-09-02T02:54:39.000000Z +60d4313301d61f6ed0d67e14e4b08784 +2011-09-28T16:47:18.682999Z +1176953 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +4743 + +StringBuilderChannel.java +file + + + + +2013-09-02T02:54:39.000000Z +cc412668f3385365abee240e91e1cbe6 +2010-09-07T06:03:18.749442Z +993222 +norman + + + + + + + + + + + + + + + + + + + + + +2028 + +PartContentBuilderMultipartAlternativeTest.java +file + + + + +2013-09-02T02:54:39.000000Z +b4de56f2ff7f5146d7a01f5bffb908fa +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +6042 + +streaming +dir + +StoreMessageResultIteratorTest.java +file + + + + +2013-09-02T02:54:39.000000Z +f63d8e35c06d25a4c1ad20f203fc59f5 +2012-12-08T06:46:05.827269Z +1418609 +eric +has-props + + + + + + + + + + + + + + + + + + + + +7014 + +SearchUtilsRFC822Test.java +file + + + + +2013-09-02T02:54:39.000000Z +426d984f221f780daa04c57b45301b09 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +5059 + +SearchUtilsMultipartMixedTest.java +file + + + + +2013-09-02T02:54:39.000000Z +8a68b3087b103f95febdc4a4341c065c +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +11630 + +MailboxEventDispatcherFlagsTest.java +file + + + + +2013-09-02T02:54:39.000000Z +a81bdc16d6b584a4c04136d1350bc57e +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +15144 + +SimpleMailboxMembership.java +file + + + + +2013-09-02T02:54:39.000000Z +1519ceb605a01839e335bbb6e02508ee +2011-10-06T14:18:49.976524Z +1179640 +felixk + + + + + + + + + + + + + + + + + + + + + +9387 + +MockAuthenticator.java +file + + + + +2013-09-02T02:54:39.000000Z +11c267418ec6cc7a9dc5a78b7de0e4e7 +2011-01-07T21:02:17.315732Z +1056505 +norman + + + + + + + + + + + + + + + + + + + + + +1797 + +search +dir + +SearchUtilsTest.java +file + + + + +2013-09-02T02:54:39.000000Z +974c755766823db7846cdbe94b480ec6 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +43273 + +MessageBuilder.java +file + + + + +2013-09-02T02:54:39.000000Z +698d775f2ab1c8c47faa798a0957a213 +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +2604 + +SimpleProperty.java +file + + + + +2013-09-02T02:54:39.000000Z +cacfb140d9beac3f2a3782302ac3104d +2010-09-07T06:03:18.749442Z +993222 +norman + + + + + + + + + + + + + + + + + + + + + +1884 + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImplTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImplTest.java.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/MessageResultImplTest.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMessageResultIteratorTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMessageResultIteratorTest.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/prop-base/StoreMessageResultIteratorTest.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcherFlagsTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcherFlagsTest.java.svn-base new file mode 100644 index 0000000..c751886 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MailboxEventDispatcherFlagsTest.java.svn-base @@ -0,0 +1,349 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Iterator; + +import javax.mail.Flags; + +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.MailboxACL; +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.mail.model.Mailbox; +import org.apache.james.mailbox.util.EventCollector; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(JMock.class) +public class MailboxEventDispatcherFlagsTest { + + MailboxEventDispatcher dispatcher; + + EventCollector collector; + + MessageResult result; + int sessionId = 10; + + private MailboxSession session = new MockMailboxSession("test") { + + @Override + public long getSessionId() { + return sessionId; + } + + }; + + private Mockery mockery = new JUnit4Mockery(); + + private Mailbox mailbox = new Mailbox() { + + @Override + public Long getMailboxId() { + return 1L; + } + + @Override + public String getNamespace() { + return null; + } + + @Override + public void setNamespace(String namespace) { + } + + @Override + public String getUser() { + return null; + } + + @Override + public void setUser(String user) { + + } + + @Override + public String getName() { + return "test"; + } + + @Override + public void setName(String name) { + } + + @Override + public long getUidValidity() { + return 0; + } + + @Override + public MailboxACL getACL() { + return SimpleMailboxACL.EMPTY; + } + + @Override + public void setACL(MailboxACL acl) { + } + + }; + + @Before + public void setUp() throws Exception { + collector = new EventCollector(); + + dispatcher = new MailboxEventDispatcher(collector); + result = mockery.mock(MessageResult.class); + mockery.checking(new Expectations() {{ + allowing(result).getUid();will(returnValue(23L)); + }}); + } + + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.ANSWERED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.ANSWERED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DELETED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DELETED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DRAFT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DRAFT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.FLAGGED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.FLAGGED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.RECENT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.RECENT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.SEEN, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.SEEN, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + public void testShouldShowMixedChanges() { + Flags originals = new Flags(); + originals.add(Flags.Flag.DRAFT); + originals.add(Flags.Flag.RECENT); + Flags updated = new Flags(); + updated.add(Flags.Flag.ANSWERED); + updated.add(Flags.Flag.DRAFT); + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.ANSWERED, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.RECENT, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.SEEN, iterator.next()); + assertFalse(iterator.hasNext()); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageBuilder.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageBuilder.java.svn-base new file mode 100644 index 0000000..64d5a97 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageBuilder.java.svn-base @@ -0,0 +1,63 @@ +/**************************************************************** + * 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 java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.store.mail.model.Message; + +public class MessageBuilder { + + public long mailboxId = 113; + public long uid = 776; + public Date internalDate = new Date(); + public int size = 8867; + public Flags flags = new Flags(); + public byte[] body = {}; + public final Map headers = new HashMap(); + public int lineNumber = 0; + + public Message build() throws Exception { + Message result = new SimpleMailboxMembership(mailboxId, uid, -1, internalDate, size, flags, body, headers); + return result; + } + + public void header(String field, String value) { + headers.put(field, value); + } + + public void setKey(int mailboxId, int uid) { + this.uid = uid; + this.mailboxId = mailboxId; + } + + public void setFlags(boolean seen, boolean flagged, boolean answered, + boolean draft, boolean deleted, boolean recent) { + if (seen) flags.add(Flags.Flag.SEEN); + if (flagged) flags.add(Flags.Flag.FLAGGED); + if (answered) flags.add(Flags.Flag.ANSWERED); + if (draft) flags.add(Flags.Flag.DRAFT); + if (deleted) flags.add(Flags.Flag.DELETED); + if (recent) flags.add(Flags.Flag.RECENT); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImplTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImplTest.java.svn-base new file mode 100644 index 0000000..1629cee --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MessageResultImplTest.java.svn-base @@ -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.store; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Date; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.junit.Before; +import org.junit.Test; + +public class MessageResultImplTest { + private MessageResultImpl msgResultA; + private MessageResultImpl msgResultACopy; + private MessageResultImpl msgResultB; + private MessageResultImpl msgResultC; + + /** + * Initialize name instances + */ + @Before + public void initNames() throws Exception + { + Date dateAB = new Date(); + Message msgA = buildMessage(100, dateAB); + Message msgB = buildMessage(100, dateAB); + Message msgC = buildMessage(200, new Date()); + + msgResultA = new MessageResultImpl(msgA); + msgResultACopy = new MessageResultImpl(msgA); + msgResultB = new MessageResultImpl(msgB); + msgResultC = new MessageResultImpl(msgC); + } + + + private Message buildMessage(int uid, Date aDate) throws Exception { + MessageBuilder builder = new MessageBuilder(); + builder.uid = uid; + builder.internalDate = aDate; + return builder.build(); + } + + + @Test + public void testEqualsNull() throws Exception + { + assertFalse(msgResultA.equals(null)); + } + + + @Test + public void testEqualsReflexive() throws Exception + { + assertEquals(msgResultA, msgResultA); + } + + + @Test + public void testCompareToReflexive() throws Exception + { + assertEquals(0, msgResultA.compareTo(msgResultA)); + } + + + @Test + public void testHashCodeReflexive() throws Exception + { + assertEquals(msgResultA.hashCode(), msgResultA.hashCode()); + } + + + @Test + public void testEqualsSymmetric() throws Exception + { + assertEquals(msgResultA, msgResultACopy); + assertEquals(msgResultACopy, msgResultA); + } + + + @Test + public void testHashCodeSymmetric() throws Exception + { + assertEquals(msgResultA.hashCode(), msgResultACopy.hashCode()); + assertEquals(msgResultACopy.hashCode(), msgResultA.hashCode()); + } + + + @Test + public void testEqualsTransitive() throws Exception + { + assertEquals(msgResultA, msgResultACopy); + assertEquals(msgResultACopy, msgResultB); + assertEquals(msgResultA, msgResultB); + } + + + @Test + public void testCompareToTransitive() throws Exception + { + assertEquals(0, msgResultA.compareTo(msgResultACopy)); + assertEquals(0, msgResultACopy.compareTo(msgResultB)); + assertEquals(0, msgResultA.compareTo(msgResultB)); + } + + + @Test + public void testHashCodeTransitive() throws Exception + { + assertEquals(msgResultA.hashCode(), msgResultACopy.hashCode()); + assertEquals(msgResultACopy.hashCode(), msgResultB.hashCode()); + assertEquals(msgResultA.hashCode(), msgResultB.hashCode()); + } + + + @Test + public void testNotEqualDiffValue() throws Exception + { + assertFalse(msgResultA.equals(msgResultC)); + assertFalse(msgResultC.equals(msgResultA)); + } + + @Test + public void testShouldReturnPositiveWhenFirstGreaterThanSecond() + throws Exception { + assertTrue( msgResultC.compareTo(msgResultB) > 0); + } + + @Test + public void testShouldReturnNegativeWhenFirstLessThanSecond() + throws Exception { + assertTrue( msgResultB.compareTo(msgResultC) < 0); + } +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MockAuthenticator.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MockAuthenticator.java.svn-base new file mode 100644 index 0000000..e22ba82 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/MockAuthenticator.java.svn-base @@ -0,0 +1,43 @@ +/**************************************************************** + * 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 java.util.HashMap; +import java.util.Map; + +public class MockAuthenticator implements Authenticator{ + + private Map users = new HashMap(); + + public boolean isAuthentic(String userid, CharSequence passwd) { + String pass = users.get(userid); + if (pass != null) { + return passwd.toString().equals(pass); + } + return false; + } + + public void addUser(String user, String password) { + users .put(user, password); + } + + public void clear() { + users.clear(); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderComplexMultipartTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderComplexMultipartTest.java.svn-base new file mode 100644 index 0000000..7fef9d5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderComplexMultipartTest.java.svn-base @@ -0,0 +1,225 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultHeader; +import org.apache.james.mailbox.store.streaming.PartContentBuilder; +import org.apache.james.mailbox.store.streaming.PartContentBuilder.PartNotFoundException; +import org.junit.Before; +import org.junit.Test; + +public class PartContentBuilderComplexMultipartTest { + + private static final String PREAMBLE = "This is the preamble"; + + private static final String CONTENT_TYPE = "Content-Type"; + + private static final String CONTENT_TYPE_HTML = "text/html;charset=us-ascii"; + + private static final String CONTENT_TYPE_PLAIN = "text/plain;charset=us-ascii"; + + private static final String CONTENT_TYPE_RFC822 = "message/rfc822"; + + private static final String OUTER_HTML_BODY = "Rhubard!

Rhubarb!Rhubard!Rhubard!

\r\n"; + + private static final String FULL_OUTER_HTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_HTML + "\r\n\r\n" + OUTER_HTML_BODY; + + private static final String OUTER_PLAIN_BODY = "Rhubarb!Rhubard!Rhubard!\r\n"; + + private static final String FULL_OUTER_PLAIN = CONTENT_TYPE + ": " + + CONTENT_TYPE_PLAIN + "\r\n\r\n" + OUTER_PLAIN_BODY; + + private static final String INNER_HTML_BODY = "Custard!

Custard!Custard!Custard!

\r\n"; + + private static final String FULL_INNER_HTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_HTML + "\r\n\r\n" + INNER_HTML_BODY; + + private static final String INNER_PLAIN_BODY = "Custard!Custard!Custard!\r\n"; + + private static final String FULL_INNER_TXT = CONTENT_TYPE + ": " + + CONTENT_TYPE_PLAIN + "\r\n\r\n" + INNER_PLAIN_BODY; + + private static final String INNERMOST_BODY = "Da!Da!Da!Dah!\r\n"; + + private static final String RFC822_PLAIN_MAIL = "From: Samual Smith \r\n" + + "To: John Smith \r\n" + + "Date: Thu, 1 Feb 2007 08:00:00 -0800 (PST)\r\n" + + "Subject: Rhubard And Custard!\r\n" + + CONTENT_TYPE + + ": " + + CONTENT_TYPE_PLAIN + "\r\n" + "\r\n" + INNERMOST_BODY; + + private static final String FULL_INNERMOST_EMAIL = CONTENT_TYPE + ": " + + CONTENT_TYPE_RFC822 + "\r\n\r\n" + RFC822_PLAIN_MAIL; + + private static final String INNER_MAIL = "From: John Smith \r\n" + + "To: Samual Smith \r\n" + + "Date: Fri, 1 Feb 2008 08:00:00 -0800 (PST)\r\n" + + "Subject: Custard!\r\n" + + "Content-Type: multipart/mixed;boundary=1729\r\n\r\n" + + PREAMBLE + + "\r\n--1729\r\n" + + FULL_INNER_TXT + + "\r\n--1729\r\n" + + FULL_INNER_HTML + + "\r\n--1729\r\n" + + FULL_INNERMOST_EMAIL + + "\r\n--1729--\r\n"; + + private static final String FULL_INNER_MAIL = CONTENT_TYPE + ": " + + CONTENT_TYPE_RFC822 + "\r\n\r\n" + INNER_MAIL; + + private static final String MULTIPART_MIXED = "From: Samual Smith \r\n" + + "To: John Smith \r\n" + + "Date: Sun, 10 Feb 2008 08:00:00 -0800 (PST)\r\n" + + "Subject: Rhubarb!\r\n" + + "Content-Type: multipart/mixed;boundary=4242\r\n\r\n" + + PREAMBLE + + "\r\n--4242\r\n" + + FULL_OUTER_HTML + + "\r\n--4242\r\n" + + FULL_INNER_MAIL + + "\r\n--4242\r\n" + + FULL_OUTER_PLAIN + + "\r\n--4242--\r\n"; + + PartContentBuilder builder; + + @Before + public void setUp() throws Exception { + builder = new PartContentBuilder(); + } + + @Test + public void testShouldNotFoundSubPartOfNonMultiPartTopLevel() + throws Exception { + int[] path = { 1, 1 }; + for (int i = 1; i < 10; i++) { + path[1] = i; + checkNotPartFound(path); + } + } + + @Test + public void testShouldNotFoundSubPartOfNonMultiInnerPart() throws Exception { + int[] path = { 2, 2, 1 }; + for (int i = 1; i < 10; i++) { + path[2] = i; + checkNotPartFound(path); + } + } + + @Test + public void testShouldLocateOuterHtml() throws Exception { + int[] path = { 1 }; + check(FULL_OUTER_HTML, OUTER_HTML_BODY, CONTENT_TYPE_HTML, path); + } + + @Test + public void testShouldLocateOuterMail() throws Exception { + int[] path = { 2 }; + check(FULL_INNER_MAIL, INNER_MAIL, CONTENT_TYPE_RFC822, path); + } + + @Test + public void testShouldLocateOuterPlain() throws Exception { + int[] path = { 3 }; + check(FULL_OUTER_PLAIN, OUTER_PLAIN_BODY, CONTENT_TYPE_PLAIN, path); + } + + @Test + public void testShouldLocateInnerHtml() throws Exception { + int[] path = { 2, 2 }; + check(FULL_INNER_HTML, INNER_HTML_BODY, CONTENT_TYPE_HTML, path); + } + + @Test + public void testShouldLocateInnerMail() throws Exception { + int[] path = { 2, 3 }; + check(FULL_INNERMOST_EMAIL, RFC822_PLAIN_MAIL, CONTENT_TYPE_RFC822, + path); + } + + @Test + public void testShouldLocateInnerPlain() throws Exception { + int[] path = { 2, 1 }; + check(FULL_INNER_TXT, INNER_PLAIN_BODY, CONTENT_TYPE_PLAIN, path); + } + + private void checkNotPartFound(int[] position) throws Exception { + try { + to(position); + fail("Part does not exist. Expected exception to be thrown."); + } catch (PartNotFoundException e) { + // expected + } + } + + private void check(String full, String body, String contentType, + int[] position) throws Exception { + checkContentType(contentType, position); + assertEquals(body, bodyContent(position)); + assertEquals(full, fullContent(position)); + } + + private String fullContent(int[] position) throws Exception { + to(position); + return IOUtils.toString(builder.getFullContent().getInputStream()); + } + + private String bodyContent(int[] position) throws Exception { + to(position); + return IOUtils.toString(builder.getMimeBodyContent().getInputStream()); + } + + private void checkContentType(String contentType, int[] position) + throws Exception { + List
headers = headers(position); + assertEquals(1, headers.size()); + ResultHeader header = (ResultHeader) headers.get(0); + assertEquals(CONTENT_TYPE, header.getName()); + assertEquals(contentType, header.getValue()); + } + + private List
headers(int[] position) throws Exception { + to(position); + return builder.getMimeHeaders(); + } + + private void to(int[] path) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(MULTIPART_MIXED).array()); + builder.parse(in); + for (int i = 0; i < path.length; i++) { + builder.to(path[i]); + } + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderMultipartAlternativeTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderMultipartAlternativeTest.java.svn-base new file mode 100644 index 0000000..e3e2a9e --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/PartContentBuilderMultipartAlternativeTest.java.svn-base @@ -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; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.List; + +import javax.management.openmbean.InvalidOpenTypeException; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultHeader; +import org.apache.james.mailbox.store.streaming.PartContentBuilder; +import org.junit.Before; +import org.junit.Test; + +public class PartContentBuilderMultipartAlternativeTest { + + private static final String CONTENT_TYPE_PLAIN = "text/plain;charset=us-ascii"; + + private static final String CONTENT_TYPE_HTML = "text/html;charset=us-ascii"; + + private static final String CONTENT_TYPE_XHTML = "application/xhtml;charset=us-ascii"; + + private static final String CONTENT_TYPE = "Content-Type"; + + private static final String ALT_PLAIN_BODY = "Rhubarb!Rhubard!Rhubard!\r\n"; + + private static final String ALT_XHTML_BODY = "" + + "Rhubard!

Rhubarb!Rhubard!Rhubard!

\r\n"; + + private static final String ALT_HTML_BODY = "Rhubard!

Rhubarb!Rhubard!Rhubard!

\r\n"; + + private static final String ALT_PART_XHTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_XHTML + "\r\n" + "\r\n" + ALT_XHTML_BODY; + + private static final String ALT_PART_HTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_HTML + "\r\n" + "\r\n" + ALT_HTML_BODY; + + private static final String ALT_PART_PLAIN = CONTENT_TYPE + ": " + + CONTENT_TYPE_PLAIN + "\r\n" + "\r\n" + ALT_PLAIN_BODY; + + private static final String MULTIPART_ALTERNATIVE = "From: Samual Smith \r\n" + + "To: John Smith \r\n" + + "Date: Sun, 10 Feb 2008 08:00:00 -0800 (PST)\r\n" + + "Subject: Rhubarb!\r\n" + + "Content-Type: multipart/alternative;boundary=4242\r\n" + + "\r\n" + + "--4242\r\n" + + ALT_PART_PLAIN + + "\r\n--4242\r\n" + + ALT_PART_HTML + + "\r\n--4242\r\n" + ALT_PART_XHTML + "\r\n--4242\r\n"; + + PartContentBuilder builder; + + @Before + public void setUp() throws Exception { + builder = new PartContentBuilder(); + } + + @Test + public void testShouldLocatePartsOfMultipartAlterative() throws Exception { + assertEquals(ALT_PLAIN_BODY, bodyContent(MULTIPART_ALTERNATIVE, 1)); + assertEquals(ALT_HTML_BODY, bodyContent(MULTIPART_ALTERNATIVE, 2)); + assertEquals(ALT_XHTML_BODY, bodyContent(MULTIPART_ALTERNATIVE, 3)); + } + + @Test + public void testShouldLocateHeadersOfMultipartAlterative() throws Exception { + checkContentType(CONTENT_TYPE_PLAIN, MULTIPART_ALTERNATIVE, 1); + checkContentType(CONTENT_TYPE_HTML, MULTIPART_ALTERNATIVE, 2); + checkContentType(CONTENT_TYPE_XHTML, MULTIPART_ALTERNATIVE, 3); + } + + @Test + public void testShouldLocateFullContentOfMultipartAlterative() + throws Exception { + assertEquals(ALT_PART_PLAIN, fullContent(MULTIPART_ALTERNATIVE, 1)); + assertEquals(ALT_PART_HTML, fullContent(MULTIPART_ALTERNATIVE, 2)); + assertEquals(ALT_PART_XHTML, fullContent(MULTIPART_ALTERNATIVE, 3)); + } + + private String fullContent(String mail, int position) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(mail).array()); + builder.parse(in); + builder.to(position); + return IOUtils.toString(builder.getFullContent().getInputStream()); + } + + private String bodyContent(String mail, int position) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(mail).array()); + builder.parse(in); + builder.to(position); + return IOUtils.toString(builder.getMimeBodyContent().getInputStream()); + } + + private void checkContentType(String contentType, String mail, int position) + throws Exception { + List
headers = headers(mail, position); + assertEquals(1, headers.size()); + ResultHeader header = (ResultHeader) headers.get(0); + assertEquals(CONTENT_TYPE, header.getName()); + assertEquals(contentType, header.getValue()); + } + + private List
headers(String mail, int position) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(mail).array()); + builder.parse(in); + builder.to(position); + return builder.getMimeHeaders(); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsMultipartMixedTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsMultipartMixedTest.java.svn-base new file mode 100644 index 0000000..952b7c5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsMultipartMixedTest.java.svn-base @@ -0,0 +1,223 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.MessageSearches; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SearchUtilsMultipartMixedTest { + + private static final String SAMPLE_INNER_MAIL_BODY_ONE = "far a modern quill doth come too"; + + private static final String SAMPLE_PART_ONE = "The better angel is a man right fair,\r\n"; + + private static final String SAMPLE_PART_TWO = "My bonds in thee are all determinate."; + + private static final String SAMPLE_PART_TWO_FIELD = "948523475273457234952345"; + + private static final String SAMPLE_INNER_MAIL_FIELD = "Inner mail sample"; + + private static final String SAMPLE_INNER_MAIL_MIME_FIELD = "8347673450223534587232312221"; + + private static final String PREMABLE = "This is the premable."; + + private static final String BODY = PREMABLE + "\r\n--1729\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n\r\n" + + "Two loves I have of comfort and despair,\r\n" + + "Which like two spirits do suggest me still:\r\n" + + SAMPLE_PART_ONE + "The worser spirit a woman colour'd ill.\r\n" + + "To win me soon to hell, my female evil,\r\n" + + "Tempteth my better angel from my side,\r\n" + + "And would corrupt my saint to be a devil,\r\n" + + "Wooing his purity with her foul pride.\r\n" + + "And whether that my angel be turn'd fiend,\r\n" + + "Suspect I may, yet not directly tell;\r\n" + + "But being both from me, both to each friend,\r\n" + + "I guess one angel in another's hell:\r\n" + + " Yet this shall I ne'er know, but live in doubt,\r\n" + + " Till my bad angel fire my good one out.\r\n" + "r\n" + + "By William Shakespere\r\n" + "\r\n--1729\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n" + + "Content-Transfer-Encoding: 7bit\r\n" + "Content-ID: 45" + + SAMPLE_PART_TWO_FIELD + "\r\n\r\n" + + "Farewell! thou art too dear for my possessing,\r\n" + + "And like enough thou know'st thy estimate,\r\n" + + "The charter of thy worth gives thee releasing;\r\n" + + SAMPLE_PART_TWO + "\r\n" + + "For how do I hold thee but by thy granting?\r\n" + + "And for that riches where is my deserving?\r\n" + + "The cause of this fair gift in me is wanting,\r\n" + + "And so my patent back again is swerving.\r\n" + + "Thy self thou gav'st, thy own worth then not knowing,\r\n" + + "Or me to whom thou gav'st it, else mistaking;\r\n" + + "So thy great gift, upon misprision growing,\r\n" + + "Comes home again, on better judgement making.\r\n" + + " Thus have I had thee, as a dream doth flatter,\r\n" + + " In sleep a king, but waking no such matter.\r\n" + "r\n" + + "By William Shakespere\r\n" + "\r\n--1729\r\n" + + "Content-Type: message/rfc822\r\n\r\n" + + "From: Timothy Tayler \r\n" + + "To: John Smith \r\n" + + "Date: Sat, 16 Feb 2008 12:00:00 +0000 (GMT)\r\n" + + "Subject: Custard " + SAMPLE_INNER_MAIL_FIELD + " \r\n" + + "Content-Type: multipart/mixed;boundary=2.50290787509\r\n\r\n" + + "--2.50290787509\r\n" + "Content-Type: text/plain\r\n" + + "Content-ID: 4657" + SAMPLE_INNER_MAIL_MIME_FIELD + "\r\n\r\n" + + "I never saw that you did painting need,\r\n" + + "And therefore to your fair no painting set;\r\n" + + "I found, or thought I found, you did exceed\r\n" + + "That barren tender of a poet's debt:\r\n" + + "And therefore have I slept in your report,\r\n" + + "That you yourself, being extant, well might show\r\n" + "How " + + SAMPLE_INNER_MAIL_BODY_ONE + " short,\r\n" + + "Speaking of worth, what worth in you doth grow.\r\n" + + "This silence for my sin you did impute,\r\n" + + "Which shall be most my glory being dumb;\r\n" + + "For I impair not beauty being mute,\r\n" + + "When others would give life, and bring a tomb.\r\n" + + " There lives more life in one of your fair eyes\r\n" + + " Than both your poets can in praise devise.\r\n" + + "\r\n--2.50290787509--\r\n" + "\r\n--1729--\r\n"; + + Message row; + + + Collection recent; + private Logger log = LoggerFactory.getLogger(this.getClass()); + @Before + public void setUp() throws Exception { + final MessageBuilder builder = new MessageBuilder(); + + builder.header("From", "Alex (); + } + + + @Test + public void testShouldNotFindWhatIsNotThere() throws Exception { + assertFalse(new MessageSearches().isMatch(SearchQuery.bodyContains("BOGUS"), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.mailContains("BOGUS"), row, + recent, log)); + } + + @Test + public void testBodyShouldFindTextInBody() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO), + row, recent, log)); + } + + @Test + public void testBodyShouldFindTextInBodyCaseInsensitive() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE.toLowerCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE.toUpperCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE + .toUpperCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO + .toUpperCase()), row, recent, log)); + } + + @Test + public void testBodyShouldNotFindTextInHeaders() throws Exception { + assertFalse(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_FIELD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_PART_TWO_FIELD), row, recent, log)); + } + + @Test + public void testTextShouldFindTextInBody() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO), + row, recent, log)); + } + + @Test + public void testTextShouldFindTextInBodyCaseInsensitive() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE.toLowerCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE.toUpperCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE + .toUpperCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO + .toUpperCase()), row, recent, log)); + } + + @Test + public void testTextShouldFindTextInHeaders() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_FIELD), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_PART_TWO_FIELD), row, recent, log)); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsRFC822Test.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsRFC822Test.java.svn-base new file mode 100644 index 0000000..a6001df --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SearchUtilsRFC822Test.java.svn-base @@ -0,0 +1,119 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.MessageSearches; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SearchUtilsRFC822Test { + + private static final String FROM_ADDRESS = "Harry row; + + private Logger log = LoggerFactory.getLogger(getClass()); + Collection recent; + + @Before + public void setUp() throws Exception { + recent = new ArrayList(); + MessageBuilder builder = new MessageBuilder(); + builder.header("From", "Alex recent; + private Logger log = LoggerFactory.getLogger(getClass()); + + private Calendar getGMT() { + return Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.UK); + } + + private Date getDate(int day, int month, int year) { + Calendar cal = getGMT(); + cal.set(year, month -1, day); + return cal.getTime(); + } + @Before + public void setUp() throws Exception { + recent = new ArrayList(); + builder = new MessageBuilder(); + builder.uid = 1009; + } + + @Test + public void testMatchSizeLessThan() throws Exception { + builder.size = SIZE; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeLessThan(SIZE - 1), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeLessThan(SIZE), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.sizeLessThan(SIZE + 1), row, + recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.sizeLessThan(Integer.MAX_VALUE), row, recent, log)); + } + + @Test + public void testMatchSizeMoreThan() throws Exception { + builder.size = SIZE; + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.sizeGreaterThan(SIZE - 1), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeGreaterThan(SIZE), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeGreaterThan(SIZE + 1), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .sizeGreaterThan(Integer.MAX_VALUE), row, recent, log)); + } + + @Test + public void testMatchSizeEquals() throws Exception { + builder.size = SIZE; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeEquals(SIZE - 1), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.sizeEquals(SIZE), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeEquals(SIZE + 1), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeEquals(Integer.MAX_VALUE), + row, recent, log)); + } + + @Test + public void testMatchInternalDateEquals() throws Exception { + builder.internalDate = SUN_SEP_9TH_2001; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(9, 9, 2000), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(8, 9, 2001), DateResolution.Day), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(9, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(10, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(9, 9, 2002), DateResolution.Day), + row, recent, log)); + } + + + @Test + public void testMatchInternalDateBefore() throws Exception { + builder.internalDate = SUN_SEP_9TH_2001; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(9, 9, 2000), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(8, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(9, 9, 2001), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateBefore(getDate(9, 9, 2002), DateResolution.Day), + row, recent, log)); + } + + @Test + public void testMatchInternalDateAfter() throws Exception { + builder.internalDate = SUN_SEP_9TH_2001; + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(9, 9, 2000), DateResolution.Day), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(8, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(9, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateAfter(getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(9, 9, 2002), DateResolution.Day), + row, recent, log)); + } + + @Test + public void testMatchHeaderDateAfter() throws Exception { + builder.header(DATE_FIELD, RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2000), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(8, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter("BOGUS", getDate(9, 9, + 2001), DateResolution.Day), row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderDateAfter() throws Exception { + builder.header(DATE_FIELD.toUpperCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2000), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(8, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter("BOGUS", getDate(9, 9, + 2001), DateResolution.Day), row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderDateAfter() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2000), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(8, + 9, 2001),DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter("BOGUS", getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testMatchHeaderDateOn() throws Exception { + builder.header(DATE_FIELD, RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(8, 9, + 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(10, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2002), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn("BOGUS", getDate(9, 9, + 2001), DateResolution.Day), row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderDateOn() throws Exception { + builder.header(DATE_FIELD.toUpperCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(8, 9, + 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(10, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn("BOGUS", getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderDateOn() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(8, 9, + 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(10, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn("BOGUS", getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testMatchHeaderDateBefore() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(8, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore("BOGUS", getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderDateBefore() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(8, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore("BOGUS", getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderDateBefore() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(8, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore("BOGUS", getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testMatchHeaderContainsCaps() throws Exception { + builder.header(SUBJECT_FIELD, TEXT.toUpperCase()); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testMatchHeaderContainsLowers() throws Exception { + builder.header(SUBJECT_FIELD, TEXT.toUpperCase()); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testMatchHeaderContains() throws Exception { + builder.header(SUBJECT_FIELD, TEXT.toUpperCase()); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testShouldMatchLowerHeaderContains() throws Exception { + builder.header(SUBJECT_FIELD.toLowerCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderContains() throws Exception { + builder.header(SUBJECT_FIELD.toUpperCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testMatchHeaderExists() throws Exception { + builder.header(SUBJECT_FIELD, TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerExists(DATE_FIELD), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerExists(SUBJECT_FIELD), + row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderExists() throws Exception { + builder.header(SUBJECT_FIELD.toLowerCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerExists(DATE_FIELD), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerExists(SUBJECT_FIELD), + row, recent, log)); + } + + @Test + public void testShouldMatchUppersHeaderExists() throws Exception { + builder.header(SUBJECT_FIELD.toLowerCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerExists(DATE_FIELD), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerExists(SUBJECT_FIELD), + row, recent, log)); + } + + @Test + public void testShouldMatchUidRange() throws Exception { + builder.setKey(1, 1729); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1728, 1728)), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1729, 1729)), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1730, 1730)), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1728)), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1729)), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1729, 1800)), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .uid(range(1730, Long.MAX_VALUE)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1730, + Long.MAX_VALUE, 1, 1728)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1730, Long.MAX_VALUE, + 1, 1729)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .uid(range(1, 1728, 1800, 1810)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1, 1729, 1729)), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1, 1800, 1800)), + row, recent, log)); + } + + @Test + public void testShouldMatchSeenFlagSet() throws Exception { + builder.setFlags(true, false, false, false, false, false); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchAnsweredFlagSet() throws Exception { + builder.setFlags(false, false, true, false, false, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.ANSWERED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchFlaggedFlagSet() throws Exception { + builder.setFlags(false, true, false, false, false, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchDraftFlagSet() throws Exception { + builder.setFlags(false, false, false, true, false, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + + @Test + public void testShouldMatchDeletedFlagSet() throws Exception { + builder.setFlags(false, false, false, false, true, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchSeenRecentSet() throws Exception { + builder.setFlags(false, false, false, false, false, false); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchSeenFlagUnSet() throws Exception { + builder.setFlags(false, true, true, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchAnsweredFlagUnSet() throws Exception { + builder.setFlags(true, true, false, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchFlaggedFlagUnSet() throws Exception { + builder.setFlags(true, false, true, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchDraftFlagUnSet() throws Exception { + builder.setFlags(true, true, true, false, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchDeletedFlagUnSet() throws Exception { + builder.setFlags(true, true, true, true, false, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchSeenRecentUnSet() throws Exception { + builder.setFlags(true, true, true, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid() + 1)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchAll() throws Exception { + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.all(), row, recent, log)); + } + + @Test + public void testShouldMatchNot() throws Exception { + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.not(SearchQuery.all()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.not(SearchQuery + .headerExists(DATE_FIELD)), row, recent, log)); + } + + @Test + public void testShouldMatchOr() throws Exception { + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.or(SearchQuery.all(), + SearchQuery.headerExists(DATE_FIELD)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.or(SearchQuery + .headerExists(DATE_FIELD), SearchQuery.all()), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .or(SearchQuery.headerExists(DATE_FIELD), SearchQuery + .headerExists(DATE_FIELD)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.or(SearchQuery.all(), + SearchQuery.all()), row, recent, log)); + } + + @Test + public void testShouldMatchAnd() throws Exception { + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.and(SearchQuery.all(), + SearchQuery.headerExists(DATE_FIELD)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.and(SearchQuery + .headerExists(DATE_FIELD), SearchQuery.all()), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .and(SearchQuery.headerExists(DATE_FIELD), SearchQuery + .headerExists(DATE_FIELD)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.and(SearchQuery.all(), + SearchQuery.all()), row, recent, log)); + } + + private SearchQuery.NumericRange[] range(long low, long high) { + SearchQuery.NumericRange[] results = { new SearchQuery.NumericRange( + low, high) }; + return results; + } + + private SearchQuery.NumericRange[] range(long lowOne, long highOne, + long lowTwo, long highTwo) { + SearchQuery.NumericRange[] results = { + new SearchQuery.NumericRange(lowOne, highOne), + new SearchQuery.NumericRange(lowTwo, highTwo) }; + return results; + } + + + @Test + public void testMatchHeaderDateOnWithOffset() throws Exception { + builder.header(DATE_FIELD, "Mon, 26 Mar 2007 00:00:00 +0300"); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(26, 3, + 2007), DateResolution.Day),row, recent, log)); + + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(25, 3, + 2007), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(27, 3, + 2007), DateResolution.Day),row, recent, log)); + } + + + @Test + public void testShouldMatchHeaderDateBeforeWithOffset() throws Exception { + builder.header(DATE_FIELD, "Mon, 26 Mar 2007 00:00:00 +0300"); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(26, 3, + 2007), DateResolution.Day),row, recent, log)); + + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(27, 3, + 2007), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(25, 3, + 2007), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchHeaderDateAfterWithOffset() throws Exception { + builder.header(DATE_FIELD, "Mon, 26 Mar 2007 00:00:00 +0300"); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(26, 3, + 2007), DateResolution.Day),row, recent, log)); + + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(27, 3, + 2007), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(25, 3, + 2007), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchAddressHeaderWithComments() throws Exception { + builder.header("To", ""); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.address(AddressType.To, "user-from@domain.org"), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.address(AddressType.From, "user-from@domain.org"), row, recent, log)); + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMembership.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMembership.java.svn-base new file mode 100644 index 0000000..ad0de3a --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleMailboxMembership.java.svn-base @@ -0,0 +1,322 @@ +/**************************************************************** + * 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 java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.SequenceInputStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.mail.Flags; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; + +public class SimpleMailboxMembership implements Message { + + private static final String TOSTRING_SEPARATOR = " "; + + public long mailboxId; + public long uid; + public Date internalDate; + public boolean recent = false; + public boolean answered = false; + public boolean deleted = false; + public boolean draft = false; + public boolean flagged = false; + public boolean seen = false; + + public SimpleMailboxMembership(long mailboxId, long uid, long modSeq, Date internalDate, int size, + Flags flags, byte[] body, final Map headers) throws Exception { + super(); + this.mailboxId = mailboxId; + this.uid = uid; + this.internalDate = internalDate; + this.size = size; + this.body = body; + final Map originalHeaders = headers; + if (originalHeaders == null) { + this.headers = new HashMap(); + } else { + this.headers = originalHeaders; + } + + this.body = body; + setFlags(flags); + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#getInternalDate() + */ + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#getMailboxId() + */ + public Long getMailboxId() { + return mailboxId; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#getUid() + */ + public long getUid() { + return uid; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isAnswered() + */ + public boolean isAnswered() { + return answered; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isDeleted() + */ + public boolean isDeleted() { + return deleted; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isDraft() + */ + public boolean isDraft() { + return draft; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isFlagged() + */ + public boolean isFlagged() { + return flagged; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isRecent() + */ + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isSeen() + */ + public boolean isSeen() { + return seen; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#unsetRecent() + */ + public void unsetRecent() { + recent = false; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#setFlags(javax.mail.Flags) + */ + public void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#createFlags() + */ + public Flags createFlags() { + final Flags flags = new Flags(); + + if (isAnswered()) { + flags.add(Flags.Flag.ANSWERED); + } + if (isDeleted()) { + flags.add(Flags.Flag.DELETED); + } + if (isDraft()) { + flags.add(Flags.Flag.DRAFT); + } + if (isFlagged()) { + flags.add(Flags.Flag.FLAGGED); + } + if (isRecent()) { + flags.add(Flags.Flag.RECENT); + } + if (isSeen()) { + flags.add(Flags.Flag.SEEN); + } + return flags; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailboxId ^ (mailboxId >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Message other = (Message) obj; + if (mailboxId != other.getMailboxId()) + return false; + if (uid != other.getUid()) + return false; + return true; + } + + public String toString() + { + final String retValue = + "mailbox(" + + "mailboxId = " + this.mailboxId + TOSTRING_SEPARATOR + + "uid = " + this.uid + TOSTRING_SEPARATOR + + "internalDate = " + this.internalDate + TOSTRING_SEPARATOR + + "size = " + this.size + TOSTRING_SEPARATOR + + "answered = " + this.answered + TOSTRING_SEPARATOR + + "deleted = " + this.deleted + TOSTRING_SEPARATOR + + "draft = " + this.draft + TOSTRING_SEPARATOR + + "flagged = " + this.flagged + TOSTRING_SEPARATOR + + "recent = " + this.recent + TOSTRING_SEPARATOR + + "seen = " + this.seen + TOSTRING_SEPARATOR + + " )"; + + return retValue; + } + + + public static final char[] NEW_LINE = { 0x0D, 0x0A }; + + public byte[] body; + public Map headers; + public List properties; + public String subType = null; + public String mediaType = null; + public Long textualLineCount = null; + + private int size; + + private long modSeq; + + + /** + * @throws IOException + * @see org.apache.james.imap.Message.mail.model.Document#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return new ByteArrayInputStream(body); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final Writer writer = new OutputStreamWriter(baos, "us-ascii"); + + Iterator> hIt = headers.entrySet().iterator(); + while (hIt.hasNext()) { + Entry header = hIt.next(); + writer.write(header.getKey()); + writer.write(": "); + writer.write(header.getValue()); + writer.write(NEW_LINE); + } + writer.write(NEW_LINE); + writer.flush(); + return new ByteArrayInputStream(baos.toByteArray()); + + } + + public long getBodyOctets() { + return body.length; + } + + public String getSubType() { + return subType; + } + + public String getMediaType() { + return mediaType; + } + + public List getProperties() { + return new ArrayList(properties); + } + + public Long getTextualLineCount() { + return textualLineCount; + } + + public long getFullContentOctets() { + return size; + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Message other) { + return (int) (getUid() - other.getUid()); + } + + public long getModSeq() { + return modSeq; + } + + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + public void setUid(long uid) { + this.uid = uid; + } + + @Override + public InputStream getFullContent() throws IOException { + return new SequenceInputStream(getHeaderContent(), getBodyContent()); + } + + + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleProperty.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleProperty.java.svn-base new file mode 100644 index 0000000..04b438f --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/SimpleProperty.java.svn-base @@ -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.store; + +import org.apache.james.mailbox.store.mail.model.Property; + +/** + * Simple implementation suitable for testing. + */ +public class SimpleProperty implements Property { + + public String localName; + public String namespace; + public String value; + + public SimpleProperty(String namespace, String localName, String value) { + super(); + this.localName = localName; + this.namespace = namespace; + this.value = value; + } + + public String getLocalName() { + return localName; + } + + public String getNamespace() { + return namespace; + } + + public String getValue() { + return value; + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIteratorTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIteratorTest.java.svn-base new file mode 100644 index 0000000..cf33957 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StoreMessageResultIteratorTest.java.svn-base @@ -0,0 +1,177 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.mail.Flags; +import javax.mail.util.SharedByteArrayInputStream; + +import junit.framework.Assert; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import org.junit.Test; + +public class StoreMessageResultIteratorTest { + + @Test + public void testBatching() { + MessageRange range = MessageRange.range(1, 10); + int batchSize = 3; + StoreMessageResultIterator it = new StoreMessageResultIterator(new MessageMapper() { + + @Override + public void endRequest() { + throw new UnsupportedOperationException(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, + org.apache.james.mailbox.store.mail.MessageMapper.FetchType type, int limit) + throws MailboxException { + long start = set.getUidFrom(); + long end = set.getUidTo(); + long calcEnd = start + limit; + if (calcEnd > end) { + calcEnd = end; + } + + List> messages = new ArrayList>(); + long i = start; + while (i < calcEnd) { + long uid = i; + SimpleMessage m = new SimpleMessage(null, 0, 0, new SharedByteArrayInputStream( + "".getBytes()), new Flags(), new PropertyBuilder(), 1L); + m.setUid(uid); + messages.add(m); + i++; + } + return messages.iterator(); + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public void delete(Mailbox mailbox, Message message) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public MessageMetaData add(Mailbox mailbox, Message message) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator updateFlags(Mailbox mailbox, Flags flags, boolean value, + boolean replace, MessageRange set) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public MessageMetaData copy(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public long getLastUid(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + }, null, range, batchSize, new FetchGroup() { + + @Override + public Set getPartContentDescriptors() { + return null; + } + + @Override + public int content() { + return FetchGroup.MINIMAL; + } + }); + + long i = 1; + while (it.hasNext()) { + MessageResult r = it.next(); + Assert.assertEquals(i++, r.getUid()); + } + Assert.assertEquals(10, i); + + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StringBuilderChannel.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StringBuilderChannel.java.svn-base new file mode 100644 index 0000000..3324823 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/.svn/text-base/StringBuilderChannel.java.svn-base @@ -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.store; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; + +public class StringBuilderChannel implements WritableByteChannel { + + private static final Charset ASCII = Charset.forName("US-ASCII"); + + public final StringBuilder builder = new StringBuilder(1024); + + public boolean isClosed = false; + + public int write(ByteBuffer src) throws IOException { + final int result = src.limit() - src.position(); + builder.append(ASCII.decode(src)); + return result; + } + + public void close() throws IOException { + isClosed = true; + } + + public boolean isOpen() { + return !isClosed; + } + + public String toString() { + return builder.toString(); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java new file mode 100644 index 0000000..c751886 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MailboxEventDispatcherFlagsTest.java @@ -0,0 +1,349 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Iterator; + +import javax.mail.Flags; + +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.MailboxACL; +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.mail.model.Mailbox; +import org.apache.james.mailbox.util.EventCollector; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(JMock.class) +public class MailboxEventDispatcherFlagsTest { + + MailboxEventDispatcher dispatcher; + + EventCollector collector; + + MessageResult result; + int sessionId = 10; + + private MailboxSession session = new MockMailboxSession("test") { + + @Override + public long getSessionId() { + return sessionId; + } + + }; + + private Mockery mockery = new JUnit4Mockery(); + + private Mailbox mailbox = new Mailbox() { + + @Override + public Long getMailboxId() { + return 1L; + } + + @Override + public String getNamespace() { + return null; + } + + @Override + public void setNamespace(String namespace) { + } + + @Override + public String getUser() { + return null; + } + + @Override + public void setUser(String user) { + + } + + @Override + public String getName() { + return "test"; + } + + @Override + public void setName(String name) { + } + + @Override + public long getUidValidity() { + return 0; + } + + @Override + public MailboxACL getACL() { + return SimpleMailboxACL.EMPTY; + } + + @Override + public void setACL(MailboxACL acl) { + } + + }; + + @Before + public void setUp() throws Exception { + collector = new EventCollector(); + + dispatcher = new MailboxEventDispatcher(collector); + result = mockery.mock(MessageResult.class); + mockery.checking(new Expectations() {{ + allowing(result).getUid();will(returnValue(23L)); + }}); + } + + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.ANSWERED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.ANSWERED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DELETED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DELETED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DRAFT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.DRAFT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.FLAGGED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.FLAGGED, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.RECENT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.RECENT, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.SEEN, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.SEEN, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + public void testShouldShowMixedChanges() { + Flags originals = new Flags(); + originals.add(Flags.Flag.DRAFT); + originals.add(Flags.Flag.RECENT); + Flags updated = new Flags(); + updated.add(Flags.Flag.ANSWERED); + updated.add(Flags.Flag.DRAFT); + 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 + .get(0); + Iterator iterator = event.getUpdatedFlags().get(0).systemFlagIterator(); + assertNotNull(iterator); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.ANSWERED, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.RECENT, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Flags.Flag.SEEN, iterator.next()); + assertFalse(iterator.hasNext()); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java new file mode 100644 index 0000000..64d5a97 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java @@ -0,0 +1,63 @@ +/**************************************************************** + * 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 java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.mail.Flags; + +import org.apache.james.mailbox.store.mail.model.Message; + +public class MessageBuilder { + + public long mailboxId = 113; + public long uid = 776; + public Date internalDate = new Date(); + public int size = 8867; + public Flags flags = new Flags(); + public byte[] body = {}; + public final Map headers = new HashMap(); + public int lineNumber = 0; + + public Message build() throws Exception { + Message result = new SimpleMailboxMembership(mailboxId, uid, -1, internalDate, size, flags, body, headers); + return result; + } + + public void header(String field, String value) { + headers.put(field, value); + } + + public void setKey(int mailboxId, int uid) { + this.uid = uid; + this.mailboxId = mailboxId; + } + + public void setFlags(boolean seen, boolean flagged, boolean answered, + boolean draft, boolean deleted, boolean recent) { + if (seen) flags.add(Flags.Flag.SEEN); + if (flagged) flags.add(Flags.Flag.FLAGGED); + if (answered) flags.add(Flags.Flag.ANSWERED); + if (draft) flags.add(Flags.Flag.DRAFT); + if (deleted) flags.add(Flags.Flag.DELETED); + if (recent) flags.add(Flags.Flag.RECENT); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageResultImplTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageResultImplTest.java new file mode 100644 index 0000000..1629cee --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageResultImplTest.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.store; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Date; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.junit.Before; +import org.junit.Test; + +public class MessageResultImplTest { + private MessageResultImpl msgResultA; + private MessageResultImpl msgResultACopy; + private MessageResultImpl msgResultB; + private MessageResultImpl msgResultC; + + /** + * Initialize name instances + */ + @Before + public void initNames() throws Exception + { + Date dateAB = new Date(); + Message msgA = buildMessage(100, dateAB); + Message msgB = buildMessage(100, dateAB); + Message msgC = buildMessage(200, new Date()); + + msgResultA = new MessageResultImpl(msgA); + msgResultACopy = new MessageResultImpl(msgA); + msgResultB = new MessageResultImpl(msgB); + msgResultC = new MessageResultImpl(msgC); + } + + + private Message buildMessage(int uid, Date aDate) throws Exception { + MessageBuilder builder = new MessageBuilder(); + builder.uid = uid; + builder.internalDate = aDate; + return builder.build(); + } + + + @Test + public void testEqualsNull() throws Exception + { + assertFalse(msgResultA.equals(null)); + } + + + @Test + public void testEqualsReflexive() throws Exception + { + assertEquals(msgResultA, msgResultA); + } + + + @Test + public void testCompareToReflexive() throws Exception + { + assertEquals(0, msgResultA.compareTo(msgResultA)); + } + + + @Test + public void testHashCodeReflexive() throws Exception + { + assertEquals(msgResultA.hashCode(), msgResultA.hashCode()); + } + + + @Test + public void testEqualsSymmetric() throws Exception + { + assertEquals(msgResultA, msgResultACopy); + assertEquals(msgResultACopy, msgResultA); + } + + + @Test + public void testHashCodeSymmetric() throws Exception + { + assertEquals(msgResultA.hashCode(), msgResultACopy.hashCode()); + assertEquals(msgResultACopy.hashCode(), msgResultA.hashCode()); + } + + + @Test + public void testEqualsTransitive() throws Exception + { + assertEquals(msgResultA, msgResultACopy); + assertEquals(msgResultACopy, msgResultB); + assertEquals(msgResultA, msgResultB); + } + + + @Test + public void testCompareToTransitive() throws Exception + { + assertEquals(0, msgResultA.compareTo(msgResultACopy)); + assertEquals(0, msgResultACopy.compareTo(msgResultB)); + assertEquals(0, msgResultA.compareTo(msgResultB)); + } + + + @Test + public void testHashCodeTransitive() throws Exception + { + assertEquals(msgResultA.hashCode(), msgResultACopy.hashCode()); + assertEquals(msgResultACopy.hashCode(), msgResultB.hashCode()); + assertEquals(msgResultA.hashCode(), msgResultB.hashCode()); + } + + + @Test + public void testNotEqualDiffValue() throws Exception + { + assertFalse(msgResultA.equals(msgResultC)); + assertFalse(msgResultC.equals(msgResultA)); + } + + @Test + public void testShouldReturnPositiveWhenFirstGreaterThanSecond() + throws Exception { + assertTrue( msgResultC.compareTo(msgResultB) > 0); + } + + @Test + public void testShouldReturnNegativeWhenFirstLessThanSecond() + throws Exception { + assertTrue( msgResultB.compareTo(msgResultC) < 0); + } +} \ No newline at end of file diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MockAuthenticator.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MockAuthenticator.java new file mode 100644 index 0000000..e22ba82 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/MockAuthenticator.java @@ -0,0 +1,43 @@ +/**************************************************************** + * 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 java.util.HashMap; +import java.util.Map; + +public class MockAuthenticator implements Authenticator{ + + private Map users = new HashMap(); + + public boolean isAuthentic(String userid, CharSequence passwd) { + String pass = users.get(userid); + if (pass != null) { + return passwd.toString().equals(pass); + } + return false; + } + + public void addUser(String user, String password) { + users .put(user, password); + } + + public void clear() { + users.clear(); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderComplexMultipartTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderComplexMultipartTest.java new file mode 100644 index 0000000..7fef9d5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderComplexMultipartTest.java @@ -0,0 +1,225 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultHeader; +import org.apache.james.mailbox.store.streaming.PartContentBuilder; +import org.apache.james.mailbox.store.streaming.PartContentBuilder.PartNotFoundException; +import org.junit.Before; +import org.junit.Test; + +public class PartContentBuilderComplexMultipartTest { + + private static final String PREAMBLE = "This is the preamble"; + + private static final String CONTENT_TYPE = "Content-Type"; + + private static final String CONTENT_TYPE_HTML = "text/html;charset=us-ascii"; + + private static final String CONTENT_TYPE_PLAIN = "text/plain;charset=us-ascii"; + + private static final String CONTENT_TYPE_RFC822 = "message/rfc822"; + + private static final String OUTER_HTML_BODY = "Rhubard!

Rhubarb!Rhubard!Rhubard!

\r\n"; + + private static final String FULL_OUTER_HTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_HTML + "\r\n\r\n" + OUTER_HTML_BODY; + + private static final String OUTER_PLAIN_BODY = "Rhubarb!Rhubard!Rhubard!\r\n"; + + private static final String FULL_OUTER_PLAIN = CONTENT_TYPE + ": " + + CONTENT_TYPE_PLAIN + "\r\n\r\n" + OUTER_PLAIN_BODY; + + private static final String INNER_HTML_BODY = "Custard!

Custard!Custard!Custard!

\r\n"; + + private static final String FULL_INNER_HTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_HTML + "\r\n\r\n" + INNER_HTML_BODY; + + private static final String INNER_PLAIN_BODY = "Custard!Custard!Custard!\r\n"; + + private static final String FULL_INNER_TXT = CONTENT_TYPE + ": " + + CONTENT_TYPE_PLAIN + "\r\n\r\n" + INNER_PLAIN_BODY; + + private static final String INNERMOST_BODY = "Da!Da!Da!Dah!\r\n"; + + private static final String RFC822_PLAIN_MAIL = "From: Samual Smith \r\n" + + "To: John Smith \r\n" + + "Date: Thu, 1 Feb 2007 08:00:00 -0800 (PST)\r\n" + + "Subject: Rhubard And Custard!\r\n" + + CONTENT_TYPE + + ": " + + CONTENT_TYPE_PLAIN + "\r\n" + "\r\n" + INNERMOST_BODY; + + private static final String FULL_INNERMOST_EMAIL = CONTENT_TYPE + ": " + + CONTENT_TYPE_RFC822 + "\r\n\r\n" + RFC822_PLAIN_MAIL; + + private static final String INNER_MAIL = "From: John Smith \r\n" + + "To: Samual Smith \r\n" + + "Date: Fri, 1 Feb 2008 08:00:00 -0800 (PST)\r\n" + + "Subject: Custard!\r\n" + + "Content-Type: multipart/mixed;boundary=1729\r\n\r\n" + + PREAMBLE + + "\r\n--1729\r\n" + + FULL_INNER_TXT + + "\r\n--1729\r\n" + + FULL_INNER_HTML + + "\r\n--1729\r\n" + + FULL_INNERMOST_EMAIL + + "\r\n--1729--\r\n"; + + private static final String FULL_INNER_MAIL = CONTENT_TYPE + ": " + + CONTENT_TYPE_RFC822 + "\r\n\r\n" + INNER_MAIL; + + private static final String MULTIPART_MIXED = "From: Samual Smith \r\n" + + "To: John Smith \r\n" + + "Date: Sun, 10 Feb 2008 08:00:00 -0800 (PST)\r\n" + + "Subject: Rhubarb!\r\n" + + "Content-Type: multipart/mixed;boundary=4242\r\n\r\n" + + PREAMBLE + + "\r\n--4242\r\n" + + FULL_OUTER_HTML + + "\r\n--4242\r\n" + + FULL_INNER_MAIL + + "\r\n--4242\r\n" + + FULL_OUTER_PLAIN + + "\r\n--4242--\r\n"; + + PartContentBuilder builder; + + @Before + public void setUp() throws Exception { + builder = new PartContentBuilder(); + } + + @Test + public void testShouldNotFoundSubPartOfNonMultiPartTopLevel() + throws Exception { + int[] path = { 1, 1 }; + for (int i = 1; i < 10; i++) { + path[1] = i; + checkNotPartFound(path); + } + } + + @Test + public void testShouldNotFoundSubPartOfNonMultiInnerPart() throws Exception { + int[] path = { 2, 2, 1 }; + for (int i = 1; i < 10; i++) { + path[2] = i; + checkNotPartFound(path); + } + } + + @Test + public void testShouldLocateOuterHtml() throws Exception { + int[] path = { 1 }; + check(FULL_OUTER_HTML, OUTER_HTML_BODY, CONTENT_TYPE_HTML, path); + } + + @Test + public void testShouldLocateOuterMail() throws Exception { + int[] path = { 2 }; + check(FULL_INNER_MAIL, INNER_MAIL, CONTENT_TYPE_RFC822, path); + } + + @Test + public void testShouldLocateOuterPlain() throws Exception { + int[] path = { 3 }; + check(FULL_OUTER_PLAIN, OUTER_PLAIN_BODY, CONTENT_TYPE_PLAIN, path); + } + + @Test + public void testShouldLocateInnerHtml() throws Exception { + int[] path = { 2, 2 }; + check(FULL_INNER_HTML, INNER_HTML_BODY, CONTENT_TYPE_HTML, path); + } + + @Test + public void testShouldLocateInnerMail() throws Exception { + int[] path = { 2, 3 }; + check(FULL_INNERMOST_EMAIL, RFC822_PLAIN_MAIL, CONTENT_TYPE_RFC822, + path); + } + + @Test + public void testShouldLocateInnerPlain() throws Exception { + int[] path = { 2, 1 }; + check(FULL_INNER_TXT, INNER_PLAIN_BODY, CONTENT_TYPE_PLAIN, path); + } + + private void checkNotPartFound(int[] position) throws Exception { + try { + to(position); + fail("Part does not exist. Expected exception to be thrown."); + } catch (PartNotFoundException e) { + // expected + } + } + + private void check(String full, String body, String contentType, + int[] position) throws Exception { + checkContentType(contentType, position); + assertEquals(body, bodyContent(position)); + assertEquals(full, fullContent(position)); + } + + private String fullContent(int[] position) throws Exception { + to(position); + return IOUtils.toString(builder.getFullContent().getInputStream()); + } + + private String bodyContent(int[] position) throws Exception { + to(position); + return IOUtils.toString(builder.getMimeBodyContent().getInputStream()); + } + + private void checkContentType(String contentType, int[] position) + throws Exception { + List
headers = headers(position); + assertEquals(1, headers.size()); + ResultHeader header = (ResultHeader) headers.get(0); + assertEquals(CONTENT_TYPE, header.getName()); + assertEquals(contentType, header.getValue()); + } + + private List
headers(int[] position) throws Exception { + to(position); + return builder.getMimeHeaders(); + } + + private void to(int[] path) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(MULTIPART_MIXED).array()); + builder.parse(in); + for (int i = 0; i < path.length; i++) { + builder.to(path[i]); + } + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderMultipartAlternativeTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderMultipartAlternativeTest.java new file mode 100644 index 0000000..e3e2a9e --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/PartContentBuilderMultipartAlternativeTest.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; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.List; + +import javax.management.openmbean.InvalidOpenTypeException; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.model.MessageResult.Header; +import org.apache.james.mailbox.store.ResultHeader; +import org.apache.james.mailbox.store.streaming.PartContentBuilder; +import org.junit.Before; +import org.junit.Test; + +public class PartContentBuilderMultipartAlternativeTest { + + private static final String CONTENT_TYPE_PLAIN = "text/plain;charset=us-ascii"; + + private static final String CONTENT_TYPE_HTML = "text/html;charset=us-ascii"; + + private static final String CONTENT_TYPE_XHTML = "application/xhtml;charset=us-ascii"; + + private static final String CONTENT_TYPE = "Content-Type"; + + private static final String ALT_PLAIN_BODY = "Rhubarb!Rhubard!Rhubard!\r\n"; + + private static final String ALT_XHTML_BODY = "" + + "Rhubard!

Rhubarb!Rhubard!Rhubard!

\r\n"; + + private static final String ALT_HTML_BODY = "Rhubard!

Rhubarb!Rhubard!Rhubard!

\r\n"; + + private static final String ALT_PART_XHTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_XHTML + "\r\n" + "\r\n" + ALT_XHTML_BODY; + + private static final String ALT_PART_HTML = CONTENT_TYPE + ": " + + CONTENT_TYPE_HTML + "\r\n" + "\r\n" + ALT_HTML_BODY; + + private static final String ALT_PART_PLAIN = CONTENT_TYPE + ": " + + CONTENT_TYPE_PLAIN + "\r\n" + "\r\n" + ALT_PLAIN_BODY; + + private static final String MULTIPART_ALTERNATIVE = "From: Samual Smith \r\n" + + "To: John Smith \r\n" + + "Date: Sun, 10 Feb 2008 08:00:00 -0800 (PST)\r\n" + + "Subject: Rhubarb!\r\n" + + "Content-Type: multipart/alternative;boundary=4242\r\n" + + "\r\n" + + "--4242\r\n" + + ALT_PART_PLAIN + + "\r\n--4242\r\n" + + ALT_PART_HTML + + "\r\n--4242\r\n" + ALT_PART_XHTML + "\r\n--4242\r\n"; + + PartContentBuilder builder; + + @Before + public void setUp() throws Exception { + builder = new PartContentBuilder(); + } + + @Test + public void testShouldLocatePartsOfMultipartAlterative() throws Exception { + assertEquals(ALT_PLAIN_BODY, bodyContent(MULTIPART_ALTERNATIVE, 1)); + assertEquals(ALT_HTML_BODY, bodyContent(MULTIPART_ALTERNATIVE, 2)); + assertEquals(ALT_XHTML_BODY, bodyContent(MULTIPART_ALTERNATIVE, 3)); + } + + @Test + public void testShouldLocateHeadersOfMultipartAlterative() throws Exception { + checkContentType(CONTENT_TYPE_PLAIN, MULTIPART_ALTERNATIVE, 1); + checkContentType(CONTENT_TYPE_HTML, MULTIPART_ALTERNATIVE, 2); + checkContentType(CONTENT_TYPE_XHTML, MULTIPART_ALTERNATIVE, 3); + } + + @Test + public void testShouldLocateFullContentOfMultipartAlterative() + throws Exception { + assertEquals(ALT_PART_PLAIN, fullContent(MULTIPART_ALTERNATIVE, 1)); + assertEquals(ALT_PART_HTML, fullContent(MULTIPART_ALTERNATIVE, 2)); + assertEquals(ALT_PART_XHTML, fullContent(MULTIPART_ALTERNATIVE, 3)); + } + + private String fullContent(String mail, int position) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(mail).array()); + builder.parse(in); + builder.to(position); + return IOUtils.toString(builder.getFullContent().getInputStream()); + } + + private String bodyContent(String mail, int position) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(mail).array()); + builder.parse(in); + builder.to(position); + return IOUtils.toString(builder.getMimeBodyContent().getInputStream()); + } + + private void checkContentType(String contentType, String mail, int position) + throws Exception { + List
headers = headers(mail, position); + assertEquals(1, headers.size()); + ResultHeader header = (ResultHeader) headers.get(0); + assertEquals(CONTENT_TYPE, header.getName()); + assertEquals(contentType, header.getValue()); + } + + private List
headers(String mail, int position) throws Exception { + InputStream in = new ByteArrayInputStream(Charset.forName("us-ascii") + .encode(mail).array()); + builder.parse(in); + builder.to(position); + return builder.getMimeHeaders(); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsMultipartMixedTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsMultipartMixedTest.java new file mode 100644 index 0000000..952b7c5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsMultipartMixedTest.java @@ -0,0 +1,223 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.MessageSearches; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SearchUtilsMultipartMixedTest { + + private static final String SAMPLE_INNER_MAIL_BODY_ONE = "far a modern quill doth come too"; + + private static final String SAMPLE_PART_ONE = "The better angel is a man right fair,\r\n"; + + private static final String SAMPLE_PART_TWO = "My bonds in thee are all determinate."; + + private static final String SAMPLE_PART_TWO_FIELD = "948523475273457234952345"; + + private static final String SAMPLE_INNER_MAIL_FIELD = "Inner mail sample"; + + private static final String SAMPLE_INNER_MAIL_MIME_FIELD = "8347673450223534587232312221"; + + private static final String PREMABLE = "This is the premable."; + + private static final String BODY = PREMABLE + "\r\n--1729\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n\r\n" + + "Two loves I have of comfort and despair,\r\n" + + "Which like two spirits do suggest me still:\r\n" + + SAMPLE_PART_ONE + "The worser spirit a woman colour'd ill.\r\n" + + "To win me soon to hell, my female evil,\r\n" + + "Tempteth my better angel from my side,\r\n" + + "And would corrupt my saint to be a devil,\r\n" + + "Wooing his purity with her foul pride.\r\n" + + "And whether that my angel be turn'd fiend,\r\n" + + "Suspect I may, yet not directly tell;\r\n" + + "But being both from me, both to each friend,\r\n" + + "I guess one angel in another's hell:\r\n" + + " Yet this shall I ne'er know, but live in doubt,\r\n" + + " Till my bad angel fire my good one out.\r\n" + "r\n" + + "By William Shakespere\r\n" + "\r\n--1729\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n" + + "Content-Transfer-Encoding: 7bit\r\n" + "Content-ID: 45" + + SAMPLE_PART_TWO_FIELD + "\r\n\r\n" + + "Farewell! thou art too dear for my possessing,\r\n" + + "And like enough thou know'st thy estimate,\r\n" + + "The charter of thy worth gives thee releasing;\r\n" + + SAMPLE_PART_TWO + "\r\n" + + "For how do I hold thee but by thy granting?\r\n" + + "And for that riches where is my deserving?\r\n" + + "The cause of this fair gift in me is wanting,\r\n" + + "And so my patent back again is swerving.\r\n" + + "Thy self thou gav'st, thy own worth then not knowing,\r\n" + + "Or me to whom thou gav'st it, else mistaking;\r\n" + + "So thy great gift, upon misprision growing,\r\n" + + "Comes home again, on better judgement making.\r\n" + + " Thus have I had thee, as a dream doth flatter,\r\n" + + " In sleep a king, but waking no such matter.\r\n" + "r\n" + + "By William Shakespere\r\n" + "\r\n--1729\r\n" + + "Content-Type: message/rfc822\r\n\r\n" + + "From: Timothy Tayler \r\n" + + "To: John Smith \r\n" + + "Date: Sat, 16 Feb 2008 12:00:00 +0000 (GMT)\r\n" + + "Subject: Custard " + SAMPLE_INNER_MAIL_FIELD + " \r\n" + + "Content-Type: multipart/mixed;boundary=2.50290787509\r\n\r\n" + + "--2.50290787509\r\n" + "Content-Type: text/plain\r\n" + + "Content-ID: 4657" + SAMPLE_INNER_MAIL_MIME_FIELD + "\r\n\r\n" + + "I never saw that you did painting need,\r\n" + + "And therefore to your fair no painting set;\r\n" + + "I found, or thought I found, you did exceed\r\n" + + "That barren tender of a poet's debt:\r\n" + + "And therefore have I slept in your report,\r\n" + + "That you yourself, being extant, well might show\r\n" + "How " + + SAMPLE_INNER_MAIL_BODY_ONE + " short,\r\n" + + "Speaking of worth, what worth in you doth grow.\r\n" + + "This silence for my sin you did impute,\r\n" + + "Which shall be most my glory being dumb;\r\n" + + "For I impair not beauty being mute,\r\n" + + "When others would give life, and bring a tomb.\r\n" + + " There lives more life in one of your fair eyes\r\n" + + " Than both your poets can in praise devise.\r\n" + + "\r\n--2.50290787509--\r\n" + "\r\n--1729--\r\n"; + + Message row; + + + Collection recent; + private Logger log = LoggerFactory.getLogger(this.getClass()); + @Before + public void setUp() throws Exception { + final MessageBuilder builder = new MessageBuilder(); + + builder.header("From", "Alex (); + } + + + @Test + public void testShouldNotFindWhatIsNotThere() throws Exception { + assertFalse(new MessageSearches().isMatch(SearchQuery.bodyContains("BOGUS"), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.mailContains("BOGUS"), row, + recent, log)); + } + + @Test + public void testBodyShouldFindTextInBody() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO), + row, recent, log)); + } + + @Test + public void testBodyShouldFindTextInBodyCaseInsensitive() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE.toLowerCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_BODY_ONE.toUpperCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_ONE + .toUpperCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.bodyContains(SAMPLE_PART_TWO + .toUpperCase()), row, recent, log)); + } + + @Test + public void testBodyShouldNotFindTextInHeaders() throws Exception { + assertFalse(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_INNER_MAIL_FIELD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .bodyContains(SAMPLE_PART_TWO_FIELD), row, recent, log)); + } + + @Test + public void testTextShouldFindTextInBody() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO), + row, recent, log)); + } + + @Test + public void testTextShouldFindTextInBodyCaseInsensitive() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE.toLowerCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO + .toLowerCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE.toUpperCase()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_ONE + .toUpperCase()), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.mailContains(SAMPLE_PART_TWO + .toUpperCase()), row, recent, log)); + } + + @Test + public void testTextShouldFindTextInHeaders() throws Exception { + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_FIELD), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_INNER_MAIL_BODY_ONE), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .mailContains(SAMPLE_PART_TWO_FIELD), row, recent, log)); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsRFC822Test.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsRFC822Test.java new file mode 100644 index 0000000..a6001df --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SearchUtilsRFC822Test.java @@ -0,0 +1,119 @@ +/**************************************************************** + * 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 static org.junit.Assert.*; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.search.MessageSearches; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SearchUtilsRFC822Test { + + private static final String FROM_ADDRESS = "Harry row; + + private Logger log = LoggerFactory.getLogger(getClass()); + Collection recent; + + @Before + public void setUp() throws Exception { + recent = new ArrayList(); + MessageBuilder builder = new MessageBuilder(); + builder.header("From", "Alex recent; + private Logger log = LoggerFactory.getLogger(getClass()); + + private Calendar getGMT() { + return Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.UK); + } + + private Date getDate(int day, int month, int year) { + Calendar cal = getGMT(); + cal.set(year, month -1, day); + return cal.getTime(); + } + @Before + public void setUp() throws Exception { + recent = new ArrayList(); + builder = new MessageBuilder(); + builder.uid = 1009; + } + + @Test + public void testMatchSizeLessThan() throws Exception { + builder.size = SIZE; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeLessThan(SIZE - 1), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeLessThan(SIZE), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.sizeLessThan(SIZE + 1), row, + recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.sizeLessThan(Integer.MAX_VALUE), row, recent, log)); + } + + @Test + public void testMatchSizeMoreThan() throws Exception { + builder.size = SIZE; + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.sizeGreaterThan(SIZE - 1), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeGreaterThan(SIZE), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeGreaterThan(SIZE + 1), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .sizeGreaterThan(Integer.MAX_VALUE), row, recent, log)); + } + + @Test + public void testMatchSizeEquals() throws Exception { + builder.size = SIZE; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeEquals(SIZE - 1), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.sizeEquals(SIZE), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeEquals(SIZE + 1), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.sizeEquals(Integer.MAX_VALUE), + row, recent, log)); + } + + @Test + public void testMatchInternalDateEquals() throws Exception { + builder.internalDate = SUN_SEP_9TH_2001; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(9, 9, 2000), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(8, 9, 2001), DateResolution.Day), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(9, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(10, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateOn(getDate(9, 9, 2002), DateResolution.Day), + row, recent, log)); + } + + + @Test + public void testMatchInternalDateBefore() throws Exception { + builder.internalDate = SUN_SEP_9TH_2001; + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(9, 9, 2000), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(8, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(9, 9, 2001), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.internalDateBefore(getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateBefore(getDate(9, 9, 2002), DateResolution.Day), + row, recent, log)); + } + + @Test + public void testMatchInternalDateAfter() throws Exception { + builder.internalDate = SUN_SEP_9TH_2001; + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(9, 9, 2000), DateResolution.Day), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(8, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(9, 9, 2001), DateResolution.Day), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.internalDateAfter(getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.internalDateAfter(getDate(9, 9, 2002), DateResolution.Day), + row, recent, log)); + } + + @Test + public void testMatchHeaderDateAfter() throws Exception { + builder.header(DATE_FIELD, RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2000), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(8, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter("BOGUS", getDate(9, 9, + 2001), DateResolution.Day), row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderDateAfter() throws Exception { + builder.header(DATE_FIELD.toUpperCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2000), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(8, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter("BOGUS", getDate(9, 9, + 2001), DateResolution.Day), row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderDateAfter() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2000), DateResolution.Day), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(8, + 9, 2001),DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter("BOGUS", getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testMatchHeaderDateOn() throws Exception { + builder.header(DATE_FIELD, RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(8, 9, + 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(10, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2002), DateResolution.Day), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn("BOGUS", getDate(9, 9, + 2001), DateResolution.Day), row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderDateOn() throws Exception { + builder.header(DATE_FIELD.toUpperCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(8, 9, + 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(10, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn("BOGUS", getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderDateOn() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(8, 9, + 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(10, + 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(9, 9, + 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn("BOGUS", getDate(9, 9, + 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testMatchHeaderDateBefore() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(8, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore("BOGUS", getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderDateBefore() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(8, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore("BOGUS", getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderDateBefore() throws Exception { + builder.header(DATE_FIELD.toLowerCase(), RFC822_SUN_SEP_9TH_2001); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2000), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(8, 9, 2001), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(9, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, + getDate(10, 9, 2001), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(9, + 9, 2002), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore("BOGUS", getDate(9, + 9, 2001), DateResolution.Day),row, recent, log)); + } + + @Test + public void testMatchHeaderContainsCaps() throws Exception { + builder.header(SUBJECT_FIELD, TEXT.toUpperCase()); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testMatchHeaderContainsLowers() throws Exception { + builder.header(SUBJECT_FIELD, TEXT.toUpperCase()); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testMatchHeaderContains() throws Exception { + builder.header(SUBJECT_FIELD, TEXT.toUpperCase()); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testShouldMatchLowerHeaderContains() throws Exception { + builder.header(SUBJECT_FIELD.toLowerCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testShouldMatchCapsHeaderContains() throws Exception { + builder.header(SUBJECT_FIELD.toUpperCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + CUSTARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(DATE_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + TEXT), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + RHUBARD), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerContains(SUBJECT_FIELD, + CUSTARD), row, recent, log)); + } + + @Test + public void testMatchHeaderExists() throws Exception { + builder.header(SUBJECT_FIELD, TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerExists(DATE_FIELD), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerExists(SUBJECT_FIELD), + row, recent, log)); + } + + @Test + public void testShouldMatchLowersHeaderExists() throws Exception { + builder.header(SUBJECT_FIELD.toLowerCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerExists(DATE_FIELD), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerExists(SUBJECT_FIELD), + row, recent, log)); + } + + @Test + public void testShouldMatchUppersHeaderExists() throws Exception { + builder.header(SUBJECT_FIELD.toLowerCase(), TEXT); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerExists(DATE_FIELD), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerExists(SUBJECT_FIELD), + row, recent, log)); + } + + @Test + public void testShouldMatchUidRange() throws Exception { + builder.setKey(1, 1729); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1728, 1728)), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1729, 1729)), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1730, 1730)), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1728)), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1729)), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1729, 1800)), row, + recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .uid(range(1730, Long.MAX_VALUE)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1730, + Long.MAX_VALUE, 1, 1728)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1730, Long.MAX_VALUE, + 1, 1729)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .uid(range(1, 1728, 1800, 1810)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1, 1729, 1729)), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.uid(range(1, 1, 1800, 1800)), + row, recent, log)); + } + + @Test + public void testShouldMatchSeenFlagSet() throws Exception { + builder.setFlags(true, false, false, false, false, false); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchAnsweredFlagSet() throws Exception { + builder.setFlags(false, false, true, false, false, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.ANSWERED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchFlaggedFlagSet() throws Exception { + builder.setFlags(false, true, false, false, false, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchDraftFlagSet() throws Exception { + builder.setFlags(false, false, false, true, false, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + + @Test + public void testShouldMatchDeletedFlagSet() throws Exception { + builder.setFlags(false, false, false, false, true, false); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchSeenRecentSet() throws Exception { + builder.setFlags(false, false, false, false, false, false); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.FLAGGED), + row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.DELETED), + row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchSeenFlagUnSet() throws Exception { + builder.setFlags(false, true, true, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchAnsweredFlagUnSet() throws Exception { + builder.setFlags(true, true, false, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchFlaggedFlagUnSet() throws Exception { + builder.setFlags(true, false, true, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchDraftFlagUnSet() throws Exception { + builder.setFlags(true, true, true, false, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchDeletedFlagUnSet() throws Exception { + builder.setFlags(true, true, true, true, false, true); + Message row = builder.build(); + recent.add(new Long(row.getUid())); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertTrue(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertFalse(new MessageSearches().isMatch( + SearchQuery.flagIsUnSet(Flags.Flag.RECENT), row, recent, log)); + } + + @Test + public void testShouldMatchSeenRecentUnSet() throws Exception { + builder.setFlags(true, true, true, true, true, true); + Message row = builder.build(); + recent.add(new Long(row.getUid() + 1)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.SEEN), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.FLAGGED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.ANSWERED), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.DRAFT), + row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .flagIsUnSet(Flags.Flag.DELETED), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.flagIsUnSet(Flags.Flag.RECENT), + row, recent, log)); + } + + @Test + public void testShouldMatchAll() throws Exception { + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.all(), row, recent, log)); + } + + @Test + public void testShouldMatchNot() throws Exception { + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.not(SearchQuery.all()), row, + recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.not(SearchQuery + .headerExists(DATE_FIELD)), row, recent, log)); + } + + @Test + public void testShouldMatchOr() throws Exception { + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.or(SearchQuery.all(), + SearchQuery.headerExists(DATE_FIELD)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.or(SearchQuery + .headerExists(DATE_FIELD), SearchQuery.all()), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .or(SearchQuery.headerExists(DATE_FIELD), SearchQuery + .headerExists(DATE_FIELD)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.or(SearchQuery.all(), + SearchQuery.all()), row, recent, log)); + } + + @Test + public void testShouldMatchAnd() throws Exception { + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.and(SearchQuery.all(), + SearchQuery.headerExists(DATE_FIELD)), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.and(SearchQuery + .headerExists(DATE_FIELD), SearchQuery.all()), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery + .and(SearchQuery.headerExists(DATE_FIELD), SearchQuery + .headerExists(DATE_FIELD)), row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.and(SearchQuery.all(), + SearchQuery.all()), row, recent, log)); + } + + private SearchQuery.NumericRange[] range(long low, long high) { + SearchQuery.NumericRange[] results = { new SearchQuery.NumericRange( + low, high) }; + return results; + } + + private SearchQuery.NumericRange[] range(long lowOne, long highOne, + long lowTwo, long highTwo) { + SearchQuery.NumericRange[] results = { + new SearchQuery.NumericRange(lowOne, highOne), + new SearchQuery.NumericRange(lowTwo, highTwo) }; + return results; + } + + + @Test + public void testMatchHeaderDateOnWithOffset() throws Exception { + builder.header(DATE_FIELD, "Mon, 26 Mar 2007 00:00:00 +0300"); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(26, 3, + 2007), DateResolution.Day),row, recent, log)); + + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(25, 3, + 2007), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateOn(DATE_FIELD, getDate(27, 3, + 2007), DateResolution.Day),row, recent, log)); + } + + + @Test + public void testShouldMatchHeaderDateBeforeWithOffset() throws Exception { + builder.header(DATE_FIELD, "Mon, 26 Mar 2007 00:00:00 +0300"); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(26, 3, + 2007), DateResolution.Day),row, recent, log)); + + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(27, 3, + 2007), DateResolution.Day),row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateBefore(DATE_FIELD, getDate(25, 3, + 2007), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchHeaderDateAfterWithOffset() throws Exception { + builder.header(DATE_FIELD, "Mon, 26 Mar 2007 00:00:00 +0300"); + Message row = builder.build(); + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(26, 3, + 2007), DateResolution.Day),row, recent, log)); + + assertFalse(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(27, 3, + 2007), DateResolution.Day),row, recent, log)); + assertTrue(new MessageSearches().isMatch(SearchQuery.headerDateAfter(DATE_FIELD, getDate(25, 3, + 2007), DateResolution.Day),row, recent, log)); + } + + @Test + public void testShouldMatchAddressHeaderWithComments() throws Exception { + builder.header("To", ""); + Message row = builder.build(); + assertTrue(new MessageSearches().isMatch(SearchQuery.address(AddressType.To, "user-from@domain.org"), row, recent, log)); + assertFalse(new MessageSearches().isMatch(SearchQuery.address(AddressType.From, "user-from@domain.org"), row, recent, log)); + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleMailboxMembership.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleMailboxMembership.java new file mode 100644 index 0000000..ad0de3a --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleMailboxMembership.java @@ -0,0 +1,322 @@ +/**************************************************************** + * 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 java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.SequenceInputStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.mail.Flags; + +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.Property; + +public class SimpleMailboxMembership implements Message { + + private static final String TOSTRING_SEPARATOR = " "; + + public long mailboxId; + public long uid; + public Date internalDate; + public boolean recent = false; + public boolean answered = false; + public boolean deleted = false; + public boolean draft = false; + public boolean flagged = false; + public boolean seen = false; + + public SimpleMailboxMembership(long mailboxId, long uid, long modSeq, Date internalDate, int size, + Flags flags, byte[] body, final Map headers) throws Exception { + super(); + this.mailboxId = mailboxId; + this.uid = uid; + this.internalDate = internalDate; + this.size = size; + this.body = body; + final Map originalHeaders = headers; + if (originalHeaders == null) { + this.headers = new HashMap(); + } else { + this.headers = originalHeaders; + } + + this.body = body; + setFlags(flags); + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#getInternalDate() + */ + public Date getInternalDate() { + return internalDate; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#getMailboxId() + */ + public Long getMailboxId() { + return mailboxId; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#getUid() + */ + public long getUid() { + return uid; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isAnswered() + */ + public boolean isAnswered() { + return answered; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isDeleted() + */ + public boolean isDeleted() { + return deleted; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isDraft() + */ + public boolean isDraft() { + return draft; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isFlagged() + */ + public boolean isFlagged() { + return flagged; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isRecent() + */ + public boolean isRecent() { + return recent; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#isSeen() + */ + public boolean isSeen() { + return seen; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#unsetRecent() + */ + public void unsetRecent() { + recent = false; + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#setFlags(javax.mail.Flags) + */ + public void setFlags(Flags flags) { + answered = flags.contains(Flags.Flag.ANSWERED); + deleted = flags.contains(Flags.Flag.DELETED); + draft = flags.contains(Flags.Flag.DRAFT); + flagged = flags.contains(Flags.Flag.FLAGGED); + recent = flags.contains(Flags.Flag.RECENT); + seen = flags.contains(Flags.Flag.SEEN); + } + + /** + * @see org.apache.james.imap.Message.mail.model.Document#createFlags() + */ + public Flags createFlags() { + final Flags flags = new Flags(); + + if (isAnswered()) { + flags.add(Flags.Flag.ANSWERED); + } + if (isDeleted()) { + flags.add(Flags.Flag.DELETED); + } + if (isDraft()) { + flags.add(Flags.Flag.DRAFT); + } + if (isFlagged()) { + flags.add(Flags.Flag.FLAGGED); + } + if (isRecent()) { + flags.add(Flags.Flag.RECENT); + } + if (isSeen()) { + flags.add(Flags.Flag.SEEN); + } + return flags; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + (int) (mailboxId ^ (mailboxId >>> 32)); + result = PRIME * result + (int) (uid ^ (uid >>> 32)); + return result; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Message other = (Message) obj; + if (mailboxId != other.getMailboxId()) + return false; + if (uid != other.getUid()) + return false; + return true; + } + + public String toString() + { + final String retValue = + "mailbox(" + + "mailboxId = " + this.mailboxId + TOSTRING_SEPARATOR + + "uid = " + this.uid + TOSTRING_SEPARATOR + + "internalDate = " + this.internalDate + TOSTRING_SEPARATOR + + "size = " + this.size + TOSTRING_SEPARATOR + + "answered = " + this.answered + TOSTRING_SEPARATOR + + "deleted = " + this.deleted + TOSTRING_SEPARATOR + + "draft = " + this.draft + TOSTRING_SEPARATOR + + "flagged = " + this.flagged + TOSTRING_SEPARATOR + + "recent = " + this.recent + TOSTRING_SEPARATOR + + "seen = " + this.seen + TOSTRING_SEPARATOR + + " )"; + + return retValue; + } + + + public static final char[] NEW_LINE = { 0x0D, 0x0A }; + + public byte[] body; + public Map headers; + public List properties; + public String subType = null; + public String mediaType = null; + public Long textualLineCount = null; + + private int size; + + private long modSeq; + + + /** + * @throws IOException + * @see org.apache.james.imap.Message.mail.model.Document#getBodyContent() + */ + public InputStream getBodyContent() throws IOException { + return new ByteArrayInputStream(body); + } + + /** + * @see org.apache.james.mailbox.store.mail.model.Message#getHeaderContent() + */ + public InputStream getHeaderContent() throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final Writer writer = new OutputStreamWriter(baos, "us-ascii"); + + Iterator> hIt = headers.entrySet().iterator(); + while (hIt.hasNext()) { + Entry header = hIt.next(); + writer.write(header.getKey()); + writer.write(": "); + writer.write(header.getValue()); + writer.write(NEW_LINE); + } + writer.write(NEW_LINE); + writer.flush(); + return new ByteArrayInputStream(baos.toByteArray()); + + } + + public long getBodyOctets() { + return body.length; + } + + public String getSubType() { + return subType; + } + + public String getMediaType() { + return mediaType; + } + + public List getProperties() { + return new ArrayList(properties); + } + + public Long getTextualLineCount() { + return textualLineCount; + } + + public long getFullContentOctets() { + return size; + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Message other) { + return (int) (getUid() - other.getUid()); + } + + public long getModSeq() { + return modSeq; + } + + public void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + public void setUid(long uid) { + this.uid = uid; + } + + @Override + public InputStream getFullContent() throws IOException { + return new SequenceInputStream(getHeaderContent(), getBodyContent()); + } + + + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleProperty.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleProperty.java new file mode 100644 index 0000000..04b438f --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/SimpleProperty.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.store; + +import org.apache.james.mailbox.store.mail.model.Property; + +/** + * Simple implementation suitable for testing. + */ +public class SimpleProperty implements Property { + + public String localName; + public String namespace; + public String value; + + public SimpleProperty(String namespace, String localName, String value) { + super(); + this.localName = localName; + this.namespace = namespace; + this.value = value; + } + + public String getLocalName() { + return localName; + } + + public String getNamespace() { + return namespace; + } + + public String getValue() { + return value; + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java new file mode 100644 index 0000000..cf33957 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java @@ -0,0 +1,177 @@ +/**************************************************************** + * 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 java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.mail.Flags; +import javax.mail.util.SharedByteArrayInputStream; + +import junit.framework.Assert; + +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageMetaData; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.model.UpdatedFlags; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.store.mail.model.Message; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMessage; +import org.junit.Test; + +public class StoreMessageResultIteratorTest { + + @Test + public void testBatching() { + MessageRange range = MessageRange.range(1, 10); + int batchSize = 3; + StoreMessageResultIterator it = new StoreMessageResultIterator(new MessageMapper() { + + @Override + public void endRequest() { + throw new UnsupportedOperationException(); + } + + @Override + public T execute(Transaction transaction) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator> findInMailbox(Mailbox mailbox, MessageRange set, + org.apache.james.mailbox.store.mail.MessageMapper.FetchType type, int limit) + throws MailboxException { + long start = set.getUidFrom(); + long end = set.getUidTo(); + long calcEnd = start + limit; + if (calcEnd > end) { + calcEnd = end; + } + + List> messages = new ArrayList>(); + long i = start; + while (i < calcEnd) { + long uid = i; + SimpleMessage m = new SimpleMessage(null, 0, 0, new SharedByteArrayInputStream( + "".getBytes()), new Flags(), new PropertyBuilder(), 1L); + m.setUid(uid); + messages.add(m); + i++; + } + return messages.iterator(); + } + + @Override + public Map expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) + throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public void delete(Mailbox mailbox, Message message) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public List findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public MessageMetaData add(Mailbox mailbox, Message message) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator updateFlags(Mailbox mailbox, Flags flags, boolean value, + boolean replace, MessageRange set) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public MessageMetaData copy(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override + public long getLastUid(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public long getHighestModSeq(Mailbox mailbox) throws MailboxException { + throw new UnsupportedOperationException(); + } + + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + }, null, range, batchSize, new FetchGroup() { + + @Override + public Set getPartContentDescriptors() { + return null; + } + + @Override + public int content() { + return FetchGroup.MINIMAL; + } + }); + + long i = 1; + while (it.hasNext()) { + MessageResult r = it.next(); + Assert.assertEquals(i++, r.getUid()); + } + Assert.assertEquals(10, i); + + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StringBuilderChannel.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StringBuilderChannel.java new file mode 100644 index 0000000..3324823 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StringBuilderChannel.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.store; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; + +public class StringBuilderChannel implements WritableByteChannel { + + private static final Charset ASCII = Charset.forName("US-ASCII"); + + public final StringBuilder builder = new StringBuilder(1024); + + public boolean isClosed = false; + + public int write(ByteBuffer src) throws IOException { + final int result = src.limit() - src.position(); + builder.append(ASCII.decode(src)); + return result; + } + + public void close() throws IOException { + isClosed = true; + } + + public boolean isOpen() { + return !isClosed; + } + + public String toString() { + return builder.toString(); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops new file mode 100644 index 0000000..67b65c8 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries new file mode 100644 index 0000000..7ab8e63 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +model +dir + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops new file mode 100644 index 0000000..4a7e7ce --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail/model +END +AbstractMessageTest.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1452807/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail/model/AbstractMessageTest.java +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/entries new file mode 100644 index 0000000..c4aa9aa --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail/model +http://svn.apache.org/repos/asf + + + +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +impl +dir + +AbstractMessageTest.java +file + + + + +2013-09-02T02:54:39.000000Z +e38c94d88b91a05768976eabff2e8bdc +2013-03-05T14:22:04.421500Z +1452807 +ieugen + + + + + + + + + + + + + + + + + + + + + +2385 + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessageTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessageTest.java.svn-base new file mode 100644 index 0000000..b72065c --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/.svn/text-base/AbstractMessageTest.java.svn-base @@ -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.mail.model; + +import static org.junit.Assert.*; + +import org.apache.james.mailbox.store.MessageBuilder; +import org.apache.james.mailbox.store.mail.model.Message; +import org.junit.Test; + +public class AbstractMessageTest { + + @Test + public void testShouldReturnPositiveWhenFirstGreaterThanSecond() + throws Exception { + Message one = buildMessage(100); + Message two = buildMessage(99); + assertTrue( one.compareTo(two) > 0); + } + + private Message buildMessage(int uid) throws Exception { + MessageBuilder builder = new MessageBuilder(); + builder.uid = uid; + return builder.build(); + } + + @Test + public void testShouldReturnNegativeWhenFirstLessThanSecond() + throws Exception { + Message one = buildMessage(98); + Message two = buildMessage(99); + assertTrue( one.compareTo(two) < 0); + } + + @Test + public void testShouldReturnZeroWhenFirstEqualsSecond() throws Exception { + Message one = buildMessage(90); + Message two = buildMessage(90); + assertEquals(0, one.compareTo(two)); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AbstractMessageTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AbstractMessageTest.java new file mode 100644 index 0000000..b72065c --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AbstractMessageTest.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.mail.model; + +import static org.junit.Assert.*; + +import org.apache.james.mailbox.store.MessageBuilder; +import org.apache.james.mailbox.store.mail.model.Message; +import org.junit.Test; + +public class AbstractMessageTest { + + @Test + public void testShouldReturnPositiveWhenFirstGreaterThanSecond() + throws Exception { + Message one = buildMessage(100); + Message two = buildMessage(99); + assertTrue( one.compareTo(two) > 0); + } + + private Message buildMessage(int uid) throws Exception { + MessageBuilder builder = new MessageBuilder(); + builder.uid = uid; + return builder.build(); + } + + @Test + public void testShouldReturnNegativeWhenFirstLessThanSecond() + throws Exception { + Message one = buildMessage(98); + Message two = buildMessage(99); + assertTrue( one.compareTo(two) < 0); + } + + @Test + public void testShouldReturnZeroWhenFirstEqualsSecond() throws Exception { + Message one = buildMessage(90); + Message two = buildMessage(90); + assertEquals(0, one.compareTo(two)); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops new file mode 100644 index 0000000..ccff443 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1347536/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl +END +SimpleMessageTest.java +K 25 +svn:wc:ra_dav:version-url +V 137 +/repos/asf/!svn/ver/1347536/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessageTest.java +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries new file mode 100644 index 0000000..4a334de --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl +http://svn.apache.org/repos/asf + + + +2012-06-07T08:49:23.066234Z +1347536 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SimpleMessageTest.java +file + + + + +2013-09-02T02:54:39.000000Z +0756e19b7a2281b8ea8997725e4b1e2a +2012-06-07T08:49:23.066234Z +1347536 +eric + + + + + + + + + + + + + + + + + + + + + +3458 + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessageTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessageTest.java.svn-base new file mode 100644 index 0000000..a9e2a20 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/.svn/text-base/SimpleMessageTest.java.svn-base @@ -0,0 +1,82 @@ +/**************************************************************** + * 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.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Calendar; + +import javax.mail.Flags; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +public class SimpleMessageTest { + private static final String MESSAGE_CONTENT = "Simple message content without special characters"; + private static final String MESSAGE_CONTENT_SPECIAL_CHAR = "Simple message content with special characters: \"'(§è!çà$*`"; + private static final SimpleMessage MESSAGE = buildMessage(MESSAGE_CONTENT); + private static final SimpleMessage MESSAGE_SPECIAL_CHAR = buildMessage(MESSAGE_CONTENT_SPECIAL_CHAR); + + @Test + public void testSize() { + assertEquals(MESSAGE_CONTENT.length(), MESSAGE.getFullContentOctets()); + } + + @Test + public void testInputStreamSize() throws IOException { + InputStream is = MESSAGE.getFullContent(); + int b = 0; + int byteCount = 0; + while ((b = is.read()) != -1) { + byteCount++; + } + assertEquals(MESSAGE_CONTENT.length(), byteCount); + } + + @Test + public void testInputStreamSizeSpecialCharacters() throws IOException { + InputStream is = MESSAGE_SPECIAL_CHAR.getFullContent(); + int b = 0; + int byteCount = 0; + while ((b = is.read()) != -1) { + byteCount++; + } + assertFalse(MESSAGE_CONTENT_SPECIAL_CHAR.length() == byteCount); + } + + @Test + public void testFullContent() throws IOException { + assertEquals(MESSAGE_CONTENT, + new String(IOUtils.toByteArray(MESSAGE.getFullContent()))); + assertEquals(MESSAGE_CONTENT_SPECIAL_CHAR, + new String(IOUtils.toByteArray(MESSAGE_SPECIAL_CHAR.getFullContent()))); + } + + private static SimpleMessage buildMessage(String content) { + return new SimpleMessage(Calendar.getInstance().getTime(), + content.length(), 0, new SharedByteArrayInputStream( + content.getBytes()), new Flags(), + new PropertyBuilder(), 1L); + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessageTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessageTest.java new file mode 100644 index 0000000..a9e2a20 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessageTest.java @@ -0,0 +1,82 @@ +/**************************************************************** + * 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.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Calendar; + +import javax.mail.Flags; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +public class SimpleMessageTest { + private static final String MESSAGE_CONTENT = "Simple message content without special characters"; + private static final String MESSAGE_CONTENT_SPECIAL_CHAR = "Simple message content with special characters: \"'(§è!çà$*`"; + private static final SimpleMessage MESSAGE = buildMessage(MESSAGE_CONTENT); + private static final SimpleMessage MESSAGE_SPECIAL_CHAR = buildMessage(MESSAGE_CONTENT_SPECIAL_CHAR); + + @Test + public void testSize() { + assertEquals(MESSAGE_CONTENT.length(), MESSAGE.getFullContentOctets()); + } + + @Test + public void testInputStreamSize() throws IOException { + InputStream is = MESSAGE.getFullContent(); + int b = 0; + int byteCount = 0; + while ((b = is.read()) != -1) { + byteCount++; + } + assertEquals(MESSAGE_CONTENT.length(), byteCount); + } + + @Test + public void testInputStreamSizeSpecialCharacters() throws IOException { + InputStream is = MESSAGE_SPECIAL_CHAR.getFullContent(); + int b = 0; + int byteCount = 0; + while ((b = is.read()) != -1) { + byteCount++; + } + assertFalse(MESSAGE_CONTENT_SPECIAL_CHAR.length() == byteCount); + } + + @Test + public void testFullContent() throws IOException { + assertEquals(MESSAGE_CONTENT, + new String(IOUtils.toByteArray(MESSAGE.getFullContent()))); + assertEquals(MESSAGE_CONTENT_SPECIAL_CHAR, + new String(IOUtils.toByteArray(MESSAGE_SPECIAL_CHAR.getFullContent()))); + } + + private static SimpleMessage buildMessage(String content) { + return new SimpleMessage(Calendar.getInstance().getTime(), + content.length(), 0, new SharedByteArrayInputStream( + content.getBytes()), new Flags(), + new PropertyBuilder(), 1L); + } + +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/all-wcprops new file mode 100644 index 0000000..353ebe8 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 105 +/repos/asf/!svn/ver/1133130/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/search +END +SearchUtilTest.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1133130/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/search/SearchUtilTest.java +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/entries new file mode 100644 index 0000000..9bceba5 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/search +http://svn.apache.org/repos/asf + + + +2011-06-07T19:04:38.888449Z +1133130 +norman + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +SearchUtilTest.java +file + + + + +2013-09-02T02:54:39.000000Z +f04014c418a8330842c5c06c8da4a60f +2011-06-07T19:04:38.888449Z +1133130 +norman + + + + + + + + + + + + + + + + + + + + + +2854 + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtilTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtilTest.java.svn-base new file mode 100644 index 0000000..67b9066 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/.svn/text-base/SearchUtilTest.java.svn-base @@ -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.store.search; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SearchUtilTest { + + @Test + public void testSimpleSubject() { + String subject ="This is my subject"; + assertEquals(subject, SearchUtil.getBaseSubject(subject)); + } + + @Test + public void testReplaceSpacesAndTabsInSubject() { + String subject ="This is my\tsubject"; + assertEquals("This is my subject", SearchUtil.getBaseSubject(subject)); + } + + @Test + public void testRemoveTrailingSpace() { + String subject ="This is my subject "; + assertEquals("This is my subject", SearchUtil.getBaseSubject(subject)); + } + + + @Test + public void testRemoveTrailingFwd() { + String subject ="This is my subject (fwd)"; + assertEquals("This is my subject", SearchUtil.getBaseSubject(subject)); + } + + + @Test + public void testSimpleExtraction() { + String expectedSubject = "Test"; + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Re: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("re: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Fwd: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("fwd: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Fwd: Re: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Fwd: Re: Test (fwd)")); + } + + @Test + public void testComplexExtraction() { + assertEquals("Test", SearchUtil.getBaseSubject("Re: re:re: fwd:[fwd: \t Test] (fwd) (fwd)(fwd) ")); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/SearchUtilTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/SearchUtilTest.java new file mode 100644 index 0000000..67b9066 --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/search/SearchUtilTest.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.store.search; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SearchUtilTest { + + @Test + public void testSimpleSubject() { + String subject ="This is my subject"; + assertEquals(subject, SearchUtil.getBaseSubject(subject)); + } + + @Test + public void testReplaceSpacesAndTabsInSubject() { + String subject ="This is my\tsubject"; + assertEquals("This is my subject", SearchUtil.getBaseSubject(subject)); + } + + @Test + public void testRemoveTrailingSpace() { + String subject ="This is my subject "; + assertEquals("This is my subject", SearchUtil.getBaseSubject(subject)); + } + + + @Test + public void testRemoveTrailingFwd() { + String subject ="This is my subject (fwd)"; + assertEquals("This is my subject", SearchUtil.getBaseSubject(subject)); + } + + + @Test + public void testSimpleExtraction() { + String expectedSubject = "Test"; + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Re: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("re: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Fwd: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("fwd: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Fwd: Re: Test")); + assertEquals(expectedSubject, SearchUtil.getBaseSubject("Fwd: Re: Test (fwd)")); + } + + @Test + public void testComplexExtraction() { + assertEquals("Test", SearchUtil.getBaseSubject("Re: re:re: fwd:[fwd: \t Test] (fwd) (fwd)(fwd) ")); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops new file mode 100644 index 0000000..e0d2cbc --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 108 +/repos/asf/!svn/ver/1127121/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/streaming +END +BodyOffsetInputStreamTest.java +K 25 +svn:wc:ra_dav:version-url +V 139 +/repos/asf/!svn/ver/1127121/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStreamTest.java +END diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/entries b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/entries new file mode 100644 index 0000000..3ab675f --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/streaming +http://svn.apache.org/repos/asf + + + +2011-05-24T15:56:14.174564Z +1127121 +norman + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +BodyOffsetInputStreamTest.java +file + + + + +2013-09-02T02:54:39.000000Z +ad62aa1327481f6d4ff053ded3499163 +2011-05-24T15:56:14.174564Z +1127121 +norman + + + + + + + + + + + + + + + + + + + + + +3023 + diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStreamTest.java.svn-base b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStreamTest.java.svn-base new file mode 100644 index 0000000..03c5c3a --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/.svn/text-base/BodyOffsetInputStreamTest.java.svn-base @@ -0,0 +1,75 @@ +/**************************************************************** + * 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.streaming; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.junit.Test; + +public class BodyOffsetInputStreamTest { + private String mail = "Subject: test\r\n\r\nbody"; + private long expectedOffset = 17; + private long bytes = mail.length(); + @Test + public void testRead() throws IOException { + BodyOffsetInputStream in = new BodyOffsetInputStream(new ByteArrayInputStream(mail.getBytes())); + + @SuppressWarnings("unused") + int i = -1; + while ((i = in.read())!= -1) { + // consume stream + } + assertEquals(expectedOffset, in.getBodyStartOffset()); + assertEquals(bytes, in.getReadBytes()); + + } + + + @Test + public void testReadWithArray() throws IOException { + BodyOffsetInputStream in = new BodyOffsetInputStream(new ByteArrayInputStream(mail.getBytes())); + + @SuppressWarnings("unused") + int i = -1; + byte[] b = new byte[8]; + while ((i = in.read(b))!= -1) { + // consume stream + } + assertEquals(expectedOffset, in.getBodyStartOffset()); + assertEquals(bytes, in.getReadBytes()); + } + + + @Test + public void testReadWithArrayBiggerThenStream() throws IOException { + BodyOffsetInputStream in = new BodyOffsetInputStream(new ByteArrayInputStream(mail.getBytes())); + + @SuppressWarnings("unused") + int i = -1; + byte[] b = new byte[4096]; + while ((i = in.read(b))!= -1) { + // consume stream + } + assertEquals(expectedOffset, in.getBodyStartOffset()); + assertEquals(bytes, in.getReadBytes()); + } +} diff --git a/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStreamTest.java b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStreamTest.java new file mode 100644 index 0000000..03c5c3a --- /dev/null +++ b/james/apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/streaming/BodyOffsetInputStreamTest.java @@ -0,0 +1,75 @@ +/**************************************************************** + * 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.streaming; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.junit.Test; + +public class BodyOffsetInputStreamTest { + private String mail = "Subject: test\r\n\r\nbody"; + private long expectedOffset = 17; + private long bytes = mail.length(); + @Test + public void testRead() throws IOException { + BodyOffsetInputStream in = new BodyOffsetInputStream(new ByteArrayInputStream(mail.getBytes())); + + @SuppressWarnings("unused") + int i = -1; + while ((i = in.read())!= -1) { + // consume stream + } + assertEquals(expectedOffset, in.getBodyStartOffset()); + assertEquals(bytes, in.getReadBytes()); + + } + + + @Test + public void testReadWithArray() throws IOException { + BodyOffsetInputStream in = new BodyOffsetInputStream(new ByteArrayInputStream(mail.getBytes())); + + @SuppressWarnings("unused") + int i = -1; + byte[] b = new byte[8]; + while ((i = in.read(b))!= -1) { + // consume stream + } + assertEquals(expectedOffset, in.getBodyStartOffset()); + assertEquals(bytes, in.getReadBytes()); + } + + + @Test + public void testReadWithArrayBiggerThenStream() throws IOException { + BodyOffsetInputStream in = new BodyOffsetInputStream(new ByteArrayInputStream(mail.getBytes())); + + @SuppressWarnings("unused") + int i = -1; + byte[] b = new byte[4096]; + while ((i = in.read(b))!= -1) { + // consume stream + } + assertEquals(expectedOffset, in.getBodyStartOffset()); + assertEquals(bytes, in.getReadBytes()); + } +} diff --git a/james/apache-james-mailbox/tool/.svn/all-wcprops b/james/apache-james-mailbox/tool/.svn/all-wcprops new file mode 100644 index 0000000..b8bf6c7 --- /dev/null +++ b/james/apache-james-mailbox/tool/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 52 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/tool +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 60 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/tool/pom.xml +END diff --git a/james/apache-james-mailbox/tool/.svn/dir-prop-base b/james/apache-james-mailbox/tool/.svn/dir-prop-base new file mode 100644 index 0000000..8b48092 --- /dev/null +++ b/james/apache-james-mailbox/tool/.svn/dir-prop-base @@ -0,0 +1,7 @@ +K 10 +svn:ignore +V 10 +target +.* + +END diff --git a/james/apache-james-mailbox/tool/.svn/entries b/james/apache-james-mailbox/tool/.svn/entries new file mode 100644 index 0000000..adf21c2 --- /dev/null +++ b/james/apache-james-mailbox/tool/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool +http://svn.apache.org/repos/asf + + + +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:37.000000Z +e8e786b33f0b5d5201a271f868e75f06 +2013-03-05T14:39:26.245277Z +1452816 +ieugen + + + + + + + + + + + + + + + + + + + + + +2874 + diff --git a/james/apache-james-mailbox/tool/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/tool/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..e395a63 --- /dev/null +++ b/james/apache-james-mailbox/tool/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,74 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-tool + Apache James :: Mailbox :: Tools + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-james-mailbox-memory + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.geronimo.specs + geronimo-annotation_1.0_spec + + + org.apache.openjpa + openjpa + + + junit + junit + + + diff --git a/james/apache-james-mailbox/tool/pom.xml b/james/apache-james-mailbox/tool/pom.xml new file mode 100644 index 0000000..e395a63 --- /dev/null +++ b/james/apache-james-mailbox/tool/pom.xml @@ -0,0 +1,74 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + apache-james-mailbox-tool + Apache James :: Mailbox :: Tools + bundle + + + + org.apache.james + apache-james-mailbox-api + + + org.apache.james + apache-james-mailbox-store + + + org.apache.james + apache-james-mailbox-memory + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + org.apache.james + apache-james-mailbox-api + test + test-jar + + + org.apache.geronimo.specs + geronimo-annotation_1.0_spec + + + org.apache.openjpa + openjpa + + + junit + junit + + + diff --git a/james/apache-james-mailbox/tool/src/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/.svn/all-wcprops new file mode 100644 index 0000000..29f08df --- /dev/null +++ b/james/apache-james-mailbox/tool/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 56 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src +END diff --git a/james/apache-james-mailbox/tool/src/.svn/entries b/james/apache-james-mailbox/tool/src/.svn/entries new file mode 100644 index 0000000..17ab340 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/.svn/entries @@ -0,0 +1,37 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + +reporting-site +dir + diff --git a/james/apache-james-mailbox/tool/src/main/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/.svn/all-wcprops new file mode 100644 index 0000000..87a6d93 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 61 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main +END diff --git a/james/apache-james-mailbox/tool/src/main/.svn/entries b/james/apache-james-mailbox/tool/src/main/.svn/entries new file mode 100644 index 0000000..4397fda --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..3693c42 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 66 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java +END diff --git a/james/apache-james-mailbox/tool/src/main/java/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/.svn/entries new file mode 100644 index 0000000..6141f71 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..bd4c31a --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java/org +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/.svn/entries new file mode 100644 index 0000000..8b21c79 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..b50208d --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 77 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..3d25230 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..87a7c78 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 83 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..a688a14 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..7ebbe36 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 91 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..c8417dd --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +copier +dir + +jpa +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/all-wcprops new file mode 100644 index 0000000..40fa157 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 98 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/copier +END +MailboxCopierImpl.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopierImpl.java +END +MailboxCopier.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopier.java +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/entries new file mode 100644 index 0000000..dfdd2bb --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/entries @@ -0,0 +1,96 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/copier +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MailboxCopierImpl.java +file + + + + +2013-09-02T02:54:37.000000Z +53c309ea99b1543c5255ea067f86a707 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +7120 + +MailboxCopier.java +file + + + + +2013-09-02T02:54:37.000000Z +d8167bcbc9a929449f195f01bd1f8797 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +1992 + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopier.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopier.java.svn-base new file mode 100644 index 0000000..1cd5e6b --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopier.java.svn-base @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.copier; + +import java.io.IOException; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.exception.MailboxException; + +/** + * Interface that exposes a method aimed to copy all mailboxes from a source + * mailbox manager to a destination mailbox manager. + * + */ +public interface MailboxCopier { + + /** + * Copy the mailboxes from a mailbox manager to another mailbox manager. The + * implementation is responsible to read all mailboxes form the injected + * srcMailboxManager and to copy all its contents to the dstMailboxManager. + * + * @param src + * @param dest + */ + void copyMailboxes(MailboxManager src, MailboxManager dest) throws MailboxException, IOException; + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierImpl.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierImpl.java.svn-base new file mode 100644 index 0000000..dd97c78 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierImpl.java.svn-base @@ -0,0 +1,162 @@ +/**************************************************************** + * 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.copier; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.store.streaming.InputStreamContent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of the {@link MailboxCopier} interface. + * + */ +public class MailboxCopierImpl implements MailboxCopier { + + private final static FetchGroup GROUP = new FetchGroup() { + + @Override + public int content() { + return FULL_CONTENT; + } + + @Override + public Set getPartContentDescriptors() { + return null; + } + + }; + + /** + * The logger. + */ + private Logger log = LoggerFactory.getLogger(MailboxCopierImpl.class.getName()); + + /** + * @see org.apache.james.mailbox.copier.MailboxCopier#copyMailboxes(org.apache.james.mailbox.MailboxManager, org.apache.james.mailbox.MailboxManager) + */ + public void copyMailboxes(MailboxManager srcMailboxManager, MailboxManager dstMailboxManager) throws MailboxException, IOException { + + Calendar start = Calendar.getInstance(); + + MailboxSession srcMailboxSession; + MailboxSession dstMailboxSession; + + List mailboxPathList = null; + + srcMailboxSession = srcMailboxManager.createSystemSession("manager", log); + srcMailboxManager.startProcessingRequest(srcMailboxSession); + mailboxPathList = srcMailboxManager.list(srcMailboxSession); + srcMailboxManager.endProcessingRequest(srcMailboxSession); + + log.info("Found " + mailboxPathList.size() + " mailboxes in source mailbox manager."); + for (int i=0; i < mailboxPathList.size(); i++) { + log.info("Mailbox#" + i + " path=" + mailboxPathList.get(i)); + } + + MailboxPath mailboxPath = null; + + for (int i=0; i < mailboxPathList.size(); i++) { + + mailboxPath = mailboxPathList.get(i); + + if ((mailboxPath.getName() != null) && (mailboxPath.getName().trim().length() > 0)) { + + log.info("Ready to copy source mailbox path=" + mailboxPath.toString()); + + srcMailboxSession = srcMailboxManager.createSystemSession(mailboxPath.getUser(), log); + dstMailboxSession = dstMailboxManager.createSystemSession(mailboxPath.getUser(), log); + + dstMailboxManager.startProcessingRequest(dstMailboxSession); + try { + dstMailboxManager.createMailbox(mailboxPath, dstMailboxSession); + log.info("Destination mailbox " + i + "/" + mailboxPathList.size() + + " created with path=" + mailboxPath.toString() + + " after " + (Calendar.getInstance().getTimeInMillis() - start.getTimeInMillis()) + " ms."); + } catch (MailboxExistsException e) { + log.error("Mailbox " + i + " with path=" + mailboxPath.toString() + " already exists.", e); + } + dstMailboxManager.endProcessingRequest(dstMailboxSession); + + srcMailboxManager.startProcessingRequest(srcMailboxSession); + MessageManager srcMessageManager = srcMailboxManager.getMailbox(mailboxPath, srcMailboxSession); + srcMailboxManager.endProcessingRequest(srcMailboxSession); + + dstMailboxManager.startProcessingRequest(dstMailboxSession); + MessageManager dstMessageManager = dstMailboxManager.getMailbox(mailboxPath, dstMailboxSession); + + int j=0; + Iterator messageResultIterator = srcMessageManager.getMessages(MessageRange.all(), GROUP, srcMailboxSession); + + while (messageResultIterator.hasNext()) { + + MessageResult messageResult = messageResultIterator.next(); + InputStreamContent content = (InputStreamContent) messageResult.getFullContent(); + + dstMailboxManager.startProcessingRequest(dstMailboxSession); + dstMessageManager.appendMessage(content.getInputStream(), messageResult.getInternalDate(), dstMailboxSession, messageResult.getFlags().contains(Flag.RECENT), messageResult.getFlags()); + dstMailboxManager.endProcessingRequest(dstMailboxSession); + log.info("Message #" + j + " appended in destination mailbox with path=" + mailboxPath.toString()); + j++; + + } + dstMailboxManager.endProcessingRequest(dstMailboxSession); + + } + + else { + + log.info("Destination mailbox " + i + "/" + mailboxPathList.size() + + " with path=" + mailboxPath.toString() + + " has a null or empty name"); + + } + + } + + log.info("Mailboxes copied in " + (Calendar.getInstance().getTimeInMillis() - start.getTimeInMillis()) + " ms."); + + } + + /** + * Set the logger. + * + * @param log + */ + public void setLog(Logger log) { + this.log = log; + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopier.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopier.java new file mode 100644 index 0000000..1cd5e6b --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopier.java @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.copier; + +import java.io.IOException; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.exception.MailboxException; + +/** + * Interface that exposes a method aimed to copy all mailboxes from a source + * mailbox manager to a destination mailbox manager. + * + */ +public interface MailboxCopier { + + /** + * Copy the mailboxes from a mailbox manager to another mailbox manager. The + * implementation is responsible to read all mailboxes form the injected + * srcMailboxManager and to copy all its contents to the dstMailboxManager. + * + * @param src + * @param dest + */ + void copyMailboxes(MailboxManager src, MailboxManager dest) throws MailboxException, IOException; + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopierImpl.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopierImpl.java new file mode 100644 index 0000000..dd97c78 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/copier/MailboxCopierImpl.java @@ -0,0 +1,162 @@ +/**************************************************************** + * 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.copier; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.mail.Flags.Flag; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageRange; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.MessageResult.FetchGroup; +import org.apache.james.mailbox.store.streaming.InputStreamContent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of the {@link MailboxCopier} interface. + * + */ +public class MailboxCopierImpl implements MailboxCopier { + + private final static FetchGroup GROUP = new FetchGroup() { + + @Override + public int content() { + return FULL_CONTENT; + } + + @Override + public Set getPartContentDescriptors() { + return null; + } + + }; + + /** + * The logger. + */ + private Logger log = LoggerFactory.getLogger(MailboxCopierImpl.class.getName()); + + /** + * @see org.apache.james.mailbox.copier.MailboxCopier#copyMailboxes(org.apache.james.mailbox.MailboxManager, org.apache.james.mailbox.MailboxManager) + */ + public void copyMailboxes(MailboxManager srcMailboxManager, MailboxManager dstMailboxManager) throws MailboxException, IOException { + + Calendar start = Calendar.getInstance(); + + MailboxSession srcMailboxSession; + MailboxSession dstMailboxSession; + + List mailboxPathList = null; + + srcMailboxSession = srcMailboxManager.createSystemSession("manager", log); + srcMailboxManager.startProcessingRequest(srcMailboxSession); + mailboxPathList = srcMailboxManager.list(srcMailboxSession); + srcMailboxManager.endProcessingRequest(srcMailboxSession); + + log.info("Found " + mailboxPathList.size() + " mailboxes in source mailbox manager."); + for (int i=0; i < mailboxPathList.size(); i++) { + log.info("Mailbox#" + i + " path=" + mailboxPathList.get(i)); + } + + MailboxPath mailboxPath = null; + + for (int i=0; i < mailboxPathList.size(); i++) { + + mailboxPath = mailboxPathList.get(i); + + if ((mailboxPath.getName() != null) && (mailboxPath.getName().trim().length() > 0)) { + + log.info("Ready to copy source mailbox path=" + mailboxPath.toString()); + + srcMailboxSession = srcMailboxManager.createSystemSession(mailboxPath.getUser(), log); + dstMailboxSession = dstMailboxManager.createSystemSession(mailboxPath.getUser(), log); + + dstMailboxManager.startProcessingRequest(dstMailboxSession); + try { + dstMailboxManager.createMailbox(mailboxPath, dstMailboxSession); + log.info("Destination mailbox " + i + "/" + mailboxPathList.size() + + " created with path=" + mailboxPath.toString() + + " after " + (Calendar.getInstance().getTimeInMillis() - start.getTimeInMillis()) + " ms."); + } catch (MailboxExistsException e) { + log.error("Mailbox " + i + " with path=" + mailboxPath.toString() + " already exists.", e); + } + dstMailboxManager.endProcessingRequest(dstMailboxSession); + + srcMailboxManager.startProcessingRequest(srcMailboxSession); + MessageManager srcMessageManager = srcMailboxManager.getMailbox(mailboxPath, srcMailboxSession); + srcMailboxManager.endProcessingRequest(srcMailboxSession); + + dstMailboxManager.startProcessingRequest(dstMailboxSession); + MessageManager dstMessageManager = dstMailboxManager.getMailbox(mailboxPath, dstMailboxSession); + + int j=0; + Iterator messageResultIterator = srcMessageManager.getMessages(MessageRange.all(), GROUP, srcMailboxSession); + + while (messageResultIterator.hasNext()) { + + MessageResult messageResult = messageResultIterator.next(); + InputStreamContent content = (InputStreamContent) messageResult.getFullContent(); + + dstMailboxManager.startProcessingRequest(dstMailboxSession); + dstMessageManager.appendMessage(content.getInputStream(), messageResult.getInternalDate(), dstMailboxSession, messageResult.getFlags().contains(Flag.RECENT), messageResult.getFlags()); + dstMailboxManager.endProcessingRequest(dstMailboxSession); + log.info("Message #" + j + " appended in destination mailbox with path=" + mailboxPath.toString()); + j++; + + } + dstMailboxManager.endProcessingRequest(dstMailboxSession); + + } + + else { + + log.info("Destination mailbox " + i + "/" + mailboxPathList.size() + + " with path=" + mailboxPath.toString() + + " has a null or empty name"); + + } + + } + + log.info("Mailboxes copied in " + (Calendar.getInstance().getTimeInMillis() - start.getTimeInMillis()) + " ms."); + + } + + /** + * Set the logger. + * + * @param log + */ + public void setLog(Logger log) { + this.log = log; + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops new file mode 100644 index 0000000..f6822da --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/entries new file mode 100644 index 0000000..37aefef --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa +http://svn.apache.org/repos/asf + + + +2011-10-06T12:15:17.538895Z +1179588 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +migrator +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops new file mode 100644 index 0000000..8f70348 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 104 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator +END +JpaMigrator.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/JpaMigrator.java +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/entries new file mode 100644 index 0000000..ccd4350 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/entries @@ -0,0 +1,68 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator +http://svn.apache.org/repos/asf + + + +2011-10-06T12:15:17.538895Z +1179588 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JpaMigrator.java +file + + + + +2013-09-02T02:54:37.000000Z +495c100c7d5d401a89af1471063c4c09 +2011-10-06T12:15:17.538895Z +1179588 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +3323 + +command +dir + +exception +dir + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigrator.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigrator.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigrator.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigrator.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigrator.java.svn-base new file mode 100644 index 0000000..89c7242 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigrator.java.svn-base @@ -0,0 +1,74 @@ +/**************************************************************** + * 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.migrator; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.apache.james.mailbox.jpa.migrator.command.JpaMigrateCommand; +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * The class that will manage the migration commands for the James JPA database. + * All SQL commands should be moved from JAVA code to a separate file. + */ +public class JpaMigrator { + + /** + * The package name where all commands reside. + */ + private static final String JPA_MIGRATION_COMMAND_PACKAGE = JpaMigrateCommand.class.getPackage().getName(); + + /**

Executes the database migration for the provided JIRAs numbers. + * For example, for the https://issues.apache.org/jira/browse/IMAP-165 JIRA, simply invoke + * with IMAP165 as parameter. + * You can also invoke with many JIRA at once. They will be all serially executed.

+ * + * TODO Extract the SQL in JAVA classes to XML file. + * TODO Log with slf4j. + * + * @param jiras the JIRAs numbers + * @throws JpaMigrateException + */ + public static void main(String[] jiras) throws JpaMigrateException { + + try { + + EntityManagerFactory factory = Persistence.createEntityManagerFactory("JamesMigrator"); + EntityManager em = factory.createEntityManager(); + + for (String jira: jiras) { + JpaMigrateCommand jiraJpaMigratable = (JpaMigrateCommand) Class.forName(JPA_MIGRATION_COMMAND_PACKAGE + "." + jira.toUpperCase() + JpaMigrateCommand.class.getSimpleName()).newInstance(); + System.out.println("Now executing " + jira + " migration."); + em.getTransaction().begin(); + jiraJpaMigratable.migrate(em); + em.getTransaction().commit(); + System.out.println(jira + " migration is successfully achieved."); + } + + } + + catch (Throwable t) { + throw new JpaMigrateException(t); + } + + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/JpaMigrator.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/JpaMigrator.java new file mode 100644 index 0000000..89c7242 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/JpaMigrator.java @@ -0,0 +1,74 @@ +/**************************************************************** + * 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.migrator; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.apache.james.mailbox.jpa.migrator.command.JpaMigrateCommand; +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * The class that will manage the migration commands for the James JPA database. + * All SQL commands should be moved from JAVA code to a separate file. + */ +public class JpaMigrator { + + /** + * The package name where all commands reside. + */ + private static final String JPA_MIGRATION_COMMAND_PACKAGE = JpaMigrateCommand.class.getPackage().getName(); + + /**

Executes the database migration for the provided JIRAs numbers. + * For example, for the https://issues.apache.org/jira/browse/IMAP-165 JIRA, simply invoke + * with IMAP165 as parameter. + * You can also invoke with many JIRA at once. They will be all serially executed.

+ * + * TODO Extract the SQL in JAVA classes to XML file. + * TODO Log with slf4j. + * + * @param jiras the JIRAs numbers + * @throws JpaMigrateException + */ + public static void main(String[] jiras) throws JpaMigrateException { + + try { + + EntityManagerFactory factory = Persistence.createEntityManagerFactory("JamesMigrator"); + EntityManager em = factory.createEntityManager(); + + for (String jira: jiras) { + JpaMigrateCommand jiraJpaMigratable = (JpaMigrateCommand) Class.forName(JPA_MIGRATION_COMMAND_PACKAGE + "." + jira.toUpperCase() + JpaMigrateCommand.class.getSimpleName()).newInstance(); + System.out.println("Now executing " + jira + " migration."); + em.getTransaction().begin(); + jiraJpaMigratable.migrate(em); + em.getTransaction().commit(); + System.out.println(jira + " migration is successfully achieved."); + } + + } + + catch (Throwable t) { + throw new JpaMigrateException(t); + } + + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/all-wcprops new file mode 100644 index 0000000..b6801bb --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/all-wcprops @@ -0,0 +1,53 @@ +K 25 +svn:wc:ra_dav:version-url +V 112 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command +END +IMAP180JpaMigrateCommand.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP180JpaMigrateCommand.java +END +JpaMigrateCommand.java +K 25 +svn:wc:ra_dav:version-url +V 135 +/repos/asf/!svn/ver/1060363/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateCommand.java +END +IMAP172JpaMigrateCommand.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP172JpaMigrateCommand.java +END +IMAP165JpaMigrateCommand.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP165JpaMigrateCommand.java +END +IMAP184JpaMigrateCommand.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP184JpaMigrateCommand.java +END +IMAP176JpaMigrateCommand.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP176JpaMigrateCommand.java +END +IMAP168JpaMigrateCommand.java +K 25 +svn:wc:ra_dav:version-url +V 142 +/repos/asf/!svn/ver/1179588/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP168JpaMigrateCommand.java +END +JpaMigrateQuery.java +K 25 +svn:wc:ra_dav:version-url +V 133 +/repos/asf/!svn/ver/1060363/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateQuery.java +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/entries new file mode 100644 index 0000000..81e7ba1 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/entries @@ -0,0 +1,300 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command +http://svn.apache.org/repos/asf + + + +2011-10-06T12:15:17.538895Z +1179588 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +IMAP180JpaMigrateCommand.java +file + + + + +2013-09-02T02:54:37.000000Z +ae726ea32ddbc5ba09683fb4ba855d94 +2011-10-06T12:15:17.538895Z +1179588 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +7294 + +JpaMigrateCommand.java +file + + + + +2013-09-02T02:54:37.000000Z +5484d6bd715c272d2961113b26a865d6 +2010-09-13T15:07:11.707160Z +996569 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1943 + +IMAP172JpaMigrateCommand.java +file + + + + +2013-09-02T02:54:37.000000Z +fa8464d15125ae1ddda034e3b180525d +2011-10-06T12:15:17.538895Z +1179588 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1986 + +IMAP165JpaMigrateCommand.java +file + + + + +2013-09-02T02:54:37.000000Z +8b3cb00fdbf15d8a3422997f41d7b430 +2011-10-06T12:15:17.538895Z +1179588 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +2149 + +IMAP184JpaMigrateCommand.java +file + + + + +2013-09-02T02:54:37.000000Z +23f417f3e5aecfe0b7c5f2b6480d9e1c +2011-10-06T12:15:17.538895Z +1179588 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1903 + +IMAP176JpaMigrateCommand.java +file + + + + +2013-09-02T02:54:37.000000Z +fce5ba885c972a4b5d756d62304efaac +2011-10-06T12:15:17.538895Z +1179588 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1915 + +IMAP168JpaMigrateCommand.java +file + + + + +2013-09-02T02:54:37.000000Z +18ecf054f0b55b58daaff4e4419ba971 +2011-10-06T12:15:17.538895Z +1179588 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +4693 + +JpaMigrateQuery.java +file + + + + +2013-09-02T02:54:37.000000Z +925870ae454c2c7e212565e8f3eecf8e +2010-09-13T15:07:11.707160Z +996569 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1677 + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP165JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP165JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP165JpaMigrateCommand.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP168JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP168JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP168JpaMigrateCommand.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP172JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP172JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP172JpaMigrateCommand.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP176JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP176JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP176JpaMigrateCommand.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP180JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP180JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP180JpaMigrateCommand.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP184JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP184JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/IMAP184JpaMigrateCommand.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateCommand.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateQuery.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateQuery.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/prop-base/JpaMigrateQuery.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP165JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP165JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..02b9588 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP165JpaMigrateCommand.java.svn-base @@ -0,0 +1,44 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA IMAP-165 is "Add index annotation on frequently used columns". + * + * Add 3 indexes for the DELETED, SEEN and RECENT columns of MEMBERSHIP table. + * + * @link https://issues.apache.org/jira/browse/IMAP-165 + * + */ +public class IMAP165JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX I_MMBRSHP_SEEN ON MEMBERSHIP (SEEN)"); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX I_MMBRSHP_RECENT ON MEMBERSHIP (RECENT)"); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX I_MMBRSHP_DELETED ON MEMBERSHIP (DELETED)"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP168JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP168JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..1e1d6a9 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP168JpaMigrateCommand.java.svn-base @@ -0,0 +1,121 @@ +/**************************************************************** + * 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.migrator.command; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + *

+ * JIRA IMAP-168 is "mailboxes can't be identified 100% unambiguously using virtual hosting". + * + * MAILBOX.NAME contains data such as + * "#mail.eric@localhost.net" + * "#mail.eric@localhost.net.INBOX" + * "#mail.eric@localhost.net.INBOX.test" + * "#mail.eric@localhost.net.Trash" + * + * It needs to be splitted into MAILBOX.NAMESPACE | MAILBOX.USER0 | MAILBOX.NAME with + * "#mail" | "eric@localhost.net" | "" ==> was created before, but is not used anymore + * "#mail" | "eric@localhost.net" | "INBOX" + * "#mail" | "eric@localhost.net" | "INBOX.test" + * "#mail" | "eric@localhost.net" | "Trash" + *

+ * + * @link https://issues.apache.org/jira/browse/IMAP-168 + * + */ +public class IMAP168JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX ADD COLUMN NAMESPACE VARCHAR(255)"); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX ADD COLUMN USER0 VARCHAR(255)"); + + Query query = em.createNativeQuery("SELECT NAME FROM MAILBOX"); + List nameList = query.getResultList(); + System.out.println("getResultList returned a result=" + nameList.size()); + for (String name: nameList) { + MailboxPath mailboxPath = new MailboxPath(name); + System.out.println(mailboxPath); + Query update = em.createNativeQuery("UPDATE MAILBOX SET NAMESPACE = ?, USER0 = ?, NAME = ? WHERE NAME = ?"); + update.setParameter(1, mailboxPath.namespace); + update.setParameter(2, mailboxPath.userName); + update.setParameter(3, mailboxPath.mailboxName); + update.setParameter(4, name); + int resultUpdate = update.executeUpdate(); + System.out.println("ExecuteUpdate returned a result=" + resultUpdate); + } + + } + + /** + * + */ + private class MailboxPath { + + protected String namespace; + protected String userName; + protected String mailboxName; + + /** + * @param name + */ + public MailboxPath (String name) { + + if (! name.startsWith("#mail")) { + throw new IllegalArgumentException("The name must begin with #private"); + } + + namespace = "#mail"; + + name = name.substring(6); + + int atIndex = name.indexOf("@"); + int firstDotIndex = name.indexOf(".", atIndex); + int secondDotIndex = name.indexOf(".", firstDotIndex + 1); + + if (secondDotIndex > 0) { + userName = name.substring(0, secondDotIndex); + mailboxName = name.substring(userName.length() + 1); + } + else { + // We don't have a mailbox name... + userName = name.substring(0); + mailboxName = ""; + } + + } + + @Override + public String toString() { + return "MailboxPath [namespace=" + namespace + + ", userName=" + userName + ", mailboxName=" + mailboxName + "]"; + } + + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP172JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP172JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..60c9e58 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP172JpaMigrateCommand.java.svn-base @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA IMAP-172 is "Cleanup JPAMailbox". + * + * Simply drop the MAILBOX.MESSAGECOUNT and MAILBOX.SIZE columns. + * + * @link https://issues.apache.org/jira/browse/IMAP-172 + * + */ +public class IMAP172JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX DROP COLUMN MESSAGECOUNT"); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX DROP COLUMN SIZE"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP176JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP176JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..d3967de --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP176JpaMigrateCommand.java.svn-base @@ -0,0 +1,42 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA 176 is "Change users' namespace to #private". + * + * Simply update the MAILBOX.NAMESPACE column with "#private" value. + * + * @link https://issues.apache.org/jira/browse/IMAP-176 + * + */ +public class IMAP176JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "UPDATE MAILBOX SET NAMESPACE = '#private'"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP180JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP180JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..81d612f --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP180JpaMigrateCommand.java.svn-base @@ -0,0 +1,158 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; +import org.apache.openjpa.kernel.DelegatingResultList; +import org.apache.openjpa.lib.rop.ResultList; + +/** + * JIRA IMAP-180 is "Add @ElementJoinColumn for Property and Header tables". + * + * 1. Add the needed columns on HEADER and PROPERTY + * ALTER TABLE HEADER ADD COLUMN MESSAGE_ID BIGINT + * ALTER TABLE PROPERTY ADD COLUMN MESSAGE_ID BIGINT + * + * 2. Link the HEADER/PROPERTY tables with the MESSAGE table + * SELECT * FROM MESSAGE_HEADER / MESSAGE_HEADER + * + * 3. Add the needed FK and indexes on HEADER and PROPERTY + * CREATE INDEX SQL100727182411700 ON HEADER(MESSAGE_ID) + * ALTER TABLE HEADER ADD CONSTRAINT SQL100727182411700 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID) + * CREATE INDEX SQL100727182411780 ON PROPERTY(MESSAGE_ID) + * ALTER TABLE PROPERTY ADD CONSTRAINT SQL100727182411780 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID) + * + * 4. Drop the MESSAGE_HEADER and MESSAGE_PROPERY tables + * DROP TABLE MESSAGE_HEADER + * DROP TABLE MESSAGE_PROPERTY + * + * @link https://issues.apache.org/jira/browse/IMAP-180 + * + */ +public class IMAP180JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + em.getTransaction().commit(); + migrateHeaders(em); + // Commit after header migration. + migrateProperties(em); + em.getTransaction().begin(); + } + + /** + * Migrate the headers. + */ + private static void migrateHeaders(EntityManager em) { + + em.getTransaction().begin(); + Query headerCountQuery = em.createNativeQuery("SELECT COUNT(MESSAGE_ID) FROM MESSAGE_HEADER", Integer.class); + Integer headerCount = (Integer) headerCountQuery.getResultList().get(0); + System.out.println("Number of headers=" + headerCount); + + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE HEADER ADD COLUMN MESSAGE_ID BIGINT"); + + Query headerQuery = em.createNativeQuery("SELECT MESSAGE_ID, HEADERS_ID FROM MESSAGE_HEADER"); + em.getTransaction().commit(); + + DelegatingResultList headerNameList = (DelegatingResultList) headerQuery.getResultList(); + ResultList rl = headerNameList.getDelegate(); + for (int i=0; i < rl.size(); i++) { + Object[] results = (Object[]) rl.get(i); + Long messageId = (Long) results[0]; + Long headerId = (Long) results[1]; + em.getTransaction().begin(); + Query update = em.createNativeQuery("UPDATE HEADER SET MESSAGE_ID = ? WHERE ID = ?"); + update.setParameter(1, messageId); + update.setParameter(2, headerId); + int result = update.executeUpdate(); + System.out.printf("ExecuteUpdate returned a result=" + result + " for header %d of %d\n", i+1, headerCount); + em.getTransaction().commit(); + } + + em.getTransaction().begin(); + System.out.println("Creating index."); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX SQL100727182411700 ON HEADER(MESSAGE_ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Creating foreign key."); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE HEADER ADD CONSTRAINT SQL100727182411700 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Dropping table."); + JpaMigrateQuery.executeUpdate(em, "DROP TABLE MESSAGE_HEADER"); + em.getTransaction().commit(); + + } + + /** + * Migrate the properties. + */ + private static void migrateProperties(EntityManager em) { + + em.getTransaction().begin(); + Query propertyCountQuery = em.createNativeQuery("SELECT COUNT(MESSAGE_ID) FROM MESSAGE_PROPERTY", Integer.class); + Integer propertyCount = (Integer) propertyCountQuery.getResultList().get(0); + System.out.println("Number of headers=" + propertyCount); + + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE PROPERTY ADD COLUMN MESSAGE_ID BIGINT"); + + Query propertyQuery = em.createNativeQuery("SELECT MESSAGE_ID, PROPERTIES_ID FROM MESSAGE_PROPERTY"); + em.getTransaction().commit(); + + DelegatingResultList propertyNameList = (DelegatingResultList) propertyQuery.getResultList(); + ResultList rl = propertyNameList.getDelegate(); + for (int i=0; i < rl.size(); i++) { + Object[] results = (Object[]) rl.get(i); + Long messageId = (Long) results[0]; + Long propertyId = (Long) results[1]; + em.getTransaction().begin(); + Query update = em.createNativeQuery("UPDATE PROPERTY SET MESSAGE_ID = ? WHERE ID = ?"); + update.setParameter(1, messageId); + update.setParameter(2, propertyId); + int result = update.executeUpdate(); + System.out.printf("ExecuteUpdate returned a result=" + result + " for property %d of %d\n", i+1, propertyCount); + em.getTransaction().commit(); + } + + em.getTransaction().begin(); + System.out.println("Creating index."); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX SQL100727182411780 ON PROPERTY(MESSAGE_ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Creating foreign key."); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE PROPERTY ADD CONSTRAINT SQL100727182411780 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Dropping table."); + JpaMigrateQuery.executeUpdate(em, "DROP TABLE MESSAGE_PROPERTY"); + em.getTransaction().commit(); + + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP184JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP184JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..e1e7edd --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/IMAP184JpaMigrateCommand.java.svn-base @@ -0,0 +1,42 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA IMAP-184 is "Remove size of MailboxMembership". + * + * Simply drop the MAILBOXMEMBERSHIP.SIZE column. + * + * @link https://issues.apache.org/jira/browse/IMAP-184 + * + */ +public class IMAP184JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOXMEMBERSHIP DROP COLUMN SIZE"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateCommand.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateCommand.java.svn-base new file mode 100644 index 0000000..6198826 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateCommand.java.svn-base @@ -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.jpa.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * A command that apply to James database the needed updates. + */ +public interface JpaMigrateCommand { + + /** + * Executes the needed SQL commands on the database via the provided JPA entity manager. + * A transaction on the provided entity manager must be begun by the caller. + * It is also the reponsibility of the caller to commit the opened transaction after + * calling the migrate method. + * + * @param em the provided Entity Manager + * @throws JpaMigrateException + */ + void migrate(EntityManager em) throws JpaMigrateException; + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateQuery.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateQuery.java.svn-base new file mode 100644 index 0000000..752c3ff --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/.svn/text-base/JpaMigrateQuery.java.svn-base @@ -0,0 +1,39 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +/** + * + */ +public class JpaMigrateQuery { + + /** + * @param em + * @param update + */ + public static void executeUpdate(EntityManager em, String update) { + Query query = em.createNativeQuery(update); + int result = query.executeUpdate(); + System.out.println("ExecuteUpdate returned a result=" + result); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP165JpaMigrateCommand.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP165JpaMigrateCommand.java new file mode 100644 index 0000000..02b9588 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP165JpaMigrateCommand.java @@ -0,0 +1,44 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA IMAP-165 is "Add index annotation on frequently used columns". + * + * Add 3 indexes for the DELETED, SEEN and RECENT columns of MEMBERSHIP table. + * + * @link https://issues.apache.org/jira/browse/IMAP-165 + * + */ +public class IMAP165JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX I_MMBRSHP_SEEN ON MEMBERSHIP (SEEN)"); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX I_MMBRSHP_RECENT ON MEMBERSHIP (RECENT)"); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX I_MMBRSHP_DELETED ON MEMBERSHIP (DELETED)"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP168JpaMigrateCommand.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP168JpaMigrateCommand.java new file mode 100644 index 0000000..1e1d6a9 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP168JpaMigrateCommand.java @@ -0,0 +1,121 @@ +/**************************************************************** + * 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.migrator.command; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + *

+ * JIRA IMAP-168 is "mailboxes can't be identified 100% unambiguously using virtual hosting". + * + * MAILBOX.NAME contains data such as + * "#mail.eric@localhost.net" + * "#mail.eric@localhost.net.INBOX" + * "#mail.eric@localhost.net.INBOX.test" + * "#mail.eric@localhost.net.Trash" + * + * It needs to be splitted into MAILBOX.NAMESPACE | MAILBOX.USER0 | MAILBOX.NAME with + * "#mail" | "eric@localhost.net" | "" ==> was created before, but is not used anymore + * "#mail" | "eric@localhost.net" | "INBOX" + * "#mail" | "eric@localhost.net" | "INBOX.test" + * "#mail" | "eric@localhost.net" | "Trash" + *

+ * + * @link https://issues.apache.org/jira/browse/IMAP-168 + * + */ +public class IMAP168JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX ADD COLUMN NAMESPACE VARCHAR(255)"); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX ADD COLUMN USER0 VARCHAR(255)"); + + Query query = em.createNativeQuery("SELECT NAME FROM MAILBOX"); + List nameList = query.getResultList(); + System.out.println("getResultList returned a result=" + nameList.size()); + for (String name: nameList) { + MailboxPath mailboxPath = new MailboxPath(name); + System.out.println(mailboxPath); + Query update = em.createNativeQuery("UPDATE MAILBOX SET NAMESPACE = ?, USER0 = ?, NAME = ? WHERE NAME = ?"); + update.setParameter(1, mailboxPath.namespace); + update.setParameter(2, mailboxPath.userName); + update.setParameter(3, mailboxPath.mailboxName); + update.setParameter(4, name); + int resultUpdate = update.executeUpdate(); + System.out.println("ExecuteUpdate returned a result=" + resultUpdate); + } + + } + + /** + * + */ + private class MailboxPath { + + protected String namespace; + protected String userName; + protected String mailboxName; + + /** + * @param name + */ + public MailboxPath (String name) { + + if (! name.startsWith("#mail")) { + throw new IllegalArgumentException("The name must begin with #private"); + } + + namespace = "#mail"; + + name = name.substring(6); + + int atIndex = name.indexOf("@"); + int firstDotIndex = name.indexOf(".", atIndex); + int secondDotIndex = name.indexOf(".", firstDotIndex + 1); + + if (secondDotIndex > 0) { + userName = name.substring(0, secondDotIndex); + mailboxName = name.substring(userName.length() + 1); + } + else { + // We don't have a mailbox name... + userName = name.substring(0); + mailboxName = ""; + } + + } + + @Override + public String toString() { + return "MailboxPath [namespace=" + namespace + + ", userName=" + userName + ", mailboxName=" + mailboxName + "]"; + } + + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP172JpaMigrateCommand.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP172JpaMigrateCommand.java new file mode 100644 index 0000000..60c9e58 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP172JpaMigrateCommand.java @@ -0,0 +1,43 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA IMAP-172 is "Cleanup JPAMailbox". + * + * Simply drop the MAILBOX.MESSAGECOUNT and MAILBOX.SIZE columns. + * + * @link https://issues.apache.org/jira/browse/IMAP-172 + * + */ +public class IMAP172JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX DROP COLUMN MESSAGECOUNT"); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOX DROP COLUMN SIZE"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP176JpaMigrateCommand.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP176JpaMigrateCommand.java new file mode 100644 index 0000000..d3967de --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP176JpaMigrateCommand.java @@ -0,0 +1,42 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA 176 is "Change users' namespace to #private". + * + * Simply update the MAILBOX.NAMESPACE column with "#private" value. + * + * @link https://issues.apache.org/jira/browse/IMAP-176 + * + */ +public class IMAP176JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "UPDATE MAILBOX SET NAMESPACE = '#private'"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP180JpaMigrateCommand.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP180JpaMigrateCommand.java new file mode 100644 index 0000000..81d612f --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP180JpaMigrateCommand.java @@ -0,0 +1,158 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; +import org.apache.openjpa.kernel.DelegatingResultList; +import org.apache.openjpa.lib.rop.ResultList; + +/** + * JIRA IMAP-180 is "Add @ElementJoinColumn for Property and Header tables". + * + * 1. Add the needed columns on HEADER and PROPERTY + * ALTER TABLE HEADER ADD COLUMN MESSAGE_ID BIGINT + * ALTER TABLE PROPERTY ADD COLUMN MESSAGE_ID BIGINT + * + * 2. Link the HEADER/PROPERTY tables with the MESSAGE table + * SELECT * FROM MESSAGE_HEADER / MESSAGE_HEADER + * + * 3. Add the needed FK and indexes on HEADER and PROPERTY + * CREATE INDEX SQL100727182411700 ON HEADER(MESSAGE_ID) + * ALTER TABLE HEADER ADD CONSTRAINT SQL100727182411700 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID) + * CREATE INDEX SQL100727182411780 ON PROPERTY(MESSAGE_ID) + * ALTER TABLE PROPERTY ADD CONSTRAINT SQL100727182411780 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID) + * + * 4. Drop the MESSAGE_HEADER and MESSAGE_PROPERY tables + * DROP TABLE MESSAGE_HEADER + * DROP TABLE MESSAGE_PROPERTY + * + * @link https://issues.apache.org/jira/browse/IMAP-180 + * + */ +public class IMAP180JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + em.getTransaction().commit(); + migrateHeaders(em); + // Commit after header migration. + migrateProperties(em); + em.getTransaction().begin(); + } + + /** + * Migrate the headers. + */ + private static void migrateHeaders(EntityManager em) { + + em.getTransaction().begin(); + Query headerCountQuery = em.createNativeQuery("SELECT COUNT(MESSAGE_ID) FROM MESSAGE_HEADER", Integer.class); + Integer headerCount = (Integer) headerCountQuery.getResultList().get(0); + System.out.println("Number of headers=" + headerCount); + + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE HEADER ADD COLUMN MESSAGE_ID BIGINT"); + + Query headerQuery = em.createNativeQuery("SELECT MESSAGE_ID, HEADERS_ID FROM MESSAGE_HEADER"); + em.getTransaction().commit(); + + DelegatingResultList headerNameList = (DelegatingResultList) headerQuery.getResultList(); + ResultList rl = headerNameList.getDelegate(); + for (int i=0; i < rl.size(); i++) { + Object[] results = (Object[]) rl.get(i); + Long messageId = (Long) results[0]; + Long headerId = (Long) results[1]; + em.getTransaction().begin(); + Query update = em.createNativeQuery("UPDATE HEADER SET MESSAGE_ID = ? WHERE ID = ?"); + update.setParameter(1, messageId); + update.setParameter(2, headerId); + int result = update.executeUpdate(); + System.out.printf("ExecuteUpdate returned a result=" + result + " for header %d of %d\n", i+1, headerCount); + em.getTransaction().commit(); + } + + em.getTransaction().begin(); + System.out.println("Creating index."); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX SQL100727182411700 ON HEADER(MESSAGE_ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Creating foreign key."); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE HEADER ADD CONSTRAINT SQL100727182411700 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Dropping table."); + JpaMigrateQuery.executeUpdate(em, "DROP TABLE MESSAGE_HEADER"); + em.getTransaction().commit(); + + } + + /** + * Migrate the properties. + */ + private static void migrateProperties(EntityManager em) { + + em.getTransaction().begin(); + Query propertyCountQuery = em.createNativeQuery("SELECT COUNT(MESSAGE_ID) FROM MESSAGE_PROPERTY", Integer.class); + Integer propertyCount = (Integer) propertyCountQuery.getResultList().get(0); + System.out.println("Number of headers=" + propertyCount); + + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE PROPERTY ADD COLUMN MESSAGE_ID BIGINT"); + + Query propertyQuery = em.createNativeQuery("SELECT MESSAGE_ID, PROPERTIES_ID FROM MESSAGE_PROPERTY"); + em.getTransaction().commit(); + + DelegatingResultList propertyNameList = (DelegatingResultList) propertyQuery.getResultList(); + ResultList rl = propertyNameList.getDelegate(); + for (int i=0; i < rl.size(); i++) { + Object[] results = (Object[]) rl.get(i); + Long messageId = (Long) results[0]; + Long propertyId = (Long) results[1]; + em.getTransaction().begin(); + Query update = em.createNativeQuery("UPDATE PROPERTY SET MESSAGE_ID = ? WHERE ID = ?"); + update.setParameter(1, messageId); + update.setParameter(2, propertyId); + int result = update.executeUpdate(); + System.out.printf("ExecuteUpdate returned a result=" + result + " for property %d of %d\n", i+1, propertyCount); + em.getTransaction().commit(); + } + + em.getTransaction().begin(); + System.out.println("Creating index."); + JpaMigrateQuery.executeUpdate(em, "CREATE INDEX SQL100727182411780 ON PROPERTY(MESSAGE_ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Creating foreign key."); + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE PROPERTY ADD CONSTRAINT SQL100727182411780 FOREIGN KEY (MESSAGE_ID) REFERENCES MESSAGE(ID)"); + em.getTransaction().commit(); + + em.getTransaction().begin(); + System.out.println("Dropping table."); + JpaMigrateQuery.executeUpdate(em, "DROP TABLE MESSAGE_PROPERTY"); + em.getTransaction().commit(); + + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP184JpaMigrateCommand.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP184JpaMigrateCommand.java new file mode 100644 index 0000000..e1e7edd --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/IMAP184JpaMigrateCommand.java @@ -0,0 +1,42 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * JIRA IMAP-184 is "Remove size of MailboxMembership". + * + * Simply drop the MAILBOXMEMBERSHIP.SIZE column. + * + * @link https://issues.apache.org/jira/browse/IMAP-184 + * + */ +public class IMAP184JpaMigrateCommand implements JpaMigrateCommand { + + /** + * @see org.apache.james.mailbox.jpa.migrator.command#migrate(javax.persistence.EntityManager) + */ + public void migrate(EntityManager em) throws JpaMigrateException { + JpaMigrateQuery.executeUpdate(em, "ALTER TABLE MAILBOXMEMBERSHIP DROP COLUMN SIZE"); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateCommand.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateCommand.java new file mode 100644 index 0000000..6198826 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateCommand.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.jpa.migrator.command; + +import javax.persistence.EntityManager; + +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; + +/** + * A command that apply to James database the needed updates. + */ +public interface JpaMigrateCommand { + + /** + * Executes the needed SQL commands on the database via the provided JPA entity manager. + * A transaction on the provided entity manager must be begun by the caller. + * It is also the reponsibility of the caller to commit the opened transaction after + * calling the migrate method. + * + * @param em the provided Entity Manager + * @throws JpaMigrateException + */ + void migrate(EntityManager em) throws JpaMigrateException; + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateQuery.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateQuery.java new file mode 100644 index 0000000..752c3ff --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/command/JpaMigrateQuery.java @@ -0,0 +1,39 @@ +/**************************************************************** + * 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.migrator.command; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +/** + * + */ +public class JpaMigrateQuery { + + /** + * @param em + * @param update + */ + public static void executeUpdate(EntityManager em, String update) { + Query query = em.createNativeQuery(update); + int result = query.executeUpdate(); + System.out.println("ExecuteUpdate returned a result=" + result); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/all-wcprops new file mode 100644 index 0000000..6377ab1 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1060363/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception +END +JpaMigrateException.java +K 25 +svn:wc:ra_dav:version-url +V 139 +/repos/asf/!svn/ver/1060363/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/JpaMigrateException.java +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/entries b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/entries new file mode 100644 index 0000000..d4799da --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception +http://svn.apache.org/repos/asf + + + +2010-09-13T15:07:11.707160Z +996569 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JpaMigrateException.java +file + + + + +2013-09-02T02:54:37.000000Z +35625fdf4deb06bc288d339543447879 +2010-09-13T15:07:11.707160Z +996569 +eric +has-props + + + + + + + + + + + + + + + + + + + + +1771 + diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/prop-base/JpaMigrateException.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/prop-base/JpaMigrateException.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/prop-base/JpaMigrateException.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/text-base/JpaMigrateException.java.svn-base b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/text-base/JpaMigrateException.java.svn-base new file mode 100644 index 0000000..4c83e18 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/.svn/text-base/JpaMigrateException.java.svn-base @@ -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.jpa.migrator.exception; + +/** + * Exception to be thrown when a problem occurs in the migration process. + * + */ +public class JpaMigrateException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public JpaMigrateException() { + } + + public JpaMigrateException(String message) { + super(message); + } + + public JpaMigrateException(Throwable cause) { + super(cause); + } + + public JpaMigrateException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/JpaMigrateException.java b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/JpaMigrateException.java new file mode 100644 index 0000000..4c83e18 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/main/java/org/apache/james/mailbox/jpa/migrator/exception/JpaMigrateException.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.jpa.migrator.exception; + +/** + * Exception to be thrown when a problem occurs in the migration process. + * + */ +public class JpaMigrateException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public JpaMigrateException() { + } + + public JpaMigrateException(String message) { + super(message); + } + + public JpaMigrateException(Throwable cause) { + super(cause); + } + + public JpaMigrateException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/james/apache-james-mailbox/tool/src/reporting-site/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/reporting-site/.svn/all-wcprops new file mode 100644 index 0000000..2730369 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/reporting-site/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 71 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/tool/src/reporting-site +END +site.xml +K 25 +svn:wc:ra_dav:version-url +V 80 +/repos/asf/!svn/ver/1078275/james/mailbox/trunk/tool/src/reporting-site/site.xml +END diff --git a/james/apache-james-mailbox/tool/src/reporting-site/.svn/entries b/james/apache-james-mailbox/tool/src/reporting-site/.svn/entries new file mode 100644 index 0000000..4112243 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/reporting-site/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/reporting-site +http://svn.apache.org/repos/asf + + + +2011-03-05T11:38:38.771020Z +1078275 +felixk + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +site.xml +file + + + + +2013-09-02T02:54:37.000000Z +5bde8261948ff1881960902a322aa196 +2011-03-05T11:38:38.771020Z +1078275 +felixk +has-props + + + + + + + + + + + + + + + + + + + + +1006 + diff --git a/james/apache-james-mailbox/tool/src/reporting-site/.svn/prop-base/site.xml.svn-base b/james/apache-james-mailbox/tool/src/reporting-site/.svn/prop-base/site.xml.svn-base new file mode 100644 index 0000000..7b57b30 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/reporting-site/.svn/prop-base/site.xml.svn-base @@ -0,0 +1,9 @@ +K 13 +svn:eol-style +V 6 +native +K 12 +svn:keywords +V 23 +Author Date Id Revision +END diff --git a/james/apache-james-mailbox/tool/src/reporting-site/.svn/text-base/site.xml.svn-base b/james/apache-james-mailbox/tool/src/reporting-site/.svn/text-base/site.xml.svn-base new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/reporting-site/.svn/text-base/site.xml.svn-base @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/tool/src/reporting-site/site.xml b/james/apache-james-mailbox/tool/src/reporting-site/site.xml new file mode 100644 index 0000000..d919164 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/reporting-site/site.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/james/apache-james-mailbox/tool/src/test/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/.svn/all-wcprops new file mode 100644 index 0000000..fded856 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 61 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test +END diff --git a/james/apache-james-mailbox/tool/src/test/.svn/entries b/james/apache-james-mailbox/tool/src/test/.svn/entries new file mode 100644 index 0000000..2b4247c --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/tool/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..d8a6968 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 66 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test/java +END diff --git a/james/apache-james-mailbox/tool/src/test/java/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/.svn/entries new file mode 100644 index 0000000..c94eea3 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..ddd8ef4 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 70 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test/java/org +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/org/.svn/entries new file mode 100644 index 0000000..beb956f --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java/org +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..f20cb99 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 77 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..360e2be --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..0673e74 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 83 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..594a0f3 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..d406752 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 91 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..76c4307 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +copier +dir + +jpa +dir + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/all-wcprops new file mode 100644 index 0000000..a63a197 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 98 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/copier +END +MailboxCopierTest.java +K 25 +svn:wc:ra_dav:version-url +V 121 +/repos/asf/!svn/ver/1242288/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/copier/MailboxCopierTest.java +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/entries new file mode 100644 index 0000000..4865b2a --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/copier +http://svn.apache.org/repos/asf + + + +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +MailboxCopierTest.java +file + + + + +2013-09-02T02:54:37.000000Z +009fea3d622a2ef04c5fb1acde56ab33 +2012-02-09T12:13:02.344953Z +1242288 +eric + + + + + + + + + + + + + + + + + + + + + +6948 + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierTest.java.svn-base b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierTest.java.svn-base new file mode 100644 index 0000000..bfd8c6c --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/.svn/text-base/MailboxCopierTest.java.svn-base @@ -0,0 +1,170 @@ +/**************************************************************** + * 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.copier; + +import java.io.IOException; +import java.util.List; + +import junit.framework.Assert; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory; +import org.apache.james.mailbox.mock.MockMailboxManager; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +/** + * Test class for the {@link MailboxCopierImpl} implementation. + * + * The InMemoryMailboxManager will be used as source and destination + * Mailbox Manager. + * + */ +public class MailboxCopierTest { + + /** + * The instance for the test mailboxCopier. + */ + private MailboxCopierImpl mailboxCopier; + + /** + * The instance for the source Mailbox Manager. + */ + private MailboxManager srcMemMailboxManager; + + /** + * The instance for the destination Mailbox Manager. + */ + private MailboxManager dstMemMailboxManager; + + /** + * Setup the mailboxCopier and the source and destination + * Mailbox Manager. + * + * We use a InMemoryMailboxManager implementation. + * + * @throws BadCredentialsException + * @throws MailboxException + */ + @Before + public void setup() throws BadCredentialsException, MailboxException { + + mailboxCopier = new MailboxCopierImpl(); + mailboxCopier.setLog(LoggerFactory.getLogger(MailboxCopierTest.class.getName())); + + srcMemMailboxManager = newInMemoryMailboxManager(); + dstMemMailboxManager = newInMemoryMailboxManager(); + + } + + /** + * Feed the source MailboxManager with the number of mailboxes and + * messages per mailbox. + * + * Copy the mailboxes to the destination Mailbox Manager, and assert the number + * of mailboxes and messages per mailbox is the same as in the source + * Mailbox Manager. + * + * @throws MailboxException + * @throws IOException + */ + @Test + public void testMailboxCopy() throws MailboxException, IOException { + if (srcMemMailboxManager instanceof StoreMailboxManager) { + ((StoreMailboxManager) srcMemMailboxManager).init(); + } + if (dstMemMailboxManager instanceof StoreMailboxManager) { + ((StoreMailboxManager) dstMemMailboxManager).init(); + } + + srcMemMailboxManager = new MockMailboxManager(srcMemMailboxManager).getMockMailboxManager(); + + assertMailboxManagerSize(srcMemMailboxManager, 1); + + mailboxCopier.copyMailboxes(srcMemMailboxManager, dstMemMailboxManager); + assertMailboxManagerSize(dstMemMailboxManager, 1); + + // We copy a second time to assert existing mailboxes does not give issue. + mailboxCopier.copyMailboxes(srcMemMailboxManager, dstMemMailboxManager); + assertMailboxManagerSize(dstMemMailboxManager, 2); + + } + + /** + * Utility method to assert the number of mailboxes and messages per mailbox + * are the ones expected. + * + * @throws MailboxException + * @throws BadCredentialsException + */ + private void assertMailboxManagerSize(MailboxManager mailboxManager, int multiplicationFactor) throws BadCredentialsException, MailboxException { + + MailboxSession mailboxSession = mailboxManager.createSystemSession("manager", LoggerFactory.getLogger("src-mailbox-copier")); + mailboxManager.startProcessingRequest(mailboxSession); + + List mailboxPathList = mailboxManager.list(mailboxSession); + + Assert.assertEquals(MockMailboxManager.EXPECTED_MAILBOXES_COUNT, mailboxPathList.size()); + + for (MailboxPath mailboxPath: mailboxPathList) { + MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, mailboxSession); + Assert.assertEquals(MockMailboxManager.MESSAGE_PER_MAILBOX_COUNT * multiplicationFactor, messageManager.getMessageCount(mailboxSession)); + } + + mailboxManager.endProcessingRequest(mailboxSession); + mailboxManager.logout(mailboxSession, true); + + } + + /** + * Utility method to instanciate a new InMemoryMailboxManger with + * the needed MailboxSessionMapperFactory, Authenticator and UidProvider. + * + * @return a new InMemoryMailboxManager + */ + private MailboxManager newInMemoryMailboxManager() { + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + return new StoreMailboxManager( + new InMemoryMailboxSessionMapperFactory(), + new Authenticator() { + public boolean isAuthentic(String userid, CharSequence passwd) { + return true; + } + }, + aclResolver, + groupMembershipResolver + ); + + } + +} diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/MailboxCopierTest.java b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/MailboxCopierTest.java new file mode 100644 index 0000000..bfd8c6c --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/copier/MailboxCopierTest.java @@ -0,0 +1,170 @@ +/**************************************************************** + * 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.copier; + +import java.io.IOException; +import java.util.List; + +import junit.framework.Assert; + +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.acl.GroupMembershipResolver; +import org.apache.james.mailbox.acl.MailboxACLResolver; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory; +import org.apache.james.mailbox.mock.MockMailboxManager; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.StoreMailboxManager; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +/** + * Test class for the {@link MailboxCopierImpl} implementation. + * + * The InMemoryMailboxManager will be used as source and destination + * Mailbox Manager. + * + */ +public class MailboxCopierTest { + + /** + * The instance for the test mailboxCopier. + */ + private MailboxCopierImpl mailboxCopier; + + /** + * The instance for the source Mailbox Manager. + */ + private MailboxManager srcMemMailboxManager; + + /** + * The instance for the destination Mailbox Manager. + */ + private MailboxManager dstMemMailboxManager; + + /** + * Setup the mailboxCopier and the source and destination + * Mailbox Manager. + * + * We use a InMemoryMailboxManager implementation. + * + * @throws BadCredentialsException + * @throws MailboxException + */ + @Before + public void setup() throws BadCredentialsException, MailboxException { + + mailboxCopier = new MailboxCopierImpl(); + mailboxCopier.setLog(LoggerFactory.getLogger(MailboxCopierTest.class.getName())); + + srcMemMailboxManager = newInMemoryMailboxManager(); + dstMemMailboxManager = newInMemoryMailboxManager(); + + } + + /** + * Feed the source MailboxManager with the number of mailboxes and + * messages per mailbox. + * + * Copy the mailboxes to the destination Mailbox Manager, and assert the number + * of mailboxes and messages per mailbox is the same as in the source + * Mailbox Manager. + * + * @throws MailboxException + * @throws IOException + */ + @Test + public void testMailboxCopy() throws MailboxException, IOException { + if (srcMemMailboxManager instanceof StoreMailboxManager) { + ((StoreMailboxManager) srcMemMailboxManager).init(); + } + if (dstMemMailboxManager instanceof StoreMailboxManager) { + ((StoreMailboxManager) dstMemMailboxManager).init(); + } + + srcMemMailboxManager = new MockMailboxManager(srcMemMailboxManager).getMockMailboxManager(); + + assertMailboxManagerSize(srcMemMailboxManager, 1); + + mailboxCopier.copyMailboxes(srcMemMailboxManager, dstMemMailboxManager); + assertMailboxManagerSize(dstMemMailboxManager, 1); + + // We copy a second time to assert existing mailboxes does not give issue. + mailboxCopier.copyMailboxes(srcMemMailboxManager, dstMemMailboxManager); + assertMailboxManagerSize(dstMemMailboxManager, 2); + + } + + /** + * Utility method to assert the number of mailboxes and messages per mailbox + * are the ones expected. + * + * @throws MailboxException + * @throws BadCredentialsException + */ + private void assertMailboxManagerSize(MailboxManager mailboxManager, int multiplicationFactor) throws BadCredentialsException, MailboxException { + + MailboxSession mailboxSession = mailboxManager.createSystemSession("manager", LoggerFactory.getLogger("src-mailbox-copier")); + mailboxManager.startProcessingRequest(mailboxSession); + + List mailboxPathList = mailboxManager.list(mailboxSession); + + Assert.assertEquals(MockMailboxManager.EXPECTED_MAILBOXES_COUNT, mailboxPathList.size()); + + for (MailboxPath mailboxPath: mailboxPathList) { + MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, mailboxSession); + Assert.assertEquals(MockMailboxManager.MESSAGE_PER_MAILBOX_COUNT * multiplicationFactor, messageManager.getMessageCount(mailboxSession)); + } + + mailboxManager.endProcessingRequest(mailboxSession); + mailboxManager.logout(mailboxSession, true); + + } + + /** + * Utility method to instanciate a new InMemoryMailboxManger with + * the needed MailboxSessionMapperFactory, Authenticator and UidProvider. + * + * @return a new InMemoryMailboxManager + */ + private MailboxManager newInMemoryMailboxManager() { + MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); + GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver(); + + return new StoreMailboxManager( + new InMemoryMailboxSessionMapperFactory(), + new Authenticator() { + public boolean isAuthentic(String userid, CharSequence passwd) { + return true; + } + }, + aclResolver, + groupMembershipResolver + ); + + } + +} diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops new file mode 100644 index 0000000..22df9ca --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1060364/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/jpa +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/entries new file mode 100644 index 0000000..a91ff80 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/jpa +http://svn.apache.org/repos/asf + + + +2011-01-18T13:39:10.338197Z +1060364 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +migrator +dir + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops new file mode 100644 index 0000000..5f0d0d9 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 104 +/repos/asf/!svn/ver/1060364/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/jpa/migrator +END +JpaMigratorTest.java +K 25 +svn:wc:ra_dav:version-url +V 125 +/repos/asf/!svn/ver/1060364/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/JpaMigratorTest.java +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/entries b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/entries new file mode 100644 index 0000000..542142f --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/tool/src/test/java/org/apache/james/mailbox/jpa/migrator +http://svn.apache.org/repos/asf + + + +2011-01-18T13:39:10.338197Z +1060364 +eric + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +JpaMigratorTest.java +file + + + + +2013-09-02T02:54:37.000000Z +e937c3bce24fcd95bcd1babc3de1ada2 +2011-01-18T13:39:10.338197Z +1060364 +eric +has-props + + + + + + + + + + + + + + + + + + + + +2777 + diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigratorTest.java.svn-base b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigratorTest.java.svn-base new file mode 100644 index 0000000..138f983 --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/prop-base/JpaMigratorTest.java.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 10 +text/plain +END diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigratorTest.java.svn-base b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigratorTest.java.svn-base new file mode 100644 index 0000000..91cfb7e --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/.svn/text-base/JpaMigratorTest.java.svn-base @@ -0,0 +1,84 @@ +/**************************************************************** + * 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.migrator; + +import org.apache.james.mailbox.jpa.migrator.JpaMigrator; +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; +import org.junit.Test; + +/** + * TODO this test class needs to be reviewed. + */ +public class JpaMigratorTest { + + @Test() + public void testImap165() { +// try { +// JpaMigrator.main(new String[]{"IMAP165"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap168() { +// try { +// JpaMigrator.main(new String[]{"IMAP168"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap172() { +// try { +// JpaMigrator.main(new String[]{"IMAP172"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap176() { +// try { +// JpaMigrator.main(new String[]{"IMAP176"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap180() { +// try { +// JpaMigrator.main(new String[]{"IMAP180"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap184() { +// try { +// JpaMigrator.main(new String[]{"IMAP184"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + +} diff --git a/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/JpaMigratorTest.java b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/JpaMigratorTest.java new file mode 100644 index 0000000..91cfb7e --- /dev/null +++ b/james/apache-james-mailbox/tool/src/test/java/org/apache/james/mailbox/jpa/migrator/JpaMigratorTest.java @@ -0,0 +1,84 @@ +/**************************************************************** + * 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.migrator; + +import org.apache.james.mailbox.jpa.migrator.JpaMigrator; +import org.apache.james.mailbox.jpa.migrator.exception.JpaMigrateException; +import org.junit.Test; + +/** + * TODO this test class needs to be reviewed. + */ +public class JpaMigratorTest { + + @Test() + public void testImap165() { +// try { +// JpaMigrator.main(new String[]{"IMAP165"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap168() { +// try { +// JpaMigrator.main(new String[]{"IMAP168"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap172() { +// try { +// JpaMigrator.main(new String[]{"IMAP172"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap176() { +// try { +// JpaMigrator.main(new String[]{"IMAP176"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap180() { +// try { +// JpaMigrator.main(new String[]{"IMAP180"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + + @Test() + public void testImap184() { +// try { +// JpaMigrator.main(new String[]{"IMAP184"}); +// } catch (JpaMigrateException e) { +// e.printStackTrace(); +// } + } + +} diff --git a/james/apache-james-mailbox/zoo-seq-provider/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/.svn/all-wcprops new file mode 100644 index 0000000..1f97e5e --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 64 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/zoo-seq-provider +END +pom.xml +K 25 +svn:wc:ra_dav:version-url +V 72 +/repos/asf/!svn/ver/1452816/james/mailbox/trunk/zoo-seq-provider/pom.xml +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/.svn/dir-prop-base b/james/apache-james-mailbox/zoo-seq-provider/.svn/dir-prop-base new file mode 100644 index 0000000..2d34865 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/.svn/dir-prop-base @@ -0,0 +1,9 @@ +K 10 +svn:ignore +V 37 +target +.settings +.classpath +.project + +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/.svn/entries new file mode 100644 index 0000000..5964d08 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/.svn/entries @@ -0,0 +1,65 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider +http://svn.apache.org/repos/asf + + + +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +src +dir + +pom.xml +file + + + + +2013-09-02T02:54:38.000000Z +60860c0c566b9418bcabb3cf8408daef +2013-03-05T14:39:26.245277Z +1452816 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +3255 + diff --git a/james/apache-james-mailbox/zoo-seq-provider/.svn/prop-base/pom.xml.svn-base b/james/apache-james-mailbox/zoo-seq-provider/.svn/prop-base/pom.xml.svn-base new file mode 100644 index 0000000..70a640b --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/.svn/prop-base/pom.xml.svn-base @@ -0,0 +1,5 @@ +K 12 +svn:keywords +V 26 +Id Author Revision HeadURL +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/.svn/text-base/pom.xml.svn-base b/james/apache-james-mailbox/zoo-seq-provider/.svn/text-base/pom.xml.svn-base new file mode 100644 index 0000000..835b31a --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/.svn/text-base/pom.xml.svn-base @@ -0,0 +1,83 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + zookeeper-sequence-provider + Apache James :: Mailbox :: Zookeeper Sequence Provider + bundle + High performance distribuited sequence provider based on ZooKeepr + + + 1.3.2 + + + + + junit + junit + test + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + ${project.groupId} + apache-james-mailbox-api + + + ${project.groupId} + apache-james-mailbox-store + + + com.netflix.curator + curator-recipes + ${curator.version} + + + com.netflix.curator + curator-framework + ${curator.version} + + + com.netflix.curator + curator-client + ${curator.version} + + + com.netflix.curator + curator-test + ${curator.version} + test + + + diff --git a/james/apache-james-mailbox/zoo-seq-provider/pom.xml b/james/apache-james-mailbox/zoo-seq-provider/pom.xml new file mode 100644 index 0000000..835b31a --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/pom.xml @@ -0,0 +1,83 @@ + + + + 4.0.0 + + + apache-james-mailbox + org.apache.james + 0.6-SNAPSHOT + ../pom.xml + + + zookeeper-sequence-provider + Apache James :: Mailbox :: Zookeeper Sequence Provider + bundle + High performance distribuited sequence provider based on ZooKeepr + + + 1.3.2 + + + + + junit + junit + test + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + ${project.groupId} + apache-james-mailbox-api + + + ${project.groupId} + apache-james-mailbox-store + + + com.netflix.curator + curator-recipes + ${curator.version} + + + com.netflix.curator + curator-framework + ${curator.version} + + + com.netflix.curator + curator-client + ${curator.version} + + + com.netflix.curator + curator-test + ${curator.version} + test + + + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/.svn/all-wcprops new file mode 100644 index 0000000..5606353 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 68 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/.svn/entries new file mode 100644 index 0000000..0e36bdf --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +test +dir + +main +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/all-wcprops new file mode 100644 index 0000000..c2b6729 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/entries new file mode 100644 index 0000000..682f5f4 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/.svn/entries @@ -0,0 +1,34 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + +resources +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/all-wcprops new file mode 100644 index 0000000..40effa2 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 78 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/entries new file mode 100644 index 0000000..e13bb11 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/java +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/all-wcprops new file mode 100644 index 0000000..9b82c43 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java/org +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/entries new file mode 100644 index 0000000..cf96af5 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/java/org +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..9ea8292 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 89 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/entries new file mode 100644 index 0000000..973da56 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..079985c --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..7513d65 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..6610efb --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..6688ea1 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +store +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops new file mode 100644 index 0000000..589fb73 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/entries new file mode 100644 index 0000000..f85e4ac --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mail +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops new file mode 100644 index 0000000..93f441e --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail +END +ZooUidProvider.java +K 25 +svn:wc:ra_dav:version-url +V 134 +/repos/asf/!svn/ver/1452487/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/ZooUidProvider.java +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries new file mode 100644 index 0000000..4f56684 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail +http://svn.apache.org/repos/asf + + + +2013-03-04T20:31:08.236868Z +1452487 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +ZooUidProvider.java +file + + + + +2013-09-02T02:54:38.000000Z +76b8d652794409ae022d9348af65d528 +2013-03-04T20:31:08.236868Z +1452487 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +4330 + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProvider.java.svn-base b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProvider.java.svn-base new file mode 100644 index 0000000..70a640b --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProvider.java.svn-base @@ -0,0 +1,5 @@ +K 12 +svn:keywords +V 26 +Id Author Revision HeadURL +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProvider.java.svn-base b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProvider.java.svn-base new file mode 100644 index 0000000..4fca305 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProvider.java.svn-base @@ -0,0 +1,96 @@ +/**************************************************************** + * 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; + +import com.google.common.base.Preconditions; +import com.netflix.curator.RetryPolicy; +import com.netflix.curator.framework.CuratorFramework; +import com.netflix.curator.framework.recipes.atomic.AtomicValue; +import com.netflix.curator.framework.recipes.atomic.DistributedAtomicLong; +import com.netflix.curator.retry.RetryOneTime; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * ZooKeeper based implementation of a distributed sequential UID generator. + */ +public class ZooUidProvider implements UidProvider { + // TODO: use ZK paths to store uid and modSeq, etc. + + public static final String UID_PATH_SUFFIX = "-uid"; + private final CuratorFramework client; + private final RetryPolicy retryPolicy; + + public ZooUidProvider(CuratorFramework client) { + this(client, new RetryOneTime(1)); + } + + public ZooUidProvider(CuratorFramework client, RetryPolicy retryPolicy) { + Preconditions.checkNotNull(client, "Curator client is null"); + Preconditions.checkNotNull(retryPolicy, "Retry policy is null"); + this.client = client; + this.retryPolicy = retryPolicy; + } + + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + if (client.isStarted()) { + DistributedAtomicLong uid = new DistributedAtomicLong(client, pathForMailbox(mailbox), retryPolicy); + AtomicValue value = null; + try { + uid.increment(); + value = uid.get(); + } catch (Exception e) { + throw new MailboxException("Exception incrementing UID for session " + session, e); + } finally { + if (value != null && value.succeeded()) { + return value.postValue(); + } else { + throw new MailboxException("Failed getting next UID for " + session); + } + } + } + throw new MailboxException("Curator client is closed."); + } + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + if (client.isStarted()) { + DistributedAtomicLong uid = new DistributedAtomicLong(client, pathForMailbox(mailbox), retryPolicy); + AtomicValue value = null; + try { + value = uid.get(); + } catch (Exception e) { + throw new MailboxException("Exception getting last UID for session " + session, e); + } finally { + if (value != null && value.succeeded()) { + return value.postValue(); + } else { + throw new MailboxException("Failed getting last UID for " + session); + } + } + } + throw new MailboxException("Curator client is closed."); + } + + public static String pathForMailbox(Mailbox mailbox) { + return mailbox.getMailboxId().toString() + UID_PATH_SUFFIX; + } +} diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/ZooUidProvider.java b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/ZooUidProvider.java new file mode 100644 index 0000000..4fca305 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/java/org/apache/james/mailbox/store/mail/ZooUidProvider.java @@ -0,0 +1,96 @@ +/**************************************************************** + * 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; + +import com.google.common.base.Preconditions; +import com.netflix.curator.RetryPolicy; +import com.netflix.curator.framework.CuratorFramework; +import com.netflix.curator.framework.recipes.atomic.AtomicValue; +import com.netflix.curator.framework.recipes.atomic.DistributedAtomicLong; +import com.netflix.curator.retry.RetryOneTime; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +/** + * ZooKeeper based implementation of a distributed sequential UID generator. + */ +public class ZooUidProvider implements UidProvider { + // TODO: use ZK paths to store uid and modSeq, etc. + + public static final String UID_PATH_SUFFIX = "-uid"; + private final CuratorFramework client; + private final RetryPolicy retryPolicy; + + public ZooUidProvider(CuratorFramework client) { + this(client, new RetryOneTime(1)); + } + + public ZooUidProvider(CuratorFramework client, RetryPolicy retryPolicy) { + Preconditions.checkNotNull(client, "Curator client is null"); + Preconditions.checkNotNull(retryPolicy, "Retry policy is null"); + this.client = client; + this.retryPolicy = retryPolicy; + } + + @Override + public long nextUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + if (client.isStarted()) { + DistributedAtomicLong uid = new DistributedAtomicLong(client, pathForMailbox(mailbox), retryPolicy); + AtomicValue value = null; + try { + uid.increment(); + value = uid.get(); + } catch (Exception e) { + throw new MailboxException("Exception incrementing UID for session " + session, e); + } finally { + if (value != null && value.succeeded()) { + return value.postValue(); + } else { + throw new MailboxException("Failed getting next UID for " + session); + } + } + } + throw new MailboxException("Curator client is closed."); + } + + @Override + public long lastUid(MailboxSession session, Mailbox mailbox) throws MailboxException { + if (client.isStarted()) { + DistributedAtomicLong uid = new DistributedAtomicLong(client, pathForMailbox(mailbox), retryPolicy); + AtomicValue value = null; + try { + value = uid.get(); + } catch (Exception e) { + throw new MailboxException("Exception getting last UID for session " + session, e); + } finally { + if (value != null && value.succeeded()) { + return value.postValue(); + } else { + throw new MailboxException("Failed getting last UID for " + session); + } + } + } + throw new MailboxException("Curator client is closed."); + } + + public static String pathForMailbox(Mailbox mailbox) { + return mailbox.getMailboxId().toString() + UID_PATH_SUFFIX; + } +} diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/all-wcprops new file mode 100644 index 0000000..63b7872 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 83 +/repos/asf/!svn/ver/1296197/james/mailbox/trunk/zoo-seq-provider/src/main/resources +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/entries new file mode 100644 index 0000000..9a237bf --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/main/resources/.svn/entries @@ -0,0 +1,28 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/main/resources +http://svn.apache.org/repos/asf + + + +2012-03-02T13:29:38.333655Z +1296197 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/all-wcprops new file mode 100644 index 0000000..98c1de0 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 73 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/entries new file mode 100644 index 0000000..d48e505 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +java +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/all-wcprops new file mode 100644 index 0000000..c29e8a1 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 78 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/entries new file mode 100644 index 0000000..50bfa5b --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test/java +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +org +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/all-wcprops new file mode 100644 index 0000000..a4b179f --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 82 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java/org +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/entries new file mode 100644 index 0000000..d113535 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test/java/org +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +apache +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/all-wcprops new file mode 100644 index 0000000..54f5457 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 89 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/entries new file mode 100644 index 0000000..7a63ec4 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +james +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/all-wcprops new file mode 100644 index 0000000..ab6acd4 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 95 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/entries new file mode 100644 index 0000000..e9b8313 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mailbox +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/all-wcprops new file mode 100644 index 0000000..6c8617c --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 103 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james/mailbox +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/entries new file mode 100644 index 0000000..bc1fe1a --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james/mailbox +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +store +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops new file mode 100644 index 0000000..94477fa --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/all-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 109 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/entries new file mode 100644 index 0000000..3e4df16 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/.svn/entries @@ -0,0 +1,31 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +mail +dir + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops new file mode 100644 index 0000000..afb5030 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 114 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail +END +ZooUidProviderTest.java +K 25 +svn:wc:ra_dav:version-url +V 138 +/repos/asf/!svn/ver/1307750/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/ZooUidProviderTest.java +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries new file mode 100644 index 0000000..0d2d8e1 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/entries @@ -0,0 +1,62 @@ +10 + +dir +1519332 +http://svn.apache.org/repos/asf/james/mailbox/trunk/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail +http://svn.apache.org/repos/asf + + + +2012-03-31T09:40:54.186930Z +1307750 +ieugen + + + + + + + + + + + + + + +13f79535-47bb-0310-9956-ffa450edef68 + +ZooUidProviderTest.java +file + + + + +2013-09-02T02:54:38.000000Z +06c424642906d11024a5d70c7361ea36 +2012-03-31T09:40:54.186930Z +1307750 +ieugen +has-props + + + + + + + + + + + + + + + + + + + + +4403 + diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProviderTest.java.svn-base b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProviderTest.java.svn-base new file mode 100644 index 0000000..70a640b --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/prop-base/ZooUidProviderTest.java.svn-base @@ -0,0 +1,5 @@ +K 12 +svn:keywords +V 26 +Id Author Revision HeadURL +END diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProviderTest.java.svn-base b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProviderTest.java.svn-base new file mode 100644 index 0000000..e0c6bb9 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/.svn/text-base/ZooUidProviderTest.java.svn-base @@ -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.store.mail; + +import com.netflix.curator.RetryPolicy; +import com.netflix.curator.framework.CuratorFramework; +import com.netflix.curator.framework.CuratorFrameworkFactory; +import com.netflix.curator.retry.RetryOneTime; +import com.netflix.curator.test.TestingServer; +import java.util.UUID; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.junit.After; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +/** + * Test for UID provider. + */ +public class ZooUidProviderTest { + + private static TestingServer testServer; + private static final int ZOO_TEST_PORT = 3123; + private final RetryPolicy retryPolicy = new RetryOneTime(1); + private CuratorFramework client; + private ZooUidProvider uuidProvider; + private ZooUidProvider longProvider; + private SimpleMailbox mailboxUUID; + private SimpleMailbox mailboxLong; + private UUID randomUUID = UUID.randomUUID(); + + @Before + public void setUp() throws Exception { + testServer = new TestingServer(ZOO_TEST_PORT); + client = CuratorFrameworkFactory.builder().connectString("localhost:" + ZOO_TEST_PORT).retryPolicy(retryPolicy). + namespace("JAMES").build(); + client.start(); + uuidProvider = new ZooUidProvider(client, retryPolicy); + longProvider = new ZooUidProvider(client, retryPolicy); + MailboxPath path1 = new MailboxPath("namespacetest", "namespaceuser", "UUID"); + MailboxPath path2 = new MailboxPath("namespacetest", "namespaceuser", "Long"); + mailboxUUID = new SimpleMailbox(path1, 1L); + mailboxUUID.setMailboxId(randomUUID); + mailboxLong = new SimpleMailbox(path2, 2L); + mailboxLong.setMailboxId(123L); + } + + @After + public void tearDown() throws Exception { + client.close(); + testServer.close(); + } + + /** + * Test of nextUid method, of class ZooUidProvider. + */ + @Test + public void testNextUid() throws Exception { + System.out.println("Testing nextUid"); + long result = uuidProvider.nextUid(null, mailboxUUID); + assertEquals("Next UID is 1", 1, result); + result = longProvider.nextUid(null, mailboxLong); + assertEquals("Next UID is 1", 1, result); + } + + /** + * Test of lastUid method, of class ZooUidProvider. + */ + @Test + public void testLastUid() throws Exception { + System.out.println("Testing lastUid"); + long result = uuidProvider.lastUid(null, mailboxUUID); + assertEquals("Next UID is 0", 0, result); + result = uuidProvider.nextUid(null, mailboxUUID); + assertEquals("Next UID is 1", 1, result); + } + + /** + * Test of lastUid method, of class ZooUidProvider. + */ + @Test + public void testLongLastUid() throws Exception { + System.out.println("Testing long lastUid"); + long result = longProvider.lastUid(null, mailboxLong); + assertEquals("Next UID is 0", 0, result); + result = longProvider.nextUid(null, mailboxLong); + assertEquals("Next UID is 1", 1, result); + } +} diff --git a/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/ZooUidProviderTest.java b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/ZooUidProviderTest.java new file mode 100644 index 0000000..e0c6bb9 --- /dev/null +++ b/james/apache-james-mailbox/zoo-seq-provider/src/test/java/org/apache/james/mailbox/store/mail/ZooUidProviderTest.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.store.mail; + +import com.netflix.curator.RetryPolicy; +import com.netflix.curator.framework.CuratorFramework; +import com.netflix.curator.framework.CuratorFrameworkFactory; +import com.netflix.curator.retry.RetryOneTime; +import com.netflix.curator.test.TestingServer; +import java.util.UUID; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; +import org.junit.After; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +/** + * Test for UID provider. + */ +public class ZooUidProviderTest { + + private static TestingServer testServer; + private static final int ZOO_TEST_PORT = 3123; + private final RetryPolicy retryPolicy = new RetryOneTime(1); + private CuratorFramework client; + private ZooUidProvider uuidProvider; + private ZooUidProvider longProvider; + private SimpleMailbox mailboxUUID; + private SimpleMailbox mailboxLong; + private UUID randomUUID = UUID.randomUUID(); + + @Before + public void setUp() throws Exception { + testServer = new TestingServer(ZOO_TEST_PORT); + client = CuratorFrameworkFactory.builder().connectString("localhost:" + ZOO_TEST_PORT).retryPolicy(retryPolicy). + namespace("JAMES").build(); + client.start(); + uuidProvider = new ZooUidProvider(client, retryPolicy); + longProvider = new ZooUidProvider(client, retryPolicy); + MailboxPath path1 = new MailboxPath("namespacetest", "namespaceuser", "UUID"); + MailboxPath path2 = new MailboxPath("namespacetest", "namespaceuser", "Long"); + mailboxUUID = new SimpleMailbox(path1, 1L); + mailboxUUID.setMailboxId(randomUUID); + mailboxLong = new SimpleMailbox(path2, 2L); + mailboxLong.setMailboxId(123L); + } + + @After + public void tearDown() throws Exception { + client.close(); + testServer.close(); + } + + /** + * Test of nextUid method, of class ZooUidProvider. + */ + @Test + public void testNextUid() throws Exception { + System.out.println("Testing nextUid"); + long result = uuidProvider.nextUid(null, mailboxUUID); + assertEquals("Next UID is 1", 1, result); + result = longProvider.nextUid(null, mailboxLong); + assertEquals("Next UID is 1", 1, result); + } + + /** + * Test of lastUid method, of class ZooUidProvider. + */ + @Test + public void testLastUid() throws Exception { + System.out.println("Testing lastUid"); + long result = uuidProvider.lastUid(null, mailboxUUID); + assertEquals("Next UID is 0", 0, result); + result = uuidProvider.nextUid(null, mailboxUUID); + assertEquals("Next UID is 1", 1, result); + } + + /** + * Test of lastUid method, of class ZooUidProvider. + */ + @Test + public void testLongLastUid() throws Exception { + System.out.println("Testing long lastUid"); + long result = longProvider.lastUid(null, mailboxLong); + assertEquals("Next UID is 0", 0, result); + result = longProvider.nextUid(null, mailboxLong); + assertEquals("Next UID is 1", 1, result); + } +}