# All files / src/middleware messageStore.coffee

22.73% Statements 20/88
0% Branches 0/47
0% Functions 0/14
23.26% Lines 20/86

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    2031x
    1x
    1x
    1x
    1x
    1x

    1x
    1x
    1x
    1x
    1x
    1x

    1x
    1x

    1x

    1x

    1x

    1x

    1x

    1x

     transactions = require "../model/transactions"
    logger = require "winston"
    Q = require "q"
    _ = require 'lodash'
    AutoRetry = require('../model/autoRetry').AutoRetry
    autoRetryUtils = require '../autoRetry'

    config = require '../config/config'
    statsdServer = config.get 'statsd'
    application = config.get 'application'
    SDC = require 'statsd-client'
    stats = require '../stats'
    os = require 'os'

    domain = "#{os.hostname()}.#{application.name}.appMetrics"
    sdc = new SDC statsdServer

    exports.transactionStatus = transactionStatus =
      PROCESSING: 'Processing'
      SUCCESSFUL: 'Successful'
      COMPLETED: 'Completed'
      COMPLETED_W_ERR: 'Completed with error(s)'
      FAILED: 'Failed'

    copyMapWithEscapedReservedCharacters = (map) ->
      escapedMap = {}
      for k, v of map
        if k.indexOf('.')>-1 or k.indexOf('$')>-1
          k = k.replace('.', '\uff0e').replace('$', '\uff04')
        escapedMap[k] = v
      return escapedMap

    exports.storeTransaction = (ctx, done) ->
      logger.info 'Storing request metadata for inbound transaction'

      ctx.requestTimestamp = new Date()

      headers = copyMapWithEscapedReservedCharacters ctx.header

      tx = new transactions.Transaction
        status: transactionStatus.PROCESSING
        clientID: ctx.authenticated?._id
        channelID: ctx.authorisedChannel._id
        clientIP: ctx.ip
        request:
          host: ctx.host?.split(':')[0]
          port: ctx.host?.split(':')[1]
          path: ctx.path
          headers: headers
          querystring: ctx.querystring
          body: ctx.body
          method: ctx.method
          timestamp: ctx.requestTimestamp

      if ctx.parentID && ctx.taskID
        tx.parentID = ctx.parentID
        tx.taskID = ctx.taskID

      if ctx.currentAttempt
        tx.autoRetryAttempt = ctx.currentAttempt

      # check if channel request body is false and remove - or if request body is empty
      if ctx.authorisedChannel.requestBody == false || tx.request.body == ''
        # reset request body
        tx.request.body = ''
        # check if method is POST|PUT|PATCH - rerun not possible without request body
        if ctx.method == 'POST' or ctx.method == 'PUT' or ctx.method == 'PATCH'
          tx.canRerun = false

      tx.save (err, tx) ->
        if err
          logger.error 'Could not save transaction metadata: ' + err
          return done err
        else
          ctx.transactionId = tx._id
          ctx.header['X-OpenHIM-TransactionID'] = tx._id.toString()
          return done null, tx

    exports.storeResponse = (ctx, done) ->

      headers = copyMapWithEscapedReservedCharacters ctx.response.header

      res =
        status: ctx.response.status
        headers: headers
        body: if not ctx.response.body then "" else ctx.response.body.toString()
        timestamp: ctx.response.timestamp

      # check if channel response body is false and remove
      if ctx.authorisedChannel.responseBody == false
        # reset request body - primary route
        res.body = ''

      update =
        response: res
        error: ctx.error

      # Set status from mediator
      if ctx.mediatorResponse?.status?
        update.status = ctx.mediatorResponse.status

      if ctx.mediatorResponse
        update.orchestrations = ctx.mediatorResponse.orchestrations if ctx.mediatorResponse.orchestrations
        update.properties = ctx.mediatorResponse.properties if ctx.mediatorResponse.properties

      transactions.Transaction.findOneAndUpdate { _id: ctx.transactionId }, update , { runValidators: true }, (err, tx) ->
        if err
          logger.error 'Could not save response metadata for transaction: ' + ctx.transactionId + '. ' + err
          return done err
        if tx is undefined or tx is null
          logger.error 'Could not find transaction: ' + ctx.transactionId
          return done err
        logger.info "stored primary response for #{tx._id}"
        return done()

    exports.storeNonPrimaryResponse = (ctx, routeObject, done) ->
      # check if channel response body is false and remove
      if ctx.authorisedChannel.responseBody == false
        routeObject.response.body = ''

      if ctx.transactionId?
        transactions.Transaction.findByIdAndUpdate ctx.transactionId, {$push: { "routes": routeObject } } , (err,tx) ->

          if err
            logger.error err
          done tx
      else
        logger.error "the request has no transactionId"

    exports.setFinalStatus = setFinalStatus = (ctx, callback) ->
      transactionId = ''
      if ctx.request?.header?["X-OpenHIM-TransactionID"]
        transactionId = ctx.request.header["X-OpenHIM-TransactionID"]
      else
        transactionId = ctx.transactionId.toString()

      transactions.Transaction.findById transactionId, (err, tx) ->
        update = {}

        if ctx.mediatorResponse?.status?
          logger.info "The transaction status has been set to #{ctx.mediatorResponse.status} by the mediator"
        else
          routeFailures = false
          routeSuccess = true
          if ctx.routes
            for route in ctx.routes
              if 500 <= route.response.status <= 599
                routeFailures = true
              if not (200 <= route.response.status <= 299)
                routeSuccess = false

          if (500 <= ctx.response.status <= 599)
            tx.status = transactionStatus.FAILED
          else
            if routeFailures
              tx.status = transactionStatus.COMPLETED_W_ERR
            if (200 <= ctx.response.status <= 299) && routeSuccess
              tx.status = transactionStatus.SUCCESSFUL
            if (400 <= ctx.response.status <= 499) && routeSuccess
              tx.status = transactionStatus.COMPLETED

          # In all other cases mark as completed
          if tx.status is 'Processing'
            tx.status = transactionStatus.COMPLETED

          ctx.transactionStatus = tx.status

          logger.info "Final status for transaction #{tx._id} : #{tx.status}"
          update.status = tx.status

        if ctx.autoRetry?
          if not autoRetryUtils.reachedMaxAttempts tx, ctx.authorisedChannel
            update.autoRetry = ctx.autoRetry
          else
            update.autoRetry = false

        if _.isEmpty update then return callback tx # nothing to do

        transactions.Transaction.findByIdAndUpdate transactionId, update, { },  (err, tx) ->
          callback tx

          # queue for autoRetry
          if update.autoRetry
            autoRetryUtils.queueForRetry tx

          if config.statsd.enabled
            stats.incrementTransactionCount ctx, ->
            stats.measureTransactionDuration ctx, ->

    exports.koaMiddleware = (next) ->
      startTime = new Date() if statsdServer.enabled
      saveTransaction = Q.denodeify exports.storeTransaction
      yield saveTransaction this
      sdc.timing "#{domain}.messageStoreMiddleware.storeTransaction", startTime if statsdServer.enabled
      yield next
      startTime = new Date() if statsdServer.enabled
      exports.storeResponse this, ->
      sdc.timing "#{domain}.messageStoreMiddleware.storeResponse", startTime if statsdServer.enabled