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