Name: spe/handlerwrappers/adventure.rb
| 1: | # Apache License, Version 2.0 |
| 2: | # |
| 3: | # Copyright (c) 2013 Juergen Mangler |
| 4: | # |
| 5: | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6: | # you may not use this file except in compliance with the License. |
| 7: | # You may obtain a copy of the License at |
| 8: | # |
| 9: | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10: | # |
| 11: | # Unless required by applicable law or agreed to in writing, software |
| 12: | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13: | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14: | # See the License for the specific language governing permissions and |
| 15: | # limitations under the License. |
| 16: | require 'xmlsimple' |
| 17: | require 'pp' |
| 18: | |
| 19: | class AdvStanza < Blather::Stanza |
| 20: | def self.new |
| 21: | node = super :message |
| 22: | node.type = :chat |
| 23: | node.id = SecureRandom.uuid |
| 24: | node |
| 25: | end |
| 26: | |
| 27: | def body=(bd) |
| 28: | node = XML::Smart::Dom::Element.new(self) |
| 29: | node.add('body',"<advmsg><advbody>#{bd}</advbody><advtimesent>" + Time.now.strftime('%d.%m.%Y %H:%M:%S') + "</advtimesent></advmsg>") |
| 30: | end |
| 31: | end |
| 32: | |
| 33: | class AdventureHandlerWrapper < WEEL::HandlerWrapperBase |
| 34: | def initialize(arguments,endpoint=nil,position=nil,continue=nil) # {{{ |
| 35: | @controller = arguments[0] |
| 36: | @handler_continue = continue |
| 37: | @handler_endpoint = endpoint |
| 38: | @handler_position = position |
| 39: | @handler_passthrough = nil |
| 40: | @handler_returnValue = nil |
| 41: | @handler_another = false |
| 42: | end # }}} |
| 43: | |
| 44: | def activity_handle(passthrough, parameters) # {{{ |
| 45: | return if @handler_another |
| 46: | raise "No endpoint defined. Stopping." if @handler_endpoint.nil? |
| 47: | @controller.notify("running/activity_calling", :instance => @controller.instance, :activity => @handler_position, :passthrough => passthrough, :endpoint => @handler_endpoint, :parameters => parameters) |
| 48: | |
| 49: | if passthrough.nil? |
| 50: | whichep = 0 |
| 51: | if parameters[:parameters] && parameters[:parameters][:SPE_PARTNER] |
| 52: | whichep = parameters[:parameters][:SPE_PARTNER].to_i rescue 0 |
| 53: | parameters[:parameters].delete(:SPE_PARTNER) |
| 54: | end |
| 55: | |
| 56: | ep = @handler_endpoint.is_a?(Array) ? @handler_endpoint[whichep] : @handler_endpoint |
| 57: | gw, op = ep.split('#',2) |
| 58: | pr, gw = gw.split(/:\/\//) |
| 59: | pr, gw = 'xmpp-w', pr if gw.nil? |
| 60: | |
| 61: | doc = XML::Smart.open_unprotected(::File.dirname(__FILE__) + '/template_req.xml') |
| 62: | doc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 63: | |
| 64: | doc.find("/g:gatewayRequest/g:header/g:operationName").each { |e| e.text = op } |
| 65: | doc.find("/g:gatewayRequest/g:header/g:interactionType").each { |e| e.text = 'asynchronous' } |
| 66: | |
| 67: | doc.find("/g:gatewayRequest/g:payload").each do |e| |
| 68: | parameters[:parameters].each do |k,v| |
| 69: | tmp = JSON.parse(v) rescue v |
| 70: | if tmp.is_a?(Hash) || tmp.is_a?(Array) |
| 71: | e.add("g:inputParameter", XmlSimple.xml_out(tmp,'keeproot'=>''), 'encoding' => 'false', 'paramName' => k) |
| 72: | else |
| 73: | e.add("g:inputParameter", v, 'encoding' => 'false', 'paramName' => k) |
| 74: | end |
| 75: | end |
| 76: | end |
| 77: | |
| 78: | result = {} |
| 79: | |
| 80: | if pr == 'xmpp-p' |
| 81: | pr = 'xmpp-r' |
| 82: | gw = '[email protected]' |
| 83: | end |
| 84: | |
| 85: | if pr == 'xmpp-r' |
| 86: | client = Riddl::Client.interface('xmpp://' + gw, ::File.dirname(__FILE__) + '/gateway.xml', :xmpp => @controller.xmpp) |
| 87: | |
| 88: | callback = Digest::MD5.hexdigest(Kernel::rand().to_s) |
| 89: | @controller.callbacks[callback] = CPEE::Callback.new("callback activity: #{@handler_position}",self,:callback,nil,nil,:xmpp) |
| 90: | @handler_passthrough = callback |
| 91: | |
| 92: | params = [] |
| 93: | params << Riddl::Header.new("CPEE_BASE",@controller.base_jid) |
| 94: | params << Riddl::Header.new("CPEE_INSTANCE",@controller.instance_jid) |
| 95: | params << Riddl::Header.new("CPEE_CALLBACK",@controller.instance_jid + '/callbacks/' + callback) |
| 96: | params << Riddl::Parameter::Complex.new("gatewayRequest","text/xml",doc.to_s) |
| 97: | |
| 98: | status, result, headers = client.post params |
| 99: | |
| 100: | raise "Could not reach #{ep}" unless status == 200 |
| 101: | else #{{{ |
| 102: | msg = AdvStanza.new |
| 103: | msg.to = gw |
| 104: | msg.body = doc.root.dump |
| 105: | |
| 106: | handlers = handler = nil |
| 107: | handler = Proc.new do |raw| |
| 108: | begin |
| 109: | bd = raw.body.gsub(/<\?[^?]+\?>/,'') |
| 110: | doc = XML::Smart::string(bd) |
| 111: | if doc.find('string(/advmsg/responseto)') == msg.id |
| 112: | callback([ Riddl::Parameter::Complex.new('gatewayResponse','text/xml',StringIO.new(doc.find('/advmsg/advbody/*').first.dump)) ]) |
| 113: | handlers.delete_if{|g,h| h == handler} unless @handler_another |
| 114: | end |
| 115: | rescue => e |
| 116: | @handler_continue.continue WEEL::Signal::Error, "xmpp-w:// problem: #{e}." |
| 117: | end |
| 118: | end |
| 119: | handlers = @controller.xmpp.register_handler(:message,&handler) |
| 120: | @controller.xmpp.write msg |
| 121: | end #}}} |
| 122: | end |
| 123: | end # }}} |
| 124: | |
| 125: | def activity_result_value # {{{ |
| 126: | @handler_returnValue |
| 127: | end # }}} |
| 128: | def activity_result_status # {{{ |
| 129: | WEEL::Status.new(1, "everything okay") |
| 130: | end # }}} |
| 131: | |
| 132: | def activity_stop # {{{ |
| 133: | unless @handler_passthrough.nil? |
| 134: | # delete callback and passthrough |
| 135: | # when engine is restarted new callbacks have to be established |
| 136: | # passthrough is set to nil, as continuation is not supported right now |
| 137: | @controller.notify("semantic/alert", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position, :alert => "The instance stopped." ) |
| 138: | @controller.callbacks.delete(@handler_passthrough) |
| 139: | @handler_passthrough = nil |
| 140: | end |
| 141: | end # }}} |
| 142: | def activity_passthrough_value # {{{ |
| 143: | @handler_passthrough |
| 144: | end # }}} |
| 145: | |
| 146: | def activity_no_longer_necessary # {{{ |
| 147: | true |
| 148: | end # }}} |
| 149: | |
| 150: | def inform_activity_done # {{{ |
| 151: | @controller.notify("running/activity_done", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position) |
| 152: | end # }}} |
| 153: | def inform_activity_manipulate # {{{ |
| 154: | @controller.notify("running/activity_manipulating", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position) |
| 155: | end # }}} |
| 156: | def inform_activity_failed(err) # {{{ |
| 157: | puts err.message |
| 158: | puts err.backtrace |
| 159: | @controller.notify("running/activity_failed", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position, :message => err.message, :line => err.backtrace[0].match(/(.*?):(\d+):/)[2], :where => err.backtrace[0].match(/(.*?):(\d+):/)[1]) |
| 160: | end # }}} |
| 161: | |
| 162: | def inform_syntax_error(err,code)# {{{ |
| 163: | puts err.message |
| 164: | puts err.backtrace |
| 165: | @controller.notify("properties/description/error", :instance => @controller.instance, :message => err.message) |
| 166: | end# }}} |
| 167: | def inform_manipulate_change(status,changed_dataelements,changed_endpoints,dataelements,endpoints) # {{{ |
| 168: | unless status.nil? |
| 169: | @controller.serialize_status! |
| 170: | @controller.notify("properties/status/change", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position, :id => status.id, :message => status.message) |
| 171: | end |
| 172: | unless changed_dataelements.nil? |
| 173: | @controller.serialize_dataelements! |
| 174: | @controller.notify("properties/dataelements/change", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position, :changed => Hash[changed_dataelements.map{|e| [e,dataelements[e]]}] ) |
| 175: | |
| 176: | changed_dataelements.each do |de| |
| 177: | doc = XML::Smart.open_unprotected(::File.dirname(__FILE__) + '/../resources/topics.xml') |
| 178: | doc.register_namespace 't', 'http://riddl.org/ns/common-patterns/notifications-producer/1.0' |
| 179: | doc.find('/t:topics/t:topic[@id="semantic"]/t:event').each do |a| |
| 180: | ann = "##{a.text}" |
| 181: | if dataelements[:ANNOTATIONS][de.to_s] =~ /#{ann}/ |
| 182: | plain = ann.gsub(/[^a-zA-Z0-9]/,'') |
| 183: | @controller.notify("semantic/#{plain}", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position, plain => dataelements[de]) |
| 184: | end |
| 185: | end |
| 186: | end |
| 187: | end |
| 188: | unless changed_endpoints.nil? |
| 189: | @controller.serialize_endpoints! |
| 190: | @controller.notify("properties/endpoints/change", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position, :changed => Hash[changed_endpoints.map{|e| [e,endpoints[e]]}] ) |
| 191: | end |
| 192: | end # }}} |
| 193: | def inform_position_change(ipc={}) # {{{ |
| 194: | @controller.serialize_positions! |
| 195: | ipc[:instance] = @controller.instance |
| 196: | @controller.notify("properties/position/change", ipc) |
| 197: | end # }}} |
| 198: | def inform_state_change(newstate) # {{{ |
| 199: | if @controller |
| 200: | @controller.serialize_state! |
| 201: | @controller.notify("properties/state/change", :info => @controller.info, :instance => @controller.instance, :state => newstate) |
| 202: | end |
| 203: | end # }}} |
| 204: | |
| 205: | def vote_sync_after # {{{ |
| 206: | @controller.call_vote("running/syncing_after", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position) |
| 207: | end # }}} |
| 208: | def vote_sync_before(parameters=nil) # {{{ |
| 209: | @controller.call_vote("running/syncing_before", :endpoint => @handler_endpoint, :instance => @controller.instance, :activity => @handler_position) |
| 210: | end # }}} |
| 211: | |
| 212: | def callback(result) #{{{ |
| 213: | if result.length == 1 && result[0].is_a?(Riddl::Parameter::Complex) && result[0].mimetype == 'text/xml' && result[0].name == 'gatewayResponse' |
| 214: | res = {} |
| 215: | begin |
| 216: | doc = XML::Smart::string(result[0].value.read) |
| 217: | doc.register_namespace 'r', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 218: | if doc.find("count(/r:gatewayResponse/r:payload/r:outputData[@paramName='summary'])") == 0 |
| 219: | @handler_another = false |
| 220: | @handler_returnValue = nil |
| 221: | @controller.callbacks.delete(@handler_passthrough) |
| 222: | @handler_passthrough = nil |
| 223: | @handler_continue.continue |
| 224: | else |
| 225: | doc.find("/r:gatewayResponse/r:payload/r:outputData[@paramName='summary']").each do |e| |
| 226: | if e.text_only? |
| 227: | sum = XML::Smart::string(e.text) |
| 228: | sum.root.namespaces.add(nil,'http://www.fp7-adventure.eu/xmlSchema/Gateways/') |
| 229: | else |
| 230: | sum = e.children.first.to_doc |
| 231: | end |
| 232: | sum.register_namespace 'r', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/' |
| 233: | sum.find("/r:summary/r:info/r:info").each do |i| |
| 234: | res[i.attributes['name'].downcase] = i.attributes['value'] |
| 235: | res[i.attributes['name'].downcase] = (Float(res[i.attributes['name'].downcase]) rescue res[i.attributes['name'].downcase]) |
| 236: | end |
| 237: | @controller.notify("running/activity_receiving", :instance => @controller.instance, :activity => @handler_position, :endpoint => @handler_endpoint, :result => JSON.generate(res)) |
| 238: | if doc.find('string(/r:gatewayResponse/r:header/r:commandSequence)') == 'INTERMEDIATE' |
| 239: | @handler_another = true |
| 240: | @handler_returnValue = res |
| 241: | @controller.notify("properties/handlerwrapper/result", :instance => @controller.instance, :activity => @handler_position, :endpoint => @handler_endpoint, :result => JSON.generate(@handler_returnValue) ) |
| 242: | @handler_continue.continue WEEL::Signal::Again |
| 243: | else |
| 244: | @handler_another = false |
| 245: | @handler_returnValue = res |
| 246: | @controller.callbacks.delete(@handler_passthrough) |
| 247: | @handler_passthrough = nil |
| 248: | @controller.notify("properties/handlerwrapper/result", :instance => @controller.instance, :activity => @handler_position, :endpoint => @handler_endpoint, :result => JSON.generate(@handler_returnValue) ) |
| 249: | @handler_continue.continue |
| 250: | end |
| 251: | end |
| 252: | end |
| 253: | rescue => err |
| 254: | @handler_continue.continue WEEL::Signal::Error, "Your gateway returns a gatewayResponse that doesn't conform to the specification." |
| 255: | end |
| 256: | else |
| 257: | @handler_continue.continue WEEL::Signal::Error, "Your gateway sends a response that I don't understand. It should have one part, content-type text/xml and be called gatewayResponse." |
| 258: | end |
| 259: | end #}}} |
| 260: | |
| 261: | def simulate(type,nesting,sequence,parent,parameters={}) #{{{ |
| 262: | @controller.notify("simulating/step", |
| 263: | :instance => @controller.instance, |
| 264: | :sequence => sequence, |
| 265: | :type => type, |
| 266: | :nesting => nesting, |
| 267: | :parent => parent, |
| 268: | :parameters => parameters |
| 269: | ) |
| 270: | end #}}} |
| 271: | |
| 272: | #def test_condition(mr,code) #{{{ |
| 273: | # p 'test condition ------------------------------------------------------------------------' |
| 274: | # client = Riddl::Client.new("http://localhost:9292",nil) |
| 275: | # resource = client.resource('/') |
| 276: | |
| 277: | # status, res = resource.post [ |
| 278: | # Riddl::Parameter::Simple.new("dataelements", JSON::generate(mr.data)), |
| 279: | # Riddl::Parameter::Simple.new("endpoints", JSON::generate(mr.endpoints)), |
| 280: | # Riddl::Parameter::Simple.new("resultname", "result"), |
| 281: | # Riddl::Parameter::Simple.new("resultvalue", JSON::generate([])), |
| 282: | # Riddl::Parameter::Simple.new("code", code) |
| 283: | # ] |
| 284: | |
| 285: | # mval = nil |
| 286: | # if status == 200 |
| 287: | # id = res[0].value |
| 288: | |
| 289: | # resource = client.resource('/' + id) |
| 290: | # status, res = resource.get |
| 291: | # if status == 200 |
| 292: | # mval = JSON::parse(res[0].value) |
| 293: | # else |
| 294: | # raise 'condition exec error B' |
| 295: | # end |
| 296: | # else |
| 297: | # raise 'condition exec error A' |
| 298: | # end |
| 299: | |
| 300: | # mval["output"] |
| 301: | #end #}}} |
| 302: | def manipulate(mr,code,result=nil,status=nil) #{{{ |
| 303: | client = Riddl::Client.new("http://fp7-adventure.eu:9292",nil) |
| 304: | resource = client.resource('/') |
| 305: | |
| 306: | od = JSON::generate(mr.original_data) |
| 307: | oe = JSON::generate(mr.original_endpoints) |
| 308: | ood = JSON::parse(od) |
| 309: | ooe = JSON::parse(oe) |
| 310: | |
| 311: | status, res = resource.post [ |
| 312: | Riddl::Parameter::Simple.new("dataelements", od), |
| 313: | Riddl::Parameter::Simple.new("endpoints", oe), |
| 314: | Riddl::Parameter::Simple.new("resultname", "result"), |
| 315: | Riddl::Parameter::Simple.new("resultvalue", result.nil? ? '{}' : JSON::generate(result)), |
| 316: | Riddl::Parameter::Simple.new("code", code) |
| 317: | ] |
| 318: | |
| 319: | mval = nil |
| 320: | if status == 200 |
| 321: | id = res[0].value |
| 322: | |
| 323: | begin |
| 324: | resource = client.resource('/' + id) |
| 325: | status, res = resource.get |
| 326: | if status == 200 |
| 327: | mval = JSON::parse(res[0].value) |
| 328: | else |
| 329: | raise 'script exec error B' |
| 330: | end |
| 331: | sleep 1 unless mval['state'] == 'executed' |
| 332: | end until mval['state'] == 'executed' |
| 333: | else |
| 334: | raise 'script exec error A' |
| 335: | end |
| 336: | |
| 337: | mval['dataelements'].each do |k,v| |
| 338: | if ood[k] != v |
| 339: | mr.data.send(k+'=',v) |
| 340: | end |
| 341: | end |
| 342: | mval['endpoints'].each do |k,v| |
| 343: | if ooe[k] != v |
| 344: | mr.endpoints.send(k+'=',v) |
| 345: | end |
| 346: | end |
| 347: | if mval['status'].is_a?(Hash) |
| 348: | if mr.status.id != mval['status']['id'].to_i |
| 349: | mr.status.update(mval['status']['id'].to_i,mval['status']['message']) |
| 350: | if mval['status']['id'].to_i == -1 |
| 351: | raise mval['status']['message'] |
| 352: | end |
| 353: | end |
| 354: | end |
| 355: | end #}}} |
| 356: | end |
