cpee 2.1.61 → 2.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,431 @@
1
+ # This file is part of CPEE.
2
+ #
3
+ # CPEE is free software: you can redistribute it and/or modify it under the terms
4
+ # of the GNU General Public License as published by the Free Software Foundation,
5
+ # either version 3 of the License, or (at your option) any later version.
6
+ #
7
+ # CPEE is distributed in the hope that it will be useful, but WITHOUT ANY
8
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9
+ # PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
+ #
11
+ # You should have received a copy of the GNU General Public License along with
12
+ # CPEE (file COPYING in the main directory). If not, see
13
+ # <http://www.gnu.org/licenses/>.
14
+
15
+ require 'charlock_holmes'
16
+ require 'mimemagic'
17
+ require 'base64'
18
+ require 'get_process_mem'
19
+ require 'cpee-eval-ruby/translation'
20
+
21
+ class ConnectionWrapper < WEEL::ConnectionWrapperBase
22
+ def self::loop_guard(arguments,id,count) # {{{
23
+ controller = arguments[0]
24
+ return false if controller.attributes['nednoamol']
25
+ tsn = Time.now
26
+ tso = controller.loop_guard[id][:timestamp] rescue Time.now
27
+ controller.loop_guard[id] = { :count => count, :timestamp => tsn }
28
+ # if we have more than 100 loop iterations and the last one took less than 2 seconds, we slow the hell down
29
+ tso + 2 > tsn && count > 100
30
+ end # }}}
31
+
32
+ def self::inform_state_change(arguments,newstate) # {{{
33
+ controller = arguments[0]
34
+ controller.notify("state/change", :state => newstate)
35
+ end # }}}
36
+ def self::inform_syntax_error(arguments,err,code)# {{{
37
+ # TODO extract spot (code) where error happened for better error handling (ruby 3.1 only)
38
+ # https://github.com/rails/rails/pull/45818/commits/3beb2aff3be712e44c34a588fbf35b79c0246ca5
39
+ puts err.message
40
+ puts err.backtrace
41
+
42
+ controller = arguments[0]
43
+ mess = err.backtrace ? err.backtrace[0].gsub(/([\w -_]+):(\d+):in.*/,'\\1, Line \2: ') : ''
44
+ mess += err.message
45
+ controller.notify("description/error", :message => mess)
46
+ end# }}}
47
+ def self::inform_connectionwrapper_error(arguments,err) # {{{
48
+ controller = arguments[0]
49
+ puts err.message
50
+ puts err.backtrace
51
+ controller.notify("executionhandler/error", :message => err.backtrace[0].gsub(/([\w -_]+):(\d+):in.*/,'\\1, Line \2: ') + err.message)
52
+ end # }}}
53
+ def self::inform_position_change(arguments,ipc={}) # {{{
54
+ controller = arguments[0]
55
+ controller.notify("position/change", ipc)
56
+ end # }}}
57
+
58
+ def initialize(arguments,position=nil,continue=nil) # {{{
59
+ @controller = arguments[0]
60
+ @handler_continue = continue
61
+ @handler_position = position
62
+ @handler_passthrough = nil
63
+ @handler_returnValue = nil
64
+ @handler_returnOptions = nil
65
+ @handler_activity_uuid = Digest::MD5.hexdigest(Kernel::rand().to_s)
66
+ @label = ''
67
+ @guard_files = []
68
+ @guard_items = []
69
+ end # }}}
70
+
71
+ def prepare(readonly, endpoints, parameters) #{{{
72
+ @handler_endpoint = endpoints.is_a?(Array) ? endpoints.map{ |ep| readonly.endpoints[ep] }.compact : readonly.endpoints[endpoints]
73
+ if @controller.attributes['twin_engine']
74
+ @handler_endpoint_orig = @handler_endpoint
75
+ @handler_endpoint = @controller.attributes['twin_engine'].to_s + '?original_endpoint=' + Riddl::Protocols::Utils::escape(@handler_endpoint)
76
+ end
77
+ params = parameters.dup
78
+ params[:arguments] = params[:arguments].dup if params[:arguments]
79
+ params[:arguments]&.map! do |ele|
80
+ t = ele.dup
81
+ if t.value.is_a?(Proc)
82
+ t.value = readonly.instance_exec &t.value
83
+ end
84
+ t
85
+ end
86
+ params
87
+ end #}}}
88
+
89
+ def additional #{{{
90
+ {
91
+ :attributes => @controller.attributes,
92
+ :cpee => {
93
+ 'base' => @controller.base_url,
94
+ 'instance' => @controller.instance_id,
95
+ 'instance_url' => @controller.instance_url,
96
+ 'instance_uuid' => @controller.uuid
97
+ },
98
+ :task => {
99
+ 'label' => @label,
100
+ 'id' => @handler_position
101
+ }
102
+ }
103
+ end #}}}
104
+
105
+ def proto_curl(parameters) #{{{
106
+ params = []
107
+ callback = Digest::MD5.hexdigest(Kernel::rand().to_s)
108
+ (parameters[:arguments] || []).each do |s|
109
+ if s.respond_to?(:mimetype)
110
+ params << Riddl::Parameter::Complex.new(s.name.to_s,v.mimetype,v.value)
111
+ else
112
+ if s.name.to_s =~ /^_Q_/
113
+ params << Riddl::Parameter::Simple.new(s.name.to_s.sub(/^_Q_/,''),CPEE::ValueHelper::generate(s.value),:query)
114
+ elsif s.name.to_s =~ /^_B_/
115
+ params << Riddl::Parameter::Simple.new(s.name.to_s.sub(/^_B_/,''),CPEE::ValueHelper::generate(s.value),:body)
116
+ elsif s.name.to_s =~ /^_H_/
117
+ params << Riddl::Header.new(s.name.to_s.sub(/^_H_/,''),CPEE::ValueHelper::generate(s.value))
118
+ elsif s.name.to_s =~ /^_C_/
119
+ params << Riddl::Parameter::Complex.new(s.name.to_s.sub(/^_C_/,''),*CPEE::ValueHelper::generate(s.value).split(';',2))
120
+ else
121
+ params << Riddl::Parameter::Simple.new(s.name.to_s,CPEE::ValueHelper::generate(s.value))
122
+ end
123
+ end
124
+ end
125
+
126
+ params << Riddl::Header.new("CPEE-BASE",@controller.base_url)
127
+ params << Riddl::Header.new("CPEE-INSTANCE",@controller.instance_id)
128
+ params << Riddl::Header.new("CPEE-INSTANCE-URL",@controller.instance_url)
129
+ params << Riddl::Header.new("CPEE-INSTANCE-UUID",@controller.uuid)
130
+ params << Riddl::Header.new("CPEE-CALLBACK",File.join(@controller.instance_url,'callbacks',callback,'/'))
131
+ params << Riddl::Header.new("CPEE-CALLBACK-ID",callback)
132
+ params << Riddl::Header.new("CPEE-ACTIVITY",@handler_position)
133
+ params << Riddl::Header.new("CPEE-LABEL",@label||'')
134
+ params << Riddl::Header.new("CPEE-TWIN-TARGET",@controller.attributes['twin_target']) if @controller.attributes['twin_target']
135
+ @controller.attributes.each do |key,value|
136
+ params << Riddl::Header.new("CPEE-ATTR-#{key.to_s.gsub(/_/,'-')}",value)
137
+ end
138
+
139
+ status = result = headers = nil
140
+ begin
141
+ tendpoint = @handler_endpoint.sub(/^http(s)?-(get|put|post|delete):/,'http\\1:')
142
+ type = $2 || parameters[:method] || 'post'
143
+
144
+ client = Riddl::Client.new(tendpoint)
145
+
146
+ @handler_passthrough = callback
147
+ @controller.callback(self,callback,:'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position)
148
+
149
+ status, result, headers = client.request type => params
150
+ @guard_files += result
151
+
152
+ if status == 561
153
+ if @controller.attributes['twin_translate']
154
+ gettrans = Riddl::Client.new(@controller.attributes['twin_translate'])
155
+ gtstatus, gtresult, gtheaders = gettrans.get
156
+ if gtstatus >= 200 && gtstatus < 300
157
+ transwhat = case headers['CPEE-TWIN-TASKTYPE']
158
+ when 'i'; 'instantiation'
159
+ when 'ir'; 'ipc-receive'
160
+ when 'is'; 'ipc-send'
161
+ else
162
+ 'instantiation'
163
+ end
164
+ JSON::parse(gtresult.first.value.read).each do |e|
165
+ if e['type'] == transwhat
166
+ @handler_endpoint = e['endpoint'] if e['endpoint']
167
+ e['arguments']&.each do |k,a|
168
+ if a.is_a? String
169
+ hname = a.gsub(/-/,'_')
170
+ a = headers[hname] if headers[hname]
171
+ elsif a.is_a? Hash
172
+ a.each do |k_ht, a_ht|
173
+ hname = a_ht.gsub(/-/,'_')
174
+ a[k_ht] = headers[hname] if headers[hname]
175
+ end
176
+ end
177
+ params.each do |p|
178
+ if p.name == k
179
+ if a.is_a? String
180
+ p.value = a
181
+ elsif a.is_a? Hash
182
+ ohash = JSON::parse(p.value) rescue {}
183
+ ohash.merge!(a)
184
+ p.value = JSON.generate(ohash)
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ else
193
+ @handler_endpoint = @handler_endpoint_orig
194
+ end
195
+ params.delete_if { |p| p.name == 'original_endpoint' }
196
+ end
197
+ end while status == 561
198
+
199
+ if status < 200 || status >= 300
200
+ headers['CPEE_SALVAGE'] = true
201
+ c = result[0]&.value
202
+ c = c.read if c.respond_to? :read
203
+ callback([ Riddl::Parameter::Complex.new('error','application/json',StringIO.new(JSON::generate({ 'status' => status, 'error' => c }))) ], headers)
204
+ else
205
+ if headers['CPEE_CALLBACK'] && headers['CPEE_CALLBACK'] == 'true' && result.any?
206
+ headers['CPEE_UPDATE'] = true
207
+ callback result, headers
208
+ elsif headers['CPEE_CALLBACK'] && headers['CPEE_CALLBACK'] == 'true' && result.empty?
209
+ if headers['CPEE_INSTANTIATION']
210
+ @controller.notify("task/instantiation", :'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position, :endpoint => @handler_endpoint, :received => CPEE::ValueHelper.parse(headers['CPEE_INSTANTIATION']))
211
+ end
212
+ if headers['CPEE_EVENT']
213
+ @controller.notify("task/#{headers['CPEE_EVENT'].gsub(/[^\w_-]/,'')}", :'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position, :endpoint => @handler_endpoint)
214
+ end
215
+ # do nothing, later on things will happend
216
+ else
217
+ callback result, headers
218
+ end
219
+ end
220
+ end #}}}
221
+
222
+ def activity_handle(passthrough, parameters) # {{{
223
+ raise "Wrong endpoint" if @handler_endpoint.nil? || @handler_endpoint.empty?
224
+ @label = parameters[:label]
225
+ @anno = parameters.delete(:annotations) rescue nil
226
+ @controller.notify("status/resource_utilization", :mib => GetProcessMem.new.mb, **Process.times.to_h)
227
+ @controller.notify("activity/calling", :'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position, :passthrough => passthrough, :endpoint => @handler_endpoint, :parameters => parameters, :annotations => @anno)
228
+ if passthrough.to_s.empty?
229
+ proto_curl parameters
230
+ else
231
+ @controller.callback(self,passthrough,:'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position)
232
+ @handler_passthrough = passthrough
233
+ end
234
+ end # }}}
235
+ def activity_manipulate_handle(parameters) #{{{
236
+ @label = parameters[:label]
237
+ end #}}}
238
+
239
+ def activity_result_value # {{{
240
+ @handler_returnValue
241
+ end # }}}
242
+ def activity_result_options # {{{
243
+ @handler_returnOptions
244
+ end # }}}
245
+
246
+ def activity_stop # {{{
247
+ unless @handler_passthrough.nil?
248
+ @controller.cancel_callback(@handler_passthrough)
249
+ end
250
+ end # }}}
251
+ def activity_passthrough_value # {{{
252
+ @handler_passthrough
253
+ end # }}}
254
+
255
+ def activity_no_longer_necessary # {{{
256
+ true
257
+ end # }}}
258
+
259
+ def activity_uuid
260
+ @handler_activity_uuid
261
+ end
262
+
263
+ def inform_activity_done # {{{
264
+ @controller.notify("activity/done", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :label => @label, :activity => @handler_position)
265
+ @controller.notify("status/resource_utilization", :mib => GetProcessMem.new.mb, **Process.times.to_h)
266
+ end # }}}
267
+ def inform_activity_manipulate # {{{
268
+ @controller.notify("activity/manipulating", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :label => @label, :activity => @handler_position)
269
+ end # }}}
270
+ def inform_activity_failed(err) # {{{
271
+ puts err.message
272
+ puts err.backtrace
273
+ @controller.notify("activity/failed", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :label => @label, :activity => @handler_position, :message => err.message, :line => err.backtrace[0].match(/(.*?):(\d+):/)[2], :where => err.backtrace[0].match(/(.*?):(\d+):/)[1])
274
+ end # }}}
275
+ def inform_manipulate_change(status,changed_dataelements,changed_endpoints,dataelements,endpoints) # {{{
276
+ unless status.nil?
277
+ @controller.notify("status/change", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :label => @label, :activity => @handler_position, :id => status.id, :message => status.message)
278
+ end
279
+ unless changed_dataelements.nil? || changed_dataelements.empty?
280
+ de = dataelements.slice(*changed_dataelements).transform_values { |v| enc = CPEE::EvalRuby::Translation::detect_encoding(v); (enc == 'OTHER' ? v : (v.encode('UTF-8',enc) rescue CPEE::EvalRuby::Translation::convert_to_base64(v))) }
281
+ @controller.notify("dataelements/change", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :label => @label, :activity => @handler_position, :changed => changed_dataelements, :values => de)
282
+ end
283
+ unless changed_endpoints.nil? || changed_endpoints.empty?
284
+ @controller.notify("endpoints/change", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :label => @label, :activity => @handler_position, :changed => changed_endpoints, :values => endpoints.slice(*changed_endpoints))
285
+ end
286
+ end # }}}
287
+
288
+ def vote_sync_after # {{{
289
+ @controller.vote("activity/syncing_after", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :activity => @handler_position, :label => @label)
290
+ end # }}}
291
+ def vote_sync_before(parameters=nil) # {{{
292
+ @controller.vote("activity/syncing_before", :'activity-uuid' => @handler_activity_uuid, :endpoint => @handler_endpoint, :activity => @handler_position, :label => @label, :parameters => parameters)
293
+ end # }}}
294
+
295
+ def callback(result=nil,options={})
296
+ status, ret, headers = Riddl::Client.new(@controller.url_result_transformation).request 'put' => result
297
+ recv = if status >= 200 && status < 300
298
+ JSON::parse(ret[0].value.read)
299
+ else
300
+ nil
301
+ end
302
+
303
+ @controller.notify("activity/receiving", :'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position, :endpoint => @handler_endpoint, :received => recv, :annotations => @anno)
304
+
305
+ @guard_files += result
306
+ @guard_files += ret
307
+
308
+ if options['CPEE_INSTANTIATION']
309
+ @controller.notify("task/instantiation", :'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position, :endpoint => @handler_endpoint, :received => CPEE::ValueHelper.parse(options['CPEE_INSTANTIATION']))
310
+ end
311
+ if options['CPEE_EVENT']
312
+ @controller.notify("task/#{options['CPEE_EVENT'].gsub(/[^\w_-]/,'')}", :'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position, :endpoint => @handler_endpoint, :received => recv)
313
+ else
314
+ @handler_returnValue = recv
315
+ @handler_returnOptions = options
316
+ end
317
+ if options['CPEE_STATUS']
318
+ @controller.notify("activity/status", :'activity-uuid' => @handler_activity_uuid, :label => @label, :activity => @handler_position, :endpoint => @handler_endpoint, :status => options['CPEE_STATUS'])
319
+ end
320
+ if options['CPEE_UPDATE']
321
+ @handler_continue.continue WEEL::Signal::Again
322
+ else
323
+ @controller.cancel_callback(@handler_passthrough)
324
+ @handler_passthrough = nil
325
+ if options['CPEE_SALVAGE']
326
+ @handler_continue.continue WEEL::Signal::Salvage
327
+ elsif options['CPEE_STOP']
328
+ @handler_continue.continue WEEL::Signal::Stop
329
+ else
330
+ @handler_continue.continue
331
+ end
332
+ end
333
+ end
334
+
335
+ def mem_guard() #{{{
336
+ @guard_files.delete_if do |p|
337
+ if p&.respond_to?(:close)
338
+ p.close
339
+ elsif p&.value&.respond_to?(:close)
340
+ p.value.close
341
+ end
342
+ true
343
+ end
344
+ GC.start
345
+ end #}}}
346
+
347
+ def test_condition(dataelements,endpoints,local,additional,code,args={})
348
+ send = []
349
+ send.push Riddl::Parameter::Simple::new('code',code)
350
+ send.push Riddl::Parameter::Complex::new('dataelements','application/json', JSON::generate(dataelements))
351
+ send.push Riddl::Parameter::Complex::new('local','application/json', JSON::generate(local)) if local
352
+ send.push Riddl::Parameter::Complex::new('endpoints','application/json', JSON::generate(endpoints))
353
+ send.push Riddl::Parameter::Complex::new('additional','application/json', JSON::generate(additional))
354
+
355
+ status, ret, headers = Riddl::Client.new(@controller.url_code).request 'put' => send
356
+ recv = if status >= 200 && status < 300
357
+ ret[0].value
358
+ else
359
+ nil
360
+ end
361
+ recv && recv == "true" ? true : false
362
+ end
363
+ def eval_expression(dataelements,endpoints,local,additional,code)
364
+ send = []
365
+ send.push Riddl::Parameter::Simple::new('code',code)
366
+ send.push Riddl::Parameter::Complex::new('dataelements','application/json', JSON::generate(dataelements))
367
+ send.push Riddl::Parameter::Complex::new('local','application/json', JSON::generate(local)) if local
368
+ send.push Riddl::Parameter::Complex::new('endpoints','application/json', JSON::generate(endpoints))
369
+ send.push Riddl::Parameter::Complex::new('additional','application/json', JSON::generate(additional))
370
+
371
+ status, ret, headers = Riddl::Client.new(@controller.url_code).request 'put' => send
372
+ recv = if status >= 200 && status < 300
373
+ ret[0].value
374
+ else
375
+ nil
376
+ end
377
+ recv
378
+ end
379
+ def manipulate(readonly,lock,dataelements,endpoints,status,local,additional,code,where,result=nil,options=nil)
380
+ lock.synchronize do
381
+ send = []
382
+ send.push Riddl::Parameter::Simple::new('code',code)
383
+ send.push Riddl::Parameter::Complex::new('dataelements','application/json', JSON::generate(dataelements))
384
+ send.push Riddl::Parameter::Complex::new('local','application/json', JSON::generate(local)) if local
385
+ send.push Riddl::Parameter::Complex::new('endpoints','application/json', JSON::generate(endpoints))
386
+ send.push Riddl::Parameter::Complex::new('additional','application/json', JSON::generate(additional))
387
+ send.push Riddl::Parameter::Complex::new('status','application/json', JSON::generate(status)) if status
388
+ send.push Riddl::Parameter::Complex::new('call_result','application/json', JSON::generate(result))
389
+ send.push Riddl::Parameter::Complex::new('call_headers','application/json', JSON::generate(options))
390
+
391
+ stat, ret, headers = Riddl::Client.new(@controller.url_code).request 'put' => send
392
+ if stat >= 200 && stat < 300
393
+ ret.shift # drop result
394
+ signal = changed_status = nil
395
+ changed_dataelements = changed_local = changed_endpoints = []
396
+ signal = ret.shift.value if ret.any? && ret[0].name == 'signal'
397
+ changed_dataelements = JSON::parse(ret.shift.value.read) if ret.any? && ret[0].name == 'changed_dataelements'
398
+ changed_endpoints = JSON::parse(ret.shift.value.read) if ret.any? && ret[0].name == 'changed_endpoints'
399
+ changed_status = JSON::parse(ret.shift.value.read) if ret.any? && ret[0].name == 'changed_status'
400
+
401
+ struct = if readonly
402
+ WEEL::ReadStructure.new(dataelements,endpoints,local,additional)
403
+ else
404
+ WEEL::ManipulateStructure.new(dataelements, endpoints, status, local, additional)
405
+ end
406
+ struct.update(changed_dataelements,changed_endpoints,changed_status)
407
+
408
+ struct
409
+ else
410
+ nil
411
+ end
412
+ end
413
+ end
414
+
415
+ def join_branches(branches) # factual, so for inclusive or [[a],[b],[c,d,e]]
416
+ @controller.notify("gateway/join", :instance_uuid => @controller.uuid, :branches => branches)
417
+ end
418
+
419
+ def simulate(type,nesting,tid,parent,parameters={}) #{{{
420
+ @controller.vote("simulating/step",
421
+ :'activity-uuid' => @handler_activity_uuid,
422
+ :label => @label,
423
+ :activity => tid,
424
+ :endpoint => @handler_endpoint,
425
+ :type => type,
426
+ :nesting => nesting,
427
+ :parent => parent,
428
+ :parameters => parameters
429
+ )
430
+ end #}}}
431
+ end
@@ -0,0 +1,210 @@
1
+ # This file is part of CPEE.
2
+ #
3
+ # CPEE is free software: you can redistribute it and/or modify it under the terms
4
+ # of the GNU General Public License as published by the Free Software Foundation,
5
+ # either version 3 of the License, or (at your option) any later version.
6
+ #
7
+ # CPEE is distributed in the hope that it will be useful, but WITHOUT ANY
8
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9
+ # PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
+ #
11
+ # You should have received a copy of the GNU General Public License along with
12
+ # CPEE (file COPYING in the main directory). If not, see
13
+ # <http://www.gnu.org/licenses/>.
14
+
15
+ require 'weel'
16
+ require 'json'
17
+ require 'redis'
18
+ require 'securerandom'
19
+ require 'riddl/client'
20
+ require 'cpee/value_helper'
21
+ require 'cpee/attributes_helper'
22
+ require 'cpee/message'
23
+ require 'cpee/redis'
24
+ require 'cpee/persistence'
25
+
26
+ require 'ostruct'
27
+ class ParaStruct < OpenStruct
28
+ def to_json(*a)
29
+ table.to_json
30
+ end
31
+ end
32
+ def ⭐(a); ParaStruct.new(a); end
33
+
34
+ class Controller
35
+ def initialize(id,dir,opts)
36
+ CPEE::redis_connect(opts,"Instance #{id}")
37
+ CPEE::Message::set_workers(opts[:workers])
38
+
39
+ @redis = opts[:redis]
40
+ @votes = []
41
+
42
+ @id = id
43
+
44
+ @attributes = {}
45
+ CPEE::Persistence::extract_list(id,opts,'attributes').each do |de|
46
+ @attributes[de[0]] = de[1]
47
+ end
48
+
49
+ @attributes_helper = AttributesHelper.new
50
+ @thread = nil
51
+ @opts = opts
52
+ @instance = nil
53
+ @loop_guard = {}
54
+
55
+ @callback_keys = {}
56
+
57
+ @subs = Thread.new do
58
+ @psredis = @opts[:redis_dyn].call "Instance #{@id} Callback Response"
59
+ @psredis.psubscribe('callback-response:*','callback-end:*') do |on|
60
+ on.pmessage do |pat, what, message|
61
+ if pat == 'callback-response:*'
62
+ _, worker, identifier = what.split(':')
63
+ if @callback_keys.has_key?(identifier)
64
+ index = message.index(' ')
65
+ mess = message[index+1..-1]
66
+ instance = message[0...index]
67
+ m = JSON.parse(mess)
68
+ resp = []
69
+ m['content']['values'].each do |e|
70
+ if e[1][0] == 'simple'
71
+ resp << Riddl::Parameter::Simple.new(e[0],e[1][1])
72
+ elsif e[1][0] == 'complex'
73
+ resp << Riddl::Parameter::Complex.new(e[0],e[1][1],File.open(e[1][2]))
74
+ end
75
+ end
76
+ @callback_keys[identifier].send(:callback,resp,m['content']['headers'])
77
+ end
78
+ end
79
+ if pat == 'callback-end:*'
80
+ _, worker, identifier = what.split(':')
81
+ @callback_keys.delete(identifier)
82
+ end
83
+ end
84
+ end
85
+ @psredis.close
86
+ rescue => e
87
+ sleep 1
88
+ retry
89
+ end
90
+ end
91
+
92
+ attr_reader :id
93
+ attr_reader :attributes
94
+ attr_reader :loop_guard
95
+
96
+ def uuid
97
+ @attributes['uuid']
98
+ end
99
+
100
+ def attributes_translated
101
+ @attributes_helper.translate(attributes,dataelements,endpoints)
102
+ end
103
+
104
+ def host
105
+ @opts[:host]
106
+ end
107
+ def base_url
108
+ File.join(@opts[:url],'/')
109
+ end
110
+ def instance_url
111
+ File.join(@opts[:url].to_s,@id.to_s,'/')
112
+ end
113
+ def instance_id
114
+ @id
115
+ end
116
+ def base
117
+ base_url
118
+ end
119
+ def instance=(inst)
120
+ @instance = inst
121
+ end
122
+ def endpoints
123
+ @instance.endpoints
124
+ end
125
+ def dataelements
126
+ @instance.data
127
+ end
128
+ def url_result_transformation
129
+ @opts[:url_result_transformation]
130
+ end
131
+ def url_code
132
+ @opts[:url_code]
133
+ end
134
+
135
+ def start
136
+ if vote("state/change", :state => 'running')
137
+ @thread = @instance.start
138
+ @thread.join
139
+ else
140
+ @thread = @instance.stop
141
+ @thread.join
142
+ end
143
+ end
144
+
145
+ def stop
146
+ ### tell the instance to stop
147
+ @instance.stop
148
+ ### end all votes or it will not work
149
+ Thread.new do # doing stuff in trap context is a nono. but in a thread its fine :-)
150
+ @votes.each do |key|
151
+ CPEE::Message::send(:'vote-response',key,base,@id,uuid,info,true,@redis)
152
+ end
153
+ end
154
+ end
155
+
156
+ def info
157
+ @attributes['info']
158
+ end
159
+
160
+ def notify(what,content={})
161
+ content[:attributes] = attributes_translated
162
+ CPEE::Message::send(:event,what,base,@id,uuid,info,content,@redis)
163
+ end
164
+
165
+ def vote(what,content={})
166
+ topic, name = what.split('/')
167
+ handler = File.join(topic,'vote',name)
168
+ votes = []
169
+
170
+ CPEE::Persistence::extract_handler(id,@opts,handler).each do |client|
171
+ voteid = Digest::MD5.hexdigest(Kernel::rand().to_s)
172
+ content[:key] = voteid
173
+ content[:attributes] = attributes_translated
174
+ content[:subscription] = client
175
+ votes << voteid
176
+ CPEE::Message::send(:vote,what,base,@id,uuid,info,content,@redis)
177
+ end
178
+
179
+ if votes.length > 0
180
+ @votes += votes
181
+ psredis = @opts[:redis_dyn].call "Instance #{@id} Vote"
182
+ collect = []
183
+ psredis.subscribe(votes.map{|e| ['vote-response:00:' + e.to_s] }.flatten) do |on|
184
+ on.message do |what, message|
185
+ index = message.index(' ')
186
+ mess = message[index+1..-1]
187
+ m = JSON.parse(mess)
188
+ collect << ((m['content'] == true || m['content'] == 'true') || false)
189
+ @votes.delete m['name']
190
+ cancel_callback m['name']
191
+ if collect.length >= votes.length
192
+ psredis.unsubscribe
193
+ end
194
+ end
195
+ end
196
+ !collect.include?(false)
197
+ else
198
+ true
199
+ end
200
+ end
201
+
202
+ def callback(hw,key,content)
203
+ CPEE::Message::send(:callback,'activity/content',base,@id,uuid,info,content.merge(:key => key),@redis)
204
+ @callback_keys[key] = hw
205
+ end
206
+
207
+ def cancel_callback(key)
208
+ CPEE::Message::send(:'callback-end',key,base,@id,uuid,info,{},@redis)
209
+ end
210
+ end