core/situation_exchange_update_event_builder.go

Summary

Maintainability
C
1 day
Test Coverage
package core

import (
    "bitbucket.org/enroute-mobi/ara/clock"
    "bitbucket.org/enroute-mobi/ara/logger"
    "bitbucket.org/enroute-mobi/ara/model"
    "bitbucket.org/enroute-mobi/ara/siri/sxml"
    "bitbucket.org/enroute-mobi/ara/uuid"
    "golang.org/x/exp/maps"
)

type SituationExchangeUpdateEventBuilder struct {
    clock.ClockConsumer
    uuid.UUIDConsumer

    partner         *Partner
    remoteCodeSpace string

    MonitoringRefs map[string]struct{}
    LineRefs       map[string]struct{}
}

type affectedModels struct {
    affectedAllLines  *model.AffectedAllLines
    affectedLines     map[model.LineId]*model.AffectedLine
    affectedStopAreas map[model.StopAreaId]*model.AffectedStopArea
}

func NewSituationExchangeUpdateEventBuilder(partner *Partner) SituationExchangeUpdateEventBuilder {
    return SituationExchangeUpdateEventBuilder{
        partner:         partner,
        remoteCodeSpace: partner.RemoteCodeSpace(),
        MonitoringRefs:  make(map[string]struct{}),
        LineRefs:        make(map[string]struct{}),
    }
}

func (builder *SituationExchangeUpdateEventBuilder) SetSituationExchangeDeliveryUpdateEvents(events *CollectUpdateEvents, xmlSituationExchangeDelivery *sxml.XMLSituationExchangeDelivery, producerRef string) {
    for _, xmlSituation := range xmlSituationExchangeDelivery.Situations() {
        builder.buildSituationExchangeUpdateEvent(events, xmlSituation, producerRef)
    }
}

func (builder *SituationExchangeUpdateEventBuilder) SetSituationExchangeUpdateEvents(event *CollectUpdateEvents, xmlResponse *sxml.XMLSituationExchangeResponse) {
    xmlSituationExchangeDeliveries := xmlResponse.SituationExchangeDeliveries()
    if len(xmlSituationExchangeDeliveries) == 0 {
        return
    }

    for _, xmlSituationExchangeDelivery := range xmlSituationExchangeDeliveries {
        builder.SetSituationExchangeDeliveryUpdateEvents(event, xmlSituationExchangeDelivery, xmlResponse.ProducerRef())
    }
}

func (builder *SituationExchangeUpdateEventBuilder) buildSituationExchangeUpdateEvent(event *CollectUpdateEvents, xmlSituation *sxml.XMLPtSituationElement, producerRef string) {
    if len(xmlSituation.Affects()) == 0 {
        return
    }

    situationEvent := &model.SituationUpdateEvent{
        Origin:         string(builder.partner.Slug()),
        CreatedAt:      builder.Clock().Now(),
        RecordedAt:     xmlSituation.RecordedAtTime(),
        SituationCode:  model.NewCode(builder.remoteCodeSpace, xmlSituation.SituationNumber()),
        Version:        xmlSituation.Version(),
        ProducerRef:    producerRef,
        ParticipantRef: xmlSituation.ParticipantRef(),
        VersionedAt:    xmlSituation.VersionedAtTime(),
    }
    situationEvent.SetId(model.SituationUpdateRequestId(builder.NewUUID()))

    situationEvent.Keywords = append(situationEvent.Keywords, xmlSituation.Keywords()...)
    situationEvent.ReportType = model.ReportType(xmlSituation.ReportType())

    var progress model.SituationProgress
    if err := progress.FromString(xmlSituation.Progress()); err == nil {
        situationEvent.Progress = progress
    } else {
        logger.Log.Debugf("%v", err)
    }

    var severity model.SituationSeverity
    if err := severity.FromString(xmlSituation.Severity()); err == nil {
        situationEvent.Severity = severity
    } else {
        logger.Log.Debugf("%v", err)
    }

    var reality model.SituationReality
    if err := reality.FromString(xmlSituation.Reality()); err == nil {
        situationEvent.Reality = reality
    } else {
        logger.Log.Debugf("%v", err)
    }

    // Summary
    s := model.NewTranslatedStringFromMap(xmlSituation.Summaries())
    situationEvent.Summary = s

    // Description
    d := model.NewTranslatedStringFromMap(xmlSituation.Descriptions())
    situationEvent.Description = d

    var alertCause model.SituationAlertCause
    if err := alertCause.FromString(xmlSituation.AlertCause()); err == nil {
        situationEvent.AlertCause = alertCause
    } else {
        logger.Log.Debugf("%v", err)
    }

    situationEvent.InternalTags = append(situationEvent.InternalTags, builder.partner.CollectSituationsInternalTags()...)

    for _, validityPeriod := range xmlSituation.ValidityPeriods() {
        period := &model.TimeRange{
            StartTime: validityPeriod.StartTime(),
            EndTime:   validityPeriod.EndTime(),
        }

        situationEvent.ValidityPeriods = append(situationEvent.ValidityPeriods, period)
    }

    for _, publicationWindow := range xmlSituation.PublicationWindows() {
        window := &model.TimeRange{
            StartTime: publicationWindow.StartTime(),
            EndTime:   publicationWindow.EndTime(),
        }

        situationEvent.PublicationWindows = append(
            situationEvent.PublicationWindows,
            window)
    }

    for _, affect := range xmlSituation.Affects() {
        affectedModels := builder.buildAffect(affect)
        if affectedModels.affectedAllLines != nil {
            situationEvent.Affects = append(situationEvent.Affects, affectedModels.affectedAllLines)
        }
        for _, affectedLine := range affectedModels.affectedLines {
            situationEvent.Affects = append(situationEvent.Affects, affectedLine)
        }
        for _, affectedStopAreas := range affectedModels.affectedStopAreas {
            situationEvent.Affects = append(situationEvent.Affects, affectedStopAreas)
        }
    }

    for _, consequence := range xmlSituation.Consequences() {
        builder.setConsequence(situationEvent, consequence)
    }

    for _, xmlPublishToWeb := range xmlSituation.PublishToWebActions() {
        builder.setPublishToWebAction(situationEvent, xmlPublishToWeb)
    }

    for _, xmlPublishToMobile := range xmlSituation.PublishToMobileActions() {
        builder.setPublishToMobileAction(situationEvent, xmlPublishToMobile)
    }

    for _, xmlPublishToDisplay := range xmlSituation.PublishToDisplayActions() {
        builder.setPublishToDisplayAction(situationEvent, xmlPublishToDisplay)
    }

    event.Situations = append(event.Situations, situationEvent)
}

func (builder *SituationExchangeUpdateEventBuilder) setPublishActionCommon(xmlCommon sxml.XMLCommonPublishingAction) (actionCommon model.ActionCommon) {
    actionCommon.Name = xmlCommon.Name()
    actionCommon.ActionType = xmlCommon.Type()
    actionCommon.Value = xmlCommon.Value()

    // Prompt
    actionCommon.Prompt = model.NewTranslatedStringFromMap(xmlCommon.Prompt())

    // scopeType
    var scopeType model.SituationScopeType
    if err := scopeType.FromString(xmlCommon.ScopeType()); err == nil {
        actionCommon.ScopeType = scopeType
    } else {
        logger.Log.Debugf("%v", err)
    }

    // affects
    for _, affect := range xmlCommon.Affects() {
        affectedModels := builder.buildAffect(affect)
        if affectedModels.affectedAllLines != nil {
            actionCommon.Affects = append(actionCommon.Affects, affectedModels.affectedAllLines)
        }
        for _, affectedLine := range affectedModels.affectedLines {
            actionCommon.Affects = append(actionCommon.Affects, affectedLine)
        }
        for _, affectedStopAreas := range affectedModels.affectedStopAreas {
            actionCommon.Affects = append(actionCommon.Affects, affectedStopAreas)
        }
    }

    // ActionStatus
    var actionStatus model.SituationActionStatus
    if err := actionStatus.FromString(xmlCommon.ActionStatus()); err == nil {
        actionCommon.ActionStatus = actionStatus
    } else {
        logger.Log.Debugf("%v", err)
    }
    // Description
    d := model.NewTranslatedStringFromMap(xmlCommon.Descriptions())
    actionCommon.Description = d

    // PublicationWindows
    for _, publicationWindow := range xmlCommon.PublicationWindows() {
        window := &model.TimeRange{
            StartTime: publicationWindow.StartTime(),
            EndTime:   publicationWindow.EndTime(),
        }

        actionCommon.PublicationWindows = append(
            actionCommon.PublicationWindows,
            window)
    }
    return
}

