Completed
Push — master ( f2f9ef...8d6f66 )
by Fike
56s queued 19s
created

lib/Execution/Transition.js   A

Complexity

Total Complexity 30
Complexity/F 1.58

Size

Lines of Code 223
Function Count 19

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
wmc 30
c 1
b 0
f 0
nc 1
mnd 1
bc 23
fnc 19
dl 0
loc 223
rs 10
bpm 1.2105
cpm 1.5789
noi 0

2 Functions

Rating   Name   Duplication   Size   Complexity  
B Transition.js ➔ Transition 0 154 3
A Transition.js ➔ statusFactory 0 8 2
1
var SDK = require('@ama-team/voxengine-sdk')
2
var Slf4j = SDK.Logger.Slf4j
3
var CancellationToken = SDK.Concurrent.CancellationToken
4
var Future = SDK.Concurrent.Future
5
var Branch = require('./Transition/Branch').Branch
6
var IllegalStateError = require('../Error').IllegalStateError
7
8
var statusFactory = function (id, successful) {
9
  var terminal = typeof successful === 'boolean'
10
  return {
11
    id: id,
12
    terminal: terminal,
13
    successful: terminal ? successful : null
14
  }
15
}
16
17
/**
18
 * @enum
19
 * @readonly
20
 */
21
var Status = {
22
  Idle: statusFactory('Idle'),
23
  Executing: statusFactory('Executing'),
24
  Aborting: statusFactory('Aborting'),
25
  /**
26
   * Transition has completed successfully
27
   */
28
  Executed: statusFactory('Executed', true),
29
  /**
30
   * Transition has been aborted successfully
31
   */
32
  Aborted: statusFactory('Aborted', true),
33
  /**
34
   * Transition has failed and thrown an error during normal execution
35
   */
36
  ExecutionFailure: statusFactory('ExecutionFailure', false),
37
  /**
38
   * Transition has been aborted and encountered an error during abort
39
   */
40
  AbortFailure: statusFactory('AbortFailure', false),
41
  /**
42
   * Transition has ended with error triggered by framework
43
   */
44
  Tripped: statusFactory('Tripped', false)
45
}
46
47
/**
48
 * @typedef {object} Transition~Options
49
 *
50
 * @property {TState} [origin]
51
 * @property {TState} target
52
 * @property {THints} hints
53
 * @property {LoggerOptions} [logger]
54
 * @property {IExecutor} executor
55
 */
56
57
/**
58
 * Represents transition from one state to another
59
 *
60
 * @param {Transition~Options} options
61
 *
62
 * @class
63
 */
64
function Transition (options) {
65
  var self = this
66
  if (!options || !options.target) {
67
    throw new IllegalStateError('Target state not provided')
68
  }
69
  var loggerName = 'ama-team.vsf.execution.transition'
70
  var origin = options.origin
71
  var target = options.target
72
  var originId = (origin && origin.id) || null
73
  var hints = options.hints
74
  options.logger = options.logger || {}
75
  options.logger.mdc = options.logger.mdc || {}
76
  options.logger.mdc['origin'] = originId
77
  options.logger.mdc['target'] = target.id
78
  var logger = Slf4j.factory(options.logger, loggerName)
79
80
  var token = new CancellationToken()
81
  var completion = new Future()
82
  var status = Status.Idle
83
84
  var launchedAt = null
85
86
  /**
87
   * @param {Transition.Status} next
88
   */
89
  function setStatus (next) {
90
    logger.trace('Changing status from {} to {}', status.id, next.id)
91
    status = next
92
  }
93
94
  function executionOptions (name) {
95
    return {
96
      name: name,
97
      handler: target[name],
98
      logger: options.logger,
99
      executor: options.executor
100
    }
101
  }
102
103
  /**
104
   * Returns function that will generate ITransitionResult from provided
105
   * value.
106
   *
107
   * @param {Status} status
108
   * @return {Function}
109
   */
110
  function factory (status) {
111
    return function (value) {
112
      return {
113
        value: value,
114
        status: status,
115
        duration: (new Date()).getTime() - launchedAt.getTime()
116
      }
117
    }
118
  }
119
120
  /**
121
   * Completes transition with provided result, if
122
   *
123
   * @param {TTransitionResult} result
124
   * @param {Status} [requiredStatus]
125
   */
126
  function complete (result, requiredStatus) {
127
    if (requiredStatus && status !== requiredStatus) {
128
      return
129
    }
130
    setStatus(result.status)
131
    logger.debug('Transition has ended with status {} and value {} in {} ms',
132
      status.id, result.value, result.duration)
133
    completion.resolve(result)
134
  }
135
136
  function run () {
137
    if (status !== Status.Idle) {
138
      throw new IllegalStateError('Tried to run ' + self + ' twice')
139
    }
140
    logger.debug('Running transition')
141
    launchedAt = new Date()
142
    setStatus(Status.Executing)
143
    var opts = executionOptions('transition')
144
    var execution = new Branch(opts)
145
    execution
146
      .run(originId, hints, token)
147
      .then(factory(Status.Executed), factory(Status.ExecutionFailure))
148
      .then(function (result) {
149
        var level = result.status.successful ? 'debug' : 'warn'
150
        var term = result.status.successful ? 'success' : 'error'
151
        logger[level]('Transition run has finished with {}: {}', term,
152
          result.value)
153
        complete(result, Status.Executing)
154
      })
155
    return completion
156
  }
157
158
  function abort () {
159
    if (status !== Status.Executing) {
160
      throw new IllegalStateError('Tried to abort ' + self)
161
    }
162
    setStatus(Status.Aborting)
163
    var options = executionOptions('abort')
164
    var execution = new Branch(options)
165
    execution
166
      .run(originId, hints)
167
      .then(factory(Status.Aborted), factory(Status.AbortFailure))
168
      .then(function (result) {
169
        var level = result.status.successful ? 'debug' : 'warn'
170
        var term = result.status.successful ? 'success' : 'error'
171
        logger[level]('Transition abort has finished with {}: {}', term,
172
          result.value)
173
        complete(result, Status.Aborting)
174
      })
175
    return completion
176
  }
177
178
  this.run = run
179
180
  this.abort = abort
181
182
  this.getStatus = function () {
183
    return status
184
  }
185
186
  this.toString = function () {
187
    return 'Transition ' + originId + ' -> ' + target.id +
188
      ' (' + status.id + ')'
189
  }
190
191
  /**
192
   * @return {TTransitionDetails}
193
   */
194
  this.toDetails = function () {
195
    return {
196
      origin: originId,
197
      target: target.id,
198
      hints: hints
199
    }
200
  }
201
202
  /**
203
   * @return {int}
204
   */
205
  this.getLaunchedAt = function () { return launchedAt }
206
207
  /**
208
   * @return {TState}
209
   */
210
  this.getTarget = function () { return target }
211
212
  this.getOrigin = function () { return origin }
213
214
  this.getHints = function () { return hints }
215
216
  this.getCompletion = function () { return completion }
217
}
218
219
Transition.Status = Status
220
221
module.exports = {
222
  Transition: Transition
223
}
224