org.esbtools.eventhandler.PollingNotificationProcessorRoute.java Source code

Java tutorial

Introduction

Here is the source code for org.esbtools.eventhandler.PollingNotificationProcessorRoute.java

Source

/*
 *  Copyright 2016 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 <http://www.gnu.org/licenses/>.
 */

package org.esbtools.eventhandler;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.apache.camel.builder.RouteBuilder;

import com.google.common.util.concurrent.Futures;

public class PollingNotificationProcessorRoute extends RouteBuilder {
    private final NotificationRepository notificationRepository;
    private final DocumentEventRepository documentEventRepository;
    private final Duration pollingInterval;
    private final Duration processTimeout;
    private final int batchSize;

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

    public PollingNotificationProcessorRoute(NotificationRepository notificationRepository,
            DocumentEventRepository documentEventRepository, Duration pollingInterval, Duration processTimeout,
            int batchSize) {
        this.notificationRepository = notificationRepository;
        this.documentEventRepository = documentEventRepository;
        this.pollingInterval = pollingInterval;
        this.batchSize = batchSize;
        this.processTimeout = Objects.requireNonNull(processTimeout, "notificationProcessTimeout");
    }

    @Override
    public void configure() throws Exception {
        from("timer:pollForNotifications" + id + "?period=" + pollingInterval.toMillis())
                .routeId("notificationProcessor-" + id).process(exchange -> {
                    List<? extends Notification> notifications = notificationRepository
                            .retrieveOldestNotificationsUpTo(batchSize);
                    Map<Notification, Future<Collection<DocumentEvent>>> notificationsToFutureEvents = new HashMap<>(
                            notifications.size());

                    // Intentionally cache all futures before waiting for any.
                    for (Notification notification : notifications) {
                        try {
                            Future<Collection<DocumentEvent>> futureEvents = notification.toDocumentEvents();
                            notificationsToFutureEvents.put(notification, futureEvents);
                        } catch (Exception e) {
                            log.error("Failed to get future document events for notification: " + notification, e);
                            notificationsToFutureEvents.put(notification, Futures.immediateFailedFuture(e));
                        }
                    }

                    Map<Notification, Collection<DocumentEvent>> notificationsToDocumentEvents = new HashMap<>();
                    List<FailedNotification> failedNotifications = new ArrayList<>();

                    for (Entry<Notification, Future<Collection<DocumentEvent>>> notificationToFutureEvents : notificationsToFutureEvents
                            .entrySet()) {
                        Notification notification = notificationToFutureEvents.getKey();
                        Future<Collection<DocumentEvent>> futureEvents = notificationToFutureEvents.getValue();
                        try {
                            Collection<DocumentEvent> events = futureEvents.get(processTimeout.toMillis(),
                                    TimeUnit.MILLISECONDS);
                            notificationsToDocumentEvents.put(notification, events);
                        } catch (ExecutionException | InterruptedException e) {
                            log.error("Failed to get document events for notification: " + notification, e);
                            failedNotifications.add(new FailedNotification(notification, e));
                        }
                    }

                    Iterator<Entry<Notification, Collection<DocumentEvent>>> notificationsToEventsIterator = notificationsToDocumentEvents
                            .entrySet().iterator();
                    while (notificationsToEventsIterator.hasNext()) {
                        Entry<Notification, Collection<DocumentEvent>> notificationToEvents = notificationsToEventsIterator
                                .next();
                        try {
                            notificationRepository.ensureTransactionActive(notificationToEvents.getKey());
                        } catch (Exception e) {
                            notificationsToEventsIterator.remove();
                            if (log.isWarnEnabled()) {
                                log.warn("Notification transaction no longer active, not processing: "
                                        + notificationToEvents.getKey(), e);
                            }
                        }
                    }

                    List<DocumentEvent> documentEvents = notificationsToDocumentEvents.values().stream()
                            .flatMap(Collection::stream).collect(Collectors.toList());

                    log.debug("Persisting {} document events via route {}: {}", documentEvents.size(),
                            exchange.getFromRouteId(), documentEvents);

                    try {
                        documentEventRepository.addNewDocumentEvents(documentEvents);
                    } catch (Exception e) {
                        log.error("Failed to persist new document events from notifications. Rolling "
                                + "back processing. Document events were: " + documentEvents, e);
                        notificationsToDocumentEvents.clear();
                    }

                    notificationRepository.markNotificationsProcessedOrFailed(
                            notificationsToDocumentEvents.keySet(), failedNotifications);
                });
    }
}