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! |