func (builder *SituationExchangeUpdateEventBuilder) setPublishToDisplayAction(situationEvent *model.SituationUpdateEvent, xmlPublishToDisplay *sxml.XMLPublishToDisplayAction) {
    da := &model.PublishToDisplayAction{}

    da.ActionCommon = builder.setPublishActionCommon(xmlPublishToDisplay.XMLCommonPublishingAction)

    if xmlPublishToDisplay.OnBoard() != nil {
        da.OnBoard = xmlPublishToDisplay.OnBoard()
    }

    if xmlPublishToDisplay.OnPlace() != nil {
        da.OnPlace = xmlPublishToDisplay.OnPlace()
    }

    situationEvent.PublishToDisplayActions = append(situationEvent.PublishToDisplayActions, da)
}

func (builder *SituationExchangeUpdateEventBuilder) setPublishToMobileAction(situationEvent *model.SituationUpdateEvent, xmlPublishToMobile *sxml.XMLPublishToMobileAction) {
    ma := &model.PublishToMobileAction{}

    ma.ActionCommon = builder.setPublishActionCommon(xmlPublishToMobile.XMLCommonPublishingAction)

    if xmlPublishToMobile.Incidents() != nil {
        ma.Incidents = xmlPublishToMobile.Incidents()
    }

    if xmlPublishToMobile.HomePage() != nil {
        ma.HomePage = xmlPublishToMobile.HomePage()
    }

    situationEvent.PublishToMobileActions = append(situationEvent.PublishToMobileActions, ma)
}

func (builder *SituationExchangeUpdateEventBuilder) setPublishToWebAction(situationEvent *model.SituationUpdateEvent, xmlPublishToWeb *sxml.XMLPublishToWebAction) {
    pa := &model.PublishToWebAction{}

    pa.ActionCommon = builder.setPublishActionCommon(xmlPublishToWeb.XMLCommonPublishingAction)

    if xmlPublishToWeb.Incidents() != nil {
        pa.Incidents = xmlPublishToWeb.Incidents()
    }

    if xmlPublishToWeb.HomePage() != nil {
        pa.HomePage = xmlPublishToWeb.HomePage()
    }

    if xmlPublishToWeb.Ticker() != nil {
        pa.Ticker = xmlPublishToWeb.Ticker()
    }

    if len(xmlPublishToWeb.SocialNetworks()) != 0 {
        pa.SocialNetworks = xmlPublishToWeb.SocialNetworks()
    }

    situationEvent.PublishToWebActions = append(situationEvent.PublishToWebActions, pa)
}

