model/situation.go

Summary

Maintainability
A
3 hrs
Test Coverage
package model

import (
    "encoding/json"
    "sync"
    "time"

    "bitbucket.org/enroute-mobi/ara/uuid"
)

type SituationId string

type Message struct {
    Content             string `json:"MessageText,omitempty"`
    Type                string `json:"MessageType,omitempty"`
    NumberOfLines       int    `json:",omitempty"`
    NumberOfCharPerLine int    `json:",omitempty"`
}

const (
    SituationReportTypeGeneral  ReportType    = "general"
    SituationReportTypeIncident ReportType    = "incident"
    SituationTypeLine           SituationType = "Line"
    SituationTypeStopArea       SituationType = "StopArea"
)

type ReportType string
type SituationType string

type SituationTranslatedString struct {
    DefaultValue string            `json:",omitempty"`
    Translations map[string]string `json:",omitempty"`
}

type Situation struct {
    model Model
    CodeConsumer
    id     SituationId
    Origin string

    RecordedAt time.Time
    Version    int `json:",omitempty"`

    VersionedAt        time.Time
    ValidityPeriods    []*TimeRange
    PublicationWindows []*TimeRange

    Progress       SituationProgress          `json:",omitempty"`
    Severity       SituationSeverity          `json:",omitempty"`
    Keywords       []string                   `json:",omitempty"`
    ReportType     ReportType                 `json:",omitempty"`
    AlertCause     SituationAlertCause        `json:",omitempty"`
    Reality        SituationReality           `json:",omitempty"`
    ProducerRef    string                     `json:",omitempty"`
    Format         string                     `json:",omitempty"`
    InternalTags   []string                   `json:",omitempty"`
    ParticipantRef string                     `json:",omitempty"`
    Summary        *SituationTranslatedString `json:",omitempty"`
    Description    *SituationTranslatedString `json:",omitempty"`

    Affects      []Affect       `json:",omitempty"`
    Consequences []*Consequence `json:",omitempty"`
}

type Consequence struct {
    Periods  []*TimeRange      `json:",omitempty"`
    Severity SituationSeverity `json:",omitempty"`
    Affects  []Affect          `json:",omitempty"`
    Blocking *Blocking         `json:",omitempty"`
}

type Blocking struct {
    JourneyPlanner bool
    RealTime       bool
}

// SubTypes of Affect
type Affect interface {
    GetType() SituationType
    GetId() ModelId
}

type AffectedStopArea struct {
    StopAreaId StopAreaId `json:",omitempty"`
    LineIds    []LineId   `json:",omitempty"`
}

func (a AffectedStopArea) GetId() ModelId {
    return ModelId(a.StopAreaId)
}

func (a AffectedStopArea) GetType() SituationType {
    return SituationTypeStopArea
}

func NewAffectedStopArea() *AffectedStopArea {
    return &AffectedStopArea{}
}

type AffectedLine struct {
    LineId               LineId                 `json:",omitempty"`
    AffectedDestinations []*AffectedDestination `json:",omitempty"`
    AffectedSections     []*AffectedSection     `json:",omitempty"`
    AffectedRoutes       []*AffectedRoute       `json:",omitempty"`
}

type AffectedDestination struct {
    StopAreaId StopAreaId
}

type AffectedSection struct {
    FirstStop StopAreaId
    LastStop  StopAreaId
}

type AffectedRoute struct {
    RouteRef    string       `json:",omitempty"`
    StopAreaIds []StopAreaId `json:",omitempty"`
}

func (a AffectedLine) GetId() ModelId {
    return ModelId(a.LineId)
}

func (a AffectedLine) GetType() SituationType {
    return SituationTypeLine
}

func NewAffectedLine() *AffectedLine {
    return &AffectedLine{}
}

type TimeRange struct {
    StartTime time.Time `json:",omitempty"`
    EndTime   time.Time `json:",omitempty"`
}

func NewSituation(model Model) *Situation {
    situation := &Situation{
        model: model,
    }

    situation.codes = make(Codes)
    return situation
}

func (situation *Situation) Id() SituationId {
    return situation.id
}

func (situation *Situation) Save() (ok bool) {
    ok = situation.model.Situations().Save(situation)
    return
}

func (c *Consequence) UnmarshalJSON(data []byte) error {
    type Alias Consequence

    aux := &struct {
        Affects []json.RawMessage
        *Alias
    }{
        Alias: (*Alias)(c),
    }

    err := json.Unmarshal(data, aux)
    if err != nil {
        return err
    }
    if aux.Affects != nil {
        for _, v := range aux.Affects {
            var affectedSubtype = struct {
                Type SituationType
            }{}
            err = json.Unmarshal(v, &affectedSubtype)
            if err != nil {
                return err
            }
            switch affectedSubtype.Type {
            case "StopArea":
                a := NewAffectedStopArea()
                json.Unmarshal(v, a)
                c.Affects = append(c.Affects, a)
            case "Line":
                l := NewAffectedLine()
                json.Unmarshal(v, l)
                c.Affects = append(c.Affects, l)
            }
        }
    }
    return nil
}

