From a4b0aee7f96ffebe26a63306329fec3aaa3a7670 Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Thu, 3 Oct 2024 13:21:39 +0100 Subject: [PATCH 01/11] [NO_REVIEW] Do work in jmap-extensions --- tmail-backend/jmap/extensions/pom.xml | 15 ++ .../contact/ContactAddedRabbitMqMessage.java | 5 + .../tmail/james/jmap/contact/JCardObject.java | 37 ++++ .../jmap/contact/JCardObjectDeserializer.java | 71 ++++++++ .../jmap/contact/OpenPaasConfiguration.java | 28 +++ .../contact/OpenPaasContactsConsumer.java | 164 ++++++++++++++++++ .../jmap/contact/OpenPaasRestClient.java | 50 ++++++ .../jmap/contact/OpenPaasUserResponse.java | 16 ++ .../contact/OpenPaasContactsConsumerTest.java | 130 ++++++++++++++ .../contact/OpenPaasServerExtension.scala | 86 +++++++++ 10 files changed, 602 insertions(+) create mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/ContactAddedRabbitMqMessage.java create mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObject.java create mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObjectDeserializer.java create mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java create mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java create mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java create mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java create mode 100644 tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java create mode 100644 tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala diff --git a/tmail-backend/jmap/extensions/pom.xml b/tmail-backend/jmap/extensions/pom.xml index 630dab2f57..7883e726e5 100644 --- a/tmail-backend/jmap/extensions/pom.xml +++ b/tmail-backend/jmap/extensions/pom.xml @@ -24,6 +24,16 @@ ${project.groupId} team-mailboxes + + ${james.groupId} + apache-james-backends-rabbitmq + + + ${james.groupId} + apache-james-backends-rabbitmq + test + test-jar + ${james.groupId} apache-james-mailbox-deleted-messages-vault @@ -48,6 +58,11 @@ test-jar test + + org.mock-server + mockserver-netty + test + ${james.groupId} james-server-data-memory diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/ContactAddedRabbitMqMessage.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/ContactAddedRabbitMqMessage.java new file mode 100644 index 0000000000..7ba49b0907 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/ContactAddedRabbitMqMessage.java @@ -0,0 +1,5 @@ +package com.linagora.tmail.james.jmap.contact; + +public record ContactAddedRabbitMqMessage(String bookId, String bookName, String contactId, + String userId, JCardObject vcard) { +} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObject.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObject.java new file mode 100644 index 0000000000..ac5650c823 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObject.java @@ -0,0 +1,37 @@ +package com.linagora.tmail.james.jmap.contact; + +import java.util.Optional; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.Preconditions; + +@JsonDeserialize(using = JCardObjectDeserializer.class) +public record JCardObject(String fn, Optional emailOpt) { + + public JCardObject { + Preconditions.checkNotNull(fn); + Preconditions.checkNotNull(emailOpt); + } + + /** + * Purpose: To specify the formatted text corresponding to the name of + * the object the vCard represents. + *

+ * Example: Mr. John Q. Public\, Esq. + */ + @Override + public String fn() { + return fn; + } + + /** + * Purpose: To specify the electronic mail address for communication + * with the object the vCard represents. + *

+ * Example: jane_doe@example.com + */ + @Override + public Optional emailOpt() { + return emailOpt; + } +} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObjectDeserializer.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObjectDeserializer.java new file mode 100644 index 0000000000..6bac3e1eb8 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObjectDeserializer.java @@ -0,0 +1,71 @@ +package com.linagora.tmail.james.jmap.contact; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.james.util.streams.Iterators; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +public class JCardObjectDeserializer extends StdDeserializer { + private static final String FN = "fn"; + private static final String EMAIL = "email"; + private static final Set SUPPORTED_PROPERTY_NAMES = Set.of(FN, EMAIL); + private static final int PROPERTY_NAME_INDEX = 0; + private static final int PROPERTIES_ARRAY_INDEX = 1; + private static final int TEXT_PROPERTY_VALUE_INDEX = 3; + + public JCardObjectDeserializer() { + this(null); + } + + protected JCardObjectDeserializer(Class vc) { + super(vc); + } + + @Override + public JCardObject deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException { + JsonNode node = p.getCodec().readTree(p); + + JsonNode jCardPropertiesArray = node.get(PROPERTIES_ARRAY_INDEX); + Map jCardProperties = + collectJCardProperties(jCardPropertiesArray.iterator()); + + if (!jCardProperties.containsKey(FN)) { + throw new RuntimeException("The FN field is required according to specification."); + } + + return new JCardObject(jCardProperties.get(FN), getOptionalFromMap(jCardProperties, EMAIL)); + } + + private static Map collectJCardProperties(Iterator propertiesIterator) { + return Iterators.toStream(propertiesIterator) + .map(JCardObjectDeserializer::getPropertyKeyValuePair) + .filter(pair -> pair != ImmutablePair.nullPair()) + .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); + } + + private static ImmutablePair getPropertyKeyValuePair(JsonNode propertyNode) { + String propertyName = propertyNode.get(PROPERTY_NAME_INDEX).asText(); + if (SUPPORTED_PROPERTY_NAMES.contains(propertyName)) { + String propertyValue = propertyNode.get(TEXT_PROPERTY_VALUE_INDEX).asText(); + return ImmutablePair.of(propertyName, propertyValue); + } else { + return ImmutablePair.nullPair(); + } + } + + private Optional getOptionalFromMap(Map map, String key) { + return Optional.ofNullable(map.getOrDefault(key, null)); + } +} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java new file mode 100644 index 0000000000..5f54d163a3 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java @@ -0,0 +1,28 @@ +package com.linagora.tmail.james.jmap.contact; + +import java.net.URL; + +public class OpenPaasConfiguration { + private final URL webClientbaseUrl; + private final String webClientUser; + private final String webClientPassword; + + public OpenPaasConfiguration(URL webClientbaseUrl, String webClientUser, + String webClientPassword) { + this.webClientbaseUrl = webClientbaseUrl; + this.webClientUser = webClientUser; + this.webClientPassword = webClientPassword; + } + + public URL getWebClientBaseUrl() { + return webClientbaseUrl; + } + + public String getWebClientUser() { + return webClientUser; + } + + public String getWebClientPassword() { + return webClientPassword; + } +} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java new file mode 100644 index 0000000000..6660f08a6b --- /dev/null +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java @@ -0,0 +1,164 @@ +package com.linagora.tmail.james.jmap.contact; + +import static org.apache.james.backends.rabbitmq.Constants.DURABLE; +import static org.apache.james.backends.rabbitmq.Constants.EMPTY_ROUTING_KEY; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.mail.internet.AddressException; + +import org.apache.james.backends.rabbitmq.RabbitMQConfiguration; +import org.apache.james.backends.rabbitmq.ReceiverProvider; +import org.apache.james.core.MailAddress; +import org.apache.james.core.Username; +import org.apache.james.jmap.api.model.AccountId; +import org.apache.james.lifecycle.api.Startable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linagora.tmail.james.jmap.EmailAddressContactInjectKeys; +import com.rabbitmq.client.BuiltinExchangeType; + +import reactor.core.Disposable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.rabbitmq.AcknowledgableDelivery; +import reactor.rabbitmq.BindingSpecification; +import reactor.rabbitmq.ExchangeSpecification; +import reactor.rabbitmq.QueueSpecification; +import reactor.rabbitmq.Receiver; +import reactor.rabbitmq.Sender; + + +public class OpenPaasContactsConsumer implements Startable, Closeable { + private static final Logger LOGGER = LoggerFactory.getLogger(OpenPaasContactsConsumer.class); + + public static final String EXCHANGE_NAME = "contacts:contact:add"; + public static final String QUEUE_NAME = "ConsumeOpenPaasContactsQueue"; + private Disposable consumeContactsDisposable; + + private final ReceiverProvider receiverProvider; + private final Sender sender; + private final RabbitMQConfiguration commonRabbitMQConfiguration; + private final EmailAddressContactSearchEngine contactSearchEngine; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final OpenPaasRestClient openPaasRestClient; + + // TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool + // would be custom configured + @Inject + public OpenPaasContactsConsumer(@Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) ReceiverProvider receiverProvider, + @Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) Sender sender, + RabbitMQConfiguration commonRabbitMQConfiguration, + EmailAddressContactSearchEngine contactSearchEngine, + OpenPaasRestClient openPaasRestClient) { + this.receiverProvider = receiverProvider; + this.sender = sender; + this.commonRabbitMQConfiguration = commonRabbitMQConfiguration; + this.contactSearchEngine = contactSearchEngine; + this.openPaasRestClient = openPaasRestClient; + } + + public void start() { + Flux.concat( + sender.declareExchange(ExchangeSpecification.exchange(EXCHANGE_NAME) + .durable(DURABLE).type(BuiltinExchangeType.FANOUT.getType())), + sender.declareQueue(QueueSpecification + .queue(QUEUE_NAME) + .durable(DURABLE)), + sender.bind(BindingSpecification.binding() + .exchange(EXCHANGE_NAME) + .queue(QUEUE_NAME) + .routingKey(EMPTY_ROUTING_KEY))) + .then() + .block(); + + consumeContactsDisposable = doConsumeContactMessages(); + } + + private Disposable doConsumeContactMessages() { + return delivery() + .subscribeOn(Schedulers.boundedElastic()) + .subscribe(delivery -> + messageConsume(delivery, new String(delivery.getBody(), StandardCharsets.UTF_8))); + } + + public Flux delivery() { + return Flux.using(receiverProvider::createReceiver, + receiver -> receiver.consumeManualAck(QUEUE_NAME), + Receiver::close); + } + + private void messageConsume(AcknowledgableDelivery ackDelivery, String messagePayload) { + Mono.just(messagePayload) + .handle((message, sink) -> { + try { + sink.next(objectMapper.readValue(message, ContactAddedRabbitMqMessage.class)); + } catch (JsonProcessingException e) { + sink.error(new RuntimeException(e)); + } + }) + .handle((msg, sink) -> { + handleMessage(msg).block(); + ackDelivery.ack(); + }) + .onErrorResume(e -> { + LOGGER.error("Failed to consume OpenPaaS added contact message", e); + return Mono.empty(); + }).subscribe(); + } + + private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage) { + LOGGER.info("Consumed jCard object message: {}", contactAddedMessage); + + String openPaasOwnerId = contactAddedMessage.userId(); + return openPaasRestClient.getUserById(openPaasOwnerId) + .map(OpenPaasUserResponse::preferredEmail) + .mapNotNull(ownerEmail -> { + JCardObject jCardObject = contactAddedMessage.vcard(); + + String contactFullname = jCardObject.fn(); + Optional contactMailAddressOpt = jCardObject.emailOpt() + .flatMap(contactEmail -> { + try { + return Optional.of(new MailAddress(contactEmail)); + } catch (AddressException e) { + return Optional.empty(); + } + }); + + if (contactMailAddressOpt.isEmpty()) { + return Mono.empty(); + } + + try { + MailAddress ownerMailAddress = new MailAddress(ownerEmail); + AccountId ownerAccountId = + AccountId.fromUsername(Username.fromMailAddress(ownerMailAddress)); + + return Mono.from(contactSearchEngine.index(ownerAccountId, + new ContactFields(contactMailAddressOpt.get(), contactFullname, "") + )).block(); + + } catch (AddressException e) { + return Mono.error(new RuntimeException( + "The user mail address fetched from OpenPaas is invalid", e)); + } + }).then(); + } + + @Override + public void close() throws IOException { + if (consumeContactsDisposable != null) { + consumeContactsDisposable.dispose(); + } + } +} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java new file mode 100644 index 0000000000..c0fcc08a0c --- /dev/null +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java @@ -0,0 +1,50 @@ +package com.linagora.tmail.james.jmap.contact; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Base64; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; + +public class OpenPaasRestClient { + private static final Duration RESPONSE_TIMEOUT = Duration.ofSeconds(10); + private static final String AUTHORIZATION_HEADER = "Authorization"; + private final OpenPaasConfiguration openPaasConfiguration; + private final HttpClient client; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public OpenPaasRestClient(OpenPaasConfiguration openPaasConfiguration) { + this.openPaasConfiguration = openPaasConfiguration; + this.client = HttpClient.create() + .baseUrl(openPaasConfiguration.getWebClientBaseUrl().toString()) + .headers(headers -> headers.add(AUTHORIZATION_HEADER, basicAuthenticationHeaderValue())) + .responseTimeout(RESPONSE_TIMEOUT); + } + + public Mono getUserById(String openPaasUserId) { + return client.get() + .uri(String.format("/users/%s", openPaasUserId)) + .responseContent() + .aggregate() + .asString(StandardCharsets.UTF_8) + .handle((content, sink) -> { + try { + sink.next(objectMapper.readValue(content, OpenPaasUserResponse.class)); + } catch (JsonProcessingException e) { + sink.error(new RuntimeException(e)); + } + }); + } + + private String basicAuthenticationHeaderValue() { + String userPassword = openPaasConfiguration.getWebClientUser() + ":" + openPaasConfiguration.getWebClientPassword(); + byte[] base64UserPassword = Base64.getEncoder().encode(userPassword.getBytes(StandardCharsets.UTF_8)); + + return "Basic " + new String(base64UserPassword, StandardCharsets.UTF_8); + } + +} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java new file mode 100644 index 0000000000..a379802b56 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java @@ -0,0 +1,16 @@ +package com.linagora.tmail.james.jmap.contact; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record OpenPaasUserResponse(@JsonProperty("id") String id, + @JsonProperty("firstname") String firstname, + @JsonProperty("lastname") String lastname, + @JsonProperty("preferredEmail") String preferredEmail, + @JsonProperty("emails") List emails, + @JsonProperty("main_phone") String mainPhone, + @JsonProperty("displayName") String displayName) { +} diff --git a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java b/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java new file mode 100644 index 0000000000..acbb21a23f --- /dev/null +++ b/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java @@ -0,0 +1,130 @@ +package com.linagora.tmail.james.jmap.contact; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.james.backends.rabbitmq.Constants.EMPTY_ROUTING_KEY; +import static org.apache.james.backends.rabbitmq.RabbitMQExtension.IsolationPolicy.WEAK; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.awaitility.Durations.TEN_SECONDS; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import org.apache.james.backends.rabbitmq.RabbitMQExtension; +import org.apache.james.jmap.api.model.AccountId; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.rabbitmq.OutboundMessage; + +class OpenPaasContactsConsumerTest { + + @RegisterExtension + static OpenPaasServerExtension openPaasServerExtension = new OpenPaasServerExtension(); + + @RegisterExtension + static RabbitMQExtension rabbitMQExtension = RabbitMQExtension.singletonRabbitMQ() + .isolationPolicy(WEAK); + + private EmailAddressContactSearchEngine searchEngine; + private OpenPaasRestClient restClient; + private OpenPaasContactsConsumer consumer; + + @BeforeEach + void setup() throws URISyntaxException { + searchEngine = new InMemoryEmailAddressContactSearchEngine(); + restClient = new OpenPaasRestClient( + new OpenPaasConfiguration( + openPaasServerExtension.getBaseUrl(), + "admin", + "admin") + ); + consumer = new OpenPaasContactsConsumer(rabbitMQExtension.getReceiverProvider(), + rabbitMQExtension.getSender(), + rabbitMQExtension.getRabbitMQ().withQuorumQueueConfiguration(), + searchEngine, restClient); + + consumer.start(); + } + + @AfterEach + void afterEach() throws IOException { + consumer.close(); + } + + @Test + void consumeMessageShouldNotCrashOnInvalidMessages() throws InterruptedException { + IntStream.range(0, 10).forEach(i -> sendMessage("BAD_PAYLOAD" + i)); + + TimeUnit.MILLISECONDS.sleep(100); + + sendMessage(""" + { + "bookId": "abc0a663bdaffe0026290xyz", + "bookName": "contacts", + "contactId": "fd9b3c98-fc77-4187-92ac-d9f58d400968", + "userId": "abc0a663bdaffe0026290xyz", + "vcard": [ + "vcard", + [ + [ "version", {}, "text", "4.0" ], + [ "kind", {}, "text", "individual" ], + [ "fn", {}, "text", "Jane Doe" ], + [ "email", {}, "text", "jhon@doe.com" ], + [ "org", {}, "text", [ "ABC, Inc.", "North American Division", "Marketing" ] ] + ] + ] + } + """); + + await().timeout(TEN_SECONDS).untilAsserted(() -> + assertThat( + Flux.from(searchEngine.autoComplete(AccountId.fromString(OpenPaasServerExtension.ALICE_EMAIL()), "jhon", 10)) + .collectList().block()) + .hasSize(1)); + } + + @Test + void contactShouldBeIndexedWhenContactUserAddedMessage() { + sendMessage(""" + { + "bookId": "abc0a663bdaffe0026290xyz", + "bookName": "contacts", + "contactId": "fd9b3c98-fc77-4187-92ac-d9f58d400968", + "userId": "abc0a663bdaffe0026290xyz", + "vcard": [ + "vcard", + [ + [ "version", {}, "text", "4.0" ], + [ "kind", {}, "text", "individual" ], + [ "fn", {}, "text", "Jane Doe" ], + [ "email", {}, "text", "jhon@doe.com" ], + [ "org", {}, "text", [ "ABC, Inc.", "North American Division", "Marketing" ] ] + ] + ] + } + """); + + await().timeout(TEN_SECONDS).untilAsserted(() -> + assertThat( + Flux.from(searchEngine.autoComplete(AccountId.fromString(OpenPaasServerExtension.ALICE_EMAIL()), "jhon", 10)) + .collectList().block()) + .hasSize(1)); + } + + private void sendMessage(String message) { + rabbitMQExtension.getSender() + .send(Mono.just(new OutboundMessage( + OpenPaasContactsConsumer.EXCHANGE_NAME, + EMPTY_ROUTING_KEY, + message.getBytes(UTF_8)))) + .block(); + } +} \ No newline at end of file diff --git a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala b/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala new file mode 100644 index 0000000000..b91e35f3e3 --- /dev/null +++ b/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala @@ -0,0 +1,86 @@ +package com.linagora.tmail.james.jmap.contact + +import com.linagora.tmail.james.jmap.contact.OpenPaasServerExtension.{ALICE_EMAIL, ALICE_USER_ID} + +import java.net.{URI, URL} +import org.junit.jupiter.api.extension.{AfterEachCallback, BeforeEachCallback, ExtensionContext, ParameterContext, ParameterResolver} +import org.mockserver.configuration.ConfigurationProperties +import org.mockserver.integration.ClientAndServer +import org.mockserver.integration.ClientAndServer.startClientAndServer +import org.mockserver.model.HttpRequest.request +import org.mockserver.model.HttpResponse.response +import org.mockserver.model.NottableString.string + +object OpenPaasServerExtension { + val ALICE_USER_ID: String = "abc0a663bdaffe0026290xyz" + val ALICE_EMAIL: String = "adoe@linagora.com" +} + +class OpenPaasServerExtension extends BeforeEachCallback with AfterEachCallback with ParameterResolver{ + + var mockServer: ClientAndServer = _ + + override def beforeEach(context: ExtensionContext): Unit = { + mockServer = startClientAndServer(0) + ConfigurationProperties.logLevel("INFO") + + mockServer.when( + request.withPath(s"/users/$ALICE_USER_ID") + .withMethod("GET") + .withHeader(string("Authorization"), string("Basic YWRtaW46YWRtaW4=")) + ).respond(response.withStatusCode(200) + .withBody(s"""{ + | "_id": "$ALICE_USER_ID", + | "firstname": "Alice", + | "lastname": "DOE", + | "preferredEmail": "$ALICE_EMAIL", + | "emails": [ + | "$ALICE_EMAIL" + | ], + | "domains": [ + | { + | "joined_at": "2020-09-03T08:16:35.682Z", + | "domain_id": "$ALICE_USER_ID" + | } + | ], + | "states": [], + | "avatars": [ + | "$ALICE_USER_ID" + | ], + | "main_phone": "01111111111", + | "accounts": [ + | { + | "timestamps": { + | "creation": "2020-09-03T08:16:35.682Z" + | }, + | "hosted": true, + | "emails": [ + | "adoe@linagora.com" + | ], + | "preferredEmailIndex": 0, + | "type": "email" + | } + | ], + | "login": { + | "failures": [], + | "success": "2024-10-04T12:59:44.469Z" + | }, + | "id": "$ALICE_USER_ID", + | "displayName": "Alice DOE", + | "objectType": "user", + | "followers": 0, + | "followings": 0 + |}""".stripMargin)) + } + + override def afterEach(context: ExtensionContext): Unit = mockServer.close() + + override def supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean = + parameterContext.getParameter.getType eq classOf[ClientAndServer] + + override def resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): AnyRef = + mockServer + + def getBaseUrl: URL = new URI(s"http://localhost:${mockServer.getLocalPort}").toURL + +} From ac4483e78005bb1848390a225fe8d698d9e81ede Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Thu, 17 Oct 2024 12:58:04 +0100 Subject: [PATCH 02/11] Rebase --- .../jmap/contact/OpenPaasConfiguration.java | 28 ------ .../jmap/contact/OpenPaasRestClient.java | 50 ----------- .../jmap/contact/OpenPaasUserResponse.java | 16 ---- .../contact/OpenPaasServerExtension.scala | 86 ------------------- .../tmail-third-party/openpaas/pom.xml | 14 +++ .../contact/ContactAddedRabbitMqMessage.java | 2 +- .../linagora/tmail}/contact/JCardObject.java | 2 +- .../contact/JCardObjectDeserializer.java | 3 +- .../contact/OpenPaasContactsConsumer.java | 27 +++--- .../contact/OpenPaasContactsConsumerTest.java | 18 ++-- 10 files changed, 40 insertions(+), 206 deletions(-) delete mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java delete mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java delete mode 100644 tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java delete mode 100644 tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala rename tmail-backend/{jmap/extensions/src/main/java/com/linagora/tmail/james/jmap => tmail-third-party/openpaas/src/main/java/com/linagora/tmail}/contact/ContactAddedRabbitMqMessage.java (78%) rename tmail-backend/{jmap/extensions/src/main/java/com/linagora/tmail/james/jmap => tmail-third-party/openpaas/src/main/java/com/linagora/tmail}/contact/JCardObject.java (95%) rename tmail-backend/{jmap/extensions/src/main/java/com/linagora/tmail/james/jmap => tmail-third-party/openpaas/src/main/java/com/linagora/tmail}/contact/JCardObjectDeserializer.java (98%) rename tmail-backend/{jmap/extensions/src/main/java/com/linagora/tmail/james/jmap => tmail-third-party/openpaas/src/main/java/com/linagora/tmail}/contact/OpenPaasContactsConsumer.java (87%) rename tmail-backend/{jmap/extensions/src/test/java/com/linagora/tmail/james/jmap => tmail-third-party/openpaas/src/test/java/com/linagora/tmail}/contact/OpenPaasContactsConsumerTest.java (93%) diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java deleted file mode 100644 index 5f54d163a3..0000000000 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasConfiguration.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.linagora.tmail.james.jmap.contact; - -import java.net.URL; - -public class OpenPaasConfiguration { - private final URL webClientbaseUrl; - private final String webClientUser; - private final String webClientPassword; - - public OpenPaasConfiguration(URL webClientbaseUrl, String webClientUser, - String webClientPassword) { - this.webClientbaseUrl = webClientbaseUrl; - this.webClientUser = webClientUser; - this.webClientPassword = webClientPassword; - } - - public URL getWebClientBaseUrl() { - return webClientbaseUrl; - } - - public String getWebClientUser() { - return webClientUser; - } - - public String getWebClientPassword() { - return webClientPassword; - } -} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java deleted file mode 100644 index c0fcc08a0c..0000000000 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasRestClient.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.linagora.tmail.james.jmap.contact; - -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.Base64; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; - -public class OpenPaasRestClient { - private static final Duration RESPONSE_TIMEOUT = Duration.ofSeconds(10); - private static final String AUTHORIZATION_HEADER = "Authorization"; - private final OpenPaasConfiguration openPaasConfiguration; - private final HttpClient client; - private final ObjectMapper objectMapper = new ObjectMapper(); - - public OpenPaasRestClient(OpenPaasConfiguration openPaasConfiguration) { - this.openPaasConfiguration = openPaasConfiguration; - this.client = HttpClient.create() - .baseUrl(openPaasConfiguration.getWebClientBaseUrl().toString()) - .headers(headers -> headers.add(AUTHORIZATION_HEADER, basicAuthenticationHeaderValue())) - .responseTimeout(RESPONSE_TIMEOUT); - } - - public Mono getUserById(String openPaasUserId) { - return client.get() - .uri(String.format("/users/%s", openPaasUserId)) - .responseContent() - .aggregate() - .asString(StandardCharsets.UTF_8) - .handle((content, sink) -> { - try { - sink.next(objectMapper.readValue(content, OpenPaasUserResponse.class)); - } catch (JsonProcessingException e) { - sink.error(new RuntimeException(e)); - } - }); - } - - private String basicAuthenticationHeaderValue() { - String userPassword = openPaasConfiguration.getWebClientUser() + ":" + openPaasConfiguration.getWebClientPassword(); - byte[] base64UserPassword = Base64.getEncoder().encode(userPassword.getBytes(StandardCharsets.UTF_8)); - - return "Basic " + new String(base64UserPassword, StandardCharsets.UTF_8); - } - -} diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java b/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java deleted file mode 100644 index a379802b56..0000000000 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasUserResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.linagora.tmail.james.jmap.contact; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public record OpenPaasUserResponse(@JsonProperty("id") String id, - @JsonProperty("firstname") String firstname, - @JsonProperty("lastname") String lastname, - @JsonProperty("preferredEmail") String preferredEmail, - @JsonProperty("emails") List emails, - @JsonProperty("main_phone") String mainPhone, - @JsonProperty("displayName") String displayName) { -} diff --git a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala b/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala deleted file mode 100644 index b91e35f3e3..0000000000 --- a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasServerExtension.scala +++ /dev/null @@ -1,86 +0,0 @@ -package com.linagora.tmail.james.jmap.contact - -import com.linagora.tmail.james.jmap.contact.OpenPaasServerExtension.{ALICE_EMAIL, ALICE_USER_ID} - -import java.net.{URI, URL} -import org.junit.jupiter.api.extension.{AfterEachCallback, BeforeEachCallback, ExtensionContext, ParameterContext, ParameterResolver} -import org.mockserver.configuration.ConfigurationProperties -import org.mockserver.integration.ClientAndServer -import org.mockserver.integration.ClientAndServer.startClientAndServer -import org.mockserver.model.HttpRequest.request -import org.mockserver.model.HttpResponse.response -import org.mockserver.model.NottableString.string - -object OpenPaasServerExtension { - val ALICE_USER_ID: String = "abc0a663bdaffe0026290xyz" - val ALICE_EMAIL: String = "adoe@linagora.com" -} - -class OpenPaasServerExtension extends BeforeEachCallback with AfterEachCallback with ParameterResolver{ - - var mockServer: ClientAndServer = _ - - override def beforeEach(context: ExtensionContext): Unit = { - mockServer = startClientAndServer(0) - ConfigurationProperties.logLevel("INFO") - - mockServer.when( - request.withPath(s"/users/$ALICE_USER_ID") - .withMethod("GET") - .withHeader(string("Authorization"), string("Basic YWRtaW46YWRtaW4=")) - ).respond(response.withStatusCode(200) - .withBody(s"""{ - | "_id": "$ALICE_USER_ID", - | "firstname": "Alice", - | "lastname": "DOE", - | "preferredEmail": "$ALICE_EMAIL", - | "emails": [ - | "$ALICE_EMAIL" - | ], - | "domains": [ - | { - | "joined_at": "2020-09-03T08:16:35.682Z", - | "domain_id": "$ALICE_USER_ID" - | } - | ], - | "states": [], - | "avatars": [ - | "$ALICE_USER_ID" - | ], - | "main_phone": "01111111111", - | "accounts": [ - | { - | "timestamps": { - | "creation": "2020-09-03T08:16:35.682Z" - | }, - | "hosted": true, - | "emails": [ - | "adoe@linagora.com" - | ], - | "preferredEmailIndex": 0, - | "type": "email" - | } - | ], - | "login": { - | "failures": [], - | "success": "2024-10-04T12:59:44.469Z" - | }, - | "id": "$ALICE_USER_ID", - | "displayName": "Alice DOE", - | "objectType": "user", - | "followers": 0, - | "followings": 0 - |}""".stripMargin)) - } - - override def afterEach(context: ExtensionContext): Unit = mockServer.close() - - override def supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean = - parameterContext.getParameter.getType eq classOf[ClientAndServer] - - override def resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): AnyRef = - mockServer - - def getBaseUrl: URL = new URI(s"http://localhost:${mockServer.getLocalPort}").toURL - -} diff --git a/tmail-backend/tmail-third-party/openpaas/pom.xml b/tmail-backend/tmail-third-party/openpaas/pom.xml index effa8f2995..f1805f3017 100644 --- a/tmail-backend/tmail-third-party/openpaas/pom.xml +++ b/tmail-backend/tmail-third-party/openpaas/pom.xml @@ -14,6 +14,20 @@ OpenPaaS integration for Twake Mail + + ${project.groupId} + jmap-extensions + + + ${james.groupId} + apache-james-backends-rabbitmq + + + ${james.groupId} + apache-james-backends-rabbitmq + test + test-jar + ${james.groupId} james-core diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/ContactAddedRabbitMqMessage.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/ContactAddedRabbitMqMessage.java similarity index 78% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/ContactAddedRabbitMqMessage.java rename to tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/ContactAddedRabbitMqMessage.java index 7ba49b0907..b2337396a2 100644 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/ContactAddedRabbitMqMessage.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/ContactAddedRabbitMqMessage.java @@ -1,4 +1,4 @@ -package com.linagora.tmail.james.jmap.contact; +package com.linagora.tmail.contact; public record ContactAddedRabbitMqMessage(String bookId, String bookName, String contactId, String userId, JCardObject vcard) { diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObject.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObject.java similarity index 95% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObject.java rename to tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObject.java index ac5650c823..dd9fd9b792 100644 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObject.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObject.java @@ -1,4 +1,4 @@ -package com.linagora.tmail.james.jmap.contact; +package com.linagora.tmail.contact; import java.util.Optional; diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObjectDeserializer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObjectDeserializer.java similarity index 98% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObjectDeserializer.java rename to tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObjectDeserializer.java index 6bac3e1eb8..ae29f35596 100644 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/JCardObjectDeserializer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObjectDeserializer.java @@ -1,4 +1,4 @@ -package com.linagora.tmail.james.jmap.contact; +package com.linagora.tmail.contact; import java.io.IOException; import java.util.Iterator; @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + public class JCardObjectDeserializer extends StdDeserializer { private static final String FN = "fn"; private static final String EMAIL = "email"; diff --git a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java similarity index 87% rename from tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java rename to tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java index 6660f08a6b..524fc750a9 100644 --- a/tmail-backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java @@ -1,4 +1,4 @@ -package com.linagora.tmail.james.jmap.contact; +package com.linagora.tmail.contact; import static org.apache.james.backends.rabbitmq.Constants.DURABLE; import static org.apache.james.backends.rabbitmq.Constants.EMPTY_ROUTING_KEY; @@ -23,7 +23,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.linagora.tmail.api.OpenPaasRestClient; import com.linagora.tmail.james.jmap.EmailAddressContactInjectKeys; +import com.linagora.tmail.james.jmap.contact.ContactFields; +import com.linagora.tmail.james.jmap.contact.EmailAddressContactSearchEngine; import com.rabbitmq.client.BuiltinExchangeType; import reactor.core.Disposable; @@ -120,9 +123,8 @@ private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage LOGGER.info("Consumed jCard object message: {}", contactAddedMessage); String openPaasOwnerId = contactAddedMessage.userId(); - return openPaasRestClient.getUserById(openPaasOwnerId) - .map(OpenPaasUserResponse::preferredEmail) - .mapNotNull(ownerEmail -> { + return openPaasRestClient.retrieveMailAddress(openPaasOwnerId) + .mapNotNull(ownerMailAddress -> { JCardObject jCardObject = contactAddedMessage.vcard(); String contactFullname = jCardObject.fn(); @@ -139,19 +141,12 @@ private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage return Mono.empty(); } - try { - MailAddress ownerMailAddress = new MailAddress(ownerEmail); - AccountId ownerAccountId = - AccountId.fromUsername(Username.fromMailAddress(ownerMailAddress)); - - return Mono.from(contactSearchEngine.index(ownerAccountId, - new ContactFields(contactMailAddressOpt.get(), contactFullname, "") - )).block(); + AccountId ownerAccountId = + AccountId.fromUsername(Username.fromMailAddress(ownerMailAddress)); - } catch (AddressException e) { - return Mono.error(new RuntimeException( - "The user mail address fetched from OpenPaas is invalid", e)); - } + return Mono.from(contactSearchEngine.index(ownerAccountId, + new ContactFields(contactMailAddressOpt.get(), contactFullname, "") + )).block(); }).then(); } diff --git a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java similarity index 93% rename from tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java rename to tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java index acbb21a23f..42deae1ce9 100644 --- a/tmail-backend/jmap/extensions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java +++ b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java @@ -1,4 +1,4 @@ -package com.linagora.tmail.james.jmap.contact; +package com.linagora.tmail.contact; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.james.backends.rabbitmq.Constants.EMPTY_ROUTING_KEY; @@ -7,23 +7,27 @@ import static org.awaitility.Awaitility.await; import static org.awaitility.Durations.TEN_SECONDS; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; - import org.apache.james.backends.rabbitmq.RabbitMQExtension; import org.apache.james.jmap.api.model.AccountId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.OutboundMessage; +import com.linagora.tmail.OpenPaasConfiguration; +import com.linagora.tmail.api.OpenPaasRestClient; +import com.linagora.tmail.api.OpenPaasServerExtension; +import com.linagora.tmail.james.jmap.contact.EmailAddressContactSearchEngine; +import com.linagora.tmail.james.jmap.contact.InMemoryEmailAddressContactSearchEngine; + class OpenPaasContactsConsumerTest { @RegisterExtension From 0353855d42e04c67640a3f6942cf8e36490566da Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Thu, 17 Oct 2024 16:22:49 +0100 Subject: [PATCH 03/11] Remove unneeded change in jmap-extensions:pom.xml --- tmail-backend/jmap/extensions/pom.xml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tmail-backend/jmap/extensions/pom.xml b/tmail-backend/jmap/extensions/pom.xml index 7883e726e5..630dab2f57 100644 --- a/tmail-backend/jmap/extensions/pom.xml +++ b/tmail-backend/jmap/extensions/pom.xml @@ -24,16 +24,6 @@ ${project.groupId} team-mailboxes - - ${james.groupId} - apache-james-backends-rabbitmq - - - ${james.groupId} - apache-james-backends-rabbitmq - test - test-jar - ${james.groupId} apache-james-mailbox-deleted-messages-vault @@ -58,11 +48,6 @@ test-jar test - - org.mock-server - mockserver-netty - test - ${james.groupId} james-server-data-memory From 54e8c92e8ae0e4388ee0e1a9b7f47cbf9c6b7dee Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Thu, 17 Oct 2024 16:47:49 +0100 Subject: [PATCH 04/11] Make the fn property in jCardObject optional --- .../linagora/tmail/contact/JCardObject.java | 8 ++++---- .../contact/JCardObjectDeserializer.java | 20 +++++++++++++------ .../contact/OpenPaasContactsConsumer.java | 19 ++++++++++-------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObject.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObject.java index dd9fd9b792..c370c291a8 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObject.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObject.java @@ -6,10 +6,10 @@ import com.google.common.base.Preconditions; @JsonDeserialize(using = JCardObjectDeserializer.class) -public record JCardObject(String fn, Optional emailOpt) { +public record JCardObject(Optional fnOpt, Optional emailOpt) { public JCardObject { - Preconditions.checkNotNull(fn); + Preconditions.checkNotNull(fnOpt); Preconditions.checkNotNull(emailOpt); } @@ -20,8 +20,8 @@ public record JCardObject(String fn, Optional emailOpt) { * Example: Mr. John Q. Public\, Esq. */ @Override - public String fn() { - return fn; + public Optional fnOpt() { + return fnOpt; } /** diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObjectDeserializer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObjectDeserializer.java index ae29f35596..b4611b408e 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObjectDeserializer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/JCardObjectDeserializer.java @@ -10,6 +10,8 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.apache.james.util.streams.Iterators; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; @@ -18,6 +20,8 @@ public class JCardObjectDeserializer extends StdDeserializer { + private static final Logger LOGGER = LoggerFactory.getLogger(JCardObjectDeserializer.class); + private static final String FN = "fn"; private static final String EMAIL = "email"; private static final Set SUPPORTED_PROPERTY_NAMES = Set.of(FN, EMAIL); @@ -43,26 +47,30 @@ public JCardObject deserialize(JsonParser p, DeserializationContext ctxt) collectJCardProperties(jCardPropertiesArray.iterator()); if (!jCardProperties.containsKey(FN)) { - throw new RuntimeException("The FN field is required according to specification."); + String json = node.toString(); + LOGGER.warn(""" + Missing 'fn' property in the provided JCard object. 'fn' is required according to the specifications. + Received data: {}. + Ensure the 'fn' property is present and correctly formatted.""", json); } - return new JCardObject(jCardProperties.get(FN), getOptionalFromMap(jCardProperties, EMAIL)); + return new JCardObject(getOptionalFromMap(jCardProperties, FN), getOptionalFromMap(jCardProperties, EMAIL)); } private static Map collectJCardProperties(Iterator propertiesIterator) { return Iterators.toStream(propertiesIterator) .map(JCardObjectDeserializer::getPropertyKeyValuePair) - .filter(pair -> pair != ImmutablePair.nullPair()) + .flatMap(Optional::stream) .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); } - private static ImmutablePair getPropertyKeyValuePair(JsonNode propertyNode) { + private static Optional> getPropertyKeyValuePair(JsonNode propertyNode) { String propertyName = propertyNode.get(PROPERTY_NAME_INDEX).asText(); if (SUPPORTED_PROPERTY_NAMES.contains(propertyName)) { String propertyValue = propertyNode.get(TEXT_PROPERTY_VALUE_INDEX).asText(); - return ImmutablePair.of(propertyName, propertyValue); + return Optional.of(ImmutablePair.of(propertyName, propertyValue)); } else { - return ImmutablePair.nullPair(); + return Optional.empty(); } } diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java index 524fc750a9..67e17c1f4d 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.linagora.tmail.api.OpenPaasRestClient; @@ -46,8 +47,8 @@ public class OpenPaasContactsConsumer implements Startable, Closeable { public static final String EXCHANGE_NAME = "contacts:contact:add"; public static final String QUEUE_NAME = "ConsumeOpenPaasContactsQueue"; - private Disposable consumeContactsDisposable; + private Disposable consumeContactsDisposable; private final ReceiverProvider receiverProvider; private final Sender sender; private final RabbitMQConfiguration commonRabbitMQConfiguration; @@ -55,8 +56,6 @@ public class OpenPaasContactsConsumer implements Startable, Closeable { private final ObjectMapper objectMapper = new ObjectMapper(); private final OpenPaasRestClient openPaasRestClient; - // TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool - // would be custom configured @Inject public OpenPaasContactsConsumer(@Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) ReceiverProvider receiverProvider, @Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) Sender sender, @@ -127,7 +126,7 @@ private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage .mapNotNull(ownerMailAddress -> { JCardObject jCardObject = contactAddedMessage.vcard(); - String contactFullname = jCardObject.fn(); + Optional contactFullnameOpt = jCardObject.fnOpt(); Optional contactMailAddressOpt = jCardObject.emailOpt() .flatMap(contactEmail -> { try { @@ -137,16 +136,20 @@ private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage } }); - if (contactMailAddressOpt.isEmpty()) { + if (contactFullnameOpt.isEmpty() || contactMailAddressOpt.isEmpty()) { return Mono.empty(); } + ContactFields contactFields = new ContactFields( + contactMailAddressOpt.get(), + contactFullnameOpt.get(), + contactFullnameOpt.get() + ); + AccountId ownerAccountId = AccountId.fromUsername(Username.fromMailAddress(ownerMailAddress)); - return Mono.from(contactSearchEngine.index(ownerAccountId, - new ContactFields(contactMailAddressOpt.get(), contactFullname, "") - )).block(); + return Mono.from(contactSearchEngine.index(ownerAccountId, contactFields)).block(); }).then(); } From 76bd33a809a7af5863e1ee36ac5a8a0377c3d869 Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Fri, 18 Oct 2024 12:48:10 +0100 Subject: [PATCH 05/11] Refactor OpenPaasContactsConsumer --- .../contact/OpenPaasContactsConsumer.java | 101 +++++++++--------- .../contact/OpenPaasContactsConsumerTest.java | 7 +- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java index 67e17c1f4d..f4eb8f9b56 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Optional; +import java.util.function.BiFunction; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -21,19 +22,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.linagora.tmail.api.OpenPaasRestClient; import com.linagora.tmail.james.jmap.EmailAddressContactInjectKeys; import com.linagora.tmail.james.jmap.contact.ContactFields; +import com.linagora.tmail.james.jmap.contact.EmailAddressContact; import com.linagora.tmail.james.jmap.contact.EmailAddressContactSearchEngine; import com.rabbitmq.client.BuiltinExchangeType; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; +import reactor.core.publisher.SignalType; import reactor.rabbitmq.AcknowledgableDelivery; import reactor.rabbitmq.BindingSpecification; import reactor.rabbitmq.ExchangeSpecification; @@ -84,13 +85,14 @@ public void start() { .block(); consumeContactsDisposable = doConsumeContactMessages(); + System.out.println("Hello, World"); } private Disposable doConsumeContactMessages() { return delivery() - .subscribeOn(Schedulers.boundedElastic()) - .subscribe(delivery -> - messageConsume(delivery, new String(delivery.getBody(), StandardCharsets.UTF_8))); + .flatMap(delivery -> messageConsume(delivery, new String(delivery.getBody(), StandardCharsets.UTF_8))) + .doOnError(e -> LOGGER.error("Failed to consume contact message", e)) + .subscribe(); } public Flux delivery() { @@ -99,58 +101,61 @@ public Flux delivery() { Receiver::close); } - private void messageConsume(AcknowledgableDelivery ackDelivery, String messagePayload) { - Mono.just(messagePayload) - .handle((message, sink) -> { - try { - sink.next(objectMapper.readValue(message, ContactAddedRabbitMqMessage.class)); - } catch (JsonProcessingException e) { - sink.error(new RuntimeException(e)); + private Mono messageConsume(AcknowledgableDelivery ackDelivery, String messagePayload) { + return Mono.just(messagePayload) + .map(this::parseContactAddedRabbitMqMessage) + .flatMap(this::handleMessage) + .doFinally(signal -> { + if (signal == SignalType.ON_COMPLETE) { + ackDelivery.ack(); + } else if (signal == SignalType.ON_ERROR) { + ackDelivery.nack(false); } }) - .handle((msg, sink) -> { - handleMessage(msg).block(); - ackDelivery.ack(); - }) - .onErrorResume(e -> { - LOGGER.error("Failed to consume OpenPaaS added contact message", e); - return Mono.empty(); - }).subscribe(); + .onErrorResume(e -> Mono.error(new RuntimeException("Failed to consume OpenPaaS added contact message", e))); } - private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage) { + private ContactAddedRabbitMqMessage parseContactAddedRabbitMqMessage(String message) { + try { + return objectMapper.readValue(message, ContactAddedRabbitMqMessage.class); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to parse ContactAddedRabbitMqMessage", e); + } + } + + private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage) { LOGGER.info("Consumed jCard object message: {}", contactAddedMessage); - String openPaasOwnerId = contactAddedMessage.userId(); - return openPaasRestClient.retrieveMailAddress(openPaasOwnerId) - .mapNotNull(ownerMailAddress -> { - JCardObject jCardObject = contactAddedMessage.vcard(); - - Optional contactFullnameOpt = jCardObject.fnOpt(); - Optional contactMailAddressOpt = jCardObject.emailOpt() - .flatMap(contactEmail -> { - try { - return Optional.of(new MailAddress(contactEmail)); - } catch (AddressException e) { - return Optional.empty(); - } - }); - - if (contactFullnameOpt.isEmpty() || contactMailAddressOpt.isEmpty()) { - return Mono.empty(); - } + return openPaasRestClient.retrieveMailAddress(contactAddedMessage.userId()) + .map(ownerMailAddress -> AccountId.fromUsername(Username.fromMailAddress(ownerMailAddress))) + .flatMap(ownerAccountId -> + Mono.justOrEmpty(toContactFields(contactAddedMessage.vcard())) + .flatMap(contactFields -> doAddContact(ownerAccountId, contactFields))); + } + + private Mono doAddContact(AccountId ownerAccountId, ContactFields contactFields) { + return Mono.from(contactSearchEngine.index(ownerAccountId, contactFields)); + } - ContactFields contactFields = new ContactFields( - contactMailAddressOpt.get(), - contactFullnameOpt.get(), - contactFullnameOpt.get() - ); + private Optional toContactFields(JCardObject jCardObject) { + Optional contactFullnameOpt = jCardObject.fnOpt(); + Optional contactMailAddressOpt = jCardObject.emailOpt() + .flatMap(contactEmail -> { + try { + return Optional.of(new MailAddress(contactEmail)); + } catch (AddressException e) { + LOGGER.warn("Invalid contact email address: {}", contactEmail, e); + return Optional.empty(); + } + }); - AccountId ownerAccountId = - AccountId.fromUsername(Username.fromMailAddress(ownerMailAddress)); + return combineOptionals(contactFullnameOpt, contactMailAddressOpt, + (contactFullname, contactMailAddress) -> + new ContactFields(contactMailAddress, contactFullname, contactFullname)); + } - return Mono.from(contactSearchEngine.index(ownerAccountId, contactFields)).block(); - }).then(); + private static Optional combineOptionals(Optional opt1, Optional opt2, BiFunction f) { + return opt1.flatMap(t1 -> opt2.map(t2 -> f.apply(t1, t2))); } @Override diff --git a/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java index 42deae1ce9..f7144185a2 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java +++ b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java @@ -9,6 +9,7 @@ import org.apache.james.backends.rabbitmq.RabbitMQExtension; import org.apache.james.jmap.api.model.AccountId; +import org.bouncycastle.oer.its.ieee1609dot2.basetypes.UINT16; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,6 +17,7 @@ import java.io.IOException; import java.net.URISyntaxException; +import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import reactor.core.publisher.Flux; @@ -38,18 +40,17 @@ class OpenPaasContactsConsumerTest { .isolationPolicy(WEAK); private EmailAddressContactSearchEngine searchEngine; - private OpenPaasRestClient restClient; private OpenPaasContactsConsumer consumer; @BeforeEach void setup() throws URISyntaxException { - searchEngine = new InMemoryEmailAddressContactSearchEngine(); - restClient = new OpenPaasRestClient( + OpenPaasRestClient restClient = new OpenPaasRestClient( new OpenPaasConfiguration( openPaasServerExtension.getBaseUrl(), "admin", "admin") ); + searchEngine = new InMemoryEmailAddressContactSearchEngine(); consumer = new OpenPaasContactsConsumer(rabbitMQExtension.getReceiverProvider(), rabbitMQExtension.getSender(), rabbitMQExtension.getRabbitMQ().withQuorumQueueConfiguration(), From 09c6eb816e3efe65b23e230b2fcd26e92eb374c3 Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Sun, 20 Oct 2024 20:56:36 +0100 Subject: [PATCH 06/11] Configure the dead letter queue --- .../contact/OpenPaasContactsConsumer.java | 37 ++++++++++++++----- .../contact/OpenPaasContactsConsumerTest.java | 2 - 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java index f4eb8f9b56..8b13479b89 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java @@ -34,7 +34,6 @@ import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.SignalType; import reactor.rabbitmq.AcknowledgableDelivery; import reactor.rabbitmq.BindingSpecification; import reactor.rabbitmq.ExchangeSpecification; @@ -46,8 +45,11 @@ public class OpenPaasContactsConsumer implements Startable, Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(OpenPaasContactsConsumer.class); + private static final boolean REQUEUE_ON_NACK = false; public static final String EXCHANGE_NAME = "contacts:contact:add"; public static final String QUEUE_NAME = "ConsumeOpenPaasContactsQueue"; + public static final String DEAD_LETTER_EXCHANGE = "contacts:contact:add:dead:letter"; + public static final String DEAD_LETTER_QUEUE = "ConsumeOpenPaasContactsQueue-dead-letter"; private Disposable consumeContactsDisposable; private final ReceiverProvider receiverProvider; @@ -74,18 +76,37 @@ public void start() { Flux.concat( sender.declareExchange(ExchangeSpecification.exchange(EXCHANGE_NAME) .durable(DURABLE).type(BuiltinExchangeType.FANOUT.getType())), + sender.declareExchange(ExchangeSpecification.exchange(DEAD_LETTER_EXCHANGE) + .durable(DURABLE)), + sender.declareExchange(ExchangeSpecification.exchange(DEAD_LETTER_EXCHANGE) + .durable(DURABLE)), + sender.declareQueue(QueueSpecification + .queue(DEAD_LETTER_QUEUE) + .durable(DURABLE) + .arguments(commonRabbitMQConfiguration.workQueueArgumentsBuilder().build())), + sender.declareQueue(QueueSpecification + .queue(DEAD_LETTER_QUEUE) + .durable(DURABLE) + .arguments(commonRabbitMQConfiguration.workQueueArgumentsBuilder().build())), sender.declareQueue(QueueSpecification .queue(QUEUE_NAME) - .durable(DURABLE)), + .durable(DURABLE) + .arguments(commonRabbitMQConfiguration.workQueueArgumentsBuilder() + .put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE) + .put("x-dead-letter-routing-key", EMPTY_ROUTING_KEY) + .build())), sender.bind(BindingSpecification.binding() .exchange(EXCHANGE_NAME) .queue(QUEUE_NAME) + .routingKey(EMPTY_ROUTING_KEY)), + sender.bind(BindingSpecification.binding() + .exchange(DEAD_LETTER_EXCHANGE) + .queue(DEAD_LETTER_QUEUE) .routingKey(EMPTY_ROUTING_KEY))) .then() .block(); consumeContactsDisposable = doConsumeContactMessages(); - System.out.println("Hello, World"); } private Disposable doConsumeContactMessages() { @@ -104,14 +125,10 @@ public Flux delivery() { private Mono messageConsume(AcknowledgableDelivery ackDelivery, String messagePayload) { return Mono.just(messagePayload) .map(this::parseContactAddedRabbitMqMessage) + .log() .flatMap(this::handleMessage) - .doFinally(signal -> { - if (signal == SignalType.ON_COMPLETE) { - ackDelivery.ack(); - } else if (signal == SignalType.ON_ERROR) { - ackDelivery.nack(false); - } - }) + .doOnSuccess(input -> ackDelivery.ack()) + .doOnError(e -> ackDelivery.nack(REQUEUE_ON_NACK)) .onErrorResume(e -> Mono.error(new RuntimeException("Failed to consume OpenPaaS added contact message", e))); } diff --git a/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java index f7144185a2..0446509860 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java +++ b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java @@ -9,7 +9,6 @@ import org.apache.james.backends.rabbitmq.RabbitMQExtension; import org.apache.james.jmap.api.model.AccountId; -import org.bouncycastle.oer.its.ieee1609dot2.basetypes.UINT16; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,7 +16,6 @@ import java.io.IOException; import java.net.URISyntaxException; -import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import reactor.core.publisher.Flux; From 642ce38476233f7045afa64b63462879d9690015 Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Mon, 21 Oct 2024 12:32:07 +0100 Subject: [PATCH 07/11] Strip extra fields from OpenPaasUserResponse --- .../com/linagora/tmail/api/OpenPaasUserResponse.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/api/OpenPaasUserResponse.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/api/OpenPaasUserResponse.java index 4756406a19..777f9c6787 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/api/OpenPaasUserResponse.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/api/OpenPaasUserResponse.java @@ -1,16 +1,8 @@ package com.linagora.tmail.api; -import java.util.List; - import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public record OpenPaasUserResponse(@JsonProperty("id") String id, - @JsonProperty("firstname") String firstname, - @JsonProperty("lastname") String lastname, - @JsonProperty("preferredEmail") String preferredEmail, - @JsonProperty("emails") List emails, - @JsonProperty("main_phone") String mainPhone, - @JsonProperty("displayName") String displayName) { +public record OpenPaasUserResponse(@JsonProperty("preferredEmail") String preferredEmail) { } From aec311c27b4d7bbf9be32e1861b772deaf4952a6 Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Mon, 21 Oct 2024 13:24:52 +0100 Subject: [PATCH 08/11] Fix typo when rebasing --- .../linagora/tmail/contact/OpenPaasContactsConsumer.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java index 8b13479b89..ecbe181bb7 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java @@ -78,12 +78,6 @@ public void start() { .durable(DURABLE).type(BuiltinExchangeType.FANOUT.getType())), sender.declareExchange(ExchangeSpecification.exchange(DEAD_LETTER_EXCHANGE) .durable(DURABLE)), - sender.declareExchange(ExchangeSpecification.exchange(DEAD_LETTER_EXCHANGE) - .durable(DURABLE)), - sender.declareQueue(QueueSpecification - .queue(DEAD_LETTER_QUEUE) - .durable(DURABLE) - .arguments(commonRabbitMQConfiguration.workQueueArgumentsBuilder().build())), sender.declareQueue(QueueSpecification .queue(DEAD_LETTER_QUEUE) .durable(DURABLE) @@ -125,7 +119,6 @@ public Flux delivery() { private Mono messageConsume(AcknowledgableDelivery ackDelivery, String messagePayload) { return Mono.just(messagePayload) .map(this::parseContactAddedRabbitMqMessage) - .log() .flatMap(this::handleMessage) .doOnSuccess(input -> ackDelivery.ack()) .doOnError(e -> ackDelivery.nack(REQUEUE_ON_NACK)) From 4a73de1a1d157ccdf1b704ab61948b8021d5924a Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Mon, 21 Oct 2024 13:38:27 +0100 Subject: [PATCH 09/11] Take the contactField computation out --- .../com/linagora/tmail/contact/OpenPaasContactsConsumer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java index ecbe181bb7..e0a2e53ff3 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java @@ -135,11 +135,11 @@ private ContactAddedRabbitMqMessage parseContactAddedRabbitMqMessage(String mess private Mono handleMessage(ContactAddedRabbitMqMessage contactAddedMessage) { LOGGER.info("Consumed jCard object message: {}", contactAddedMessage); - + Optional contactFieldsOpt = toContactFields(contactAddedMessage.vcard()); return openPaasRestClient.retrieveMailAddress(contactAddedMessage.userId()) .map(ownerMailAddress -> AccountId.fromUsername(Username.fromMailAddress(ownerMailAddress))) .flatMap(ownerAccountId -> - Mono.justOrEmpty(toContactFields(contactAddedMessage.vcard())) + Mono.justOrEmpty(contactFieldsOpt) .flatMap(contactFields -> doAddContact(ownerAccountId, contactFields))); } From 21669d12659e302285a2c589366ffe3ab4b011d3 Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Mon, 21 Oct 2024 15:32:11 +0100 Subject: [PATCH 10/11] Fix rabbitmq queue poisoning --- .../tmail/contact/OpenPaasContactsConsumer.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java index e0a2e53ff3..bb037a55df 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java +++ b/tmail-backend/tmail-third-party/openpaas/src/main/java/com/linagora/tmail/contact/OpenPaasContactsConsumer.java @@ -106,7 +106,6 @@ public void start() { private Disposable doConsumeContactMessages() { return delivery() .flatMap(delivery -> messageConsume(delivery, new String(delivery.getBody(), StandardCharsets.UTF_8))) - .doOnError(e -> LOGGER.error("Failed to consume contact message", e)) .subscribe(); } @@ -120,9 +119,15 @@ private Mono messageConsume(AcknowledgableDelivery ackDeliv return Mono.just(messagePayload) .map(this::parseContactAddedRabbitMqMessage) .flatMap(this::handleMessage) - .doOnSuccess(input -> ackDelivery.ack()) - .doOnError(e -> ackDelivery.nack(REQUEUE_ON_NACK)) - .onErrorResume(e -> Mono.error(new RuntimeException("Failed to consume OpenPaaS added contact message", e))); + .doOnSuccess(result -> { + LOGGER.warn("Consumed contact successfully '{}'", result); + ackDelivery.ack(); + }) + .onErrorResume(error -> { + LOGGER.error("Error when consume message '{}'", messagePayload, error); + ackDelivery.nack(REQUEUE_ON_NACK); + return Mono.empty(); + }); } private ContactAddedRabbitMqMessage parseContactAddedRabbitMqMessage(String message) { From 7d0f4191dbee52bd52a340af4e99b96af8a74fc1 Mon Sep 17 00:00:00 2001 From: HoussemNasri Date: Mon, 21 Oct 2024 16:05:54 +0100 Subject: [PATCH 11/11] Define expected behavior in tests --- .../contact/OpenPaasContactsConsumerTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java index 0446509860..6950268537 100644 --- a/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java +++ b/tmail-backend/tmail-third-party/openpaas/src/test/java/com/linagora/tmail/contact/OpenPaasContactsConsumerTest.java @@ -122,6 +122,27 @@ void contactShouldBeIndexedWhenContactUserAddedMessage() { .hasSize(1)); } + @Test + void contactDisplayNameShouldBeSetFromTheReceivedOpenPaasContactObject() { + + } + + @Test + void givenDisplayNameFromOpenPaasNotEmptyThenStoredDisplayNameShouldBeOverridden() { + + } + + @Test + void givenDisplayNameFromOpenPaasIsEmptyThenStoredDisplayNameShouldPersist() { + + } + + @Test + void automaticContactIndexingShouldNotOverrideContactInfoFromOpenPaas() { + // The automatic contact indexing is triggered when you send or receive a message + // from someone, then their contact info be automatically indexed in the contacts' search engine. + } + private void sendMessage(String message) { rabbitMQExtension.getSender() .send(Mono.just(new OutboundMessage(