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!