core/general_message_update_event_builder.go

Summary

Maintainability
A
2 hrs
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"
    "github.com/sym01/htmlsanitizer"
    "golang.org/x/exp/maps"
)

type GeneralMessageUpdateEventBuilder struct {
    clock.ClockConsumer
    uuid.UUIDConsumer

    partner         *Partner
    remoteCodeSpace string

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

type LineSection struct {
    LineRef   string
    FirstStop string
    LastStop  string
}

func NewGeneralMessageUpdateEventBuilder(partner *Partner) GeneralMessageUpdateEventBuilder {
    return GeneralMessageUpdateEventBuilder{
        partner:         partner,
        remoteCodeSpace: partner.RemoteCodeSpace(),

        MonitoringRefs: make(map[string]struct{}),
        LineRefs:       make(map[string]struct{}),
    }
}

func (builder *GeneralMessageUpdateEventBuilder) SetGeneralMessageDeliveryUpdateEvents(event *[]*model.SituationUpdateEvent, xmlResponse *sxml.XMLGeneralMessageDelivery, producerRef string) {
    xmlGeneralMessageEvents := xmlResponse.XMLGeneralMessages()
    if len(xmlGeneralMessageEvents) == 0 {
        return
    }

    for _, xmlGeneralMessageEvents := range xmlGeneralMessageEvents {
        builder.buildGeneralMessageUpdateEvent(event, xmlGeneralMessageEvents, producerRef)
    }
}

func (builder *GeneralMessageUpdateEventBuilder) SetGeneralMessageResponseUpdateEvents(event *[]*model.SituationUpdateEvent, xmlResponse *sxml.XMLGeneralMessageResponse) {
    xmlGeneralMessageEvents := xmlResponse.XMLGeneralMessages()
    if len(xmlGeneralMessageEvents) == 0 {
        return
    }

    for _, xmlGeneralMessageEvents := range xmlGeneralMessageEvents {
        builder.buildGeneralMessageUpdateEvent(event, xmlGeneralMessageEvents, xmlResponse.ProducerRef())
    }
}

func (builder *GeneralMessageUpdateEventBuilder) buildGeneralMessageUpdateEvent(event *[]*model.SituationUpdateEvent, xmlGeneralMessageEvent *sxml.XMLGeneralMessage, producerRef string) {
    if xmlGeneralMessageEvent.Content() == nil {
        return
    }

    situationEvent := &model.SituationUpdateEvent{
        Origin:        string(builder.partner.Slug()),
        CreatedAt:     builder.Clock().Now(),
        RecordedAt:    xmlGeneralMessageEvent.RecordedAtTime(),
        SituationCode: model.NewCode(builder.remoteCodeSpace, xmlGeneralMessageEvent.InfoMessageIdentifier()),
        Version:       xmlGeneralMessageEvent.InfoMessageVersion(),
        ProducerRef:   producerRef,
    }
    situationEvent.SetId(model.SituationUpdateRequestId(builder.NewUUID()))

    situationEvent.Format = xmlGeneralMessageEvent.FormatRef()
    situationEvent.Keywords = append(situationEvent.Keywords, xmlGeneralMessageEvent.InfoChannelRef())
    situationEvent.ReportType = builder.setReportType(xmlGeneralMessageEvent.InfoChannelRef())

    timeRange := &model.TimeRange{
        StartTime: xmlGeneralMessageEvent.RecordedAtTime(),
        EndTime:   xmlGeneralMessageEvent.ValidUntilTime(),
    }
    situationEvent.ValidityPeriods = []*model.TimeRange{timeRange}

    content := xmlGeneralMessageEvent.Content().(sxml.IDFGeneralMessageStructure)

    situationEvent.InternalTags = append(situationEvent.InternalTags, builder.partner.CollectSituationsInternalTags()...)
    builder.buildSituationAndDescriptionFromMessages(content.Messages(), situationEvent)

    builder.setAffects(situationEvent, &content)

    *event = append(*event, situationEvent)
}

func (builder *GeneralMessageUpdateEventBuilder) buildSituationAndDescriptionFromMessages(messages []*sxml.XMLMessage, event *model.SituationUpdateEvent) {
    sanitizer := htmlsanitizer.NewHTMLSanitizer()
    for _, xmlMessage := range messages {
        sanitizedMessageText, err := sanitizer.Sanitize([]byte(xmlMessage.MessageText()))
        if err != nil {
            logger.Log.Debugf("Cannot sanitize xml message: %v", err)
            continue
        }
        builder.buildSituationAndDescriptionFromMessage(xmlMessage.MessageType(), string(sanitizedMessageText), event)
    }
}

func (builder *GeneralMessageUpdateEventBuilder) buildSituationAndDescriptionFromMessage(messageType, messageText string, event *model.SituationUpdateEvent) {
    switch messageType {
    case "shortMessage":
        event.Summary = &model.SituationTranslatedString{
            DefaultValue: messageText,
        }
    default:
        event.Description = &model.SituationTranslatedString{
            DefaultValue: messageText,
        }
    }
}

func (builder *GeneralMessageUpdateEventBuilder) setReportType(infoChannelRef string) model.ReportType {
    switch infoChannelRef {
    case "Perturbation":
        return model.SituationReportTypeIncident
    default:
        return model.SituationReportTypeGeneral
    }

}

func (builder *GeneralMessageUpdateEventBuilder) setAffectedStopArea(event *model.SituationUpdateEvent, stopPointRef string) {
    stopPointRefCode := model.NewCode(builder.remoteCodeSpace, stopPointRef)
    stopArea, ok := builder.partner.Model().StopAreas().FindByCode(stopPointRefCode)
    if !ok {
        return
    }
    affect := model.NewAffectedStopArea()
    affect.StopAreaId = stopArea.Id()

    event.Affects = append(event.Affects, affect)

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

func (builder *GeneralMessageUpdateEventBuilder) setAffectedLine(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 *GeneralMessageUpdateEventBuilder) setAffectedRoute(lineId model.LineId, route string, affectedLines map[model.LineId]*model.AffectedLine) {
    affectedRoute := model.AffectedRoute{RouteRef: route}
    affectedLines[model.LineId(lineId)].AffectedRoutes =
        append(affectedLines[model.LineId(lineId)].AffectedRoutes, &affectedRoute)
}

func (builder *GeneralMessageUpdateEventBuilder) setAffectedDestination(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[model.LineId(lineId)].AffectedDestinations =
        append(affectedLines[model.LineId(lineId)].AffectedDestinations, &affectedDestination)

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

func (builder *GeneralMessageUpdateEventBuilder) setAffectedSection(section LineSection, affectedLines map[model.LineId]*model.AffectedLine) {
    LineRefCode := model.NewCode(builder.remoteCodeSpace, section.LineRef)
    line, ok := builder.partner.Model().Lines().FindByCode(LineRefCode)
    if !ok {
        return
    }

    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 if exists
    affectedLine, ok := affectedLines[line.Id()]
    if ok {
        affectedLine.AffectedSections = append(affectedLine.AffectedSections, affectedSection)

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

    // otherwise create new AffectedLine
    affectedLine = model.NewAffectedLine()
    affectedLine.LineId = line.Id()
    affectedLine.AffectedSections = append(affectedLine.AffectedSections, affectedSection)
    affectedLines[line.Id()] = affectedLine

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

func (builder *GeneralMessageUpdateEventBuilder) setAffects(event *model.SituationUpdateEvent, content *sxml.IDFGeneralMessageStructure) {
    affectedLines := make(map[model.LineId]*model.AffectedLine)
    for _, lineRef := range content.LineRefs() {
        builder.setAffectedLine(lineRef, affectedLines)
    }

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

        for _, destination := range content.DestinationRef() {
            builder.setAffectedDestination(lineId, destination, affectedLines)
        }
        for _, route := range content.RouteRef() {
            builder.setAffectedRoute(lineId, route, affectedLines)
        }
    }

    for _, section := range content.LineSections() {
        lineSection := LineSection{
            LineRef:   section.LineRef(),
            FirstStop: section.FirstStop(),
            LastStop:  section.LastStop(),
        }

        builder.setAffectedSection(lineSection, affectedLines)
    }

    // Fill affectedLines
    for _, affectedLine := range affectedLines {
        event.Affects = append(event.Affects, affectedLine)
    }

    // Fill affectedStopAreas
    for _, stopPointRef := range content.StopPointRef() {
        builder.setAffectedStopArea(event, stopPointRef)
    }
}