Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JAMES-2214 Draft saving #1120

Merged
merged 19 commits into from Nov 15, 2017
Merged

JAMES-2214 Draft saving #1120

merged 19 commits into from Nov 15, 2017

Conversation

chibenwa
Copy link
Member

No description provided.

Copy link

@mbaechler mbaechler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments along the way, mostly about naming.
Overall, I'm happy to see that things are fitting quite well in existing codebase and that you managed to do that so quickly !

@@ -279,8 +258,10 @@ private MessageWithId handleOutboxMessages(CreationMessageEntry entry, MailboxSe
if (!isRequestForSending(entry.getValue(), session)) {
throw new IllegalStateException("Messages for everything but outbox should have been filtered earlier");
}
MetaDataWithContent newMessage = createMessageInOutbox(entry, outbox, session);
return sendMessage(entry.getCreationId(), newMessage, session);
MetaDataWithContent newMessage = messageAppender.createMessageInMailbox(entry, outbox, session);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the previous version was simpler to understand.

Copy link
Member Author

@chibenwa chibenwa Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but see later on: we need a valid Jmap message when saving a draft : hence I came back and did not factorize JMAP message computation.

Moreover think to the Big Picture: That extraction is ready for draft moving to outbox.

this.mimeMessageConverter = mimeMessageConverter;
}

public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appendMessageInMailbox

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you wish

}

public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry,
MessageManager mailbox,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's a jmap API, why don't you take a MailboxId here ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well SystemMailboxes provider directly gives MessageManager.

  • No need to perform an extra lookup
  • We already have it in caller code.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what caller code ? you don't know the caller, you are creating an API.
Or maybe it's a private class used by a single consumer ?
Otherwise, please make the API as consistent as you would like to find it as a developper browsing the API.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

benoit, if you want to keep this optimization you can provide the 2 methods, one with MessageId, and one with the MessageManager.

Copy link
Member Author

@chibenwa chibenwa Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok for duplicated methods of course.

Ps: I don't concider this really an optimization.

It is more I have a superset of MailboxId, I wish to be calling the API with my superset of MailboxId, not doing a .getMailboxId.

this.mimeMessageConverter = mimeMessageConverter;
}

public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you import ValueWithId so that we only read MessageWithId here ?

this.mailFactory = mailFactory;
}

public void sendMessage(Message jmapMessage,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are the two first argument related ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One is generated from the other.

You have 3 related things:

  • CreationMessage
  • MessageMetadata: once you saved it in store
  • JMAP message is used only for the enveloppe.

We shoudl have the MailFactory taking an Enveloppe instead of a full JMAP message.

}

@Test
public void setMessageShouldRejectCreateInDraftAndOutbox() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...CreateInBothDraftAndOutboxForASingleMessage

}

@Test
public void setMessageShouldRequireDraftFlag() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in any case or just in Draft mailbox ?

MessageWithId created = handleOutboxMessages(create, mailboxSession);
responseBuilder.created(created.getCreationId(), created.getValue());

performCreate(create, responseBuilder, mailboxSession);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleCreate is calling performCreate.
I think that bad naming scheme reflect the lack of proper abstraction. What was wrong with this version ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not like the double check to the feature:

  • 1 check to see if it's draft OR outbox, 1 second check to perform the feature (that was always sending).

Generally, error management is performed in a separated method. Is that a bad thing? I tend to concider separating these concerns is good.

responseBuilder.created(created.getCreationId(), created.getValue());
}

private void validateDraftKeywords(CreationMessageEntry entry) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert

@@ -1396,6 +1396,40 @@ public void setMessageShouldRequireDraftInOnlyDraftMailbox() {
}

@Test
public void setMessageShouldBeCompatibleWithIsDraftProperty() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does it mean ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have two distinct way to interact with flags:

  • Through **is* ** properties
  • Through keywords object

I just want to check INBOX will be able to save a message with isDraft, as they do not support keywords yet.

