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

org.esbtools.eventhandler.PollingDocumentEventProcessorRoute Maven / Gradle / Ivy

/*
 *  Copyright 2015 esbtools Contributors and/or its affiliates.
 *
 *  This file is part of esbtools.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see .
 */

package org.esbtools.eventhandler;

import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.Futures;
import org.apache.camel.builder.RouteBuilder;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

public class PollingDocumentEventProcessorRoute extends RouteBuilder {
    private final DocumentEventRepository documentEventRepository;
    private final Duration pollingInterval;
    private final int batchSize;
    private final String documentEndpoint;
    private final String failureEndpoint;

    private static final AtomicInteger idCounter = new AtomicInteger(1);
    private final int id = idCounter.getAndIncrement();

    public PollingDocumentEventProcessorRoute(DocumentEventRepository documentEventRepository,
            Duration pollingInterval, int batchSize, String documentEndpoint,
            String failureEndpoint) {
        this.documentEventRepository = documentEventRepository;
        this.pollingInterval = pollingInterval;
        this.batchSize = batchSize;
        this.documentEndpoint = documentEndpoint;
        this.failureEndpoint = failureEndpoint;
    }

    @Override
    public void configure() throws Exception {
        from("timer:pollForDocumentEvents" + id + "?period=" + pollingInterval.toMillis())
        .routeId("documentEventProcessor-" + id)
        .process(exchange -> {
            List documentEvents = documentEventRepository
                    .retrievePriorityDocumentEventsUpTo(batchSize);
            Map> eventsToFutureDocuments =
                    new HashMap<>(documentEvents.size());

            // Intentionally cache all futures before resolving them.
            for (DocumentEvent event : documentEvents) {
                try {
                    eventsToFutureDocuments.put(event, event.lookupDocument());
                } catch (Exception e) {
                    log.error("Failed to get future document for document event: " + event, e);
                    eventsToFutureDocuments.put(event, Futures.immediateFailedFuture(e));
                }
            }

            Map eventsToDocuments = new HashMap<>(documentEvents.size());
            List failedEvents = new ArrayList<>();

            for (Map.Entry> eventToFutureDocument
                    : eventsToFutureDocuments.entrySet()) {
                DocumentEvent event = eventToFutureDocument.getKey();
                Future futureDoc = eventToFutureDocument.getValue();

                try {
                    eventsToDocuments.put(event, futureDoc.get());
                } catch (ExecutionException | InterruptedException e) {
                    log.error("Failed to get future document for document event: " + event, e);
                    failedEvents.add(new FailedDocumentEvent(event, e));
                }
            }

            try {
                documentEventRepository.markDocumentEventsPublishedOrFailed(
                        Collections.emptyList(), failedEvents);
            } catch (Exception e) {
                if (log.isErrorEnabled()) {
                    log.error("Failed to update failed events. They will be reprocessed. " +
                            "Failures were: " + failedEvents, e);
                }
            }

            Iterator> eventsToDocumentsIterator =
                    eventsToDocuments.entrySet().iterator();
            while (eventsToDocumentsIterator.hasNext()) {
                Map.Entry eventToDocument = eventsToDocumentsIterator.next();
                try {
                    documentEventRepository.ensureTransactionActive(eventToDocument.getKey());
                } catch (Exception e) {
                    eventsToDocumentsIterator.remove();
                    if (log.isWarnEnabled()) {
                        log.warn("Event transaction no longer active, not processing: " +
                                eventToDocument.getKey(), e);
                    }
                }
            }

            log.debug("Publishing {} documents on route {}: {}",
                    eventsToDocuments.size(), exchange.getFromRouteId(), eventsToDocuments.values());

            exchange.getIn().setBody(Iterables.concat(eventsToDocuments.entrySet(), failedEvents));
        })
        .split(body())
        .streaming()
        .choice()
            .when(e -> e.getIn().getBody() instanceof FailedDocumentEvent).to(failureEndpoint)
            .otherwise()
                .process(exchange -> {
                    Map.Entry eventToDocument =
                            exchange.getIn().getBody(Map.Entry.class);
                    exchange.setProperty("originalEvent", eventToDocument.getKey());
                    exchange.getIn().setBody(eventToDocument.getValue());
                })
                .to(documentEndpoint)
                // If producing to documentEndpoint succeeded, update original event status...
                // TODO(ahenning): This updates event status one at a time. We could consider using
                // aggregation strategy with splitter to update all in bulk which would take
                // advantage of repository implementations which can update many statuses in one
                // call.
                .process(exchange -> {
                    DocumentEvent event = exchange.getProperty("originalEvent", DocumentEvent.class);

                    if (event == null) {
                        throw new IllegalStateException("Could not get original event from " +
                                "exchange. Won't update event status as published. Exchange was: " +
                                exchange);
                    }

                    documentEventRepository.markDocumentEventsPublishedOrFailed(
                            Collections.singleton(event), Collections.emptyList());
                });
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy