Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[WIP] [ISSUE-1163] Inject OpenPaaS user contacts into Tmail's auto-complete database #1215

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

HoussemNasri
Copy link
Member

@HoussemNasri HoussemNasri commented Oct 3, 2024

Fix #1163

Comment on lines 23 to 24
// TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool
// would be custom configured
Copy link
Member

Choose a reason for hiding this comment

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

I support this! A Lot!

We could...

Have a file named openpaas.properties which allow overriding RABBITMQ connection settings, if absent we can fallback to the default ChannelPool. It would be annotated with @nAmed("openpaas")

Then we can use it for OpenPaaS related communications in particular have a OpenPaaSAMQPForwardAttribute that extends `AmqpForwardAttribute and reuses this.

Actually such a task might be a good prelimiary worrk to this PR!

Comment on lines 41 to 50
Boolean.TRUE.equals(channelPool.getChannelMono()
.map(channel -> {
try {
channel.queueDeclarePassive(QUEUE_NAME);
return true;
}
catch (IOException e) {
return false;
}
}).block());
Copy link
Member

Choose a reason for hiding this comment

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

That's a VERY good finding, I wasn't aware of this methods.

So if the queue already exist but with slightly different arguments does it throws? Can you please quickly test that? If no, then we have a solution to our major RabbitMQ migration nightmares Cc @QuangHoang1210 @Arsnael @vttranlina and we could use it more widely in the source code...

}

consumeContactsDisposable = channelPool.createReceiver()
.consumeAutoAck(QUEUE_NAME)
Copy link
Member

Choose a reason for hiding this comment

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

Consume autoACK is a bad idea that pretty much guaranty message loss

image

no nack on error means after 1 day consumer shutdown. Oups.

Please use manual ACKs.

Copy link
Member

@quantranhong1999 quantranhong1999 left a comment

Choose a reason for hiding this comment

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

Read it

Comment on lines 65 to 66
sender.declareExchange(ExchangeSpecification.exchange(TOPIC)
.durable(DURABLE)),
Copy link
Member

Choose a reason for hiding this comment

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

declare the exchange as fanout (because that's the settings openpaas uses.

Copy link
Member Author

Choose a reason for hiding this comment

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

Where can I find the RabbitMQ config OpenPaaS uses?

Copy link
Member

Choose a reason for hiding this comment

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

image

Because I can check it in real world RabbitMQ admin ;-)

.durable(DURABLE)),
sender.declareQueue(QueueSpecification
.queue(QUEUE_NAME)
.durable(evaluateDurable(DURABLE, commonRabbitMQConfiguration.isQuorumQueuesUsed()))),
Copy link
Member

Choose a reason for hiding this comment

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

Queue MUST always be durable.

Copy link
Member

Choose a reason for hiding this comment

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

And ideally queue settings shall be obtained from commonRabbitMQConfiguration

Comment on lines 98 to 102
Mono.just(messagePayload)
.map(message -> gson.fromJson(message, OpenPaasContactMessage.class))
.<ContactAddedRabbitMqMessage>handle((message, sink) -> {
Copy link
Member

Choose a reason for hiding this comment

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

We likely do not need that "just" and can inline the reactor transformations.

MailAddress mailAddress = new MailAddress(jCardObject.email());
Username emailAsUsername = Username.fromMailAddress(mailAddress);

Mono.from(contactSearchEngine.index(AccountId.fromUsername(emailAsUsername),
Copy link
Member

Choose a reason for hiding this comment

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

Bob creates a contact named "alice" and we add here alice contact into alice address book.

What would be needed here is to resolve the email address of bob (to compute his AccountId) and then add alice contact in it.

For this we need the call to openpaas user API

We can hide such an operation behind an interface to isolate this from the rest of the code here.

Comment on lines 35 to 45
String userPassword = openPaasConfiguration.getWebClientUser() + ":" + openPaasConfiguration.getWebClientPassword();
byte[] base64UserPassword = Base64.getEncoder().encode(userPassword.getBytes(StandardCharsets.UTF_8));
Copy link
Member

Choose a reason for hiding this comment

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

We can store this as a field to avoid to recompute it on every request

Copy link
Member

@chibenwa chibenwa left a comment

Choose a reason for hiding this comment

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

Cool but IMO we would deserve to have dedicated tests for OpenPaaSWebClient class.

(Test in isolation for this)

@HoussemNasri HoussemNasri force-pushed the sync-openpaas-contact branch 2 times, most recently from 54c2718 to 4dbd165 Compare October 9, 2024 12:42
@HoussemNasri HoussemNasri force-pushed the sync-openpaas-contact branch 3 times, most recently from d2aa3de to 06f8730 Compare October 17, 2024 12:34
@@ -24,6 +24,16 @@
<groupId>${project.groupId}</groupId>
<artifactId>team-mailboxes</artifactId>
</dependency>
<dependency>
Copy link
Member

Choose a reason for hiding this comment

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

Why are there modifs in jmap extensions pom file? Accident?

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jmap-extensions</artifactId>
Copy link
Member

@chibenwa chibenwa Oct 17, 2024

Choose a reason for hiding this comment

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

Ideally we should refactor the maven module architecture to extract the "contact data model" (and its implementations?) into a separate maven module.

(ticket?)

Comment on lines 9 to 15
public record OpenPaasUserResponse(@JsonProperty("id") String id,
@JsonProperty("firstname") String firstname,
@JsonProperty("lastname") String lastname,
@JsonProperty("preferredEmail") String preferredEmail,
@JsonProperty("emails") List<String> emails,
@JsonProperty("main_phone") String mainPhone,
@JsonProperty("displayName") String displayName) {
Copy link
Member

Choose a reason for hiding this comment

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

Strip uneeded fields?

Comment on lines 45 to 55
if (!jCardProperties.containsKey(FN)) {
throw new RuntimeException("The FN field is required according to specification.");
}
Copy link
Member

Choose a reason for hiding this comment

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

I believe we should be more lenient (be lenient on what you accept strict on what you send)

Can we make FN optional in our datmodel? Or position it to an empty string?

.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
}

private static ImmutablePair<String, String> getPropertyKeyValuePair(JsonNode propertyNode) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private static ImmutablePair<String, String> getPropertyKeyValuePair(JsonNode propertyNode) {
private static ImmutablePair<String, String> getPropertyKeyValuePair(JsonNode propertyNode) {
Suggested change
private static ImmutablePair<String, String> getPropertyKeyValuePair(JsonNode propertyNode) {
private static Optional<ImmutablePair<String, String>> getPropertyKeyValuePair(JsonNode propertyNode) {

Copy link
Member

Choose a reason for hiding this comment

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

Or Stream<...> so that we can then use flatMap


public static final String EXCHANGE_NAME = "contacts:contact:add";
public static final String QUEUE_NAME = "ConsumeOpenPaasContactsQueue";
private Disposable consumeContactsDisposable;
Copy link
Member

Choose a reason for hiding this comment

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

Move it after final fields.

Comment on lines 58 to 59
// TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool
// would be custom configured
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool
// would be custom configured

Comment on lines +61 to +62
public OpenPaasContactsConsumer(@Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) ReceiverProvider receiverProvider,
@Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) Sender sender,
Copy link
Member

Choose a reason for hiding this comment

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

Inject a RabbitMQCHannelPool

Comment on lines +77 to +79
sender.declareQueue(QueueSpecification
.queue(QUEUE_NAME)
.durable(DURABLE)),
Copy link
Member

Choose a reason for hiding this comment

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

.arguments(configuration.workQueueArgumentsBuilder()
.deadLetter("xxx-dead-letter")
.build()));

Copy link
Member

Choose a reason for hiding this comment

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

We likly need also to create the deadletter queue

Receiver::close);
}

private void messageConsume(AcknowledgableDelivery ackDelivery, String messagePayload) {
Copy link
Member

Choose a reason for hiding this comment

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

Mono

.flatMap(contactEmail -> {
try {
return Optional.of(new MailAddress(contactEmail));
} catch (AddressException e) {
Copy link
Member

Choose a reason for hiding this comment

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

Logger.warn please

}
});

if (contactMailAddressOpt.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's extract the ContactAddedRabbitMqMessage => ContactFields


return Mono.from(contactSearchEngine.index(ownerAccountId,
new ContactFields(contactMailAddressOpt.get(), contactFullname, "")
)).block();
Copy link
Member

Choose a reason for hiding this comment

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

.block in production code is a red flag

assertThat(
Flux.from(searchEngine.autoComplete(AccountId.fromString(OpenPaasServerExtension.ALICE_EMAIL()), "jhon", 10))
.collectList().block())
.hasSize(1));
Copy link
Member

Choose a reason for hiding this comment

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

We could be more precise on the assert: what is the result given by the search engine?

import com.linagora.tmail.james.jmap.contact.EmailAddressContactSearchEngine;
import com.linagora.tmail.james.jmap.contact.InMemoryEmailAddressContactSearchEngine;

class OpenPaasContactsConsumerTest {
Copy link
Member

Choose a reason for hiding this comment

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

Test edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tmail: Inject user contacts into auto-complete database
4 participants