func (builder *SituationExchangeUpdateEventBuilder) setConsequence(situationEvent *model.SituationUpdateEvent, xmlConsequence *sxml.XMLConsequence) {
    consequence := &model.Consequence{}
    for _, xmlPeriod := range xmlConsequence.Periods() {
        period := &model.TimeRange{
            StartTime: xmlPeriod.StartTime(),
            EndTime:   xmlPeriod.EndTime(),
        }
        consequence.Periods = append(consequence.Periods, period)
    }

    var condition model.SituationCondition
    if err := condition.FromString(xmlConsequence.Condition()); err == nil {
        consequence.Condition = condition
    } else {
        logger.Log.Debugf("Condition: %v", err)
    }

    var severity model.SituationSeverity
    if err := severity.FromString(xmlConsequence.Severity()); err == nil {
        consequence.Severity = severity
    } else {
        logger.Log.Debugf("Consequence: %v", err)
    }

    for _, affect := range xmlConsequence.Affects() {
        affectedModels := builder.buildAffect(affect)
        if affectedModels.affectedAllLines != nil {
            consequence.Affects = append(consequence.Affects, affectedModels.affectedAllLines)
        }
        for _, affectedLine := range affectedModels.affectedLines {
            consequence.Affects = append(consequence.Affects, affectedLine)
        }
        for _, affectedStopAreas := range affectedModels.affectedStopAreas {
            consequence.Affects = append(consequence.Affects, affectedStopAreas)
        }
    }

    if xmlConsequence.HasBlocking() {
        blocking := &model.Blocking{
            JourneyPlanner: xmlConsequence.JourneyPlanner(),
            RealTime:       xmlConsequence.RealTime(),
        }
        consequence.Blocking = blocking
    }

    situationEvent.Consequences = append(situationEvent.Consequences, consequence)
}

func (builder *SituationExchangeUpdateEventBuilder) buildAffectedStopArea(xmlAffectedStopPoint *sxml.XMLAffectedStopPoint, affectedStopAreas map[model.StopAreaId]*model.AffectedStopArea) {
    stopPointRef := xmlAffectedStopPoint.StopPointRef()
    stopPointRefCode := model.NewCode(builder.remoteCodeSpace, stopPointRef)
    stopArea, ok := builder.partner.Model().StopAreas().FindByCode(stopPointRefCode)
    if !ok {
        return
    }
    affect := model.NewAffectedStopArea()
    affect.StopAreaId = stopArea.Id()

    for _, lineRef := range xmlAffectedStopPoint.LineRefs() {
        LineRefCode := model.NewCode(builder.remoteCodeSpace, lineRef)
        line, ok := builder.partner.Model().Lines().FindByCode(LineRefCode)
        if !ok {
            logger.Log.Debugf("Unknown Line with code %s", LineRefCode.String())
            continue
        }
        builder.LineRefs[LineRefCode.Value()] = struct{}{}
        affect.LineIds = append(affect.LineIds, line.Id())
    }

    affectedStopAreas[affect.StopAreaId] = affect

    // Logging
    builder.MonitoringRefs[stopPointRefCode.Value()] = struct{}{}
}

func (builder *SituationExchangeUpdateEventBuilder) buildAffectedLine(lineRef string, affectedLines map[model.LineId]*model.AffectedLine) {
    LineRefCode := model.NewCode(builder.remoteCodeSpace, lineRef)
    line, ok := builder.partner.Model().Lines().FindByCode(LineRefCode)
    if !ok {
        return
    }
    affect := model.NewAffectedLine()
    affect.LineId = line.Id()
    affectedLines[affect.LineId] = affect
    builder.LineRefs[LineRefCode.Value()] = struct{}{}
}

func (builder *SituationExchangeUpdateEventBuilder) buildAffectedRoute(lineId model.LineId, xmlAffectedRoute *sxml.XMLAffectedRoute, affectedLines map[model.LineId]*model.AffectedLine) {
    affectedRoute := &model.AffectedRoute{}
    if xmlAffectedRoute.RouteRef() != "" {
        affectedRoute.RouteRef = xmlAffectedRoute.RouteRef()
    }
    for _, xmlAffectedStopPoint := range xmlAffectedRoute.AffectedStopPoints() {
        stopPointRef := xmlAffectedStopPoint.StopPointRef()
        stopPointRefCode := model.NewCode(builder.remoteCodeSpace, stopPointRef)
        stopArea, ok := builder.partner.Model().StopAreas().FindByCode(stopPointRefCode)
        if !ok {
            continue

        }
        affectedRoute.StopAreaIds = append(affectedRoute.StopAreaIds, stopArea.Id())
        builder.MonitoringRefs[stopPointRefCode.Value()] = struct{}{}
    }

    if affectedRoute != (&model.AffectedRoute{}) {
        affectedLines[lineId].AffectedRoutes =
            append(affectedLines[lineId].AffectedRoutes, affectedRoute)
    }
}

