Please disable Adblockers and enable JavaScript for domain CEWebS.cs.univie.ac.at! We have NO ADS, but they may interfere with some of our course material.
Name: server.rb
1: | #!/usr/bin/ruby |
2: | require 'pp' |
3: | require 'json' |
4: | require 'fileutils' |
5: | require 'rubygems' |
6: | require 'riddl/server' |
7: | require 'riddl/client' |
8: | require 'riddl/utils/notifications_producer' |
9: | require 'riddl/utils/fileserve' |
10: | |
11: | port = File.read(File.dirname(__FILE__)+'/port').strip |
12: | lh = File.read(File.dirname(__FILE__)+'/localhost').strip |
13: | |
14: | class Continue #{{{ |
15: | def initialize |
16: | @q = Queue.new |
17: | @m = Mutex.new |
18: | end |
19: | def waiting? |
20: | @m.synchronize do |
21: | !@q.empty? |
22: | end |
23: | end |
24: | def continue(*args) |
25: | @q.push(args.length <= 1 ? args[0] : args) |
26: | end |
27: | def clear |
28: | @q.clear |
29: | end |
30: | def wait |
31: | @q.deq |
32: | end |
33: | end #}}} |
34: | |
35: | class NotificationsHandler < Riddl::Utils::Notifications::Producer::HandlerBase #{{{ |
36: | def ws_open(socket) |
37: | @data.communication[@key] = socket |
38: | @data.events.each do |a| |
39: | if a[1].has_key?(@key) |
40: | a[1][@key] = socket |
41: | end |
42: | end |
43: | @data.votes.each do |a| |
44: | if a[1].has_key?(@key) |
45: | a[1][@key] = socket |
46: | end |
47: | end |
48: | end |
49: | def ws_close |
50: | delete |
51: | end |
52: | def ws_message(data) |
53: | begin |
54: | doc = XML::Smart::string(data) |
55: | callback = doc.find("string(/vote/@id)") |
56: | result = doc.find("string(/vote)") |
57: | @data.callbacks[callback].callback([Riddl::Parameter::Simple.new('wsvote',result)]) |
58: | @data.callbacks.delete(callback) |
59: | rescue => e |
60: | puts e.message |
61: | puts e.backtrace |
62: | puts "Invalid message over websocket" |
63: | end |
64: | end |
65: | |
66: | def create |
67: | @data.notifications.subscriptions[@key].read do |doc| |
68: | turl = doc.find('string(/n:subscription/@url)') |
69: | url = turl == '' ? nil : turl |
70: | @data.communication[@key] = url |
71: | doc.find('/n:subscription/n:topic').each do |t| |
72: | t.find('n:event').each do |e| |
73: | @data.events["#{t.attributes['id']}/#{e}"] ||= {} |
74: | @data.events["#{t.attributes['id']}/#{e}"][@key] = (url == "" ? nil : url) |
75: | end |
76: | t.find('n:vote').each do |e| |
77: | @data.votes["#{t.attributes['id']}/#{e}"] ||= {} |
78: | @data.votes["#{t.attributes['id']}/#{e}"][@key] = (url == "" ? nil : url) |
79: | end |
80: | end |
81: | end |
82: | end |
83: | def delete |
84: | @data.notifications.subscriptions[@key].delete if @data.notifications.subscriptions.include?(@key) |
85: | @data.communication[@key].io.close_connection if @data.communication[@key].class == Riddl::Utils::Notifications::Producer::WS |
86: | @data.communication.delete(@key) |
87: | |
88: | @data.events.each do |eve,keys| |
89: | keys.delete_if{|k,v| @key == k} |
90: | end |
91: | @data.votes.each do |eve,keys| |
92: | keys.delete_if do |k,v| |
93: | if @key == k |
94: | @data.callbacks.each{|voteid,cb|cb.delete_if!(eve,k)} |
95: | true |
96: | end |
97: | end |
98: | end |
99: | end |
100: | def update |
101: | if @data.notifications.subscriptions.include?(@key) |
102: | url = @data.communication[@key] |
103: | evs = [] |
104: | vos = [] |
105: | @data.events.each { |e,v| evs << e } |
106: | @data.votes.each { |e,v| vos << e } |
107: | @data.notifications.subscriptions[@key].read do |doc| |
108: | turl = doc.find('string(/n:subscription/@url)') |
109: | url = turl == '' ? url : turl |
110: | @data.communication[@key] = url |
111: | doc.find('/n:subscription/n:topic').each do |t| |
112: | t.find('n:event').each do |e| |
113: | @data.events["#{t.attributes['id']}/#{e}"] ||= {} |
114: | @data.events["#{t.attributes['id']}/#{e}"][@key] = url |
115: | evs.delete("#{t.attributes['id']}/#{e}") |
116: | end |
117: | t.find('n:vote').each do |e| |
118: | @data.votes["#{t.attributes['id']}/#{e}"] ||= {} |
119: | @data.votes["#{t.attributes['id']}/#{e}"][@key] = url |
120: | vos.delete("#{t.attributes['id']}/#{e}") |
121: | end |
122: | end |
123: | end |
124: | evs.each { |e| @data.events[e].delete(@key) if @data.events[e] } |
125: | vos.each do |e| |
126: | @data.callbacks.each{|voteid,cb|cb.delete_if!(e,@key)} |
127: | @data.votes[e].delete(@key) if @data.votes[e] |
128: | end |
129: | end |
130: | end |
131: | end #}}} |
132: | |
133: | class ActivityHappens < Riddl::Implementation #{{{ |
134: | def response |
135: | controller = @a[0] |
136: | |
137: | activity = {} |
138: | activity['label'] = @h.keys.include?('CPEE_INSTANCE') ? "#{@h['CPEE_LABEL']} (#{@h['CPEE_INSTANCE'].split('/').last})" : "DUMMY LABEL" |
139: | activity['user'] = '*' |
140: | activity['url'] = @h['CPEE_CALLBACK'] |
141: | activity['id'] = @h['CPEE_CALLBACK'].split('/').last |
142: | |
143: | activity['cpee_activity_id'] = @h['CPEE_ACTIVITY'] |
144: | activity['cpee_base'] = @h['CPEE_BASE'] |
145: | activity['cpee_instance'] = @h['CPEE_INSTANCE'] |
146: | |
147: | activity['uuid'] = @h['CPEE_ATTR_UUID'] |
148: | |
149: | omo = @p.shift.value |
150: | activity['orgmodel'] = @h[ 'CPEE_ATTR_' + omo.upcase] || omo |
151: | |
152: | dom = @p.shift.value |
153: | domain = activity['domain'] = @h[ 'CPEE_ATTR_' + dom.upcase] || dom |
154: | |
155: | |
156: | |
157: | activity['wl_instance'] = "#{controller.opts[:url]}/#{domain}" |
158: | |
159: | activity['form'] = @p.shift.value |
160: | activity['unit'] = @p.first.name == 'unit' ? @p.shift.value : '*' |
161: | activity['role'] = @p.first.name == 'role' ? @p.shift.value : '*' |
162: | activity['parameters'] = JSON.generate(@p) |
163: | status, content, headers = Riddl::Client.new(activity['orgmodel']).get |
164: | if status == 200 |
165: | begin |
166: | xml = content[0].value.read |
167: | schema = XML::Smart.open(@a[0].opts['ORG_SCHEMA']) |
168: | org_xml = XML::Smart.string(xml) |
169: | raise 'a fucked up xml (wink wink)' unless org_xml.validate_against(schema) |
170: | org_xml.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0' |
171: | rescue => e |
172: | puts e.message |
173: | puts e.backtrace |
174: | @a[0][domain].notify('task/invalid', :callback_id => activity['id'], :reason => 'orgmodel invalid',:domain => activity['domain'], :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'] ) if @a[0].keys.include? domain |
175: | @status = 404 |
176: | return |
177: | end |
178: | attributes = "" |
179: | if activity['role'] != '*' |
180: | attributes += "@role='#{activity['role']}'" |
181: | attributes += " and " if activity['unit'] != '*' |
182: | end |
183: | attributes += "@unit='#{activity['unit']}'" if activity['unit'] != '*' |
184: | user = org_xml.find("/o:organisation/o:subjects/o:subject[o:relation[#{attributes}]]").map{ |e| e.attributes['uid'] } |
185: | if user.empty? |
186: | @a[0][domain].notify('task/invalid', :callback_id => activity['id'], :reason => 'no users found for this combination of unit/role',:domain => activity['domain'],: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'] ) if @a[0].keys.include? domain |
187: | @status = 404 |
188: | return |
189: | end |
190: | @a[0].add_activity domain, activity |
191: | @a[0][domain].add_orgmodel Riddl::Protocols::Utils::escape(activity['orgmodel']), xml |
192: | Thread.new do |
193: | results = @a[0][domain].vote('task/add', :user => user , :domain => activity['domain'],: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'] ) |
194: | if (results.length == 1) && (user.include? results[0]) |
195: | activity["user"] = results[0] |
196: | info = user_info(activity,activity["user"]) |
197: | @a[0][domain].notify('task/add', :user => user,:callback_id => activity['id'], :domain => activity['domain'],: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'] ) |
198: | @a[0][domain].notify('user/take', :user => results[0], :callback_id => activity['id'], :domain => activity['domain'],: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']) |
199: | else |
200: | @a[0][domain].notify('task/add', :user => user,:callback_id => activity['id'], :domain => activity['domain'],: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']) if @a[0].keys.include? domain |
201: | end |
202: | end |
203: | @headers << Riddl::Header.new('CPEE_CALLBACK','true') |
204: | else |
205: | @status = 404 |
206: | end |
207: | end |
208: | end #}}} |
209: | |
210: | class TaskDel < Riddl::Implementation #{{{ |
211: | def response |
212: | index = @a[0].activities.index{ |e| e["id"] == @r.last } |
213: | if index |
214: | |
215: | activity = @a[0].activities.delete_at(index) |
216: | @a[0].activities.serialize |
217: | if @r.length == 3 |
218: | @a[0].notify('task/delete', :callback_id => activity['id'], :domain => activity['domain'],: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']) |
219: | Riddl::Client.new(activity['url']).put |
220: | else |
221: | info = user_info(activity,activity['user']) |
222: | @a[0].notify('user/finish', :callback_id => activity['id'], :user => activity['user'], :role => activity['role'],:domain => activity['domain'],: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']) |
223: | end |
224: | else |
225: | @status = 404 |
226: | end |
227: | end |
228: | end #}}} |
229: | |
230: | class Show_Domains < Riddl::Implementation #{{{ |
231: | def response |
232: | out = XML::Smart.string('<domains/>') |
233: | @a[0].keys.each { |x| out.root.add('domain', :name=> x)} |
234: | Riddl::Parameter::Complex.new("domains","text/xml") do |
235: | out.to_s |
236: | end |
237: | end |
238: | end #}}} |
239: | |
240: | class Show_Domain_Tasks < Riddl::Implementation #{{{ |
241: | def response |
242: | out = XML::Smart.string('<tasks/>') |
243: | @a[0].orgmodels.each do |fname| |
244: | doc = XML::Smart.open(File.dirname(__FILE__) + "/domains/#{Riddl::Protocols::Utils::unescape(@r.last)}/orgmodels/#{fname}") |
245: | doc.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0' |
246: | @a[0].activities.each do |activity| |
247: | 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'] |
248: | x.add "label" , activity['label'] |
249: | x.add "role" , activity['role'] |
250: | x.add "unit" , activity['unit'] |
251: | |
252: | if activity['user']!='*' |
253: | user = doc.find("/o:organisation/o:subjects/o:subject[@uid='#{activity['user']}']").first |
254: | x.add "user", user.attributes['id'], :uid => user.attributes['uid'] |
255: | else |
256: | |
257: | xpath = '' |
258: | xpath = "[@role='#{activity['role']}' and @unit='#{activity['unit']}']" if (activity['unit'] != '*' && activity['role'] != '*' ) |
259: | xpath = "[@role='#{activity['role']}']" if (activity['unit'] == '*' && activity['role'] != '*' ) |
260: | xpath = "[@unit='#{activity['unit']}']" if (activity['unit'] != '*' && activity['role'] == '*' ) |
261: | |
262: | doc.find("/o:organisation/o:subjects/o:subject[o:relation#{xpath}]").each{|e| x.add "user", e.attributes['id'], :uid => e.attributes['uid'] } |
263: | end |
264: | end |
265: | end |
266: | Riddl::Parameter::Complex.new("domain_tasks","text/xml", out.to_s) |
267: | end |
268: | end #}}} |
269: | |
270: | class Show_Tasks < Riddl:: Implementation #{{{ |
271: | def response |
272: | out = XML::Smart.string('<tasks/>') |
273: | tasks = {} |
274: | @a[0].orgmodels.each do |e| |
275: | XML::Smart.open("domains/#{@a[0].domain}/orgmodels/#{e}") do |doc| |
276: | doc.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0' |
277: | doc.find("/o:organisation/o:subjects/o:subject[@uid='#{@r[-2]}']/o:relation").each do |rel| |
278: | @a[0].activities.each do |activity| |
279: | if (activity['role']=='*' || activity['role'].casecmp(rel.attributes['role']) == 0) && (activity['unit'] == '*' || activity['unit'].casecmp(rel.attributes['unit']) == 0) && (activity['user']=='*' || activity['user']==@r[-2]) |
280: | tasks["#{activity['id']}"] = {:uid => activity['user'], :label => activity['label'] } |
281: | end |
282: | end |
283: | end |
284: | end |
285: | end |
286: | tasks.each{|k,v| out.root.add("task", :id => k, :uid => v[:uid], :label => v[:label])} |
287: | x = Riddl::Parameter::Complex.new("return","text/xml") do |
288: | out.to_s |
289: | end |
290: | x |
291: | end |
292: | end #}}} |
293: | |
294: | class TaskTake < Riddl::Implementation #{{{ |
295: | def response |
296: | index = @a[0].activities.index{ |c| c["id"] == @r.last } |
297: | if index |
298: | activity = @a[0].activities[index] |
299: | activity["user"] = @r[-3] if user_ok(activity,@r[-3]) |
300: | info = user_info(activity,@r[-3]) |
301: | @a[0].activities.serialize |
302: | @a[0].notify('user/take', :user => @r[-3], :callback_id => activity['id'], :domain => activity['domain'], :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, :wl_instance => activity['wl_instance']) |
303: | Riddl::Client.new(@a[0].activities[index]['url']).put [ |
304: | Riddl::Header.new('CPEE_UPDATE','true'), |
305: | Riddl::Header.new('CPEE_UPDATE_STATUS','take') |
306: | ] |
307: | else |
308: | @status = 404 |
309: | end |
310: | end |
311: | end #}}} |
312: | |
313: | class TaskGiveBack < Riddl::Implementation #{{{ |
314: | def response |
315: | index = @a[0].activities.index{ |c| c["id"] == @r.last } |
316: | if index && (@a[0].activities[index]['user'] == @r[-3]) |
317: | activity = @a[0].activities[index] |
318: | activity["user"] = '*' |
319: | callback_id = @a[0].activities[index]['id'] |
320: | @a[0].activities.serialize |
321: | @a[0].notify('user/giveback', :callback_id => activity['id'], :domain => activity['domain'], :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'], :wl_instance => activity['wl_instance']) |
322: | Riddl::Client.new(@a[0].activities[index]['url']).put [ |
323: | Riddl::Header.new('CPEE_UPDATE','true'), |
324: | Riddl::Header.new('CPEE_UPDATE_STATUS','giveback') |
325: | ] |
326: | else |
327: | @status = 404 |
328: | end |
329: | end |
330: | end #}}} |
331: | |
332: | class TaskDetails < Riddl::Implementation #{{{ |
333: | def response |
334: | index = @a[0].activities.index{ |c| c["id"] == @r.last } |
335: | if index |
336: | Riddl::Parameter::Complex.new "data","application/json", JSON.generate({'url' => @a[0].activities[index]['url'], 'form' => @a[0].activities[index]['form'], 'parameters' => @a[0].activities[index]['parameters'], 'label' => @a[0].activities[index]['label']}) |
337: | else |
338: | @status = 404 |
339: | end |
340: | end |
341: | end #}}} |
342: | |
343: | class ExCallback < Riddl::Implementation #{{{ |
344: | def response |
345: | controller = @a[0] |
346: | id = @r[0].to_i |
347: | callback = @r[2] |
348: | controller[id].mutex.synchronize do |
349: | if controller[id].callbacks.has_key?(callback) |
350: | controller[id].callbacks[callback].callback(@p,@h) |
351: | end |
352: | end |
353: | end |
354: | end #}}} |
355: | |
356: | class Callbacks < Riddl::Implementation #{{{ |
357: | def response |
358: | controller = @a[0] |
359: | opts = @a[0].opts |
360: | id = @r[0].to_i |
361: | unless controller[id] |
362: | @status = 400 |
363: | return |
364: | end |
365: | Riddl::Parameter::Complex.new("info","text/xml") do |
366: | activity = XML::Smart::string("<callbacks details='#{opts[:mode]}'/>") |
367: | if opts[:mode] == :debug |
368: | controller[id].callbacks.each do |k,v| |
369: | activity.root.add("callback",{"id" => k},"[#{v.protocol.to_s}] #{v.info}") |
370: | end |
371: | end |
372: | activity.to_s |
373: | end |
374: | end |
375: | end #}}} |
376: | |
377: | class GetOrgModels < Riddl::Implementation #{{{ |
378: | def response |
379: | out = XML::Smart.string('<orgmodels/>') |
380: | @a[0].orgmodels.each{|e| out.root.add("orgmodel", e)} |
381: | Riddl::Parameter::Complex.new "return","text/xml", out.to_s |
382: | end |
383: | end #}}} |
384: | |
385: | class Activities < Array #{{{ |
386: | def initialize(domain) |
387: | super() |
388: | @domain = domain |
389: | end |
390: | |
391: | def unserialize |
392: | self.clear.replace JSON.parse!(File.read(File.dirname(__FILE__) + "/domains/#{@domain}/activities.sav")) rescue [] |
393: | end |
394: | |
395: | def serialize |
396: | Thread.new do |
397: | File.write File.dirname(__FILE__) + "/domains/#{@domain}/activities.sav", JSON.pretty_generate(self) |
398: | end |
399: | end |
400: | end #}}} |
401: | |
402: | class ControllerItem #{{{ |
403: | attr_reader :communication, :events, :notifications, :activities, :notifications_handler, :votes, :votes_results, :mutex, :callbacks, :opts, :orgmodels, :domain |
404: | |
405: | def initialize(domain,opts) |
406: | @events = {} |
407: | @votes = {} |
408: | @votes_results = {} |
409: | @mutex = Mutex.new |
410: | @opts = opts |
411: | @communication = {} |
412: | @callbacks = {} |
413: | @domain = domain |
414: | @activities = Activities.new(domain) |
415: | @orgmodels = [] |
416: | @notifications = Riddl::Utils::Notifications::Producer::Backend.new( |
417: | File.dirname(__FILE__) + "/topics.xml", |
418: | File.dirname(__FILE__) + "/domains/#{domain}/notifications/" |
419: | ) |
420: | @notifications_handler = NotificationsHandler.new(self) |
421: | @notifications.subscriptions.keys.each do |key| |
422: | @notifications_handler.key(key).create |
423: | end |
424: | end |
425: | |
426: | class Callback #{{{ |
427: | def initialize(info,handler,method,event,key,protocol,*data) |
428: | @info = info |
429: | @event = event |
430: | @key = key |
431: | @data = data |
432: | @handler = handler |
433: | @protocol = protocol |
434: | @method = method.class == Symbol ? method : :callback |
435: | end |
436: | |
437: | attr_reader :info, :protocol, :method |
438: | |
439: | def delete_if!(event,key) |
440: | if @key == key && @event == event |
441: | puts "=====" |
442: | puts *@data |
443: | puts *@data.length |
444: | puts "====" |
445: | puts @method |
446: | puts "====" |
447: | #TODO JUERGEN SOLLTE KONTROLLIEREN |
448: | @handler.send @method, :DELETE,nil, *@data |
449: | end |
450: | nil |
451: | end |
452: | |
453: | def callback(result=nil,options=[]) |
454: | @handler.send @method, result, options, *@data |
455: | end |
456: | end #}}} |
457: | |
458: | def add_orgmodel(name,content) #{{{ |
459: | FileUtils.mkdir_p(File.dirname(__FILE__) + "/domains/#{@domain}/orgmodels/") |
460: | @orgmodels << name unless @orgmodels.include?(name) |
461: | File.write(File.dirname(__FILE__) + "/domains/#{@domain}/orgmodels/" + name, content) |
462: | end #}}} |
463: | |
464: | def notify(what,content={})# {{{ |
465: | item = @events[what] |
466: | if item |
467: | item.each do |ke,ur| |
468: | Thread.new(ke,ur) do |key,url| |
469: | notf = build_message(key,what,content) |
470: | if url.class == String |
471: | client = Riddl::Client.new(url,'http://riddl.org/ns/common-patterns/notifications-consumer/1.0/consumer.xml') |
472: | params = notf.map{|ke,va|Riddl::Parameter::Simple.new(ke,va)} |
473: | params << Riddl::Header.new("WORKLIST_BASE",@opts[:url]) |
474: | params << Riddl::Header.new("WORKLIST_DOMAIN",@domain) |
475: | client.post params |
476: | elsif url.class == Riddl::Utils::Notifications::Producer::WS |
477: | e = XML::Smart::string("<event/>") |
478: | notf.each do |k,v| |
479: | e.root.add(k,v) |
480: | end |
481: | url.send(e.to_s) rescue nil |
482: | end |
483: | end |
484: | end |
485: | end |
486: | end # }}} |
487: | |
488: | def vote(what,content={})# {{{ |
489: | voteid = Digest::MD5.hexdigest(Kernel::rand().to_s) |
490: | item = @votes[what] |
491: | if item && item.length > 0 |
492: | continue = Continue.new |
493: | @votes_results[voteid] = [] |
494: | inum = 0 |
495: | item.each do |key,url| |
496: | if url.class == String |
497: | inum += 1 |
498: | elsif url.class == Riddl::Utils::Notifications::Producer::WS |
499: | inum += 1 unless url.closed? |
500: | end |
501: | end |
502: | item.each do |key,url| |
503: | Thread.new(key,url,content.dup) do |k,u,c| |
504: | callback = Digest::MD5.hexdigest(Kernel::rand().to_s) |
505: | notf = build_message(k,what,c,'vote',callback) |
506: | if u.class == String |
507: | client = Riddl::Client.new(u,'http://riddl.org/ns/common-patterns/notifications-consumer/1.0/consumer.xml',:xmpp => @opts[:xmpp]) |
508: | params = notf.map{|ke,va|Riddl::Parameter::Simple.new(ke,va)} |
509: | params << Riddl::Header.new("WORKLIST_BASE",@opts[:url]) |
510: | params << Riddl::Header.new("WORKLIST_DOMAIN",@domain) |
511: | @mutex.synchronize do |
512: | status, result, headers = client.post params |
513: | if headers["WORKLIST_CALLBACK"] && headers["WORKLIST_CALLBACK"] == 'true' |
514: | @callbacks[callback] = Callback.new("vote #{notf.find{|a,b| a == 'notification'}[1]}", self, :vote_callback, what, k, :http, continue, voteid, callback, inum) |
515: | else |
516: | vote_callback(result,nil,continue,voteid,callback,inum) |
517: | end |
518: | end |
519: | elsif u.class == Riddl::Utils::Notifications::Producer::WS |
520: | @callbacks[callback] = Callback.new("vote #{notf.find{|a,b| a == 'notification'}[1]}", self, :vote_callback, what, k, :ws, continue, voteid, callback, inum) |
521: | e = XML::Smart::string("<vote/>") |
522: | notf.each do |ke,va| |
523: | e.root.add(ke,va) |
524: | end |
525: | u.send(e.to_s) rescue nil |
526: | end |
527: | end |
528: | |
529: | end |
530: | continue.wait |
531: | |
532: | @votes_results.delete(voteid).compact.uniq |
533: | else |
534: | [] |
535: | end |
536: | end # }}} |
537: | |
538: | def vote_callback(result,options,continue,voteid,callback,num)# {{{ |
539: | @callbacks.delete(callback) |
540: | if result == :DELETE |
541: | @votes_results[voteid] << nil |
542: | else |
543: | @votes_results[voteid] << ((result && result[0]) ? result[0].value : nil) |
544: | end |
545: | if (num == @votes_results[voteid].length) |
546: | continue.continue |
547: | end |
548: | end # }}} |
549: | |
550: | def build_message(key,what,content,type='event',callback=nil)# {{{ |
551: | res = [] |
552: | res << ['key' , key] |
553: | res << ['topic' , ::File::dirname(what)] |
554: | res << [type , ::File::basename(what)] |
555: | res << ['notification' , JSON::generate(content)] |
556: | res << ['callback' , callback] unless callback.nil? |
557: | res << ['fingerprint-with-consumer-secret', Digest::MD5.hexdigest(res.join(''))] |
558: | end # }}} |
559: | |
560: | end #}}} |
561: | |
562: | class Controller < Hash #{{{ |
563: | attr_reader :opts # geht ohne net |
564: | def initialize(opts) |
565: | super() |
566: | @opts = opts |
567: | Dir::glob(File.dirname(__FILE__) + '/domains/*').each do |f| |
568: | domain = File.basename(f) |
569: | self[domain] = ControllerItem.new(domain,@opts) |
570: | self[domain].activities.unserialize |
571: | Dir::glob("#{f}/orgmodels/*").each do |g| |
572: | self[domain].add_orgmodel File.basename(g), File.read(g) |
573: | end |
574: | end |
575: | end |
576: | |
577: | def add_activity(domain,activity) |
578: | self[domain] ||= ControllerItem.new(domain,@opts) |
579: | self[domain].activities << activity |
580: | self[domain].activities.serialize |
581: | end |
582: | end #}}} |
583: | |
584: | class AssignTask < Riddl::Implementation #{{{ |
585: | def response |
586: | index = @a[0].activities.index{ |c| c["id"] == @r.last } |
587: | if index |
588: | user = @p[0].value |
589: | @a[0].activities[index]["user"] = user if user_ok(@a[0].activities[index],user) |
590: | callback_id = @a[0].activities[index]['id'] |
591: | info = user_info(@a[0].activities[index],user) |
592: | @a[0].activities.serialize |
593: | @a[0].notify('user/take', :index => callback_id, :user => @p[0].value, :organisation => info) |
594: | Riddl::Client.new(@a[0].activities[index]['url']).put [ |
595: | Riddl::Header.new('CPEE_UPDATE','true'), |
596: | Riddl::Header.new('CPEE_UPDATE_STATUS','take') |
597: | ] |
598: | else |
599: | @status = 404 |
600: | end |
601: | end |
602: | end #}}} |
603: | |
604: | def user_ok(task,user) |
605: | status, resp = Riddl::Client.new(task['orgmodel']).resource("/").get |
606: | orgmodel = XML::Smart.string(resp[0].value.read) |
607: | orgmodel.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0' |
608: | subjects = orgmodel.find('/o:organisation/o:subjects/o:subject') |
609: | unit = task['unit'] |
610: | role = task['role'] |
611: | if (unit=='*') |
612: | if (role=='*') |
613: | subjects.each{|s| return true if s.attributes['uid']==user} |
614: | else |
615: | orgmodel.find("/o:organisation/o:subjects/o:subject[o:relation/@role='#{role}']").each do |s| |
616: | return true if user==s.attributes['uid'] |
617: | end |
618: | end |
619: | else |
620: | if (role=='*') |
621: | orgmodel.find("/o:organisation/o:subjects/o:subject[o:relation/@unit='#{unit}']").each do |s| |
622: | return true if user==s.attributes['uid'] |
623: | end |
624: | else |
625: | orgmodel.find("/o:organisation/o:subjects/o:subject[o:relation/@unit='#{unit}' and o:relation/@role='#{role}']").each do |s| |
626: | return true if user==s.attributes['uid'] |
627: | end |
628: | end |
629: | end |
630: | false |
631: | end |
632: | |
633: | def user_info(task,user) |
634: | orgmodel = XML::Smart.open(task['orgmodel']) |
635: | orgmodel.register_namespace 'o', 'http://cpee.org/ns/organisation/1.0' |
636: | user = orgmodel.find("/o:organisation/o:subjects/o:subject[@uid='#{user}']/o:relation") |
637: | {}.tap{ |t| user.map{|u| (t[u.attributes['unit']]||=[]) << u.attributes['role']}} |
638: | end |
639: | |
640: | |
641: | Riddl::Server.new(::File.dirname(__FILE__) + '/worklist.xml', :port => port, :host => lh) do |
642: | accessible_description true |
643: | cross_site_xhr true |
644: | |
645: | @riddl_opts['ORG_SCHEMA'] = ::File.dirname(__FILE__) + '/organisation.rng' |
646: | |
647: | controller = Controller.new(@riddl_opts) |
648: | interface 'main' do |
649: | run ActivityHappens,controller if post 'activityhappens' |
650: | run Show_Domains,controller if get |
651: | on resource do |r| |
652: | domain = r[:h]['RIDDL_DECLARATION_PATH'].split('/')[1] |
653: | domain = Riddl::Protocols::Utils::unescape(domain) |
654: | if controller.keys.include? domain |
655: | run Show_Domain_Tasks,controller[domain] if get |
656: | on resource 'callbacks' do |
657: | run Callbacks,controller[domain] if get |
658: | on resource do |
659: | run ExCallback,controller[domain] if put |
660: | end |
661: | end |
662: | on resource 'orgmodels' do |
663: | run GetOrgModels, controller[domain] if get |
664: | end |
665: | on resource 'tasks' do |
666: | on resource do |
667: | run AssignTask,controller[domain] if put 'uid' |
668: | run TaskDel,controller[domain] if delete |
669: | end |
670: | end |
671: | on resource do |
672: | on resource 'tasks' do |
673: | run Show_Tasks,controller[domain] if get |
674: | on resource do |r| |
675: | run TaskDetails,controller[domain] if get |
676: | run TaskTake,controller[domain] if put 'take' |
677: | run TaskGiveBack,controller[domain] if put 'giveback' |
678: | run TaskDel,controller[domain] if delete |
679: | end |
680: | end |
681: | end |
682: | end |
683: | end |
684: | end |
685: | |
686: | interface 'events' do |r| |
687: | domain = r[:h]['RIDDL_DECLARATION_PATH'].split('/')[1] |
688: | domain = Riddl::Protocols::Utils::unescape(domain) |
689: | use Riddl::Utils::Notifications::Producer::implementation(controller[domain].notifications, controller[domain].notifications_handler) if controller.keys.include? domain |
690: | end |
691: | |
692: | end.loop! |