I find this name explicit. Any proposal for improvment?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose " check INBOX will be able to save a message with isDraft,"
I'm kidding but "should be compatible" doesn't say what you explained.
So setMessageShouldAllowDraftCreationWhenUsingIsDraftProperty ?

private Mail buildMessage(MessageFactory.MetaDataWithContent message, Message jmapMessage) throws MessagingException {
try {
return mailFactory.build(message, jmapMessage);
} catch (IOException e) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you wrap the IOException directly in MailFactory.build ?

@@ -363,6 +364,11 @@ private void assertValidFromProvided(ImmutableList.Builder<ValidationResult> err
public boolean isIn(MessageManager mailbox) {
return mailboxIds.contains(mailbox.getId().serialize());
}

public boolean isInOnly(MessageManager mailbox) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTheOnlyOne or highlander

}

public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry,
MessageManager mailbox,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

benoit, if you want to keep this optimization you can provide the 2 methods, one with MessageId, and one with the MessageManager.

.isInline(attachment.isIsInline())
.build());
} catch (AttachmentNotFoundException e) {
// should not happen (checked before)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this comment still true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, but it can be safely removed I assume.

session))
.isInstanceOf(AttachmentsNotFoundException.class)
.matches(e -> ((AttachmentsNotFoundException)e).getAttachmentIds().containsAll(ImmutableSet.of(unknownBlobId1, unknownBlobId2)));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have 2 negative test cases, can you add positive test cases?
assertAttachmentExistsShouldNotThrowWhenKnownBlobId
assertAttachmentsExistShouldNotThrowWhenKnownBlobIds

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would also need the mix between positive and negative.

Ps: I did not do it as I only intended to move code around.

}

@Test
public void setMessageShouldNotCheckFromWhenDraft() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setMessages (with a s at the end), and same remark in every test case

" \"setMessages\","+
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"invalid@domain.com\"}," +

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also add a test case with an email without "@"?

@@ -294,25 +292,6 @@ public void processShouldNotSpoolMailWhenNotSavingToOutbox() throws Exception {
}

@Test

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to add any unit test? (yes I've seen that you did a great coverage in integration tests)

Copy link
Member Author

@chibenwa chibenwa Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With mocking?

Sorry, but it ties down to much SetMessagesCreationProcessor with lower layers, I really don't like it.

If you ask me for it, I prefer porting these tests to rely on InMemoryMailboxManager through InMemoryIntegrationResousources.

BTW now coverage with unit test will be duplication, isn't it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was just a question. It's not really needed as you are doing all these integration tests. Sometime you need to add some unit tests (so yes with mocking) to check special behaviour such as storage returning some exceptions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have coverage for the error management I introduced, and BTW extended it ;-)

I don't think we end up in that scenario.

