JAMES-1664 refactoring of getMessages handling to make it simpler with a bit of encapsulation
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/4c8403c3 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/4c8403c3 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/4c8403c3 Branch: refs/heads/master Commit: 4c8403c34a2f0e2bb620fda41853dce1ec54f3ae Parents: 8c19208 Author: Matthieu Baechler <matthieu.baech...@gmail.com> Authored: Wed Jan 27 09:59:52 2016 +0100 Committer: Antoine Duprat <antdup...@gmail.com> Committed: Mon Feb 1 13:21:05 2016 +0100 ---------------------------------------------------------------------- .../jmap/json/FieldNamePropertyFilter.java | 41 +++ .../james/jmap/methods/GetMessagesMethod.java | 97 ++------ .../jmap/methods/JmapResponseWriterImpl.java | 1 + .../james/jmap/model/GetMessagesRequest.java | 57 +---- .../james/jmap/model/MessageHeaderProperty.java | 69 ------ .../james/jmap/model/MessageProperties.java | 248 +++++++++++++++++++ .../james/jmap/model/MessageProperty.java | 69 ------ .../jmap/methods/GetMessagesMethodTest.java | 19 +- .../jmap/model/GetMessagesRequestTest.java | 14 +- .../jmap/model/MessageHeaderPropertyTest.java | 44 +++- .../james/jmap/model/MessagePropertiesTest.java | 82 ++++++ .../james/jmap/model/MessagePropertyTest.java | 3 +- 12 files changed, 455 insertions(+), 289 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/FieldNamePropertyFilter.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/FieldNamePropertyFilter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/FieldNamePropertyFilter.java new file mode 100644 index 0000000..40766cc --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/FieldNamePropertyFilter.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.jmap.json; + + +import com.fasterxml.jackson.databind.ser.PropertyWriter; +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; + +import java.util.function.Predicate; + +public class FieldNamePropertyFilter extends SimpleBeanPropertyFilter { + + private final Predicate<String> predicate; + + public FieldNamePropertyFilter(Predicate<String> predicate) { + this.predicate = predicate; + } + + @Override + protected boolean include(PropertyWriter writer) { + return predicate.test(writer.getName()); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java index e030d3f..a2ad348 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java @@ -22,7 +22,6 @@ package org.apache.james.jmap.methods; import java.util.Iterator; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -30,14 +29,14 @@ import java.util.stream.StreamSupport; import javax.inject.Inject; import org.apache.commons.lang.NotImplementedException; +import org.apache.james.jmap.json.FieldNamePropertyFilter; import org.apache.james.jmap.model.ClientId; import org.apache.james.jmap.model.GetMessagesRequest; import org.apache.james.jmap.model.GetMessagesResponse; import org.apache.james.jmap.model.Message; -import org.apache.james.jmap.model.MessageHeaderProperty; import org.apache.james.jmap.model.MessageId; -import org.apache.james.jmap.model.MessageProperty; -import org.apache.james.jmap.model.Property; +import org.apache.james.jmap.model.MessageProperties; +import org.apache.james.jmap.model.MessageProperties.HeaderProperty; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.model.MessageRange; @@ -50,18 +49,15 @@ import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.james.util.streams.Collectors; import org.javatuples.Pair; -import com.fasterxml.jackson.databind.ser.PropertyWriter; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; +import com.fasterxml.jackson.databind.ser.PropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import com.github.fge.lambdas.Throwing; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; public class GetMessagesMethod<Id extends MailboxId> implements Method { - public static final Set<MessageProperty> MANDATORY_PROPERTIES = ImmutableSet.of(MessageProperty.id, MessageProperty.threadId, MessageProperty.mailboxIds); public static final String HEADERS_FILTER = "headersFilter"; private static final Method.Request.Name METHOD_NAME = Method.Request.name("getMessages"); private static final Method.Response.Name RESPONSE_NAME = Method.Response.name("messages"); @@ -92,88 +88,27 @@ public class GetMessagesMethod<Id extends MailboxId> implements Method { Preconditions.checkNotNull(mailboxSession); Preconditions.checkArgument(request instanceof GetMessagesRequest); GetMessagesRequest getMessagesRequest = (GetMessagesRequest) request; - Optional<ImmutableSet<MessageProperty>> requestedProperties = getMessagesRequest.getProperties(); - Optional<ImmutableSet<MessageHeaderProperty>> headerProperties = getMessagesRequest.getHeaderProperties(); + MessageProperties outputProperties = getMessagesRequest.getProperties().toOutputProperties(); return Stream.of(JmapResponse.builder().clientId(clientId) - .response(getMessagesResponse(mailboxSession, getMessagesRequest, requestedProperties)) + .response(getMessagesResponse(mailboxSession, getMessagesRequest)) .responseName(RESPONSE_NAME) - .properties(handleSpecificProperties(requestedProperties, headerProperties)) - .filterProvider(Optional.of(buildFilteringHeadersFilterProvider(headerProperties))) + .properties(outputProperties.getOptionalMessageProperties()) + .filterProvider(buildOptionalHeadersFilteringFilterProvider(outputProperties)) .build()); } - private Optional<Set<? extends Property>> handleSpecificProperties(Optional<ImmutableSet<MessageProperty>> requestedProperties, Optional<ImmutableSet<MessageHeaderProperty>> headerProperties) { - Set<MessageProperty> toAdd = Sets.newHashSet(); - Set<MessageProperty> toRemove = Sets.newHashSet(); - toAdd.addAll(ensureContainsMandatoryFields(requestedProperties)); - handleBody(requestedProperties, toAdd, toRemove); - handleHeadersProperties(headerProperties, toAdd, toRemove); - ImmutableSet<MessageProperty> resultProperties = Sets.union( - Sets.difference(requestedProperties.isPresent() ? requestedProperties.get() : ImmutableSet.of(), toRemove) - , toAdd) - .immutableCopy(); - if (resultProperties.isEmpty()) { - return Optional.empty(); - } - return Optional.of(resultProperties); - } - - private void handleHeadersProperties(Optional<ImmutableSet<MessageHeaderProperty>> headerProperties, Set<MessageProperty> toAdd, Set<MessageProperty> toRemove) { - if (headerProperties.isPresent() && !headerProperties.get().isEmpty()) { - toAdd.add(MessageProperty.headers); - } - } - - private Set<MessageProperty> ensureContainsMandatoryFields(Optional<ImmutableSet<MessageProperty>> requestedProperties) { - return MANDATORY_PROPERTIES.stream() - .filter(mandatoryProperty -> propertyToAdd(mandatoryProperty, requestedProperties)) - .collect(Collectors.toImmutableSet()); - } - - private boolean propertyToAdd(MessageProperty property, Optional<ImmutableSet<MessageProperty>> requestedProperties) { - return requestedProperties.isPresent() && - !requestedProperties - .filter(properties -> properties.contains(property)) - .flatMap(Optional::of) - .isPresent(); - } - - private void handleBody(Optional<ImmutableSet<MessageProperty>> requestedProperties, Set<MessageProperty> toAdd, Set<MessageProperty> toRemove) { - if (requestedProperties.isPresent() && requestedProperties.get().contains(MessageProperty.body)) { - toAdd.add(MessageProperty.textBody); - toRemove.add(MessageProperty.body); - } - } - - private SimpleFilterProvider buildFilteringHeadersFilterProvider(Optional<ImmutableSet<MessageHeaderProperty>> headerProperties) { - return new SimpleFilterProvider() - .addFilter(HEADERS_FILTER, buildPropertyFilter(headerProperties)) - .addFilter(JmapResponseWriterImpl.PROPERTIES_FILTER, SimpleBeanPropertyFilter.serializeAll()); + private Optional<SimpleFilterProvider> buildOptionalHeadersFilteringFilterProvider(MessageProperties properties) { + return properties.getOptionalHeadersProperties() + .map(this::buildHeadersPropertyFilter) + .map(propertyFilter -> new SimpleFilterProvider() + .addFilter(HEADERS_FILTER, propertyFilter)); } - private SimpleBeanPropertyFilter buildPropertyFilter(Optional<ImmutableSet<MessageHeaderProperty>> headerProperties) { - if (!headerProperties.isPresent()) { - return SimpleBeanPropertyFilter.serializeAll(); - } else { - return new IncludeMessagePropertyPropertyFilter(headerProperties.get()); - } + private PropertyFilter buildHeadersPropertyFilter(ImmutableSet<HeaderProperty> headerProperties) { + return new FieldNamePropertyFilter((fieldName) -> headerProperties.contains(HeaderProperty.fromFieldName(fieldName))); } - - private static class IncludeMessagePropertyPropertyFilter extends SimpleBeanPropertyFilter { - private final Set<MessageHeaderProperty> propertiesToInclude; - public IncludeMessagePropertyPropertyFilter(Set<MessageHeaderProperty> propertiesToInclude) { - this.propertiesToInclude = propertiesToInclude; - } - - @Override - protected boolean include(PropertyWriter writer) { - String currentProperty = writer.getName(); - return propertiesToInclude.contains(MessageHeaderProperty.fromField(currentProperty)); - } - } - - private GetMessagesResponse getMessagesResponse(MailboxSession mailboxSession, GetMessagesRequest getMessagesRequest, Optional<ImmutableSet<MessageProperty>> requestedProperties) { + private GetMessagesResponse getMessagesResponse(MailboxSession mailboxSession, GetMessagesRequest getMessagesRequest) { getMessagesRequest.getAccountId().ifPresent(GetMessagesMethod::notImplemented); Function<MessageId, Stream<Pair<MailboxMessage<Id>, MailboxPath>>> loadMessages = loadMessage(mailboxSession); http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/JmapResponseWriterImpl.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/JmapResponseWriterImpl.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/JmapResponseWriterImpl.java index 2ddbc42..f56a067 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/JmapResponseWriterImpl.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/JmapResponseWriterImpl.java @@ -67,6 +67,7 @@ public class JmapResponseWriterImpl implements JmapResponseWriter { FilterProvider filterProvider = jmapResponse .getFilterProvider() .orElseGet(SimpleFilterProvider::new) + .setDefaultFilter(SimpleBeanPropertyFilter.serializeAll()) .addFilter(PROPERTIES_FILTER, getPropertiesFilter(jmapResponse.getProperties())); objectMapper.setFilterProvider(filterProvider); http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/GetMessagesRequest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/GetMessagesRequest.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/GetMessagesRequest.java index 708dc3d..4b67c55 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/GetMessagesRequest.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/GetMessagesRequest.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Optional; import org.apache.james.jmap.methods.JmapRequest; -import org.apache.james.util.streams.Collectors; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; @@ -42,12 +41,12 @@ public class GetMessagesRequest implements JmapRequest { private Optional<String> accountId; private final ImmutableList.Builder<MessageId> ids; - private ImmutableSet.Builder<String> propertiesBuilder; + private Optional<ImmutableSet<String>> properties; private Builder() { accountId = Optional.empty(); ids = ImmutableList.builder(); - propertiesBuilder = null; + properties = Optional.empty(); } public Builder accountId(String accountId) { @@ -61,59 +60,23 @@ public class GetMessagesRequest implements JmapRequest { } public Builder properties(List<String> properties) { - if (propertiesBuilder == null) { - propertiesBuilder = ImmutableSet.builder(); - } - this.propertiesBuilder.addAll(properties); + this.properties = Optional.ofNullable(properties).map(ImmutableSet::copyOf); return this; } public GetMessagesRequest build() { - return new GetMessagesRequest(accountId, ids.build(), messageProperties(propertiesBuilder), messageHeaderProperties(propertiesBuilder)); - } - - private Optional<ImmutableSet<MessageProperty>> messageProperties(ImmutableSet.Builder<String> messageProperties) { - if (messageProperties == null) { - return Optional.empty(); - } - return toOptional(messageProperties.build().stream() - .filter(property -> !isHeaderProperty(property)) - .map(MessageProperty::valueOf) - .collect(Collectors.toImmutableSet()), - MessageProperty.class); - } - - private Optional<ImmutableSet<MessageHeaderProperty>> messageHeaderProperties(ImmutableSet.Builder<String> headerProperties) { - if (headerProperties == null) { - return Optional.empty(); - } - return toOptional(headerProperties.build().stream() - .filter(this::isHeaderProperty) - .map(MessageHeaderProperty::valueOf) - .collect(Collectors.toImmutableSet()), - MessageHeaderProperty.class); - } - - private boolean isHeaderProperty(String property) { - return property.startsWith(MessageHeaderProperty.HEADER_PROPERTY_PREFIX); - } - - private <T extends Property> Optional<ImmutableSet<T>> toOptional(ImmutableSet<T> set, Class<T> clazz) { - return Optional.of(set); + return new GetMessagesRequest(accountId, ids.build(), new MessageProperties(properties)); } } - + private final Optional<String> accountId; private final ImmutableList<MessageId> ids; - private final Optional<ImmutableSet<MessageProperty>> properties; - private final Optional<ImmutableSet<MessageHeaderProperty>> headerProperties; + private final MessageProperties properties; - public GetMessagesRequest(Optional<String> accountId, ImmutableList<MessageId> ids, - Optional<ImmutableSet<MessageProperty>> properties, Optional<ImmutableSet<MessageHeaderProperty>> headerProperties) { + public GetMessagesRequest(Optional<String> accountId, ImmutableList<MessageId> ids, MessageProperties properties) { this.accountId = accountId; this.ids = ids; this.properties = properties; - this.headerProperties = headerProperties; } public Optional<String> getAccountId() { @@ -124,11 +87,7 @@ public class GetMessagesRequest implements JmapRequest { return ids; } - public Optional<ImmutableSet<MessageProperty>> getProperties() { + public MessageProperties getProperties() { return properties; } - - public Optional<ImmutableSet<MessageHeaderProperty>> getHeaderProperties() { - return headerProperties; - } } http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageHeaderProperty.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageHeaderProperty.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageHeaderProperty.java deleted file mode 100644 index 4a51415..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageHeaderProperty.java +++ /dev/null @@ -1,69 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.model; - -import java.util.Locale; -import java.util.Objects; - -public class MessageHeaderProperty implements Property { - - public static final String HEADER_PROPERTY_PREFIX = "headers."; - - public static MessageHeaderProperty from(MessageProperty messageProperty) { - return new MessageHeaderProperty(HEADER_PROPERTY_PREFIX + messageProperty.asFieldName().toLowerCase(Locale.US)); - } - - public static MessageHeaderProperty fromField(String field) { - return new MessageHeaderProperty(HEADER_PROPERTY_PREFIX + field.toLowerCase(Locale.US)); - } - - public static MessageHeaderProperty valueOf(String property) { - return new MessageHeaderProperty(property.toLowerCase(Locale.US)); - } - - private String property; - - private MessageHeaderProperty(String property) { - this.property = property; - } - - @Override - public String asFieldName() { - return property; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof MessageHeaderProperty) { - MessageHeaderProperty other = (MessageHeaderProperty) obj; - return Objects.equals(this.property, other.property); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(property); - } - - @Override - public String toString() { - return Objects.toString(property); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperties.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperties.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperties.java new file mode 100644 index 0000000..a24bf9a --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperties.java @@ -0,0 +1,248 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.util.Arrays; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import com.google.common.collect.Sets; +import org.apache.james.util.streams.Collectors; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; + +public class MessageProperties { + + public static final ImmutableSet<MessageProperty> MANDATORY_PROPERTIES = ImmutableSet.of(MessageProperty.id, MessageProperty.threadId, MessageProperty.mailboxIds); + + private final Optional<ImmutableSet<MessageProperty>> messageProperties; + private final Optional<ImmutableSet<HeaderProperty>> headersProperties; + + public MessageProperties(Optional<ImmutableSet<String>> properties) { + this.messageProperties = properties.map(this::toMessageProperties); + this.headersProperties = properties.map(this::toHeadersProperties); + } + + private MessageProperties(Optional<ImmutableSet<MessageProperty>> messageProperties, + Optional<ImmutableSet<HeaderProperty>> headersProperties) { + this.messageProperties = messageProperties; + this.headersProperties = headersProperties; + } + + private ImmutableSet<MessageProperty> toMessageProperties(ImmutableSet<String> properties) { + return properties.stream().flatMap(MessageProperty::find).collect(Collectors.toImmutableSet()); + } + + private ImmutableSet<HeaderProperty> toHeadersProperties(ImmutableSet<String> properties) { + return properties.stream().flatMap(HeaderProperty::find).collect(Collectors.toImmutableSet()); + } + + public Optional<ImmutableSet<HeaderProperty>> getOptionalHeadersProperties() { + return headersProperties; + } + + public Optional<ImmutableSet<MessageProperty>> getOptionalMessageProperties() { + return messageProperties; + } + + public MessageProperties toOutputProperties() { + return this.ensureContains(MANDATORY_PROPERTIES) + .selectBody() + .overrideHeadersFilteringOnHeadersMessageProperty() + .ensureHeadersMessageProperty(); + } + + private ImmutableSet<MessageProperty> buildOutputMessageProperties() { + return this.messageProperties.orElseGet(() -> MessageProperty.allOutputProperties()); + } + + private MessageProperties usingProperties(Sets.SetView<MessageProperty> properties) { + return new MessageProperties( + Optional.of(properties.immutableCopy()), + headersProperties); + } + + private MessageProperties ensureContains(ImmutableSet<MessageProperty> mandatoryFields) { + return usingProperties(Sets.union(buildOutputMessageProperties(), mandatoryFields)); + } + + private MessageProperties selectBody() { + ImmutableSet<MessageProperty> messageProperties = buildOutputMessageProperties(); + if (messageProperties.contains(MessageProperty.body)) { + return usingProperties( + Sets.difference( + Sets.union(messageProperties, ImmutableSet.of(MessageProperty.textBody)), + ImmutableSet.of(MessageProperty.body))); + } + return this; + } + + private MessageProperties ensureHeadersMessageProperty() { + if (headersProperties.isPresent() && !headersProperties.get().isEmpty()) { + return usingProperties(Sets.union( + buildOutputMessageProperties(), + ImmutableSet.of(MessageProperty.headers))); + } + return this; + } + + private MessageProperties overrideHeadersFilteringOnHeadersMessageProperty() { + if (buildOutputMessageProperties().contains(MessageProperty.headers)) { + return new MessageProperties(messageProperties, Optional.empty()); + } + return this; + } + + + private enum PropertyType { + INPUTONLY, + INPUTOUTPUT + } + + public enum MessageProperty implements Property { + id("id"), + blobId("blobId"), + threadId("threadId"), + mailboxIds("mailboxIds"), + inReplyToMessageId("inReplyToMessageId"), + isUnread("isUnread"), + isFlagged("isFlagged"), + isAnswered("isAnswered"), + isDraft("isDraft"), + hasAttachment("hasAttachment"), + headers("headers"), + from("from"), + to("to"), + cc("cc"), + bcc("bcc"), + replyTo("replyTo"), + subject("subject"), + date("date"), + size("size"), + preview("preview"), + textBody("textBody"), + htmlBody("htmlBody"), + attachments("attachments"), + attachedMessages("attachedMessages"), + body("body", PropertyType.INPUTONLY); + + private final String property; + private final PropertyType type; + + MessageProperty(String property) { + this(property, PropertyType.INPUTOUTPUT); + } + + MessageProperty(String property, PropertyType type) { + this.property = property; + this.type = type; + } + + @Override + public String asFieldName() { + return property; + } + + public static Stream<MessageProperty> find(String property) { + Preconditions.checkNotNull(property); + return Arrays.stream(values()).filter(entry -> entry.property.equals(property)); + } + + public static ImmutableSet<MessageProperty> allOutputProperties() { + return Arrays.stream(values()).filter(MessageProperty::outputProperty).collect(Collectors.toImmutableSet()); + } + + private static boolean outputProperty(MessageProperty p) { + switch (p.type) { + case INPUTONLY: + return false; + case INPUTOUTPUT: + return true; + default: + throw new IllegalStateException(); + } + } + } + + + public static class HeaderProperty implements Property { + + public static final String HEADER_PROPERTY_PREFIX = "headers."; + + public static HeaderProperty fromFieldName(String field) { + Preconditions.checkArgument(!isMessageHeaderProperty(field)); + return new HeaderProperty(field.toLowerCase(Locale.US)); + } + + public static HeaderProperty valueOf(String property) { + Preconditions.checkArgument(isMessageHeaderProperty(property)); + return new HeaderProperty(stripPrefix(property).toLowerCase(Locale.US)); + } + + private static String stripPrefix(String property) { + return property.substring(HEADER_PROPERTY_PREFIX.length()); + } + + public static boolean isMessageHeaderProperty(String property) { + Preconditions.checkNotNull(property); + return property.startsWith(HEADER_PROPERTY_PREFIX); + } + + public static Stream<HeaderProperty> find(String property) { + if (isMessageHeaderProperty(property)) { + return Stream.of(valueOf(property)); + } else { + return Stream.of(); + } + } + + private String fieldName; + + private HeaderProperty(String fieldName) { + this.fieldName = fieldName; + } + + @Override + public String asFieldName() { + return fieldName; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof HeaderProperty) { + HeaderProperty other = (HeaderProperty) obj; + return Objects.equals(this.fieldName, other.fieldName); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(fieldName); + } + + @Override + public String toString() { + return Objects.toString(fieldName); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperty.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperty.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperty.java deleted file mode 100644 index 0e230d4..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageProperty.java +++ /dev/null @@ -1,69 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.model; - -import java.util.Arrays; -import java.util.stream.Stream; - -import com.google.common.base.Preconditions; - -public enum MessageProperty implements Property { - - id("id"), - blobId("blobId"), - threadId("threadId"), - mailboxIds("mailboxIds"), - inReplyToMessageId("inReplyToMessageId"), - isUnread("isUnread"), - isFlagged("isFlagged"), - isAnswered("isAnswered"), - isDraft("isDraft"), - hasAttachment("hasAttachment"), - headers("headers"), - from("from"), - to("to"), - cc("cc"), - bcc("bcc"), - replyTo("replyTo"), - subject("subject"), - date("date"), - size("size"), - preview("preview"), - textBody("textBody"), - htmlBody("htmlBody"), - attachments("attachments"), - attachedMessages("attachedMessages"), - body("body"); - - private final String property; - - private MessageProperty(String property) { - this.property = property; - } - - @Override - public String asFieldName() { - return property; - } - - public static Stream<MessageProperty> find(String property) { - Preconditions.checkNotNull(property); - return Arrays.stream(values()).filter(entry -> entry.property.equals(property)); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java index 5974697..3ba78c5 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang.NotImplementedException; import org.apache.james.jmap.model.ClientId; @@ -37,7 +38,7 @@ import org.apache.james.jmap.model.GetMessagesRequest; import org.apache.james.jmap.model.GetMessagesResponse; import org.apache.james.jmap.model.Message; import org.apache.james.jmap.model.MessageId; -import org.apache.james.jmap.model.MessageProperty; +import org.apache.james.jmap.model.MessageProperties.MessageProperty; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageManager; import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; @@ -57,6 +58,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; +import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -211,10 +214,13 @@ public class GetMessagesMethodTest { .build(); GetMessagesMethod<InMemoryId> testee = new GetMessagesMethod<>(mailboxSessionMapperFactory, mailboxSessionMapperFactory); - List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); + Stream<JmapResponse> result = testee.process(request, clientId, session); - assertThat(result).hasSize(1); - assertThat(result.get(0).getProperties()).isEmpty(); + assertThat(result).hasSize(1) + .extracting(JmapResponse::getProperties) + .flatExtracting(Optional::get) + .asList() + .containsOnlyElementsOf(MessageProperty.allOutputProperties()); } @Test @@ -294,7 +300,7 @@ public class GetMessagesMethodTest { } @Test - public void processShouldReturnAPreconfiguredObjectMapperFilteringHeaders() throws Exception { + public void processShouldReturnPropertyFilterWhenFilteringHeadersRequested() throws Exception { MessageManager inbox = mailboxManager.getMailbox(inboxPath, session); Date now = new Date(); ByteArrayInputStream message1Content = new ByteArrayInputStream(("From: u...@domain.tld\r\n" @@ -315,8 +321,9 @@ public class GetMessagesMethodTest { .hasSize(1) .extracting(JmapResponse::getFilterProvider) .are(new Condition<>(Optional::isPresent, "present")); + SimpleFilterProvider actualFilterProvider = result.get(0).getFilterProvider().get(); ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.setFilterProvider(result.get(0).getFilterProvider().get()); + objectMapper.setFilterProvider(actualFilterProvider.setDefaultFilter(SimpleBeanPropertyFilter.serializeAll())); String response = objectMapper.writer().writeValueAsString(result.get(0)); assertThat(JsonPath.parse(response).<Map<String, String>>read("$.response.list[0].headers")).containsOnly(MapEntry.entry("from", "u...@domain.tld"), MapEntry.entry("header2", "Header2Content")); } http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/GetMessagesRequestTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/GetMessagesRequestTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/GetMessagesRequestTest.java index 8cecead..2d81ba9 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/GetMessagesRequestTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/GetMessagesRequestTest.java @@ -21,6 +21,8 @@ package org.apache.james.jmap.model; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.apache.james.jmap.model.MessageProperties.HeaderProperty; +import org.apache.james.jmap.model.MessageProperties.MessageProperty; import org.junit.Test; import com.google.common.collect.ImmutableList; @@ -60,8 +62,8 @@ public class GetMessagesRequestTest { .ids() .build(); assertThat(result).isNotNull(); - assertThat(result.getProperties()).isEmpty(); - assertThat(result.getHeaderProperties()).isEmpty(); + assertThat(result.getProperties().getOptionalMessageProperties()).isEmpty(); + assertThat(result.getProperties().getOptionalHeadersProperties()).isEmpty(); } @Test @@ -72,8 +74,8 @@ public class GetMessagesRequestTest { .properties(ImmutableList.of()) .build(); assertThat(result).isNotNull(); - assertThat(result.getProperties()).isPresent(); - assertThat(result.getHeaderProperties()).isPresent(); + assertThat(result.getProperties().getOptionalMessageProperties()).hasValue(ImmutableSet.of()); + assertThat(result.getProperties().getOptionalHeadersProperties()).hasValue(ImmutableSet.of()); } @Test @@ -84,7 +86,7 @@ public class GetMessagesRequestTest { .properties(ImmutableList.of("id", "headers.subject", "threadId", "headers.test")) .build(); assertThat(result).isNotNull(); - assertThat(result.getProperties()).contains(ImmutableSet.of(MessageProperty.id, MessageProperty.threadId)); - assertThat(result.getHeaderProperties()).contains(ImmutableSet.of(MessageHeaderProperty.valueOf("headers.subject"), MessageHeaderProperty.valueOf("headers.test"))); + assertThat(result.getProperties().getOptionalMessageProperties()).hasValue(ImmutableSet.of(MessageProperty.id, MessageProperty.threadId)); + assertThat(result.getProperties().getOptionalHeadersProperties()).hasValue(ImmutableSet.of(HeaderProperty.valueOf("headers.subject"), HeaderProperty.valueOf("headers.test"))); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageHeaderPropertyTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageHeaderPropertyTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageHeaderPropertyTest.java index 1c2860e..f3fdada 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageHeaderPropertyTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageHeaderPropertyTest.java @@ -19,40 +19,68 @@ package org.apache.james.jmap.model; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.apache.james.jmap.model.MessageProperties.HeaderProperty; import org.junit.Test; public class MessageHeaderPropertyTest { - @Test(expected=NullPointerException.class) + @Test + public void fromFieldNameShouldLowercaseFieldName() { + assertThat(HeaderProperty.fromFieldName("FiElD")).isEqualTo(HeaderProperty.fromFieldName("field")); + } + + @Test + public void fromFieldNameShouldThrowWhenStartWithHeaderPrefix() { + assertThatThrownBy(() -> HeaderProperty.fromFieldName("headers.FiElD")).isInstanceOf(IllegalArgumentException.class); + } + + @Test public void valueOfShouldThrowWhenNull() { - MessageHeaderProperty.valueOf(null); + assertThatThrownBy(() -> HeaderProperty.valueOf(null)).isInstanceOf(NullPointerException.class); } - @Test(expected=NullPointerException.class) + @Test public void valueOfalueOfShouldThrowWhenNull() { - MessageHeaderProperty.valueOf(null); + assertThatThrownBy(() -> HeaderProperty.valueOf(null)).isInstanceOf(NullPointerException.class); } @Test public void valueOfShouldReturnLowerCasedProperty() { - MessageHeaderProperty headerProperty = MessageHeaderProperty.valueOf("ProP"); + HeaderProperty headerProperty = HeaderProperty.valueOf("headers.ProP"); assertThat(headerProperty.asFieldName()).isEqualTo("prop"); } @Test + public void valueOfShouldThrowWhenValueIsNotHeader() { + assertThatThrownBy(() -> HeaderProperty.valueOf("ProP")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void findShouldReturnStreamWhenValueStartsWithRightString() { + assertThat(HeaderProperty.find(HeaderProperty.HEADER_PROPERTY_PREFIX + "myvalue")) + .contains(HeaderProperty.valueOf(HeaderProperty.HEADER_PROPERTY_PREFIX + "myvalue")); + } + + @Test + public void findShouldReturnEmptyStreamWhenValueStartsWithWrongString() { + assertThat(HeaderProperty.find("bad value" + HeaderProperty.HEADER_PROPERTY_PREFIX + "myvalue")).isEmpty(); + } + + @Test public void equalsShouldBeTrueWhenIdenticalProperties() { - assertThat(MessageHeaderProperty.valueOf("prop")).isEqualTo(MessageHeaderProperty.valueOf("prop")); + assertThat(HeaderProperty.valueOf("headers.prop")).isEqualTo(HeaderProperty.valueOf("headers.prop")); } @Test public void equalsShouldBeFalseWhenDifferentProperties() { - assertThat(MessageHeaderProperty.valueOf("prop")).isNotEqualTo(MessageHeaderProperty.valueOf("other")); + assertThat(HeaderProperty.valueOf("headers.prop")).isNotEqualTo(HeaderProperty.valueOf("headers.other")); } @Test public void equalsShouldBeTrueWhenDifferentCaseProperties() { - assertThat(MessageHeaderProperty.valueOf("prOP")).isEqualTo(MessageHeaderProperty.valueOf("PRop")); + assertThat(HeaderProperty.valueOf("headers.prOP")).isEqualTo(HeaderProperty.valueOf("headers.PRop")); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertiesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertiesTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertiesTest.java new file mode 100644 index 0000000..f6eb239 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertiesTest.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.jmap.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.apache.james.jmap.model.MessageProperties.MessageProperty; +import org.apache.james.jmap.model.MessageProperties.HeaderProperty; +import org.junit.Test; + +import com.google.common.collect.ImmutableSet; + +public class MessagePropertiesTest { + + @Test + public void toOutputPropertiesShouldReturnAllMessagePropertiesWhenAbsent() { + MessageProperties actual = new MessageProperties(Optional.empty()).toOutputProperties(); + assertThat(actual.getOptionalMessageProperties()).hasValue(MessageProperty.allOutputProperties()); + } + + @Test + public void toOutputPropertiesShouldReturnEmptyHeaderPropertiesWhenAbsent() { + MessageProperties actual = new MessageProperties(Optional.empty()).toOutputProperties(); + assertThat(actual.getOptionalHeadersProperties()).isEmpty(); + } + + @Test + public void toOutputPropertiesShouldReturnTextBodyWhenBodyRequested() { + MessageProperties actual = new MessageProperties(Optional.of(ImmutableSet.of("body"))).toOutputProperties(); + assertThat(actual.getOptionalMessageProperties()) + .hasValueSatisfying(value -> + assertThat(value).contains(MessageProperty.textBody).doesNotContain(MessageProperty.body)); + } + + @Test + public void toOutputPropertiesShouldReturnMandatoryPropertiesWhenEmptyRequest() { + MessageProperties actual = new MessageProperties(Optional.of(ImmutableSet.of())).toOutputProperties(); + assertThat(actual.getOptionalMessageProperties()) + .hasValue(ImmutableSet.of(MessageProperty.id, MessageProperty.threadId, MessageProperty.mailboxIds)); + } + + @Test + public void toOutputPropertiesShouldReturnAllHeadersWhenHeadersAndIndividualHeadersRequested() { + MessageProperties actual = new MessageProperties( + Optional.of(ImmutableSet.of("headers.X-Spam-Score", "headers"))).toOutputProperties(); + assertThat(actual.getOptionalMessageProperties()).hasValueSatisfying( + value -> assertThat(value).contains(MessageProperty.headers) + ); + assertThat(actual.getOptionalHeadersProperties()).isEmpty(); + } + + @Test + public void toOutputPropertiesShouldReturnHeadersMessagePropertyWhenIndividualHeadersRequested() { + MessageProperties actual = new MessageProperties( + Optional.of(ImmutableSet.of("headers.X-Spam-Score"))).toOutputProperties(); + assertThat(actual.getOptionalMessageProperties()).hasValueSatisfying( + value -> assertThat(value).contains(MessageProperty.headers) + ); + assertThat(actual.getOptionalHeadersProperties()).hasValueSatisfying( + value -> assertThat(value).contains(HeaderProperty.fromFieldName("x-spam-score")) + ); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4c8403c3/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertyTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertyTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertyTest.java index f9157f4..731a66e 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertyTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePropertyTest.java @@ -18,9 +18,10 @@ ****************************************************************/ package org.apache.james.jmap.model; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.apache.james.jmap.model.MessageProperties.MessageProperty; import org.junit.Test; public class MessagePropertyTest { --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org