func (situation *Situation) UnmarshalJSON(data []byte) error {
    type Alias Situation

    aux := &struct {
        Codes   map[string]string
        Affects []json.RawMessage
        *Alias
    }{
        Alias: (*Alias)(situation),
    }

    err := json.Unmarshal(data, aux)
    if err != nil {
        return err
    }
    if aux.Affects != nil {
        for _, v := range aux.Affects {
            var affectedSubtype = struct {
                Type SituationType
            }{}
            err = json.Unmarshal(v, &affectedSubtype)
            if err != nil {
                return err
            }
            switch affectedSubtype.Type {
            case "StopArea":
                a := NewAffectedStopArea()
                json.Unmarshal(v, a)
                situation.Affects = append(situation.Affects, a)
            case "Line":
                l := NewAffectedLine()
                json.Unmarshal(v, l)
                situation.Affects = append(situation.Affects, l)
            }
        }
    }
    if aux.Codes != nil {
        situation.CodeConsumer.codes = NewCodesFromMap(aux.Codes)
    }
    return nil
}

func (affect AffectedStopArea) MarshalJSON() ([]byte, error) {
    type Alias AffectedStopArea
    aux := struct {
        Type SituationType
        Alias
    }{
        Type:  SituationTypeStopArea,
        Alias: (Alias)(affect),
    }

    return json.Marshal(&aux)
}

func (affect AffectedLine) MarshalJSON() ([]byte, error) {
    type Alias AffectedLine
    aux := struct {
        Type SituationType
        Alias
    }{
        Type:  SituationTypeLine,
        Alias: (Alias)(affect),
    }

    return json.Marshal(&aux)
}

func (situation *Situation) MarshalJSON() ([]byte, error) {
    type Alias Situation
    aux := struct {
        Codes       Codes      `json:",omitempty"`
        RecordedAt  *time.Time `json:",omitempty"`
        VersionedAt *time.Time `json:",omitempty"`
        *Alias
        Id      SituationId
        Affects []Affect `json:",omitempty"`
    }{
        Id:    situation.id,
        Alias: (*Alias)(situation),
    }

    if !situation.Codes().Empty() {
        aux.Codes = situation.Codes()
    }

    if !situation.RecordedAt.IsZero() {
        aux.RecordedAt = &situation.RecordedAt
    }

    if !situation.VersionedAt.IsZero() {
        aux.VersionedAt = &situation.VersionedAt
    }

    if len(situation.Affects) != 0 {
        aux.Affects = situation.Affects
    }

    return json.Marshal(&aux)
}

func (situation *Situation) GMValidUntil() time.Time {
    if len(situation.ValidityPeriods) == 0 {
        return time.Time{}
    }
    return situation.ValidityPeriods[0].EndTime
}

func (situation *Situation) GetGMChannel() (string, bool) {
    switch {
    case situation.containsKeyword("Perturbation"):
        return "Perturbation", true
    case situation.containsKeyword("Information"):
        return "Information", true
    case situation.containsKeyword("Commercial"):
        return "Commercial", true
    default:
        return "", false
    }
}

func (situation *Situation) containsKeyword(str string) bool {
    if len(situation.Keywords) == 0 {
        return false
    }
    for _, v := range situation.Keywords {
        if v == str {
            return true
        }
    }
    return false
}

type MemorySituations struct {
    uuid.UUIDConsumer

    model *MemoryModel

    mutex            *sync.RWMutex
    GMbroadcastEvent func(event SituationBroadcastEvent)
    SXbroadcastEvent func(event SituationBroadcastEvent)
    byIdentifier     map[SituationId]*Situation
}

type Situations interface {
    uuid.UUIDInterface

    New() Situation
    Find(id SituationId) (Situation, bool)
    FindByCode(code Code) (Situation, bool)
    FindAll() []Situation
    Save(situation *Situation) bool
    Delete(situation *Situation) bool
}

func NewMemorySituations() *MemorySituations {
    return &MemorySituations{
        mutex:        &sync.RWMutex{},
        byIdentifier: make(map[SituationId]*Situation),
    }
}

func (manager *MemorySituations) New() Situation {
    situation := NewSituation(manager.model)
    return *situation
}

func (manager *MemorySituations) Find(id SituationId) (Situation, bool) {
    manager.mutex.RLock()
    situation, ok := manager.byIdentifier[id]
    manager.mutex.RUnlock()

    if ok {
        return *situation, true
    }
    return Situation{}, false
}

func (manager *MemorySituations) FindAll() (situations []Situation) {
    manager.mutex.RLock()
    defer manager.mutex.RUnlock()

    if len(manager.byIdentifier) == 0 {
        return []Situation{}
    }
    for _, situation := range manager.byIdentifier {
        situations = append(situations, *situation)
    }
    return
}

func (manager *MemorySituations) FindByCode(code Code) (Situation, bool) {
    manager.mutex.RLock()
    defer manager.mutex.RUnlock()

    for _, situation := range manager.byIdentifier {
        situationCode, _ := situation.Code(code.CodeSpace())
        if situationCode.Value() == code.Value() {
            return *situation, true
        }
    }
    return Situation{}, false
}

func (manager *MemorySituations) Save(situation *Situation) bool {
    manager.mutex.Lock()
    defer manager.mutex.Unlock()

    if situation.Id() == "" {
        situation.id = SituationId(manager.NewUUID())
    }
    situation.model = manager.model
    manager.byIdentifier[situation.Id()] = situation

    event := SituationBroadcastEvent{
        SituationId: situation.id,
    }

    if manager.GMbroadcastEvent != nil {
        manager.GMbroadcastEvent(event)
    }

    if manager.SXbroadcastEvent != nil {
        manager.SXbroadcastEvent(event)
    }
    return true
}

func (manager *MemorySituations) Delete(situation *Situation) bool {
    manager.mutex.Lock()
    defer manager.mutex.Unlock()

    delete(manager.byIdentifier, situation.Id())
    return true
}