@@ -300,6 +303,7 @@ public void processShouldNotSendWhenSavingToDrafts() throws Exception {
.create(
creationMessageId, creationMessageBuilder.mailboxId(DRAFTS_ID.serialize()).build())
.build();
when(mockedMailboxManager.getMailbox(any(MailboxId.class), any())).thenReturn(drafts);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c/r

@@ -53,6 +53,11 @@ public StoreAttachmentManager(AttachmentMapperFactory attachmentMapperFactory, M
}

@Override
public boolean exists(AttachmentId attachmentId, MailboxSession session) throws MailboxException {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no existing test for AttachmentManager, only tests on mapper.

As testing this might require either:

  • Creating generic tests (AbstractAttachmentManagerTests blah)
  • Relying on mocking (there is a little test with mocking, but I really don't like mocking)

So I would go for the first one, if you realy want it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no that's ok, manager is just wiring so we can go without these tests

@@ -58,7 +59,7 @@ public MessageAppender(AttachmentManager attachmentManager, MIMEMessageConverter
this.mimeMessageConverter = mimeMessageConverter;
}

public MessageFactory.MetaDataWithContent appendMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry,
public MessageFactory.MetaDataWithContent appendMessageInMailbox(CreationMessageEntry createdEntry,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the remark was about importing ValueWithId, not MessageWithId, so you keep MessageWithId.CreationMessageEntry

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't get it. Naming CreationMessageEntry seems to convey enough meaning, I believe we can get a ride of MessageWithId


public MailboxSendingNotAllowedException(Collection<String> allowedFroms) {
public MailboxSendingNotAllowedException(String allowedFroms) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Froms/From/

@chibenwa
Copy link
Member Author

test this please

@chibenwa
Copy link
Member Author

I do not succeed to reproduce CI issue locally:

CassandraSetMessagesMethodTest>SetMessagesMethodTest.attachmentsAndBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithTextBodyAndHtmlAttachment:3725 » ConditionTimeout
[6400799653078367686daca4bef60295411039a4] [ERROR]   CassandraSetMessagesMethodTest>SetMessagesMethodTest.attachmentsShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesTextAttachment:3560 » ConditionTimeout

@chibenwa
Copy link
Member Author

Still the CI error:

[a368dd37ef0c0df1ae8c9dbbe341ff438ea45035] [ERROR] Errors: 
[a368dd37ef0c0df1ae8c9dbbe341ff438ea45035] [ERROR]   CassandraSetMessagesMethodTest>SetMessagesMethodTest.attachmentsShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesTextAttachment:3593 » ConditionTimeout
[a368dd37ef0c0df1ae8c9dbbe341ff438ea45035] [INFO] 
[a368dd37ef0c0df1ae8c9dbbe341ff438ea45035] [ERROR] Tests run: 1776, Failures: 0, Errors: 1, Skipped: 1

}

@Test
public void setMessagesShouldBeCompatibleWithIsDraftProperty() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you didn't change this method name based on my suggestion ?

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;

public class Envelope {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the choosen wording, Envelope usually includes headers and here that's not the case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a better idea? I can not come up with a better name....

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would strip Sender and call the remaining fields "Recipients".

LOGGER.error("Invalid mail address", emailer.getEmail());
throw Throwables.propagate(e);
}
Sets.union(envelope.getTo(), envelope.getCc()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

envelope.listRecipients ?

@@ -101,4 +117,49 @@ public void assertAttachmentsExistShouldThrowWhenUnknownBlobIds() throws Mailbox
.matches(e -> ((AttachmentsNotFoundException)e).getAttachmentIds().containsAll(ImmutableSet.of(unknownBlobId1, unknownBlobId2)));
}

@Test
public void assertAttachmentsExistShouldNotThrowThrowWhenKnownBlobIds() throws Exception {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/ThrowThrow/Throw/

@chibenwa chibenwa force-pushed the JAMES-2214 branch 2 times, most recently from a222f59 to feac3b1 Compare November 15, 2017 08:29
@chibenwa chibenwa mentioned this pull request Nov 15, 2017
This is required as recipients should not be validated when saving a draft, but only when sending it.
When we reached the code about saving message in Outbox, we already enforced the message should only be in outbox, hence that code was not needed.
Before, messageIds was stated, which was wrong. That error properties was not tested.
MailboxRightsException leads to think it concerns mailbox rights, but was raised when having rights on a delegated mailbox
…tion

  MailboxNotImplementedException and the "Not yet implemented" message is misleading.

  JMAP spec does not support, whatever version you take, saving messages in other mailboxes than Outbox or Draft.

  The error message name should explain that the given mailboxes were invalid. Moreover, the error message should explain which state was not met by the provided mailboxes.
 - More fluent API
 - Avoid retrieving full attachment content for existence checks
We need a more readable way to concat (n) streams than chaining Stream.concat calls
@aduprat aduprat merged commit 5df4371 into linagora:master Nov 15, 2017
vttranlina pushed a commit to vttranlina/james-project that referenced this pull request Aug 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants