Name: gateway-uvi/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 list
33:
    list = []
34:
    %w{in out}.each do |e|
35:
      Dir[File.dirname(__FILE__) + "/#{e}/*"].each do |f|
36:
        meta = {}
37:
        doc = XML::Smart.open(f + '/xml')
38:
        doc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/'
39:
        meta['id'] = File.basename(f)
40:
        meta['type'] = e
41:
        meta['op'] = doc.find('string(//g:header/g:operationName)')
42:
        meta['seq'] = doc.find('string(//g:header/g:commandSequence)')
43:
        meta['val'] = doc.find('string(//g:header/g:validity)')
44:
        meta.merge!(JSON.parse(File.read(f + '/meta'))) if File.exists?(f + '/meta')
45:
        list << meta
46:
      end if @para.empty? || @para[0] == e
47:
    end  
48:
    list.sort{|a,b| [a['type'],a['id']] <=> [b['type'],b['id']]}
49:
  end
50:
  def details
51:
    return [] if @para.length == 0
52:
    path = if @para.length == 1 || @para[0].to_i.to_s == @para[0]
53:
      'in'
54:
    else
55:
      @para.shift == 'in' ? 'in' : 'out'
56:
    end
57:
    name = File.dirname(__FILE__) + "/#{path}/#{@para[0].to_i}/xml"
58:
    File.exists?(name) ? File.read(name) : nil
59:
  end
60:
  def delete
61:
    return "won't do" if @para.length == 0
62:
    path = if @para.length == 1 || @para[0].to_i.to_s == @para[0]
63:
      'in'
64:
    else
65:
      @para.shift == 'in' ? 'in' : 'out'
66:
    end
67:
    mess = nil
68:
    @para.each do |par|
69:
      name = File.dirname(__FILE__) + "/#{path}/#{par[0].to_i}"
70:
      if File.exists?(name)
71:
        FileUtils.rm_r(name)
72:
      else
73:
        mess = "some don't exist"
74:
      end
75:
    end
76:
    mess
77:
  end
78:
end #}}}
79:
 
80:
class Save < Riddl::Implementation
81:
  def response
82:
    begin
83:
      id = id.nil? ? 0 : id + 1
84:
      Dir.mkdir(File.dirname(__FILE__) + "/in/#{id}")
85:
    rescue SystemCallError
86:
      retry
87:
    end
88:
 
89:
    meta = {:answer => @h['CPEE_CALLBACK'], :time => Time.now.to_s}
90:
    File.write(File.dirname(__FILE__) + "/in/#{id}/meta", JSON.pretty_generate(meta))
91:
    File.write(File.dirname(__FILE__) + "/in/#{id}/xml", @p[0].value.read)
92:
    @headers << Riddl::Header.new('CPEE_CALLBACK', 'true')
93:
    nil
94:
  end
95:
end
96:
 
97:
class InList < Riddl::Implementation #{{{
98:
  def response
99:
    Riddl::Parameter::Complex.new("result","application/json",JSON::pretty_generate(Worker.new('in').list))
100:
  end
101:
end #}}}
102:
class OutList < Riddl::Implementation #{{{
103:
  def response
104:
    Riddl::Parameter::Complex.new("result","application/json",JSON::pretty_generate(Worker.new('out').list))
105:
  end
106:
end #}}}
107:
class InDetails < Riddl::Implementation #{{{
108:
  def response
109:
    if (det = Worker.new('in',@r[-1]).details).nil?
110:
      @status = 404
111:
    else  
112:
      Riddl::Parameter::Complex.new("gatewayRequest","text/xml",det)
113:
    end  
114:
  end
115:
end #}}}
116:
class OutDetails < Riddl::Implementation #{{{
117:
  def response
118:
    if (det = Worker.new('out',@r[-1]).details).nil?
119:
      @status = 404
120:
    else  
121:
      Riddl::Parameter::Complex.new("gatewayResponse","text/xml",det)
122:
    end  
123:
  end
124:
end #}}}
125:
class InDelete < Riddl::Implementation #{{{
126:
  def response
127:
    @status = 404 if Worker.new('in',@r[-1]).delete.nil?
128:
  end
129:
end #}}}
130:
class OutDelete < Riddl::Implementation #{{{
131:
  def response
132:
    @status = 404 if Worker.new('out',@r[-1]).delete.nil?
133:
  end
134:
end #}}}
135:
 
136:
class Command < Riddl::Implementation
137:
  ECMDS = %w{list send details delete answer upload help}
138:
H_list = <<-end #{{{
139:
(NOTHING|in|out)
140:
  List all request and possible responses
141:
end
142:
#}}}
143:
H_help = <<-end #{{{
144:
 
145:
  Show this.
146:
end
147:
#}}}
148:
H_send = <<-end #{{{
149:
INTEGER
150:
  Send one of the listed response messages to the correlation (PUSH).
151:
end
152:
#}}}
153:
 H_details = <<-end #{{{
154:
(NOTHING|in|out)? INTEGER
155:
  Send the contents of one of the listed request or response messages. If
156:
  second argument is omitted, details for request messages are shown.
157:
  Examples:
158:
    details 1
159:
    details in 1
160:
    details out 1
161:
end
162:
#}}}
163:
H_delete = <<-end #{{{
164:
(NOTHING|in|out)? INTEGER
165:
  Delete one or more of the request messages - this answers with an empty
166:
  response.
167:
  Examples:
168:
    delete 1 2 3
169:
    delete in 1 2 3
170:
    delete out 1 2 3
171:
end
172:
#}}}
173:
H_answer = <<-end #{{{
174:
INTEGER INTEGER
175:
  Answer a request (in) message (e.g. 1) with a response (out) message (e.g.
176:
  2). The available messages are shown with list. The request (in) message is
177:
  then deleted.
178:
  Example: answer 1 2
179:
end
180:
#}}}
181:
H_upload = <<-end #{{{
182:
URL
183:
  store a new response message by loading it from a URL.
184:
  Example:
185:
    upload http://fp7-adventure.eu/components/response-messages/somemessage.xml
186:
end
187:
#}}}
188:
 
189:
  def response
190:
    cmd = @p.shift.value
191:
    para = @p.map{|x|x.value}
192:
    res = case cmd
193:
      when 'commands'
194:
        ECMDS
195:
      when 'help'
196:
        ECMDS.flatten.map do |m|
197:
          h = self.class.const_defined?("H_#{m}".to_sym) ? self.class.const_get("H_#{m}".to_sym) : "\n"
198:
          "#{m.to_s.red.bold} #{h}"
199:
        end.join
200:
      when 'list'
201:
        Worker.new(*para).list
202:
      when 'details'
203:
        Worker.new(*para).details
204:
      when 'send'
205:
        return nil if para.length != 1
206:
        n2 = File.dirname(__FILE__) + "/out/#{para[0].to_i}/xml"
207:
        if para.length == 1 && File.exists?(n2)
208:
          data = File.read(n2)
209:
          client = Riddl::Client.interface("xmpp://[email protected]", "callback.xml", :xmpp => @env['xmpp'])
210:
          client.put Riddl::Parameter::Complex.new("gatewayResponse","text/xml",data)
211:
        end  
212:
        nil
213:
      when 'delete'
214:
        Worker.new(*para).delete
215:
      when 'upload'
216:
        return nil if para.length != 1
217:
        status, res = Riddl::Client.new(para[0]).get
218:
        if status == 200
219:
          fil = res[0].value.read
220:
          success = begin
221:
            XML::Smart::string(fil).validate_against(XML::Smart::open_unprotected(File.dirname(__FILE__) + "/rngs/response.rng"))
222:
          rescue
223:
            false
224:
          end
225:
          if success
226:
            begin
227:
              id = id.nil? ? 0 : id + 1
228:
              Dir.mkdir(File.dirname(__FILE__) + "/out/#{id}")
229:
            rescue SystemCallError
230:
              retry
231:
            end
232:
            File.write(File.dirname(__FILE__) + "/out/#{id}/xml",fil)
233:
            nil
234:
          else
235:
            "not a valid response message"
236:
          end
237:
        else
238:
          "not found"
239:
        end  
240:
      when 'answer'
241:
        n1 = File.dirname(__FILE__) + "/in/#{para[0].to_i}/meta"
242:
        n2 = File.dirname(__FILE__) + "/out/#{para[1].to_i}/xml"
243:
        if para.length == 2 && File.exists?(n1) && File.exists?(n2)
244:
          to = JSON.parse(File.read(n1))
245:
          data = File.read(n2)
246:
          client = Riddl::Client.interface("xmpp://" + to['answer'], "callback.xml", :xmpp => @env['xmpp'])
247:
          client.put Riddl::Parameter::Complex.new("gatewayResponse","text/xml",data)
248:
          doc = XML::Smart.string(data)
249:
          doc.register_namespace 'g', 'http://www.fp7-adventure.eu/xmlSchema/Gateways/'
250:
          Worker.new('in',para[0]).delete if doc.find('string(//g:header/g:commandSequence)') == 'LAST'
251:
        end  
252:
        nil
253:
      else
254:
        "that's not a valid command"
255:
    end || []
256:
    res = [res] unless res.is_a?(Hash) || res.is_a?(Array)
257:
    Riddl::Parameter::Complex.new("result","application/json",JSON::generate(res))
258:
  end
259:
end
260:
 
261:
Riddl::Server.new(File.dirname(__FILE__) + '/gateway.xml', :port => 9300) do
262:
  xmpp '[email protected]', 'adventure_gateway_uvi'
263:
  accessible_description true
264:
  cross_site_xhr true
265:
 
266:
  on resource do
267:
    run Save if post 'gateway-request'
268:
    run Command if put 'command'
269:
    on resource 'in' do
270:
      run InList if get
271:
      on resource do
272:
        run InDetails if get
273:
        run InDelete if delete
274:
      end
275:
    end
276:
    on resource 'out' do
277:
      run OutList if get
278:
      on resource do
279:
        run OutDetails if get
280:
        run OutDelete if delete
281:
      end
282:
    end
283:
  end
284:
end.loop!