Name: correlation/server
| 1: | #!/usr/bin/ruby |
| 2: | # Apache License, Version 2.0 |
| 3: | # |
| 4: | # Copyright (c) 2013 Juergen Mangler |
| 5: | # |
| 6: | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7: | # you may not use this file except in compliance with the License. |
| 8: | # You may obtain a copy of the License at |
| 9: | # |
| 10: | # http://www.apache.org/licenses/LICENSE-2.0 |
| 11: | # |
| 12: | # Unless required by applicable law or agreed to in writing, software |
| 13: | # distributed under the License is distributed on an "AS IS" BASIS, |
| 14: | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15: | # See the License for the specific language governing permissions and |
| 16: | # limitations under the License. |
| 17: | |
| 18: | require 'rubygems' |
| 19: | require 'riddl/server' |
| 20: | require 'json' |
| 21: | require 'xml/smart' |
| 22: | require 'fileutils' |
| 23: | require 'term/ansicolor' |
| 24: | class String |
| 25: | include Term::ANSIColor |
| 26: | end |
| 27: | |
| 28: | class Worker #{{{ |
| 29: | def initialize(*para) |
| 30: | @para = para |
| 31: | end |
| 32: | def process(doc,mdoc,sdoc,f) #{{{ |
| 33: | if doc.find("string(/g:gatewayRequest/g:header/g:operationName)") == mdoc.find("string(/g:gatewayResponse/g:header/g:operationName)") |
| 34: | ips = doc.find("/g:gatewayRequest/g:payload/g:inputParameter") |
| 35: | count = 0 |
| 36: | ips.each do |e| |
| 37: | i = sdoc.find("/r:summary/r:info/r:info[@name=\"#{e.attributes['paramName']}\"]") |
| 38: | count += 1 if i.any? && i.first.attributes['value'] == e.text |
| 39: | end |
| 40: | count == ips.length ? File.basename(File.dirname(f)) : nil |
| 41: | end |
| 42: | end |
| 43: | private :process #}}} |
| 44: | def process_rule #{{{ |
| 45: | doc = @para[0] |
| 46: | ret = [] |
| 47: | Dir.glob(File.dirname(__FILE__) + "/messages/*/xml").sort{|a,b| a.split('/')[2].to_i <=> b.split('/')[2].to_i}.each do |f| |
| 48: | XML::Smart.open_unprotected(f) do |mdoc| |
| 49: | sdoc = nil |
| 50: | mdoc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 51: | mdoc.find("/g:gatewayResponse/g:payload/g:outputData[@paramName='summary']").each do |e| |
| 52: | if e.text_only? |
| 53: | sdoc = XML::Smart::string(e.text) |
| 54: | sdoc.root.namespaces.add(nil,'http://www.fp7-adventure.eu/xmlSchema/Gateways/') |
| 55: | else |
| 56: | sdoc = e.children.first.to_doc |
| 57: | end |
| 58: | sdoc.register_namespace 'r', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 59: | end |
| 60: | ret << process(doc,mdoc,sdoc,f) |
| 61: | end |
| 62: | end |
| 63: | ret.compact |
| 64: | end #}}} |
| 65: | def process_message #{{{ |
| 66: | mdoc = @para[0] |
| 67: | sdoc = nil |
| 68: | mdoc.find("/g:gatewayResponse/g:payload/g:outputData[@paramName='summary']").each do |e| |
| 69: | if e.text_only? |
| 70: | sdoc = XML::Smart::string(e.text) |
| 71: | sdoc.root.namespaces.add(nil,'http://www.fp7-adventure.eu/xmlSchema/Gateways/') |
| 72: | else |
| 73: | sdoc = e.children.first.to_doc |
| 74: | end |
| 75: | sdoc.register_namespace 'r', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 76: | end |
| 77: | ret = [] |
| 78: | Dir.glob(File.dirname(__FILE__) + "/rules/*/xml").each do |f| |
| 79: | XML::Smart.open_unprotected(f) do |doc| |
| 80: | doc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 81: | ret << process(doc,mdoc,sdoc,f) |
| 82: | end |
| 83: | end |
| 84: | ret.compact |
| 85: | end #}}} |
| 86: | def list #{{{ |
| 87: | list = [] |
| 88: | %w{rules messages}.each do |e| |
| 89: | Dir[File.dirname(__FILE__) + "/#{e}/*"].each do |f| |
| 90: | meta = {} |
| 91: | meta['id'] = File.basename(f) |
| 92: | meta['type'] = e.chop |
| 93: | meta.merge!(JSON.parse(File.read(f + '/meta'))) if File.exists?(f + '/meta') |
| 94: | list << meta |
| 95: | end if @para.empty? || @para[0] == e |
| 96: | end |
| 97: | list.sort{|a,b| [a['type'],a['id'].to_i] <=> [b['type'],b['id'].to_i]} |
| 98: | end #}}} |
| 99: | def details #{{{ |
| 100: | return [] if @para.length < 2 && (@para[0] != 'rule' || @para[0] != 'message') |
| 101: | path = @para[0] == 'rule' ? 'rules' : 'messages' |
| 102: | name = File.dirname(__FILE__) + "/#{path}/#{@para[1].to_i}/xml" |
| 103: | File.exists?(name) ? File.read(name) : nil |
| 104: | end #}}} |
| 105: | def delete #{{{ |
| 106: | if @para.length > 1 && (@para[0] == 'rule' || @para[0] == 'message') |
| 107: | path = @para.shift == 'rule' ? 'rules' : 'messages' |
| 108: | mess = nil |
| 109: | @para.each do |par| |
| 110: | name = File.dirname(__FILE__) + "/#{path}/#{par.to_i}" |
| 111: | if File.exists?(name) |
| 112: | FileUtils.rm_r(name) |
| 113: | else |
| 114: | mess = "some don't exist" |
| 115: | end |
| 116: | end |
| 117: | mess |
| 118: | else |
| 119: | "won't do" |
| 120: | end |
| 121: | end #}}} |
| 122: | end #}}} |
| 123: | |
| 124: | class RuleProcess < Riddl::Implementation #{{{ |
| 125: | def response |
| 126: | rdoc = XML::Smart::string(@p[0].value.read) |
| 127: | rdoc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 128: | meta = { |
| 129: | 'op' => rdoc.find('string(//g:header/g:operationName)'), |
| 130: | 'time' => Time.now.to_s, |
| 131: | 'source' => @h['CPEE_CALLBACK'] |
| 132: | } |
| 133: | |
| 134: | save = true |
| 135: | messages = Worker.new(rdoc).process_rule |
| 136: | if messages.length > 0 |
| 137: | what = JSON.parse(File.read(File.dirname(__FILE__) + "/messages/#{messages[0]}/meta")) |
| 138: | mdoc = XML::Smart.open_unprotected(File.dirname(__FILE__) + "/messages/#{messages[0]}/xml") |
| 139: | mdoc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 140: | client = Riddl::Client.interface("xmpp://" + meta['source'], "callback.xml", :xmpp => @env['xmpp']) |
| 141: | client.put Riddl::Parameter::Complex.new("gatewayResponse","text/xml",mdoc.to_s) |
| 142: | save = false if mdoc.find('string(//g:header/g:commandSequence)') == 'LAST' |
| 143: | Worker.new('message',messages[0]).delete if what['validity'] == 'FOREVER' |
| 144: | end |
| 145: | |
| 146: | if save |
| 147: | begin |
| 148: | id = id.nil? ? 0 : id + 1 |
| 149: | Dir.mkdir(File.dirname(__FILE__) + "/rules/#{id}") |
| 150: | rescue SystemCallError |
| 151: | retry |
| 152: | end |
| 153: | |
| 154: | File.write(File.dirname(__FILE__) + "/rules/#{id}/meta", JSON.pretty_generate(meta)) |
| 155: | File.write(File.dirname(__FILE__) + "/rules/#{id}/xml", rdoc.to_s) |
| 156: | end |
| 157: | nil |
| 158: | end |
| 159: | end #}}} |
| 160: | class MessageProcess < Riddl::Implementation #{{{ |
| 161: | def response |
| 162: | mdoc = XML::Smart::string(@p[0].value.read) |
| 163: | mdoc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 164: | meta = { |
| 165: | 'source' => @env['from'] ? @env['from'].split('/',2)[0] : 'some http', |
| 166: | 'op' => mdoc.find('string(//g:header/g:operationName)'), |
| 167: | 'validity' => (val = mdoc.find('string(//g:header/g:validity)')) == '' ? 'NOW' : val, |
| 168: | 'time' => Time.now.to_s |
| 169: | } |
| 170: | |
| 171: | rules = Worker.new(mdoc).process_message |
| 172: | id = nil |
| 173: | if meta['validity'] == 'UNTIL' |
| 174: | Dir.glob(File.dirname(__FILE__) + "/messages/*/meta").each do |f| |
| 175: | tmeta = JSON.parse(File.read(f)) |
| 176: | if tmeta['source'] == meta['source'] && tmeta['op'] == meta['op'] && tmeta['validity'] == meta['validity'] |
| 177: | id = File.basename(File.dirname(f)) |
| 178: | end |
| 179: | end |
| 180: | end |
| 181: | if (meta['validity'] == 'FOREVER' && rules.empty?) || (meta['validity'] == 'UNTIL' && id.nil?) |
| 182: | begin |
| 183: | id = id.nil? ? 0 : id + 1 |
| 184: | Dir.mkdir(File.dirname(__FILE__) + "/messages/#{id}") |
| 185: | rescue SystemCallError |
| 186: | retry |
| 187: | end |
| 188: | end |
| 189: | |
| 190: | if id |
| 191: | File.write(File.dirname(__FILE__) + "/messages/#{id}/meta", JSON.pretty_generate(meta)) |
| 192: | File.write(File.dirname(__FILE__) + "/messages/#{id}/xml", mdoc.to_s) |
| 193: | end |
| 194: | rules.each do |r| |
| 195: | to = JSON.parse(File.read(File.dirname(__FILE__) + "/rules/#{r}/meta")) |
| 196: | client = Riddl::Client.interface("xmpp://" + to['source'], "callback.xml", :xmpp => @env['xmpp']) |
| 197: | client.put Riddl::Parameter::Complex.new("gatewayResponse","text/xml",mdoc.to_s) |
| 198: | Worker.new('rule',r).delete if mdoc.find('string(//g:header/g:commandSequence)') == 'LAST' |
| 199: | end |
| 200: | nil |
| 201: | end |
| 202: | end #}}} |
| 203: | |
| 204: | class RuleList < Riddl::Implementation #{{{ |
| 205: | def response |
| 206: | Riddl::Parameter::Complex.new("result","application/json",JSON::pretty_generate(Worker.new('rules').list)) |
| 207: | end |
| 208: | end #}}} |
| 209: | class MessageList < Riddl::Implementation #{{{ |
| 210: | def response |
| 211: | Riddl::Parameter::Complex.new("result","application/json",JSON::pretty_generate(Worker.new('messages').list)) |
| 212: | end |
| 213: | end #}}} |
| 214: | class RuleDetails < Riddl::Implementation #{{{ |
| 215: | def response |
| 216: | if (det = Worker.new('rule',@r[-1]).details).nil? |
| 217: | @status = 404 |
| 218: | else |
| 219: | Riddl::Parameter::Complex.new("gatewayRequest","text/xml",det) |
| 220: | end |
| 221: | end |
| 222: | end #}}} |
| 223: | class MessageDetails < Riddl::Implementation #{{{ |
| 224: | def response |
| 225: | if (det = Worker.new('message',@r[-1]).details).nil? |
| 226: | @status = 404 |
| 227: | else |
| 228: | Riddl::Parameter::Complex.new("gatewayResponse","text/xml",det) |
| 229: | end |
| 230: | end |
| 231: | end #}}} |
| 232: | class RuleDelete < Riddl::Implementation #{{{ |
| 233: | def response |
| 234: | @status = 404 if Worker.new('rule',@r[-1]).delete.nil? |
| 235: | end |
| 236: | end #}}} |
| 237: | class MessageDelete < Riddl::Implementation #{{{ |
| 238: | def response |
| 239: | @status = 404 if Worker.new('message',@r[-1]).delete.nil? |
| 240: | end |
| 241: | end #}}} |
| 242: | |
| 243: | class Command < Riddl::Implementation |
| 244: | ECMDS = %w{list details delete correlate help} |
| 245: | H_list = <<-end #{{{ |
| 246: | (rules|messages) |
| 247: | List all request and possible responses |
| 248: | end |
| 249: | #}}} |
| 250: | H_help = <<-end #{{{ |
| 251: | |
| 252: | Show this. |
| 253: | end |
| 254: | #}}} |
| 255: | H_details = <<-end #{{{ |
| 256: | (rule|message) ID |
| 257: | Send the contents of one of the listed request or response messages. If |
| 258: | second argument is omitted, details for request messages are shown. |
| 259: | end |
| 260: | #}}} |
| 261: | H_delete = <<-end #{{{ |
| 262: | (rule|message) ID |
| 263: | Delete one of the request messages - this answers with an empty response |
| 264: | (LAST). |
| 265: | Example: delete rule 1 |
| 266: | end |
| 267: | #}}} |
| 268: | H_correlate = <<-end #{{{ |
| 269: | RULE-ID MESSAGE-ID |
| 270: | Manually correlate a rule to a message. While correlation is typically |
| 271: | automatic according to the parameters of the rule, this command can be used |
| 272: | to manually send back a message to execution. |
| 273: | The message is deleted according to normal NOW/UNTIL/FOREVER rules. |
| 274: | Example: correlate 1 2 |
| 275: | end |
| 276: | #}}} |
| 277: | |
| 278: | def response |
| 279: | cmd = @p.shift.value |
| 280: | para = @p.map{|x|x.value} |
| 281: | res = case cmd |
| 282: | when 'commands' |
| 283: | ECMDS |
| 284: | when 'help' |
| 285: | ECMDS.flatten.map do |m| |
| 286: | h = self.class.const_defined?("H_#{m}".to_sym) ? self.class.const_get("H_#{m}".to_sym) : "\n" |
| 287: | "#{m.to_s.red.bold} #{h}" |
| 288: | end.join |
| 289: | when 'correlate' |
| 290: | n1 = File.dirname(__FILE__) + "/rules/#{para[0].to_i}/meta" |
| 291: | n2 = File.dirname(__FILE__) + "/messages/#{para[1].to_i}/xml" |
| 292: | if para.length == 2 && File.exists?(n1) && File.exists?(n2) |
| 293: | to = JSON.parse(File.read(n1)) |
| 294: | data = File.read(n2) |
| 295: | client = Riddl::Client.interface("xmpp://" + to['source'], "callback.xml", :xmpp => @env['xmpp']) |
| 296: | client.put Riddl::Parameter::Complex.new("gatewayResponse","text/xml",data) |
| 297: | doc = XML::Smart.string(data) |
| 298: | doc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 299: | Worker.new('rule',para[0]).delete if doc.find('string(//g:header/g:commandSequence)') == 'LAST' |
| 300: | Worker.new('message',para[1]).delete if ['NOW','FOREVER'].include?(doc.find('string(//g:header/g:validity)')) |
| 301: | end |
| 302: | nil |
| 303: | when 'list' |
| 304: | Worker.new(*para).list |
| 305: | when 'details' |
| 306: | Worker.new(*para).details |
| 307: | when 'delete' |
| 308: | Worker.new(*para).delete |
| 309: | else |
| 310: | "that's not a valid command" |
| 311: | end || [] |
| 312: | res = [res] unless res.is_a?(Hash) || res.is_a?(Array) |
| 313: | Riddl::Parameter::Complex.new("result","application/json",JSON::generate(res)) |
| 314: | end |
| 315: | end |
| 316: | |
| 317: | Riddl::Server.new(File.dirname(__FILE__) + '/correlation.xml', :port => 9301) do |
| 318: | xmpp '[email protected]', 'adventure_correlation' |
| 319: | accessible_description true |
| 320: | cross_site_xhr true |
| 321: | |
| 322: | on resource do |
| 323: | run RuleProcess if post 'gateway-request' |
| 324: | run MessageProcess if put 'gateway-response' |
| 325: | run Command if put 'command' |
| 326: | on resource 'rules' do |
| 327: | run RuleList if get |
| 328: | on resource do |
| 329: | run RuleDetails if get |
| 330: | run RuleDelete if delete |
| 331: | end |
| 332: | end |
| 333: | on resource 'messages' do |
| 334: | run MessageList if get |
| 335: | on resource do |
| 336: | run MessageDetails if get |
| 337: | run MessageDelete if delete |
| 338: | end |
| 339: | end |
| 340: | end |
| 341: | end.loop! |
