Skip to content

Commit

Permalink
ISSUE-1382 One vcard should support multiple addresses (#1404)
Browse files Browse the repository at this point in the history
  • Loading branch information
quantranhong1999 authored Dec 13, 2024
1 parent 8990084 commit 409de91
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linagora.tmail.contact;

import java.util.List;
import java.util.Optional;

import org.apache.james.core.MailAddress;
Expand All @@ -11,12 +12,12 @@
import com.linagora.tmail.james.jmap.contact.ContactFields;

@JsonDeserialize(using = JCardObjectDeserializer.class)
public record JCardObject(Optional<String> fnOpt, Optional<MailAddress> emailOpt) {
public record JCardObject(Optional<String> fnOpt, List<MailAddress> mailAddresses) {
public static final Logger LOGGER = LoggerFactory.getLogger(JCardObject.class);

public JCardObject {
Preconditions.checkNotNull(fnOpt);
Preconditions.checkNotNull(emailOpt);
Preconditions.checkNotNull(mailAddresses);
}

/**
Expand All @@ -31,27 +32,21 @@ public Optional<String> fnOpt() {
}

/**
* Purpose: To specify the electronic mail address for communication
* Purpose: To specify the electronic mail addresses for communication
* with the object the vCard represents.
* <p>
* Example: jane_doe@example.com
* Example: ["jane_doe_at_work@example.com", "jane_doe_at_home@example.com"]
*/
@Override
public Optional<MailAddress> emailOpt() {
return emailOpt;
public List<MailAddress> mailAddresses() {
return mailAddresses;
}

public Optional<ContactFields> asContactFields() {
public List<ContactFields> asContactFields() {
Optional<String> contactFullnameOpt = fnOpt();
Optional<MailAddress> contactMailAddressOpt = emailOpt();

if (contactMailAddressOpt.isEmpty()) {
return Optional.empty();
}

MailAddress contactMailAddress = contactMailAddressOpt.get();
String contactFullname = contactFullnameOpt.orElse("");

return Optional.of(new ContactFields(contactMailAddress, contactFullname, ""));
return mailAddresses()
.stream()
.map(address -> new ContactFields(address, contactFullnameOpt.orElse(""), ""))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.mail.internet.AddressException;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.james.core.MailAddress;
import org.apache.james.util.streams.Iterators;
import org.slf4j.Logger;
Expand All @@ -20,6 +19,8 @@
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;


public class JCardObjectDeserializer extends StdDeserializer<JCardObject> {
Expand All @@ -46,7 +47,7 @@ public JCardObject deserialize(JsonParser p, DeserializationContext ctxt)
JsonNode node = p.getCodec().readTree(p);

JsonNode jCardPropertiesArray = node.get(PROPERTIES_ARRAY_INDEX);
Map<String, String> jCardProperties =
Multimap<String, String> jCardProperties =
collectJCardProperties(jCardPropertiesArray.iterator());

if (!jCardProperties.containsKey(FN)) {
Expand All @@ -57,24 +58,28 @@ public JCardObject deserialize(JsonParser p, DeserializationContext ctxt)
Ensure the 'fn' property is present and correctly formatted.""", json);
}

Optional<MailAddress> maybeMailAddress = getOptionalFromMap(jCardProperties, EMAIL)
List<MailAddress> mailAddresses = jCardProperties.get(EMAIL)
.stream()
.flatMap(email -> {
try {
return Optional.of(new MailAddress(email));
return Stream.of(new MailAddress(email));
} catch (AddressException e) {
LOGGER.info("Invalid contact mail address '{}' found in JCard Object", email);
return Optional.empty();
return Stream.empty();
}
});
})
.toList();

return new JCardObject(getOptionalFromMap(jCardProperties, FN), maybeMailAddress);
return new JCardObject(getFormattedName(jCardProperties), mailAddresses);
}

private static Map<String, String> collectJCardProperties(Iterator<JsonNode> propertiesIterator) {
return Iterators.toStream(propertiesIterator)
private static Multimap<String, String> collectJCardProperties(Iterator<JsonNode> propertiesIterator) {
Multimap<String, String> multimap = ArrayListMultimap.create();
Iterators.toStream(propertiesIterator)
.map(JCardObjectDeserializer::getPropertyKeyValuePair)
.flatMap(Optional::stream)
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
.forEach(pair -> multimap.put(pair.getKey(), pair.getValue()));
return multimap;
}

private static Optional<ImmutablePair<String, String>> getPropertyKeyValuePair(JsonNode propertyNode) {
Expand All @@ -87,7 +92,9 @@ private static Optional<ImmutablePair<String, String>> getPropertyKeyValuePair(J
}
}

private Optional<String> getOptionalFromMap(Map<String, String> map, String key) {
return Optional.ofNullable(map.getOrDefault(key, null));
private Optional<String> getFormattedName(Multimap<String, String> multimap) {
return multimap.get(FN)
.stream()
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,20 @@ private Mono<?> messageConsume(AcknowledgableDelivery ackDelivery, byte[] messag
});
}

private Mono<?> handleMessage(ContactRabbitMqMessage contactMessage, ContactHandler contactHandler) {
private Mono<Void> handleMessage(ContactRabbitMqMessage contactMessage, ContactHandler contactHandler) {
LOGGER.trace("Consumed jCard object message: {}", contactMessage);
return Mono.justOrEmpty(contactMessage.vcard().asContactFields())
.flatMap(openPaasContact -> openPaasRestClient.retrieveMailAddress(contactMessage.openPaasUserId())
return Mono.fromCallable(() -> contactMessage.vcard().asContactFields())
.flatMap(openPaasContacts -> openPaasRestClient.retrieveMailAddress(contactMessage.openPaasUserId())
.map(this::getAccountIdFromMailAddress)
.flatMap(ownerAccountId -> contactHandler.handleContact(ownerAccountId, openPaasContact)));
.flatMap(ownerAccountId -> Flux.fromIterable(openPaasContacts)
.flatMap(contact -> contactHandler.handleContact(ownerAccountId, contact))
.then()));
}

private Mono<EmailAddressContact> indexContactIfNeeded(AccountId ownerAccountId, ContactFields openPaasContact) {
return Mono.from(contactSearchEngine.get(ownerAccountId, openPaasContact.address()))
.onErrorResume(ContactNotFoundException.class, e -> Mono.empty())
.switchIfEmpty(
Mono.from(contactSearchEngine.index(ownerAccountId, openPaasContact)))
.switchIfEmpty(Mono.from(contactSearchEngine.index(ownerAccountId, openPaasContact)))
.flatMap(existingContact -> {
if (!openPaasContact.firstname().isBlank()) {
return Mono.from(contactSearchEngine.index(ownerAccountId, openPaasContact));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.linagora.tmail.contact;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.Optional;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import jakarta.mail.internet.AddressException;

import org.apache.james.core.MailAddress;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JCardObjectDeserializerTest {
class JCardObjectDeserializerTest {
static ObjectMapper objectMapper;

@BeforeAll
Expand All @@ -30,12 +34,12 @@ void shouldNotThrowWhenMailAddressIsInvalid() {
]
""");

assertThat(cardObject.emailOpt()).isEmpty();
assertThat(cardObject.mailAddresses()).isEmpty();
assertThat(cardObject.fnOpt()).isEqualTo(Optional.of("Jhon Doe"));
}

@Test
void shouldNotThrowWhenJCardContainsUnknownProperties() {
void shouldNotThrowWhenJCardContainsUnknownProperties() throws AddressException {
JCardObject cardObject = deserialize("""
[
"vcard",
Expand All @@ -48,7 +52,86 @@ void shouldNotThrowWhenJCardContainsUnknownProperties() {
]
]""");

assertThat(cardObject.emailOpt()).isEqualTo(Optional.of("jhon@doe.com"));
assertThat(cardObject.mailAddresses().getFirst()).isEqualTo(new MailAddress("jhon@doe.com"));
assertThat(cardObject.fnOpt()).isEqualTo(Optional.of("Jhon Doe"));
}

@Test
void shouldSupportMultipleEmailAddresses() throws AddressException {
JCardObject cardObject = deserialize("""
[
"vcard",
[
[
"version",
{},
"text",
"4.0"
],
[
"uid",
{},
"text",
"9e342040-440f-4f7f-a603-3d4f8f7c3952"
],
[
"fn",
{},
"text",
"Jhon Doe"
],
[
"n",
{},
"text",
[
"",
"Jhon Doe",
"",
"",
""
]
],
[
"email",
{
"type": "Work"
},
"text",
"jhondoe@yahoo.co.in"
],
[
"email",
{
"type": "Home"
},
"text",
"jhondoe@gmail.com"
],
[
"tel",
{
"type": "Work"
},
"uri",
"57847703"
],
[
"tel",
{
"type": "Mobile"
},
"uri",
"2839192"
]
],
[]
]""");

assertThat(cardObject.mailAddresses())
.containsExactlyInAnyOrder(
new MailAddress("jhondoe@yahoo.co.in"),
new MailAddress("jhondoe@gmail.com"));
assertThat(cardObject.fnOpt()).isEqualTo(Optional.of("Jhon Doe"));
}

Expand All @@ -63,12 +146,12 @@ void shouldNotThrowWhenEmailPropertyNotPresent() {
]
""");

assertThat(cardObject.emailOpt()).isEmpty();
assertThat(cardObject.mailAddresses()).isEmpty();
assertThat(cardObject.fnOpt()).isEqualTo(Optional.of("Jhon Doe"));
}

@Test
void shouldNotThrowWhenFnPropertyNotPresent() {
void shouldNotThrowWhenFnPropertyNotPresent() throws AddressException {
JCardObject cardObject = deserialize("""
[
"vcard",
Expand All @@ -78,7 +161,7 @@ void shouldNotThrowWhenFnPropertyNotPresent() {
]
""");

assertThat(cardObject.emailOpt()).isEqualTo(Optional.of("jhon@doe.com"));
assertThat(cardObject.mailAddresses().getFirst()).isEqualTo(new MailAddress("jhon@doe.com"));
assertThat(cardObject.fnOpt()).isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,32 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.apache.james.core.MailAddress;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import jakarta.mail.internet.AddressException;

public class JCardObjectTest {
import org.apache.james.core.MailAddress;
import org.junit.jupiter.api.Test;

import com.linagora.tmail.james.jmap.contact.ContactFields;

class JCardObjectTest {

@Test
void asContactFieldsShouldNotThrowIfFullnameNotProvided() {
JCardObject cardObject = new JCardObject(Optional.empty(), maybeMailAddress("Jhon@doe.com"));
assertThat(cardObject.asContactFields()).isPresent();
assertThat(cardObject.asContactFields()).isNotEmpty();
}

@Test
void asContactFieldsShouldConvertFieldsCorrectly() {
JCardObject cardObject = new JCardObject(Optional.of("Jhon Doe"), maybeMailAddress("Jhon@doe.com"));
assertThat(cardObject.asContactFields()).isPresent();
assertThat(cardObject.asContactFields().get().firstname()).isEqualTo("Jhon Doe");
assertThat(cardObject.asContactFields().get().surname()).isEmpty();
assertThat(cardObject.asContactFields().get().address()).isEqualTo("Jhon@doe.com");
void asContactFieldsShouldConvertFieldsCorrectly() throws AddressException {
JCardObject cardObject = new JCardObject(Optional.of("Jhon Doe"),
List.of(new MailAddress("jhon@doe.com"), new MailAddress("jhon2@doe.com")));

assertThat(cardObject.asContactFields())
.containsExactlyInAnyOrder(new ContactFields(new MailAddress("jhon@doe.com"), "Jhon Doe", ""),
new ContactFields(new MailAddress("jhon2@doe.com"), "Jhon Doe", ""));
}

@Test
Expand All @@ -36,9 +40,9 @@ void shouldThrowIfFnOrEmailOptionalIsNull() {
.isInstanceOf(NullPointerException.class);
}

private Optional<MailAddress> maybeMailAddress(String email) {
private List<MailAddress> maybeMailAddress(String email) {
try {
return Optional.of(new MailAddress(email));
return List.of(new MailAddress(email));
} catch (AddressException e) {
throw new RuntimeException(e);
}
Expand Down
Loading

0 comments on commit 409de91

Please sign in to comment.