-
Notifications
You must be signed in to change notification settings - Fork 22
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
base: master
Are you sure you want to change the base?
Conversation
// TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool | ||
// would be custom configured |
There was a problem hiding this comment.
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!
Boolean.TRUE.equals(channelPool.getChannelMono() | ||
.map(channel -> { | ||
try { | ||
channel.queueDeclarePassive(QUEUE_NAME); | ||
return true; | ||
} | ||
catch (IOException e) { | ||
return false; | ||
} | ||
}).block()); |
There was a problem hiding this comment.
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...
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
consumeContactsDisposable = channelPool.createReceiver() | ||
.consumeAutoAck(QUEUE_NAME) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
.../src/main/scala/com/linagora/tmail/james/jmap/contact/openpaas/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read it
f4a2edd
to
9984af3
Compare
...nsions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java
Outdated
Show resolved
Hide resolved
...nsions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java
Outdated
Show resolved
Hide resolved
sender.declareExchange(ExchangeSpecification.exchange(TOPIC) | ||
.durable(DURABLE)), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.durable(DURABLE)), | ||
sender.declareQueue(QueueSpecification | ||
.queue(QUEUE_NAME) | ||
.durable(evaluateDurable(DURABLE, commonRabbitMQConfiguration.isQuorumQueuesUsed()))), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
...extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
...extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
...p/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactMessage.java
Outdated
Show resolved
Hide resolved
.../extensions/src/main/java/com/linagora/tmail/james/jmap/contact/jCardObjectDeserializer.java
Outdated
Show resolved
Hide resolved
.../extensions/src/main/java/com/linagora/tmail/james/jmap/contact/jCardObjectDeserializer.java
Outdated
Show resolved
Hide resolved
.../extensions/src/main/java/com/linagora/tmail/james/jmap/contact/jCardObjectDeserializer.java
Outdated
Show resolved
Hide resolved
...backend/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/jCardObject.java
Outdated
Show resolved
Hide resolved
...extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
...extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
Mono.just(messagePayload) | ||
.map(message -> gson.fromJson(message, OpenPaasContactMessage.class)) | ||
.<ContactAddedRabbitMqMessage>handle((message, sink) -> { |
There was a problem hiding this comment.
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.
...extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
...nsions/src/test/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumerTest.java
Outdated
Show resolved
Hide resolved
MailAddress mailAddress = new MailAddress(jCardObject.email()); | ||
Username emailAsUsername = Username.fromMailAddress(mailAddress); | ||
|
||
Mono.from(contactSearchEngine.index(AccountId.fromUsername(emailAsUsername), |
There was a problem hiding this comment.
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.
String userPassword = openPaasConfiguration.getWebClientUser() + ":" + openPaasConfiguration.getWebClientPassword(); | ||
byte[] base64UserPassword = Base64.getEncoder().encode(userPassword.getBytes(StandardCharsets.UTF_8)); |
There was a problem hiding this comment.
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
74dfe12
to
7f43080
Compare
There was a problem hiding this 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)
...d/jmap/extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasWebClient.java
Outdated
Show resolved
Hide resolved
54c2718
to
4dbd165
Compare
...extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
...extensions/src/main/java/com/linagora/tmail/james/jmap/contact/OpenPaasContactsConsumer.java
Outdated
Show resolved
Hide resolved
d2aa3de
to
06f8730
Compare
@@ -24,6 +24,16 @@ | |||
<groupId>${project.groupId}</groupId> | |||
<artifactId>team-mailboxes</artifactId> | |||
</dependency> | |||
<dependency> |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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?)
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strip uneeded fields?
if (!jCardProperties.containsKey(FN)) { | ||
throw new RuntimeException("The FN field is required according to specification."); | ||
} |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private static ImmutablePair<String, String> getPropertyKeyValuePair(JsonNode propertyNode) { | |
private static ImmutablePair<String, String> getPropertyKeyValuePair(JsonNode propertyNode) { |
private static ImmutablePair<String, String> getPropertyKeyValuePair(JsonNode propertyNode) { | |
private static Optional<ImmutablePair<String, String>> getPropertyKeyValuePair(JsonNode propertyNode) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
// TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool | ||
// would be custom configured |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO: Create a separate RabbitMQ module for OpenPaaS communication so the injected channel pool | |
// would be custom configured |
public OpenPaasContactsConsumer(@Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) ReceiverProvider receiverProvider, | ||
@Named(EmailAddressContactInjectKeys.AUTOCOMPLETE) Sender sender, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inject a RabbitMQCHannelPool
sender.declareQueue(QueueSpecification | ||
.queue(QUEUE_NAME) | ||
.durable(DURABLE)), |
There was a problem hiding this comment.
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()));
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test edge cases.
- Create OpenPaasContactsConsumer to handle contact addition messages from RabbitMQ - Add OpenPaasContactMessage class to represent incoming contact data - Implement message consumption and indexing logic - Add unit tests for OpenPaasContactsConsumer
- For now, it only consumes the /api/users/{openpaas_user_id} endpoint
- Done some good stuff in Reactive programming
a3348d8
to
94a9b41
Compare
Fix #1163