func (builder *SituationExchangeUpdateEventBuilder) buildAffectedDestination(lineId model.LineId, destination string, affectedLines map[model.LineId]*model.AffectedLine) {
    destinationCode := model.NewCode(builder.remoteCodeSpace, destination)
    stopArea, ok := builder.partner.Model().StopAreas().FindByCode(destinationCode)
    if !ok {
        return
    }

    affectedDestination := model.AffectedDestination{StopAreaId: stopArea.Id()}
    affectedLines[lineId].AffectedDestinations =
        append(affectedLines[lineId].AffectedDestinations, &affectedDestination)

    // Logging
    builder.MonitoringRefs[destinationCode.Value()] = struct{}{}
}

func (builder *SituationExchangeUpdateEventBuilder) buildAffectedSection(lineId model.LineId, section *sxml.XMLAffectedSection, affectedLines map[model.LineId]*model.AffectedLine) {
    firstStopRef := section.FirstStop()
    firstStopCode := model.NewCode(builder.remoteCodeSpace, firstStopRef)
    firstStopArea, ok := builder.partner.Model().StopAreas().FindByCode(firstStopCode)
    if !ok {
        return
    }
    lastStopRef := section.LastStop()
    lastStopCode := model.NewCode(builder.remoteCodeSpace, lastStopRef)
    lastStopArea, ok := builder.partner.Model().StopAreas().FindByCode(lastStopCode)
    if !ok {
        return
    }

    affectedSection := &model.AffectedSection{
        FirstStop: firstStopArea.Id(),
        LastStop:  lastStopArea.Id(),
    }

    // Fill already existing AffectedLine
    affectedLine, ok := affectedLines[lineId]
    if ok {
        affectedLine.AffectedSections = append(affectedLine.AffectedSections, affectedSection)
        builder.MonitoringRefs[firstStopCode.Value()] = struct{}{}
        builder.MonitoringRefs[lastStopCode.Value()] = struct{}{}
        return
    }

    // Logging
    builder.MonitoringRefs[firstStopCode.Value()] = struct{}{}
    builder.MonitoringRefs[lastStopCode.Value()] = struct{}{}
}

func (builder *SituationExchangeUpdateEventBuilder) buildAffect(xmlAffect *sxml.XMLAffect) (affects *affectedModels) {
    models := affectedModels{
        affectedLines:     make(map[model.LineId]*model.AffectedLine),
        affectedStopAreas: make(map[model.StopAreaId]*model.AffectedStopArea),
    }

    for _, xmlAffectedNetwork := range xmlAffect.AffectedNetworks() {
        if xmlAffectedNetwork.AllLines() {
            models.affectedAllLines = model.NewAffectedAllLines()
            continue
        }
        for _, lineRef := range xmlAffectedNetwork.LineRefs() {
            builder.buildAffectedLine(lineRef, models.affectedLines)
        }

        if len(models.affectedLines) == 1 {
            // get the LineId
            lineId := maps.Keys(models.affectedLines)[0]

            for _, xmlAffectedRoute := range xmlAffectedNetwork.AffectedRoutes() {
                builder.buildAffectedRoute(lineId, xmlAffectedRoute, models.affectedLines)
            }
            for _, section := range xmlAffectedNetwork.AffectedSections() {
                builder.buildAffectedSection(lineId, section, models.affectedLines)
            }
            for _, destination := range xmlAffectedNetwork.AffectedDestinations() {
                builder.buildAffectedDestination(lineId, destination, models.affectedLines)
            }
        }
    }

    for _, xmlAffectedStopPoint := range xmlAffect.AffectedStopPoints() {
        builder.buildAffectedStopArea(xmlAffectedStopPoint, models.affectedStopAreas)
    }

    affects = &models
    return affects
}