cpee-worklist 1.0.0

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,432 @@
1
+ # This file is part of CPEE-WORKLIST
2
+ #
3
+ # CPEE-WORKLIST is free software: you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as published by the
5
+ # Free Software Foundation, either version 3 of the License, or (at your
6
+ # option) any later version.
7
+ #
8
+ # CPEE-WORKLIST is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with CPEE-WORKLIST (file LICENSE in the main directory). If not, see
15
+ # <http://www.gnu.org/licenses/>.
16
+
17
+ require 'pp'
18
+ require 'json'
19
+ require 'fileutils'
20
+ require 'rubygems'
21
+ require 'riddl/server'
22
+ require 'riddl/client'
23
+ require 'riddl/utils/notifications_producer'
24
+ require 'riddl/utils/fileserve'
25
+ require 'cpee/redis'
26
+ require 'cpee/message'
27
+ require 'cpee/persistence'
28
+ require 'cpee/attributes_helper'
29
+ require 'cpee/implementation_notifications'
30
+ require 'cpee/implementation_callbacks'
31
+ require 'chronic_duration'
32
+
33
+ require_relative 'activities'
34
+ require_relative 'controller'
35
+ require_relative 'utils'
36
+ require_relative 'user'
37
+
38
+ CPEE::Message::who = 'cpee-worklist'
39
+ CPEE::Message::type = 'worklist'
40
+
41
+ module CPEE
42
+ module Worklist
43
+
44
+ SERVER = File.expand_path(File.join(__dir__,'implementation.xml'))
45
+
46
+ class GetStatus < Riddl::Implementation #{{{
47
+ def response
48
+ status = File.read(File.join(@a[0],'users','status.txt')) rescue 'Currently no tasks available.'
49
+ Riddl::Parameter::Simple.new("status",status)
50
+ end
51
+ end #}}}
52
+
53
+ class SetStatus < Riddl::Implementation #{{{
54
+ def response
55
+ @a[0].notify('user/status', { 'status' => @p[0].value })
56
+ File.write(File.join(@a[1],'users','status.txt'),@p[0].value)
57
+ end
58
+ end #}}}
59
+
60
+ class ActivityHappens < Riddl::Implementation #{{{
61
+ def response
62
+ controller = @a[0]
63
+
64
+ activity = {}
65
+ activity['process'] = @h.keys.include?('CPEE_ATTR_INFO') ? "#{@h['CPEE_ATTR_INFO']} (#{@h['CPEE_INSTANCE'].split('/').last})" : "DUMMY PROCESS (#{@h['CPEE_INSTANCE'].split('/').last})"
66
+ activity['label'] = @h.keys.include?('CPEE_INSTANCE') ? "#{@h['CPEE_LABEL']}" : 'DUMMY LABEL'
67
+ activity['user'] = []
68
+ activity['url'] = @h['CPEE_CALLBACK']
69
+ activity['id'] = @h['CPEE_CALLBACK_ID']
70
+
71
+ activity['cpee_activity_id'] = @h['CPEE_ACTIVITY']
72
+ activity['cpee_base'] = @h['CPEE_BASE']
73
+ activity['cpee_instance'] = @h['CPEE_INSTANCE']
74
+
75
+ activity['uuid'] = @h['CPEE_ATTR_UUID']
76
+
77
+ omo = @p.shift.value
78
+ activity['orgmodel'] = @h[ 'CPEE_ATTR_' + omo.upcase] || omo
79
+
80
+ activity['form'] = @p.shift.value
81
+ activity['unit'] = @p.first.name == 'unit' ? @p.shift.value : '*'
82
+ activity['role'] = @p.first.name == 'role' ? @p.shift.value : '*'
83
+ activity['priority'] = @p.first.name == 'priority' ? @p.shift.value.to_i : 1
84
+ activity['collect'] = @p.first.name == 'collect' ? @p.shift.value.to_i : nil
85
+ activity['deadline'] = @p.first.name == 'deadline' ? ((Time.now + ChronicDuration.parse(@p.shift.value)) rescue nil): nil
86
+ activity['restrictions'] = []
87
+ rests = JSON::parse(@p.shift.value) rescue nil
88
+ activity['restrictions'] << rests unless rests.nil?
89
+ activity['parameters'] = JSON::parse(@p.shift.value) rescue {}
90
+ status, content, headers = Riddl::Client.new(activity['orgmodel']).get
91
+ if status == 200
92
+ begin
93
+ xml = content[0].value.read
94
+ schema = XML::Smart.open(@a[0].opts[:ORG_SCHEMA])
95
+ org_xml = XML::Smart.string(xml)
96
+ raise 'a fucked up xml (wink wink)' unless org_xml.validate_against(schema)
97
+ org_xml.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0'
98
+ rescue => e
99
+ puts e.message
100
+ puts e.backtrace
101
+ @a[0].notify('task/invalid', :callback_id => activity['id'], :reason => 'orgmodel invalid', :instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'] )
102
+ @status = 404
103
+ return
104
+ end
105
+ attributes = ""
106
+ if activity['role'] != '*'
107
+ attributes += "@role='#{activity['role']}'"
108
+ attributes += " and " if activity['unit'] != '*'
109
+ end
110
+ attributes += "@unit='#{activity['unit']}'" if activity['unit'] != '*'
111
+ user = org_xml.find("/o:organisation/o:subjects/o:subject[o:relation[#{attributes}]]").map{ |e| e.attributes['uid'] }
112
+
113
+ if activity['collect']
114
+ activity['collect_max'] = user.length
115
+ activity['collected'] = 0
116
+ end
117
+
118
+ if user.empty?
119
+ @a[0].notify('task/invalid', :callback_id => activity['id'], :reason => 'no users found for this combination of unit/role',:instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'] )
120
+ @status = 404
121
+ return
122
+ end
123
+ @a[0].add_activity activity
124
+ @a[0].add_orgmodel Riddl::Protocols::Utils::escape(activity['orgmodel']), xml
125
+ Thread.new do
126
+ # TODO immediate vote for adding by external subscribers
127
+ # results = @a[0].vote('task/add', :user => user , :instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'] )
128
+ # if (results.length == 1) && (user.include? results[0])
129
+ # activity['user'] = results[0]
130
+ # info = CPEE::Worklist::User::info(@a[0].opts,activity,activity['user'])
131
+ # @a[0].notify('task/add', :user => user,:callback_id => activity['id'], :instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'], :wl_instance => activity['wl_instance'] )
132
+ # @a[0].notify('user/take', :user => results[0], :callback_id => activity['id'], :instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'], :organisation => info, :wl_instance => activity['wl_instance'])
133
+ # else
134
+ @a[0].notify('task/add', :user => user,:callback_id => activity['id'], :instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'])
135
+ # end
136
+ end
137
+ @headers << Riddl::Header.new('CPEE_CALLBACK','true')
138
+ else
139
+ @status = 404
140
+ end
141
+ end
142
+ end #}}}
143
+
144
+ class TaskDel < Riddl::Implementation #{{{
145
+ def response
146
+ index = @a[0].activities.index{ |e| e["id"] == @r.last }
147
+ if index
148
+ activity = @a[0].activities[index]
149
+ if activity['collected'] && (activity['collected'] + 1) < activity['collect_max']
150
+ activity['collected'] += 1
151
+ activity['restrictions'] << { "restriction" => { "mode" => "prohibit", "id" => @r[-3] } }
152
+ @a[0].activities.serialize
153
+ @a[0].notify('user/finish', :callback_id => activity['id'], :user => @r[-3], :role => activity['role'],:instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'])
154
+ else
155
+ activity = @a[0].activities.delete_at(index)
156
+ @a[0].activities.serialize
157
+ if @r.length == 3
158
+ @a[0].notify('task/delete', :callback_id => activity['id'], :instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'])
159
+ Riddl::Client.new(activity['url']).put
160
+ else
161
+ info = CPEE::Worklist::User::info(@a[0].opts,activity,@r[-3])
162
+ @a[0].notify('user/finish', :callback_id => activity['id'], :user => @r[-3], :role => activity['role'],:instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'])
163
+ end
164
+ end
165
+ else
166
+ @status = 404
167
+ end
168
+ end
169
+ end #}}}
170
+
171
+ class ShowTasks < Riddl::Implementation #{{{
172
+ def response
173
+ out = XML::Smart.string('<tasks/>')
174
+ umodels = @a[0].orgmodels.map do |fname|
175
+ doc = XML::Smart.open_unprotected(File.join(@a[0].opts[:top],'orgmodels',fname))
176
+ doc.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0'
177
+ doc
178
+ end
179
+ @a[0].activities.each do |activity|
180
+ x = out.root.add "task", :callback_id => activity['id'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :instance_uuid => activity['uuid'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel']
181
+ x.add "process" , activity['process']
182
+ x.add "label" , activity['label']
183
+ x.add "role" , activity['role']
184
+ x.add "unit" , activity['unit']
185
+
186
+ if activity['user'].any?
187
+ umodels.each do |doc|
188
+ activity['user'].each do |user|
189
+ if user = doc.find("/o:organisation/o:subjects/o:subject[@uid='#{user}']").first
190
+ x.add "user", user.attributes['id'], :uid => user.attributes['uid']
191
+ break
192
+ end
193
+ end
194
+ end
195
+ else
196
+ xpath = ''
197
+ xpath = "[@role='#{activity['role']}' and @unit='#{activity['unit']}']" if (activity['unit'] != '*' && activity['role'] != '*' )
198
+ xpath = "[@role='#{activity['role']}']" if (activity['unit'] == '*' && activity['role'] != '*' )
199
+ xpath = "[@unit='#{activity['unit']}']" if (activity['unit'] != '*' && activity['role'] == '*' )
200
+
201
+ umodels.each do |doc|
202
+ if (tmp = doc.find("/o:organisation/o:subjects/o:subject[o:relation#{xpath}]")).length > 0
203
+ tmp.each{|e| x.add "user", e.attributes['id'], :uid => e.attributes['uid'] }
204
+ end
205
+ end
206
+ end
207
+ end
208
+ Riddl::Parameter::Complex.new("tasks","text/xml", out.to_s)
209
+ end
210
+ end #}}}
211
+
212
+ class ShowUserTasks < Riddl:: Implementation #{{{
213
+ def response
214
+ out = XML::Smart.string('<tasks/>')
215
+ tasks = {}
216
+ @a[0].orgmodels.each do |e|
217
+ XML::Smart.open(File.join(@a[0].opts[:top],'orgmodels',e)) do |doc|
218
+ doc.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0'
219
+ doc.find("/o:organisation/o:subjects/o:subject[@uid='#{@r[-2]}']/o:relation").each do |rel|
220
+ @a[0].activities.each do |activity|
221
+ restrict = false
222
+ activity['restrictions'].each do |restriction|
223
+ restrict = true if restriction['restriction']['mode'] == 'prohibit' && restriction['restriction']['id'] == @r[-2]
224
+ end
225
+ if (
226
+ activity['role']=='*' ||
227
+ activity['role'].casecmp(rel.attributes['role']) == 0
228
+ ) && (
229
+ activity['unit'] == '*' ||
230
+ activity['unit'].casecmp(rel.attributes['unit']) == 0
231
+ ) && (
232
+ activity['collect'] ||
233
+ activity['user'].empty? ||
234
+ activity['user'].include?(@r[-2])
235
+ ) && !restrict
236
+ tasks["#{activity['id']}"] = { :all => activity.has_key?('collect') && !activity['collect'].nil?, :uid => @r[-2], :priority => activity['priority'], :label => activity['process'] + ': ' + activity['label'] }
237
+ tasks["#{activity['id']}"][:deadline] = activity['deadline'] if activity['deadline']
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+ tasks.sort_by{ |k,e| e[:priority] }.each{|k,v| out.root.add("task", v.merge(:id => k))}
244
+ x = Riddl::Parameter::Complex.new("return","text/xml") do
245
+ out.to_s
246
+ end
247
+ x
248
+ end
249
+ end #}}}
250
+
251
+ class TaskTake < Riddl::Implementation #{{{
252
+ def response
253
+ index = @a[0].activities.index{ |c| c["id"] == @r.last }
254
+ if index
255
+ activity = @a[0].activities[index]
256
+ activity['user'].push @r[-3]if CPEE::Worklist::User::ok?(@a[0].opts,activity,@r[-3])
257
+ info = CPEE::Worklist::User::info(@a[0].opts,activity,@r[-3])
258
+ @a[0].activities.serialize
259
+ @a[0].notify('user/take', :user => @r[-3], :callback_id => activity['id'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'],:instance_uuid => activity['uuid'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'], :organisation => info)
260
+ Riddl::Client.new(@a[0].activities[index]['url']).put [
261
+ Riddl::Header.new('CPEE-UPDATE','true'),
262
+ Riddl::Header.new('CPEE-STATUS','take'),
263
+ Riddl::Header.new('CPEE-EVENT','take')
264
+ ]
265
+ else
266
+ @status = 404
267
+ end
268
+ end
269
+ end #}}}
270
+
271
+ class TaskGiveBack < Riddl::Implementation #{{{
272
+ def response
273
+ index = @a[0].activities.index{ |c| c["id"] == @r.last }
274
+ if index && (@a[0].activities[index]['user'] == @r[-3])
275
+ activity = @a[0].activities[index]
276
+ activity['user'] = []
277
+ callback_id = @a[0].activities[index]['id']
278
+ @a[0].activities.serialize
279
+ @a[0].notify('user/giveback', :callback_id => activity['id'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'],:instance_uuid => activity['uuid'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'])
280
+ Riddl::Client.new(@a[0].activities[index]['url']).put [
281
+ Riddl::Header.new('CPEE-UPDATE','true'),
282
+ Riddl::Header.new('CPEE-STATUS','giveback'),
283
+ Riddl::Header.new('CPEE-EVENT','giveback')
284
+ ]
285
+ else
286
+ @status = 404
287
+ end
288
+ end
289
+ end #}}}
290
+
291
+ class TaskDetails < Riddl::Implementation #{{{
292
+ def response
293
+ index = @a[0].activities.index{ |c| c["id"] == @r.last }
294
+ if index
295
+ Riddl::Parameter::Complex.new "data","application/json", JSON.generate({:collect => @a[0].activities[index].has_key?('collect') && !@a[0].activities[index]['collect'].nil?, 'url' => @a[0].activities[index]['url'], 'form' => @a[0].activities[index]['form'], 'parameters' => @a[0].activities[index]['parameters'], 'label' => @a[0].activities[index]['label']})
296
+ else
297
+ @status = 404
298
+ end
299
+ end
300
+ end #}}}
301
+
302
+ class GetOrgModels < Riddl::Implementation #{{{
303
+ def response
304
+ out = XML::Smart.string('<orgmodels/>')
305
+ @a[0].orgmodels.each{|e| out.root.add("orgmodel", e)}
306
+ Riddl::Parameter::Complex.new "return","text/xml", out.to_s
307
+ end
308
+ end #}}}
309
+
310
+ class AssignTask < Riddl::Implementation #{{{
311
+ def response
312
+ index = @a[0].activities.index{ |c| c["id"] == @r.last }
313
+ if index
314
+ user = @p[0].value
315
+ @a[0].activities[index]["user"] = user if CPEE::Worklist::User::ok?(@a[0].opts,@a[0].activities[index],user)
316
+ callback_id = @a[0].activities[index]['id']
317
+ info = CPEE::Worklist::User::info(@a[0].opts,@a[0].activities[index],user)
318
+ @a[0].activities.serialize
319
+ @a[0].notify('user/take', :index => callback_id, :user => @p[0].value, :organisation => info)
320
+ Riddl::Client.new(@a[0].activities[index]['url']).put [
321
+ Riddl::Header.new('CPEE-UPDATE','true'),
322
+ Riddl::Header.new('CPEE-STATUS','take'),
323
+ Riddl::Header.new('CPEE-EVENT','take')
324
+ ]
325
+ else
326
+ @status = 404
327
+ end
328
+ end
329
+ end #}}}
330
+
331
+ def self::implementation(opts)
332
+ opts[:ORG_SCHEMA] = ::File.join(__dir__, 'organisation.rng')
333
+ opts[:topics] = ::File.join(__dir__, 'topics.xml')
334
+
335
+ opts[:top] ||= ::File.join(__dir__, 'data')
336
+
337
+ opts[:watchdog_frequency] ||= 7
338
+ opts[:watchdog_start_off] ||= false
339
+
340
+ ### set redis_cmd to nil if you want to do global
341
+ ### at least redis_path or redis_url and redis_db have to be set if you do global
342
+ opts[:redis_path] ||= 'redis.sock' # use e.g. /tmp/redis.sock for global stuff. Look it up in your redis config
343
+ opts[:redis_db] ||= 0
344
+ ### optional redis stuff
345
+ opts[:redis_url] ||= nil
346
+ opts[:redis_cmd] ||= 'redis-server --port 0 --unixsocket #redis_path# --unixsocketperm 600 --pidfile #redis_pid# --dir #redis_db_dir# --dbfilename #redis_db_name# --databases 1 --save 900 1 --save 300 10 --save 60 10000 --rdbcompression yes --daemonize yes'
347
+ opts[:redis_pid] ||= 'redis.pid' # use e.g. /var/run/redis.pid if you do global. Look it up in your redis config
348
+ opts[:redis_db_name] ||= 'redis.rdb' # use e.g. /var/lib/redis.rdb for global stuff. Look it up in your redis config
349
+
350
+ controller = CPEE::Worklist::Controller.new(opts)
351
+
352
+ CPEE::redis_connect opts, 'Server Main'
353
+
354
+ opts[:sse_keepalive_frequency] ||= 10
355
+ opts[:sse_connections] = {}
356
+
357
+ opts[:finalize_frequency] ||= 10
358
+
359
+ CPEE::Message::set_workers(1)
360
+
361
+ Proc.new do
362
+ parallel do
363
+ CPEE::Worklist::watch_services(opts[:watchdog_start_off],opts[:redis_url],File.join(opts[:basepath],opts[:redis_path]),opts[:redis_db])
364
+ EM.add_periodic_timer(opts[:watchdog_frequency]) do ### start services
365
+ CPEE::Worklist::watch_services(opts[:watchdog_start_off],opts[:redis_url],File.join(opts[:basepath],opts[:redis_path]),opts[:redis_db])
366
+ end
367
+ EM.defer do ### catch all sse connections
368
+ CPEE::Notifications::sse_distributor(opts)
369
+ end
370
+ EM.add_periodic_timer(opts[:sse_keepalive_frequency]) do
371
+ CPEE::Notifications::sse_heartbeat(opts)
372
+ end
373
+ EM.add_periodic_timer(opts[:finalize_frequency]) do
374
+ controller.activities.each_with_index do |activity,index|
375
+ begin
376
+ if activity['collect'] && activity['collected'] && activity['deadline'] && activity['collected'] >= activity['collect'] && Time.parse(activity['deadline'].to_s) < Time.now
377
+ activity = controller.activities.delete_at(index)
378
+ controller.activities.serialize
379
+ controller.notify('user/finish', :callback_id => activity['id'], :instance_uuid => activity['uuid'], :cpee_callback => activity['url'], :cpee_instance => activity['cpee_instance'], :cpee_base => activity['cpee_base'], :cpee_label => activity['label'], :cpee_activity => activity['cpee_activity_id'], :orgmodel => activity['orgmodel'])
380
+ Riddl::Client.new(activity['url']).put
381
+ end
382
+ rescue => e
383
+ puts e.message
384
+ puts e.backtrace
385
+ end
386
+ end
387
+ end
388
+ end
389
+
390
+ cleanup do
391
+ CPEE::Worklist::cleanup_services(opts[:watchdog_start_off])
392
+ end
393
+
394
+ interface 'main' do
395
+ run ActivityHappens,controller if post 'activityhappens'
396
+ run ShowTasks,controller if get
397
+ on resource 'callbacks' do
398
+ use CPEE::Callbacks::implementation(opts)
399
+ end
400
+ on resource 'orgmodels' do
401
+ run GetOrgModels,controller if get
402
+ end
403
+ on resource 'tasks' do
404
+ on resource do
405
+ run AssignTask,controller if put 'uid'
406
+ run TaskDel,controller if delete
407
+ end
408
+ end
409
+ on resource do
410
+ run SetStatus, controller, opts[:top] if put 'status'
411
+ run GetStatus, opts[:top] if get
412
+ on resource 'tasks' do
413
+ run ShowUserTasks,controller if get
414
+ on resource do |r|
415
+ run TaskDetails,controller if get
416
+ run TaskTake,controller if put 'take'
417
+ run TaskGiveBack,controller if put 'giveback'
418
+ run TaskDel,controller if delete
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ interface 'notifications' do |r|
425
+ use CPEE::Notifications::implementation('worklist',opts)
426
+ end
427
+
428
+ end
429
+ end
430
+
431
+ end
432
+ end
@@ -0,0 +1,35 @@
1
+ <!--
2
+ This file is part of CPEE-WORKLIST
3
+
4
+ CPEE-WORKLIST is free software: you can redistribute it and/or modify it
5
+ under the terms of the GNU Lesser General Public License as published by the
6
+ Free Software Foundation, either version 3 of the License, or (at your
7
+ option) any later version.
8
+
9
+ CPEE-WORKLIST is distributed in the hope that it will be useful, but WITHOUT
10
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12
+ details.
13
+
14
+ You should have received a copy of the GNU Lesser General Public License
15
+ along with CPEE-WORKLIST (file LICENSE in the main directory). If not, see
16
+ <http://www.gnu.org/licenses/>.
17
+ -->
18
+
19
+ <declaration xmlns="http://riddl.org/ns/declaration/1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
20
+ <interface name="main">
21
+ <xi:include href="wlengine.xml"/>
22
+ </interface>
23
+ <interface name="notifications">
24
+ <xi:include href="http://www.riddl.org/ns/common-patterns/notifications-producer/2.0/producer.xml"/>
25
+ </interface>
26
+
27
+ <facade>
28
+ <tile>
29
+ <layer name="main"/>
30
+ <layer name="notifications">
31
+ <apply-to>/</apply-to>
32
+ </layer>
33
+ </tile>
34
+ </facade>
35
+ </declaration>
@@ -0,0 +1,82 @@
1
+ <grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes" ns="http://cpee.org/ns/organisation/1.0">
2
+ <start>
3
+ <element name="organisation">
4
+ <ref name="units"/>
5
+ <ref name="roles"/>
6
+ <ref name="subjects"/>
7
+ </element>
8
+ </start>
9
+
10
+ <define name="units">
11
+ <element name='units'>
12
+ <zeroOrMore>
13
+ <element name='unit'>
14
+ <ref name="thing"/>
15
+ </element>
16
+ </zeroOrMore>
17
+ </element>
18
+ </define>
19
+
20
+ <define name="roles">
21
+ <element name='roles'>
22
+ <zeroOrMore>
23
+ <element name='role'>
24
+ <ref name="thing"/>
25
+ </element>
26
+ </zeroOrMore>
27
+ </element>
28
+ </define>
29
+
30
+ <define name="thing">
31
+ <attribute name="id">
32
+ <data type="string"/>
33
+ </attribute>
34
+ <zeroOrMore>
35
+ <element name='parent'>
36
+ <data type="string"/>
37
+ </element>
38
+ </zeroOrMore>
39
+ <ref name="permissions"/>
40
+ </define>
41
+
42
+ <define name="subjects">
43
+ <element name='subjects'>
44
+ <zeroOrMore>
45
+ <ref name="subject"/>
46
+ </zeroOrMore>
47
+ </element>
48
+ </define>
49
+
50
+ <define name="subject">
51
+ <element name='subject'>
52
+ <attribute name="id">
53
+ <data type="string"/>
54
+ </attribute>
55
+ <optional>
56
+ <attribute name="uid">
57
+ <data type="string"/>
58
+ </attribute>
59
+ </optional>
60
+ <oneOrMore>
61
+ <element name='relation'>
62
+ <attribute name="role">
63
+ <data type="string"/>
64
+ </attribute>
65
+ <attribute name="unit">
66
+ <data type="string"/>
67
+ </attribute>
68
+ </element>
69
+ </oneOrMore>
70
+ <zeroOrMore>
71
+ <element><anyName/><data type="string"/></element>
72
+ </zeroOrMore>
73
+ </element>
74
+ </define>
75
+
76
+ <define name="permissions">
77
+ <element name='permissions'>
78
+ <empty/>
79
+ </element>
80
+ </define>
81
+
82
+ </grammar>
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # This file is part of CPEE-WORKLIST
4
+ #
5
+ # CPEE-WORKLIST is free software: you can redistribute it and/or modify it
6
+ # under the terms of the GNU Lesser General Public License as published by the
7
+ # Free Software Foundation, either version 3 of the License, or (at your
8
+ # option) any later version.
9
+ #
10
+ # CPEE-WORKLIST is distributed in the hope that it will be useful, but WITHOUT
11
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
+ # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with CPEE-WORKLIST (file LICENSE in the main directory). If not, see
17
+ # <http://www.gnu.org/licenses/>.
18
+
19
+ require 'json'
20
+ require 'redis'
21
+ require 'daemonite'
22
+ require 'cpee/redis'
23
+
24
+ Daemonite.new do |opts|
25
+ opts[:runtime_opts] += [
26
+ ["--url=URL", "-uURL", "Specify redis url", ->(p){ opts[:redis_url] = p }],
27
+ ["--path=PATH", "-pPATH", "Specify redis path, e.g. /tmp/redis.sock", ->(p){ opts[:redis_path] = p }],
28
+ ["--db=DB", "-dDB", "Specify redis db, e.g. 1", ->(p) { opts[:redis_db] = p.to_i }]
29
+ ]
30
+
31
+ on startup do
32
+ opts[:redis_path] ||= '/tmp/redis.sock'
33
+ opts[:redis_db] ||= 1
34
+
35
+ CPEE::redis_connect opts, 'Server Routing End'
36
+ opts[:pubsubredis] = opts[:redis_dyn].call 'Server Routing End Sub'
37
+ end
38
+
39
+ run do
40
+ opts[:pubsubredis].psubscribe('callback-end:*') do |on|
41
+ on.pmessage do |pat, what, message|
42
+ _, worker, key = what.split(':',3)
43
+ index = message.index(' ')
44
+ instance = message[0...index]
45
+ opts[:redis].multi do |multi|
46
+ multi.srem("worklist:#{instance}/callbacks",key)
47
+ multi.del("worklist:#{instance}/callback/#{key}/uuid")
48
+ multi.del("worklist:#{instance}/callback/#{key}/label")
49
+ multi.del("worklist:#{instance}/callback/#{key}/position")
50
+ multi.del("worklist:#{instance}/callback/#{key}/type")
51
+ multi.del("worklist:#{instance}/callback/#{key}/subscription")
52
+ end
53
+ rescue => e
54
+ puts e.message
55
+ puts e.backtrace
56
+ end
57
+ end
58
+ end
59
+ end.go!