JAMES-1692 add SetMessagesCreationProcessor impl. for sending message add CreationMessage to hold new messages. build up MIME Messages for james to append to OUTBOX extract SetMessagesProcessor<Id> interface
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/99ac325d Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/99ac325d Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/99ac325d Branch: refs/heads/master Commit: 99ac325d5f9bab58363a62b50a1ff7965611473b Parents: 0625fb5 Author: Fabien Vignon <[email protected]> Authored: Fri Feb 12 19:40:12 2016 +0100 Committer: Raphael Ouazana <[email protected]> Committed: Tue Mar 1 15:42:53 2016 +0100 ---------------------------------------------------------------------- .../org/apache/james/jmap/MethodsModule.java | 2 + .../jmap/methods/SetMessagesMethodTest.java | 2 - .../MailboxRoleNotFoundException.java | 36 +++ .../jmap/methods/MIMEMessageConverter.java | 150 +++++++++ .../james/jmap/methods/MessageWithId.java | 48 +++ .../methods/SetMessagesCreationProcessor.java | 174 +++++++++++ .../james/jmap/methods/SetMessagesMethod.java | 9 +- .../jmap/methods/SetMessagesProcessor.java | 29 ++ .../methods/SetMessagesUpdateProcessor.java | 4 +- .../james/jmap/model/CreationMessage.java | 305 +++++++++++++++++++ .../james/jmap/model/SetMessagesRequest.java | 12 +- .../james/jmap/model/SetMessagesResponse.java | 27 +- .../SetMessagesCreationProcessorTest.java | 131 ++++++++ .../jmap/model/SetMessagesResponseTest.java | 11 +- 14 files changed, 913 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java ---------------------------------------------------------------------- diff --git a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java index 27faf5a..4e57d20 100644 --- a/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java +++ b/server/container/cassandra-guice/src/main/java/org/apache/james/jmap/MethodsModule.java @@ -28,6 +28,7 @@ import org.apache.james.jmap.methods.JmapRequestParserImpl; import org.apache.james.jmap.methods.JmapResponseWriter; import org.apache.james.jmap.methods.JmapResponseWriterImpl; import org.apache.james.jmap.methods.Method; +import org.apache.james.jmap.methods.SetMessagesCreationProcessor; import org.apache.james.jmap.methods.SetMessagesMethod; import org.apache.james.jmap.methods.SetMessagesUpdateProcessor; import org.apache.james.mailbox.cassandra.CassandraId; @@ -54,6 +55,7 @@ public class MethodsModule extends AbstractModule { methods.addBinding().to(new TypeLiteral<GetMessagesMethod<CassandraId>>(){}); methods.addBinding().to(new TypeLiteral<SetMessagesMethod<CassandraId>>(){}); bind(SetMessagesUpdateProcessor.class).to(new TypeLiteral<SetMessagesUpdateProcessor<CassandraId>>(){}); + bind(SetMessagesCreationProcessor.class).to(new TypeLiteral<SetMessagesCreationProcessor<CassandraId>>(){}); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java index fcc3101..3acaf8c 100644 --- a/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java +++ b/server/protocols/jmap-integration-testing/src/test/java/org/apache/james/jmap/methods/SetMessagesMethodTest.java @@ -648,7 +648,6 @@ public abstract class SetMessagesMethodTest { } @Test - @Ignore("pending SetMessages's send messages feature implementation") public void setMessageShouldReturnCreatedMessageWhenSendingMessage() { String messageCreationId = "user|inbox|1"; String requestBody = "[" + @@ -701,7 +700,6 @@ public abstract class SetMessagesMethodTest { } @Test - @Ignore("pending SetMessages's send messages feature implementation") public void setMessagesShouldCreateMessageInOutboxWhenSendingMessage() throws MailboxException { // Given String messageCreationId = "user|inbox|1"; http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.java new file mode 100644 index 0000000..88692c8 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxRoleNotFoundException.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.jmap.exceptions; + +import org.apache.james.jmap.model.mailbox.Role; + +public class MailboxRoleNotFoundException extends RuntimeException { + + private final Role role; + + public MailboxRoleNotFoundException(Role role) { + super(String.format("Could not find any mailbox with role '%s'", role.serialize())); + this.role = role; + } + + public Role getRole() { + return role; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java new file mode 100644 index 0000000..05d0229 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java @@ -0,0 +1,150 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.TimeZone; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.apache.james.jmap.model.CreationMessage; +import org.apache.james.jmap.model.Emailer; +import org.apache.james.mime4j.Charsets; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.dom.FieldParser; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.MessageBuilder; +import org.apache.james.mime4j.dom.TextBody; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.field.UnstructuredField; +import org.apache.james.mime4j.field.Fields; +import org.apache.james.mime4j.field.UnstructuredFieldImpl; +import org.apache.james.mime4j.io.InputStreams; +import org.apache.james.mime4j.message.BasicBodyFactory; +import org.apache.james.mime4j.message.BodyFactory; +import org.apache.james.mime4j.message.DefaultMessageBuilder; +import org.apache.james.mime4j.message.DefaultMessageWriter; +import org.apache.james.mime4j.message.HeaderImpl; +import org.apache.james.mime4j.stream.Field; +import org.apache.james.mime4j.stream.RawField; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; + +public class MIMEMessageConverter { + + private final MessageBuilder messageBuilder; + private final BodyFactory bodyFactory; + + public MIMEMessageConverter() { + this.messageBuilder = new DefaultMessageBuilder(); + this.bodyFactory = new BasicBodyFactory(); + } + + public byte[] convert(MessageWithId.CreationMessageEntry creationMessageEntry) { + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + DefaultMessageWriter writer = new DefaultMessageWriter(); + try { + writer.writeMessage(convertToMime(creationMessageEntry), buffer); + } catch (IOException e) { + throw Throwables.propagate(e); + } + return buffer.toByteArray(); + } + + @VisibleForTesting Message convertToMime(MessageWithId.CreationMessageEntry creationMessageEntry) { + if (creationMessageEntry == null || creationMessageEntry.getMessage() == null) { + throw new IllegalArgumentException("creationMessageEntry is either null or has null message"); + } + + Message message = messageBuilder.newMessage(); + message.setBody(createTextBody(creationMessageEntry.getMessage())); + message.setHeader(buildMimeHeaders(creationMessageEntry.getCreationId(), creationMessageEntry.getMessage())); + return message; + } + + private Header buildMimeHeaders(String creationId, CreationMessage newMessage) { + Header messageHeaders = new HeaderImpl(); + + // add From: and Sender: headers + newMessage.getFrom().map(this::convertEmailToMimeHeader) + .map(Fields::from) + .ifPresent(f -> messageHeaders.addField(f)); + newMessage.getFrom().map(this::convertEmailToMimeHeader) + .map(Fields::sender) + .ifPresent(f -> messageHeaders.addField(f)); + + // add Reply-To: + messageHeaders.addField(Fields.replyTo(newMessage.getReplyTo().stream() + .map(rt -> convertEmailToMimeHeader(rt)) + .collect(Collectors.toList()))); + // add To: headers + messageHeaders.addField(Fields.to(newMessage.getTo().stream() + .map(this::convertEmailToMimeHeader) + .collect(Collectors.toList()))); + // add Cc: headers + messageHeaders.addField(Fields.cc(newMessage.getCc().stream() + .map(this::convertEmailToMimeHeader) + .collect(Collectors.toList()))); + // add Bcc: headers + messageHeaders.addField(Fields.bcc(newMessage.getBcc().stream() + .map(this::convertEmailToMimeHeader) + .collect(Collectors.toList()))); + // add Subject: header + messageHeaders.addField(Fields.subject(newMessage.getSubject())); + // set creation Id as MessageId: header + messageHeaders.addField(Fields.messageId(creationId)); + + // date(String fieldName, Date date, TimeZone zone) + // note that date conversion probably lose milliseconds ! + messageHeaders.addField(Fields.date("Date", + Date.from(newMessage.getDate().toInstant()), TimeZone.getTimeZone(newMessage.getDate().getZone()) + )); + newMessage.getInReplyToMessageId().ifPresent(addInReplyToHeader(messageHeaders::addField)); + return messageHeaders; + } + + private Consumer<String> addInReplyToHeader(Consumer<Field> headerAppender) { + return msgId -> { + FieldParser<UnstructuredField> parser = UnstructuredFieldImpl.PARSER; + RawField rawField = new RawField("In-Reply-To", msgId); + headerAppender.accept(parser.parse(rawField, DecodeMonitor.SILENT)); + }; + } + + private TextBody createTextBody(CreationMessage newMessage) { + try { + return bodyFactory.textBody( + InputStreams.create(newMessage.getTextBody().orElse(""), Charsets.UTF_8), + Charsets.UTF_8.name()); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + private Mailbox convertEmailToMimeHeader(Emailer address) { + String[] splitAddress = address.getEmail().split("@", 2); + return new Mailbox(address.getName(), null, splitAddress[0], splitAddress[1]); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java new file mode 100644 index 0000000..fbc7105 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.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.jmap.methods; + +import org.apache.james.jmap.model.CreationMessage; + +public class MessageWithId<T> { + + private String creationId; + private T message; + + public MessageWithId(String creationId, T message) { + this.creationId = creationId; + this.message = message; + } + + public String getCreationId() { + return creationId; + } + + public T getMessage() { + return message; + } + + public static class CreationMessageEntry extends MessageWithId<CreationMessage> { + public CreationMessageEntry(String creationId, CreationMessage message) { + super(creationId, message); + } + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java new file mode 100644 index 0000000..3cb11db --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java @@ -0,0 +1,174 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import java.util.Date; +import java.util.Optional; +import java.util.function.Function; + +import javax.inject.Inject; +import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException; +import org.apache.james.jmap.model.CreationMessage; +import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageId; +import org.apache.james.jmap.model.SetMessagesRequest; +import org.apache.james.jmap.model.SetMessagesResponse; +import org.apache.james.jmap.model.mailbox.Role; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +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.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.MailboxMapperFactory; +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.MailboxId; +import org.apache.james.mailbox.store.mail.model.MailboxMessage; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.fge.lambdas.functions.ThrowingFunction; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; + +public class SetMessagesCreationProcessor<Id extends MailboxId> implements SetMessagesProcessor<Id> { + + private static final Logger LOGGER = LoggerFactory.getLogger(SetMessagesCreationProcessor.class); + + private final MailboxMapperFactory<Id> mailboxMapperFactory; + private final MailboxManager mailboxManager; + private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory; + private final MIMEMessageConverter mimeMessageConverter; + + @Inject + @VisibleForTesting + SetMessagesCreationProcessor(MailboxMapperFactory<Id> mailboxMapperFactory, + MailboxManager mailboxManager, + MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory, MIMEMessageConverter mimeMessageConverter) { + this.mailboxMapperFactory = mailboxMapperFactory; + this.mailboxManager = mailboxManager; + this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; + this.mimeMessageConverter = mimeMessageConverter; + } + + @Override + public SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession) { + Mailbox<Id> outbox; + try { + outbox = getOutbox(mailboxSession).orElseThrow(() -> new MailboxRoleNotFoundException(Role.OUTBOX)); + } catch (MailboxException | MailboxRoleNotFoundException e) { + LOGGER.error("Unable to find a mailbox with role 'outbox'!"); + throw Throwables.propagate(e); + } + return request.getCreate().entrySet().stream() + .map(e -> new MessageWithId.CreationMessageEntry(e.getKey(), e.getValue())) + .map(nuMsg -> createMessageInOutbox(nuMsg, mailboxSession, outbox, buildMessageIdFunc(mailboxSession, outbox))) + .map(msg -> SetMessagesResponse.builder().created(ImmutableMap.of(msg.getCreationId(), msg.getMessage())).build()) + .reduce(SetMessagesResponse.builder(), SetMessagesResponse.Builder::accumulator, SetMessagesResponse.Builder::combiner) + .build(); + } + + @VisibleForTesting + protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, + MailboxSession session, + Mailbox<Id> outbox, Function<Long, MessageId> buildMessageIdFromUid) { + try { + MessageMapper<Id> messageMapper = mailboxSessionMapperFactory.createMessageMapper(session); + MailboxMessage<Id> newMailboxMessage = buildMailboxMessage(createdEntry, outbox); + messageMapper.add(outbox, newMailboxMessage); + return new MessageWithId<>(createdEntry.getCreationId(), Message.fromMailboxMessage(newMailboxMessage, buildMessageIdFromUid)); + } catch (MailboxException e) { + throw Throwables.propagate(e); + } catch (MailboxRoleNotFoundException e) { + LOGGER.error("Could not find mailbox '%s' while trying to save message.", e.getRole().serialize()); + throw Throwables.propagate(e); + } + } + + private Function<Long, MessageId> buildMessageIdFunc(MailboxSession session, Mailbox<Id> outbox) { + MailboxPath outboxPath = new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), outbox.getName()); + return uid -> new MessageId(session.getUser(), outboxPath, uid); + } + + private MailboxMessage<Id> buildMailboxMessage(MessageWithId.CreationMessageEntry createdEntry, Mailbox<Id> outbox) { + byte[] messageContent = mimeMessageConverter.convert(createdEntry); + SharedInputStream content = new SharedByteArrayInputStream(messageContent); + long size = messageContent.length; + int bodyStartOctet = 0; + + Flags flags = getMessageFlags(createdEntry.getMessage()); + PropertyBuilder propertyBuilder = buildPropertyBuilder(); + Id mailboxId = outbox.getMailboxId(); + Date internalDate = Date.from(createdEntry.getMessage().getDate().toInstant()); + + return new SimpleMailboxMessage<>(internalDate, size, + bodyStartOctet, content, flags, propertyBuilder, mailboxId); + } + + @VisibleForTesting + protected Optional<Mailbox<Id>> getOutbox(MailboxSession session) throws MailboxException { + return mailboxManager.search(MailboxQuery.builder(session) + .privateUserMailboxes().build(), session).stream() + .map(MailboxMetaData::getPath) + .filter(this::hasRoleOutbox) + .map(loadMailbox(session)) + .findFirst(); + } + + private boolean hasRoleOutbox(MailboxPath mailBoxPath) { + return Role.from(mailBoxPath.getName()) + .map(Role.OUTBOX::equals) + .orElse(false); + } + + private ThrowingFunction<MailboxPath, Mailbox<Id>> loadMailbox(MailboxSession session) { + return path -> mailboxMapperFactory.getMailboxMapper(session).findMailboxByPath(path); + } + + private PropertyBuilder buildPropertyBuilder() { + return new PropertyBuilder(); + } + + private Flags getMessageFlags(CreationMessage message) { + Flags result = new Flags(); + if (!message.isIsUnread()) { + result.add(Flags.Flag.SEEN); + } + if (message.isIsFlagged()) { + result.add(Flags.Flag.FLAGGED); + } + if (message.isIsAnswered() || message.getInReplyToMessageId().isPresent()) { + result.add(Flags.Flag.ANSWERED); + } + if (message.isIsDraft()) { + result.add(Flags.Flag.DRAFT); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java index f14cdba..5202026 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesMethod.java @@ -56,13 +56,17 @@ public class SetMessagesMethod<Id extends MailboxId> implements Method { private final MailboxMapperFactory<Id> mailboxMapperFactory; private final MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory; private final SetMessagesUpdateProcessor<Id> messageUpdater; + private final SetMessagesCreationProcessor messageCreator; @Inject @VisibleForTesting SetMessagesMethod(MailboxMapperFactory<Id> mailboxMapperFactory, - MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory, SetMessagesUpdateProcessor<Id> messageUpdater) { + MailboxSessionMapperFactory<Id> mailboxSessionMapperFactory, + SetMessagesUpdateProcessor<Id> messageUpdater, + SetMessagesCreationProcessor messageCreator) { this.mailboxMapperFactory = mailboxMapperFactory; this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; this.messageUpdater = messageUpdater; + this.messageCreator = messageCreator; } @Override @@ -95,7 +99,8 @@ public class SetMessagesMethod<Id extends MailboxId> implements Method { private SetMessagesResponse setMessagesResponse(SetMessagesRequest request, MailboxSession mailboxSession) throws MailboxException { SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder(); processDestroy(request.getDestroy(), mailboxSession, responseBuilder); - messageUpdater.processUpdates(request, mailboxSession).mergeInto(responseBuilder); + messageUpdater.process(request, mailboxSession).mergeInto(responseBuilder); + messageCreator.process(request, mailboxSession).mergeInto(responseBuilder); return responseBuilder.build(); } http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java new file mode 100644 index 0000000..4c7e00d --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesProcessor.java @@ -0,0 +1,29 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import org.apache.james.jmap.model.SetMessagesRequest; +import org.apache.james.jmap.model.SetMessagesResponse; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.store.mail.model.MailboxId; + +public interface SetMessagesProcessor<Id extends MailboxId> { + SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession); +} http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java index f62e390..ed7e219 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java @@ -52,7 +52,7 @@ import com.google.common.collect.ImmutableSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SetMessagesUpdateProcessor<Id extends MailboxId> { +public class SetMessagesUpdateProcessor<Id extends MailboxId> implements SetMessagesProcessor<Id> { private static final int LIMIT_BY_ONE = 1; private static final Logger LOGGER = LoggerFactory.getLogger(SetMessagesUpdateProcessor.class); @@ -71,7 +71,7 @@ public class SetMessagesUpdateProcessor<Id extends MailboxId> { this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; } - public SetMessagesResponse processUpdates(SetMessagesRequest request, MailboxSession mailboxSession) { + public SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession) { SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder(); request.buildUpdatePatches(updatePatchConverter).forEach( (id, patch) -> { if (patch.isValid()) { http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java new file mode 100644 index 0000000..697c9c7 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java @@ -0,0 +1,305 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.model; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.james.jmap.methods.GetMessagesMethod; +import org.apache.james.jmap.methods.JmapResponseWriterImpl; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +@JsonDeserialize(builder = CreationMessage.Builder.class) +public class CreationMessage { + + public static Builder builder() { + return new Builder(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private ImmutableList<String> mailboxIds; + private String inReplyToMessageId; + private boolean isUnread; + private boolean isFlagged; + private boolean isAnswered; + private boolean isDraft; + private final ImmutableMap.Builder<String, String> headers; + private Emailer from; + private final ImmutableList.Builder<Emailer> to; + private final ImmutableList.Builder<Emailer> cc; + private final ImmutableList.Builder<Emailer> bcc; + private final ImmutableList.Builder<Emailer> replyTo; + private String subject; + private ZonedDateTime date; + private String textBody; + private String htmlBody; + private final ImmutableList.Builder<Attachment> attachments; + private final ImmutableMap.Builder<String, SubMessage> attachedMessages; + + private Builder() { + to = ImmutableList.builder(); + cc = ImmutableList.builder(); + bcc = ImmutableList.builder(); + replyTo = ImmutableList.builder(); + attachments = ImmutableList.builder(); + attachedMessages = ImmutableMap.builder(); + headers = ImmutableMap.builder(); + } + + public Builder mailboxIds(ImmutableList<String> mailboxIds) { + this.mailboxIds = mailboxIds; + return this; + } + + public Builder inReplyToMessageId(String inReplyToMessageId) { + this.inReplyToMessageId = inReplyToMessageId; + return this; + } + + public Builder isUnread(boolean isUnread) { + this.isUnread = isUnread; + return this; + } + + public Builder isFlagged(boolean isFlagged) { + this.isFlagged = isFlagged; + return this; + } + + public Builder isAnswered(boolean isAnswered) { + this.isAnswered = isAnswered; + return this; + } + + public Builder isDraft(boolean isDraft) { + this.isDraft = isDraft; + return this; + } + + public Builder headers(ImmutableMap<String, String> headers) { + this.headers.putAll(headers); + return this; + } + + public Builder from(Emailer from) { + this.from = from; + return this; + } + + public Builder to(List<Emailer> to) { + this.to.addAll(to); + return this; + } + + public Builder cc(List<Emailer> cc) { + this.cc.addAll(cc); + return this; + } + + public Builder bcc(List<Emailer> bcc) { + this.bcc.addAll(bcc); + return this; + } + + public Builder replyTo(List<Emailer> replyTo) { + this.replyTo.addAll(replyTo); + return this; + } + + public Builder subject(String subject) { + this.subject = subject; + return this; + } + + public Builder date(ZonedDateTime date) { + this.date = date; + return this; + } + + public Builder textBody(String textBody) { + this.textBody = textBody; + return this; + } + + public Builder htmlBody(String htmlBody) { + this.htmlBody = htmlBody; + return this; + } + + public Builder attachments(List<Attachment> attachments) { + this.attachments.addAll(attachments); + return this; + } + + public Builder attachedMessages(Map<String, SubMessage> attachedMessages) { + this.attachedMessages.putAll(attachedMessages); + return this; + } + + private static boolean areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, ImmutableMap<String, SubMessage> attachedMessages) { + return attachments.stream() + .map(Attachment::getBlobId) + .allMatch(attachedMessages::containsKey); + } + + public CreationMessage build() { + Preconditions.checkState(mailboxIds != null, "'mailboxIds' is mandatory"); + Preconditions.checkState(headers != null, "'headers' is mandatory"); + Preconditions.checkState(!Strings.isNullOrEmpty(subject), "'subject' is mandatory"); + ImmutableList<Attachment> attachments = this.attachments.build(); + ImmutableMap<String, SubMessage> attachedMessages = this.attachedMessages.build(); + Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachments'"); + + if (date == null) { + date = ZonedDateTime.now(); + } + + return new CreationMessage(mailboxIds, Optional.ofNullable(inReplyToMessageId), isUnread, isFlagged, isAnswered, isDraft, headers.build(), Optional.ofNullable(from), + to.build(), cc.build(), bcc.build(), replyTo.build(), subject, date, Optional.ofNullable(textBody), Optional.ofNullable(htmlBody), attachments, attachedMessages); + } + } + + private final ImmutableList<String> mailboxIds; + private final Optional<String> inReplyToMessageId; + private final boolean isUnread; + private final boolean isFlagged; + private final boolean isAnswered; + private final boolean isDraft; + private final ImmutableMap<String, String> headers; + private final Optional<Emailer> from; + private final ImmutableList<Emailer> to; + private final ImmutableList<Emailer> cc; + private final ImmutableList<Emailer> bcc; + private final ImmutableList<Emailer> replyTo; + private final String subject; + private final ZonedDateTime date; + private final Optional<String> textBody; + private final Optional<String> htmlBody; + private final ImmutableList<Attachment> attachments; + private final ImmutableMap<String, SubMessage> attachedMessages; + + @VisibleForTesting + CreationMessage(ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, ImmutableMap<String, String> headers, Optional<Emailer> from, + ImmutableList<Emailer> to, ImmutableList<Emailer> cc, ImmutableList<Emailer> bcc, ImmutableList<Emailer> replyTo, String subject, ZonedDateTime date, Optional<String> textBody, Optional<String> htmlBody, ImmutableList<Attachment> attachments, + ImmutableMap<String, SubMessage> attachedMessages) { + this.mailboxIds = mailboxIds; + this.inReplyToMessageId = inReplyToMessageId; + this.isUnread = isUnread; + this.isFlagged = isFlagged; + this.isAnswered = isAnswered; + this.isDraft = isDraft; + this.headers = headers; + this.from = from; + this.to = to; + this.cc = cc; + this.bcc = bcc; + this.replyTo = replyTo; + this.subject = subject; + this.date = date; + this.textBody = textBody; + this.htmlBody = htmlBody; + this.attachments = attachments; + this.attachedMessages = attachedMessages; + } + + public ImmutableList<String> getMailboxIds() { + return mailboxIds; + } + + public Optional<String> getInReplyToMessageId() { + return inReplyToMessageId; + } + + public boolean isIsUnread() { + return isUnread; + } + + public boolean isIsFlagged() { + return isFlagged; + } + + public boolean isIsAnswered() { + return isAnswered; + } + + public boolean isIsDraft() { + return isDraft; + } + + public ImmutableMap<String, String> getHeaders() { + return headers; + } + + public Optional<Emailer> getFrom() { + return from; + } + + public ImmutableList<Emailer> getTo() { + return to; + } + + public ImmutableList<Emailer> getCc() { + return cc; + } + + public ImmutableList<Emailer> getBcc() { + return bcc; + } + + public ImmutableList<Emailer> getReplyTo() { + return replyTo; + } + + public String getSubject() { + return subject; + } + + public ZonedDateTime getDate() { + return date; + } + + public Optional<String> getTextBody() { + return textBody; + } + + public Optional<String> getHtmlBody() { + return htmlBody; + } + + public ImmutableList<Attachment> getAttachments() { + return attachments; + } + + public ImmutableMap<String, SubMessage> getAttachedMessages() { + return attachedMessages; + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java index aadb886..8e25ac5 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java @@ -48,7 +48,7 @@ public class SetMessagesRequest implements JmapRequest { private String accountId; private String ifInState; - private ImmutableMap.Builder<MessageId, Message> create; + private ImmutableMap.Builder<String, CreationMessage> create; private ImmutableMap.Builder<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> updatesProvider; private ImmutableList.Builder<MessageId> destroy; @@ -73,8 +73,8 @@ public class SetMessagesRequest implements JmapRequest { return this; } - public Builder create(Map<MessageId, Message> creates) { - this.create.putAll(creates); + public Builder create(Map<String, CreationMessage> creations) { + this.create.putAll(creations); return this; } @@ -95,11 +95,11 @@ public class SetMessagesRequest implements JmapRequest { private final Optional<String> accountId; private final Optional<String> ifInState; - private final Map<MessageId, Message> create; + private final Map<String, CreationMessage> create; private final Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update; private final List<MessageId> destroy; - @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<MessageId, Message> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update, List<MessageId> destroy) { + @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<String, CreationMessage> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update, List<MessageId> destroy) { this.accountId = accountId; this.ifInState = ifInState; this.create = create; @@ -115,7 +115,7 @@ public class SetMessagesRequest implements JmapRequest { return ifInState; } - public Map<MessageId, Message> getCreate() { + public Map<String, CreationMessage> getCreate() { return create; } http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java index 304f8c3..c096c8a 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java @@ -20,9 +20,11 @@ package org.apache.james.jmap.model; import java.util.List; import java.util.Map; +import java.util.function.BiFunction; import com.google.common.base.Strings; import org.apache.commons.lang.NotImplementedException; +import org.apache.james.jmap.methods.MessageWithId; import org.apache.james.jmap.methods.Method; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -42,10 +44,18 @@ public class SetMessagesResponse implements Method.Response { @JsonPOJOBuilder(withPrefix = "") public static class Builder { + public static Builder accumulator(Builder accumulator, SetMessagesResponse response) { + return response.mergeInto(accumulator); + } + + public static Builder combiner(Builder firstBuilder, Builder secondBuilder) { + return secondBuilder.build().mergeInto(firstBuilder); + } + private String accountId; private String oldState; private String newState; - private ImmutableList.Builder<Message> created; + private ImmutableMap.Builder<String, Message> created; private ImmutableList.Builder<MessageId> updated; private ImmutableList.Builder<MessageId> destroyed; private ImmutableMap.Builder<MessageId, SetError> notCreated; @@ -53,7 +63,7 @@ public class SetMessagesResponse implements Method.Response { private ImmutableMap.Builder<MessageId, SetError> notDestroyed; private Builder() { - created = ImmutableList.builder(); + created = ImmutableMap.builder(); updated = ImmutableList.builder(); destroyed = ImmutableList.builder(); notCreated = ImmutableMap.builder(); @@ -73,8 +83,8 @@ public class SetMessagesResponse implements Method.Response { throw new NotImplementedException(); } - public Builder created(List<Message> created) { - this.created.addAll(created); + public Builder created(Map<String, Message> created) { + this.created.putAll(created); return this; } @@ -122,14 +132,14 @@ public class SetMessagesResponse implements Method.Response { private final String accountId; private final String oldState; private final String newState; - private final List<Message> created; + private final Map<String, Message> created; private final List<MessageId> updated; private final List<MessageId> destroyed; private final Map<MessageId, SetError> notCreated; private final Map<MessageId, SetError> notUpdated; private final Map<MessageId, SetError> notDestroyed; - @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, List<Message> created, List<MessageId> updated, List<MessageId> destroyed, + @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, Map<String, Message> created, List<MessageId> updated, List<MessageId> destroyed, Map<MessageId, SetError> notCreated, Map<MessageId, SetError> notUpdated, Map<MessageId, SetError> notDestroyed) { this.accountId = accountId; this.oldState = oldState; @@ -158,7 +168,7 @@ public class SetMessagesResponse implements Method.Response { } @JsonSerialize - public List<Message> getCreated() { + public Map<String, Message> getCreated() { return created; } @@ -187,7 +197,7 @@ public class SetMessagesResponse implements Method.Response { return notDestroyed; } - public void mergeInto(SetMessagesResponse.Builder responseBuilder) { + public SetMessagesResponse.Builder mergeInto(SetMessagesResponse.Builder responseBuilder) { responseBuilder.created(getCreated()); responseBuilder.updated(getUpdated()); responseBuilder.destroyed(getDestroyed()); @@ -203,5 +213,6 @@ public class SetMessagesResponse implements Method.Response { if(! Strings.isNullOrEmpty(getNewState())) { responseBuilder.accountId(getAccountId()); } + return responseBuilder; } } http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java new file mode 100644 index 0000000..0b73ee2 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java @@ -0,0 +1,131 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.ZonedDateTime; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.james.jmap.model.CreationMessage; +import org.apache.james.jmap.model.Emailer; +import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageId; +import org.apache.james.jmap.model.SetMessagesRequest; +import org.apache.james.jmap.model.SetMessagesResponse; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +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.MailboxId; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class SetMessagesCreationProcessorTest { + + @Test + @SuppressWarnings("unchecked") + public void processShouldReturnEmptyCreatedWhenRequestHasEmptyCreate() { + SetMessagesCreationProcessor<MailboxId> sut = new SetMessagesCreationProcessor<MailboxId>(null, null, null, null) { + @Override + protected Optional<Mailbox<MailboxId>> getOutbox(MailboxSession session) throws MailboxException { + Mailbox<MailboxId> fakeOutbox = mock(Mailbox.class); + when(fakeOutbox.getName()).thenReturn("outbox"); + return Optional.of(fakeOutbox); + } + }; + SetMessagesRequest requestWithEmptyCreate = SetMessagesRequest.builder().build(); + + SetMessagesResponse result = sut.process(requestWithEmptyCreate, buildStubbedSession()); + + assertThat(result.getCreated()).isEmpty(); + assertThat(result.getNotCreated()).isEmpty(); + } + + private MailboxSession buildStubbedSession() { + MailboxSession.User stubUser = mock(MailboxSession.User.class); + when(stubUser.getUserName()).thenReturn("user"); + MailboxSession stubSession = mock(MailboxSession.class); + when(stubSession.getPathDelimiter()).thenReturn('.'); + when(stubSession.getUser()).thenReturn(stubUser); + when(stubSession.getPersonalSpace()).thenReturn("#private"); + return stubSession; + } + + @Test + @SuppressWarnings("unchecked") + public void processShouldReturnNonEmptyCreatedWhenRequestHasNonEmptyCreate() throws MailboxException { + + MessageMapper<MailboxId> stubMapper = mock(MessageMapper.class); + MailboxSessionMapperFactory<MailboxId> mockSessionMapperFactory = mock(MailboxSessionMapperFactory.class); + when(mockSessionMapperFactory.createMessageMapper(any(MailboxSession.class))) + .thenReturn(stubMapper); + + SetMessagesCreationProcessor<MailboxId> sut = new SetMessagesCreationProcessor<MailboxId>(null, null, mockSessionMapperFactory, null) { + @Override + protected MessageWithId<Message> createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox<MailboxId> outbox, Function<Long, MessageId> buildMessageIdFromUid) { + return new MessageWithId<>(createdEntry.creationId, getFakeMessage()); + } + @Override + protected Optional<Mailbox<MailboxId>> getOutbox(MailboxSession session) throws MailboxException { + Mailbox<MailboxId> fakeOutbox = mock(Mailbox.class); + when(fakeOutbox.getName()).thenReturn("outbox"); + return Optional.of(fakeOutbox); + } + }; + + SetMessagesRequest creationRequest = SetMessagesRequest.builder() + .create(ImmutableMap.of("anything-really", CreationMessage.builder() + .from(Emailer.builder().name("alice").email("[email protected]").build()) + .to(ImmutableList.of(Emailer.builder().name("bob").email("[email protected]").build())) + .subject("Hey! ") + .mailboxIds(ImmutableList.of("mailboxId")) + .build() + )) + .build() + ; + + SetMessagesResponse result = sut.process(creationRequest, buildStubbedSession()); + + assertThat(result.getCreated()).isNotEmpty(); + assertThat(result.getNotCreated()).isEmpty(); + } + + private Message getFakeMessage() { + return Message.builder() + .id(org.apache.james.jmap.model.MessageId.of("user|outbox|1")) + .blobId("anything") + .threadId("anything") + .mailboxIds(ImmutableList.of("mailboxId")) + .headers(ImmutableMap.of()) + .subject("anything") + .size(0) + .date(ZonedDateTime.now()) + .preview("anything") + .build(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/99ac325d/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java index e2bc7c7..9877651 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java @@ -55,7 +55,7 @@ public class SetMessagesResponseTest { @Test public void builderShouldWork() { ZonedDateTime currentDate = ZonedDateTime.now(); - ImmutableList<Message> created = ImmutableList.of( + ImmutableMap<String, Message> created = ImmutableMap.of("user|created|1", Message.builder() .id(MessageId.of("user|created|1")) .blobId("blobId") @@ -105,8 +105,8 @@ public class SetMessagesResponseTest { assertThat(emptyBuilder.build()).isEqualToComparingFieldByField(testee); } - private ImmutableList<Message> buildMessage(String messageId) { - return ImmutableList.of(Message.builder() + private ImmutableMap<String, Message> buildMessage(String messageId) { + return ImmutableMap.of(messageId, Message.builder() .id(MessageId.of(messageId)) .blobId("blobId") .threadId("threadId") @@ -152,10 +152,7 @@ public class SetMessagesResponseTest { SetMessagesResponse mergedResponse = nonEmptyBuilder.build(); // Then - List<String> createdMessageIds = mergedResponse.getCreated().stream() - .map(m -> m.getId().serialize()) - .collect(Collectors.toList()); - assertThat(createdMessageIds).containsExactly(buildersCreatedMessageId, createdMessageId); + assertThat(mergedResponse.getCreated().keySet()).containsExactly(buildersCreatedMessageId, createdMessageId); } @Test --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
