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