All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.allegro.tech.hermes.consumers.consumer.sender.http.JettyBroadCastMessageSender Maven / Gradle / Ivy

There is a newer version: 2.8.0
Show newest version
package pl.allegro.tech.hermes.consumers.consumer.sender.http;

import com.google.common.collect.ImmutableList;
import org.eclipse.jetty.client.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.allegro.tech.hermes.consumers.consumer.Message;
import pl.allegro.tech.hermes.consumers.consumer.ResilientMessageSender;
import pl.allegro.tech.hermes.consumers.consumer.sender.MessageSender;
import pl.allegro.tech.hermes.consumers.consumer.sender.MessageSendingResult;
import pl.allegro.tech.hermes.consumers.consumer.sender.MultiMessageSendingResult;
import pl.allegro.tech.hermes.consumers.consumer.sender.SingleMessageSendingResult;
import pl.allegro.tech.hermes.consumers.consumer.sender.http.HttpRequestData.HttpRequestDataBuilder;
import pl.allegro.tech.hermes.consumers.consumer.sender.http.headers.HttpHeadersProvider;
import pl.allegro.tech.hermes.consumers.consumer.sender.http.headers.HttpRequestHeaders;
import pl.allegro.tech.hermes.consumers.consumer.sender.resolver.EndpointAddressResolutionException;
import pl.allegro.tech.hermes.consumers.consumer.sender.resolver.ResolvableEndpointAddress;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

public class JettyBroadCastMessageSender implements MessageSender {

    private static final Logger logger = LoggerFactory.getLogger(JettyBroadCastMessageSender.class);

    private final HttpRequestFactory requestFactory;
    private final ResolvableEndpointAddress endpoint;
    private final HttpHeadersProvider requestHeadersProvider;
    private final SendingResultHandlers sendingResultHandlers;
    private final Function exceptionMapper = MessageSendingResult::failedResult;
    private final ResilientMessageSender resilientMessageSender;

    public JettyBroadCastMessageSender(HttpRequestFactory requestFactory,
                                       ResolvableEndpointAddress endpoint,
                                       HttpHeadersProvider requestHeadersProvider,
                                       SendingResultHandlers sendingResultHandlers,
                                       ResilientMessageSender resilientMessageSender
    ) {
        this.requestFactory = requestFactory;
        this.endpoint = endpoint;
        this.requestHeadersProvider = requestHeadersProvider;
        this.sendingResultHandlers = sendingResultHandlers;
        this.resilientMessageSender = resilientMessageSender;
    }

    @Override
    public CompletableFuture send(Message message) {
        try {
            return sendMessage(message).thenApply(MultiMessageSendingResult::new);
        } catch (Exception exception) {
            return CompletableFuture.completedFuture(exceptionMapper.apply(exception));
        }
    }

    private CompletableFuture> sendMessage(Message message) {
        try {
            Set> results = collectResults(message);
            return mergeResults(results);
        } catch (EndpointAddressResolutionException exception) {
            return CompletableFuture.completedFuture(Collections.singletonList(exceptionMapper.apply(exception)));
        }
    }

    private Set> collectResults(
            Message message
    ) throws EndpointAddressResolutionException {
        var currentResults = sendPendingMessages(message);
        var results = new HashSet<>(currentResults);

        // add previously succeeded uris to the result set so that successful uris from all attempts are retained.
        // this way a MessageSendingResult can be considered successful even when the last send attempt
        // did not send to any uri, e.g. because all uris returned by endpoint resolver were already sent to in the past.
        for (String succeededUri : message.getSucceededUris()) {
            try {
                var uri = new URI(succeededUri);
                var result = MessageSendingResult.succeededResult(uri);
                results.add(CompletableFuture.completedFuture(result));
            } catch (URISyntaxException exception) {
                logger.error("Error while parsing already sent broadcast URI {}", succeededUri, exception);
            }
        }
        return results;
    }

    private Set> sendPendingMessages(Message message) throws EndpointAddressResolutionException {
        final HttpRequestData requestData = new HttpRequestDataBuilder()
                .withRawAddress(endpoint.getRawAddress())
                .build();

        HttpRequestHeaders headers = requestHeadersProvider.getHeaders(message, requestData);

        List resolvedUris = endpoint.resolveAllFor(message).stream()
                .filter(uri -> message.hasNotBeenSentTo(uri.toString())).toList();

        if (resolvedUris.isEmpty()) {
            logger.debug("Empty resolved URIs for message: {}", message.getId());
            return Collections.emptySet();
        } else {
            return resolvedUris.stream()
                    .map(uri -> requestFactory.buildRequest(message, uri, headers))
                    .map(this::processResponse)
                    .collect(Collectors.toSet());
        }
    }

    private CompletableFuture> mergeResults(Set> results) {
        return CompletableFuture.allOf(results.toArray(new CompletableFuture[results.size()]))
                .thenApply(v -> results.stream()
                        .map(CompletableFuture::join)
                        .reduce(
                                ImmutableList.builder(),
                                (builder, element) -> builder.add(element),
                                (listA, listB) -> listA.addAll(listB.build())
                        ).build());
    }

    private CompletableFuture processResponse(Request request) {
        return resilientMessageSender.send(
                resultFuture -> request.send(sendingResultHandlers.handleSendingResultForBroadcast(resultFuture)),
                exceptionMapper);

    }

    @Override
    public void stop() {
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy