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

org.opentripplanner.ext.siri.updater.azure.SiriAzureETUpdater Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.ext.siri.updater.azure;

import com.azure.messaging.servicebus.ServiceBusErrorContext;
import com.azure.messaging.servicebus.ServiceBusReceivedMessageContext;
import jakarta.xml.bind.JAXBException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import javax.xml.stream.XMLStreamException;
import org.apache.hc.core5.net.URIBuilder;
import org.opentripplanner.ext.siri.SiriTimetableSnapshotSource;
import org.opentripplanner.updater.spi.ResultLogger;
import org.opentripplanner.updater.spi.UpdateResult;
import org.opentripplanner.updater.trip.UpdateIncrementality;
import org.opentripplanner.updater.trip.metrics.TripUpdateMetrics;
import org.rutebanken.siri20.util.SiriXml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure;
import uk.org.siri.siri20.ServiceDelivery;

public class SiriAzureETUpdater extends AbstractAzureSiriUpdater {

  private static final Logger LOG = LoggerFactory.getLogger(SiriAzureSXUpdater.class);

  private static final AtomicLong MESSAGE_COUNTER = new AtomicLong(0);

  private final LocalDate fromDateTime;
  private final SiriTimetableSnapshotSource snapshotSource;

  private final Consumer recordMetrics;

  public SiriAzureETUpdater(
    SiriAzureETUpdaterParameters config,
    SiriTimetableSnapshotSource snapshotSource
  ) {
    super(config);
    this.fromDateTime = config.getFromDateTime();
    this.snapshotSource = snapshotSource;
    this.recordMetrics = TripUpdateMetrics.streaming(config);
  }

  @Override
  protected void messageConsumer(ServiceBusReceivedMessageContext messageContext) {
    var message = messageContext.getMessage();
    MESSAGE_COUNTER.incrementAndGet();

    if (MESSAGE_COUNTER.get() % 100 == 0) {
      LOG.debug("Total SIRI-ET messages received={}", MESSAGE_COUNTER.get());
    }

    try {
      var updates = parseSiriEt(message.getBody().toString(), message.getMessageId());
      if (!updates.isEmpty()) {
        processMessage(updates);
      }
    } catch (JAXBException | XMLStreamException e) {
      LOG.error(e.getLocalizedMessage(), e);
    }
  }

  @Override
  protected void errorConsumer(ServiceBusErrorContext errorContext) {
    defaultErrorConsumer(errorContext);
  }

  @Override
  protected void initializeData(String url, Consumer consumer)
    throws URISyntaxException {
    if (url == null) {
      LOG.info("No history url set up for Siri Azure ET Updater");
      return;
    }

    URI uri = new URIBuilder(url)
      .addParameter("fromDateTime", fromDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE))
      .build();

    LOG.info("Fetching initial Siri ET data from {}, timeout is {} ms.", uri, timeout);
    var siri = fetchInitialSiriData(uri);

    if (siri.isEmpty()) {
      LOG.info("Got empty ET response from history endpoint");
      return;
    }

    // This is fine since runnables are scheduled after each other
    processHistory(siri.get());
  }

  private Future processMessage(List updates) {
    return super.saveResultOnGraph.execute(context -> {
      var result = snapshotSource.applyEstimatedTimetable(
        fuzzyTripMatching() ? context.siriFuzzyTripMatcher() : null,
        context.entityResolver(feedId),
        feedId,
        UpdateIncrementality.DIFFERENTIAL,
        updates
      );
      ResultLogger.logUpdateResultErrors(feedId, "siri-et", result);
      recordMetrics.accept(result);
    });
  }

  private void processHistory(ServiceDelivery siri) {
    var updates = siri.getEstimatedTimetableDeliveries();

    if (updates == null || updates.isEmpty()) {
      LOG.info("Did not receive any ET messages from history endpoint");
      return;
    }

    try {
      long t1 = System.currentTimeMillis();
      var f = processMessage(updates);
      f.get();
      LOG.info("Azure ET updater initialized in {} ms.", (System.currentTimeMillis() - t1));
    } catch (ExecutionException | InterruptedException e) {
      throw new SiriAzureInitializationException("Error applying history", e);
    }
  }

  private List parseSiriEt(String siriXmlMessage, String id)
    throws JAXBException, XMLStreamException {
    var siri = SiriXml.parseXml(siriXmlMessage);
    if (
      siri.getServiceDelivery() == null ||
      siri.getServiceDelivery().getEstimatedTimetableDeliveries() == null ||
      siri.getServiceDelivery().getEstimatedTimetableDeliveries().isEmpty()
    ) {
      if (siri.getHeartbeatNotification() != null) {
        LOG.debug("Received SIRI heartbeat message");
      } else {
        LOG.info("Empty Siri message {}: {}", id, siriXmlMessage);
      }
      return new ArrayList<>();
    }

    return siri.getServiceDelivery().getEstimatedTimetableDeliveries();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy