jcarley-simplews 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +22 -0
- data/bin/start_jobs_ws +53 -0
- data/bin/start_ws +48 -0
- data/lib/rake_pipeline.rb +237 -0
- data/lib/simplews.rb +425 -0
- data/lib/simplews/jobs.rb +502 -0
- data/lib/simplews/notifier.rb +138 -0
- data/lib/simplews/rake.rb +72 -0
- metadata +90 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Miguel Vazquez
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
= simplews
|
2
|
+
|
3
|
+
This library simplifies the creation of SOAP web services. It is based on
|
4
|
+
soap4r, and adds a DSL for serving methods that create automatically the WSDL.
|
5
|
+
Additionally, it offers support for asynchronous jobs.
|
6
|
+
|
7
|
+
Documentation and detailed examples available at http://bioinfovm05.dacya.ucm.es/simplews/index.html
|
8
|
+
|
9
|
+
== Note on Patches/Pull Requests
|
10
|
+
|
11
|
+
* Fork the project.
|
12
|
+
* Make your feature addition or bug fix.
|
13
|
+
* Add tests for it. This is important so I don't break it in a
|
14
|
+
future version unintentionally.
|
15
|
+
* Commit, do not mess with rakefile, version, or history.
|
16
|
+
(if you want to have your own version, that is fine but
|
17
|
+
bump version in a commit by itself I can ignore when I pull)
|
18
|
+
* Send me a pull request. Bonus points for topic branches.
|
19
|
+
|
20
|
+
== Copyright
|
21
|
+
|
22
|
+
Copyright (c) 2009 Miguel Vazquez. See LICENSE for details.
|
data/bin/start_jobs_ws
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'simplews'
|
4
|
+
require "optparse"
|
5
|
+
require 'simplews/rake'
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: #{ $_ } [options] <config_file>"
|
10
|
+
|
11
|
+
opts.on("-h", "--host HOST", String, "Select host interface, localhost by default") do |host|
|
12
|
+
options[:host] = host
|
13
|
+
end
|
14
|
+
|
15
|
+
opts.on("-p", "--port PORT", Integer, "Select interface port, 1984 by default") do |port|
|
16
|
+
options[:port] = port
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on("-n", "--name NAME", String, "Select the name of the web server interface, SimpleWS by default") do |name|
|
20
|
+
options[:name] = name
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-d", "--description DESCRIPTION", String, "Description of the service") do |description|
|
24
|
+
options[:description] = description
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-w", "--wsdl FILENAME", String, "Save the WSDL file") do |filename|
|
28
|
+
options[:wsdl] = filename
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-t", "--path PATH", String, "Work directory for the WS") do |filename|
|
32
|
+
options[:directory] = filename
|
33
|
+
end
|
34
|
+
|
35
|
+
end.parse!
|
36
|
+
|
37
|
+
filename = ARGV[0]
|
38
|
+
|
39
|
+
server = SimpleWS::Jobs.new(options[:name], options[:description], options[:host], options[:port], options[:directory]) do
|
40
|
+
actions = File.open(filename)do |f| f.read end
|
41
|
+
eval actions
|
42
|
+
end
|
43
|
+
|
44
|
+
server.wsdl(options[:wsdl]) if options[:wsdl]
|
45
|
+
|
46
|
+
trap('INT'){
|
47
|
+
puts "Stopping server"
|
48
|
+
server.shutdown
|
49
|
+
}
|
50
|
+
|
51
|
+
server.start
|
52
|
+
|
53
|
+
|
data/bin/start_ws
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'simplews'
|
4
|
+
require "optparse"
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: #{ $_ } [options] <config_file>"
|
9
|
+
|
10
|
+
opts.on("-h", "--host HOST", String, "Select host interface, localhost by default") do |host|
|
11
|
+
options[:host] = host
|
12
|
+
end
|
13
|
+
|
14
|
+
opts.on("-p", "--port PORT", Integer, "Select interface port, 1984 by default") do |port|
|
15
|
+
options[:port] = port
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on("-n", "--name NAME", String, "Select the name of the web server interface, SimpleWS by default") do |name|
|
19
|
+
options[:name] = name
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-d", "--description DESCRIPTION", String, "Description of the service") do |description|
|
23
|
+
options[:description] = description
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-w", "--wsdl FILENAME", String, "Save the WSDL file") do |filename|
|
27
|
+
options[:wsdl] = filename
|
28
|
+
end
|
29
|
+
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
filename = ARGV[0]
|
33
|
+
|
34
|
+
server = SimpleWS.new(options[:name], options[:description], options[:host], options[:port]) do
|
35
|
+
actions = File.open(filename)do |f| f.read end
|
36
|
+
eval actions
|
37
|
+
end
|
38
|
+
|
39
|
+
server.wsdl(options[:wsdl]) if options[:wsdl]
|
40
|
+
|
41
|
+
trap('INT'){
|
42
|
+
puts "Stopping server"
|
43
|
+
server.shutdown
|
44
|
+
}
|
45
|
+
|
46
|
+
server.start
|
47
|
+
|
48
|
+
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
# Include the step_dependencies and step_def methods to simplify Pipelines. Steps depend on
|
4
|
+
# the step strictly above by default. The output of the step is save marshaled,
|
5
|
+
# except for Strings which are save as text. The input of the step, the output
|
6
|
+
# of the previous step if availabe is accessed with the input method
|
7
|
+
#
|
8
|
+
# Example::
|
9
|
+
#
|
10
|
+
# step_def :text do
|
11
|
+
# "Text to revert"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# step_def :revert do
|
15
|
+
# text = input
|
16
|
+
# text.reverse
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
module Rake::Pipeline
|
20
|
+
|
21
|
+
module Rake::Pipeline::Step
|
22
|
+
|
23
|
+
class << self
|
24
|
+
|
25
|
+
@@step_descriptions = {}
|
26
|
+
def step_descriptions
|
27
|
+
@@step_descriptions
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_description(re, step, message)
|
31
|
+
@@step_descriptions[re] = "#{ step }: #{ message }"
|
32
|
+
end
|
33
|
+
|
34
|
+
@@last_step = nil
|
35
|
+
def step_dependencies(name, dependencies = nil)
|
36
|
+
|
37
|
+
re = Regexp.new(/(?:^|\/)#{name}\/.*$/)
|
38
|
+
|
39
|
+
# Take the last_description and associate it with the name
|
40
|
+
if Rake.application.last_description
|
41
|
+
add_description(re, name, Rake.application.last_description)
|
42
|
+
end
|
43
|
+
|
44
|
+
if dependencies.nil? && ! @@last_step.nil?
|
45
|
+
dependencies = @@last_step
|
46
|
+
end
|
47
|
+
@@last_step = name
|
48
|
+
|
49
|
+
# Generate the Hash definition
|
50
|
+
case
|
51
|
+
when dependencies.nil?
|
52
|
+
re
|
53
|
+
when String === dependencies || Symbol === dependencies
|
54
|
+
{re => lambda{|filename| filename.sub(name.to_s,dependencies.to_s) }}
|
55
|
+
when Array === dependencies
|
56
|
+
{re => lambda{|filename| dependencies.collect{|dep| filename.sub(name.to_s, dep.to_s) } }}
|
57
|
+
when Proc === dependencies
|
58
|
+
{re => dependencies}
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_filename(filename)
|
64
|
+
filename.match(/^(.*?)([^\/]*)\/([^\/]*)$/)
|
65
|
+
{
|
66
|
+
:prefix => $1 == "" ? "." : $1,
|
67
|
+
:step => $2,
|
68
|
+
:job => $3,
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module Rake::Pipeline::Info
|
75
|
+
|
76
|
+
def self.info_file(filename)
|
77
|
+
info = Rake::Pipeline::Step.parse_filename(filename)
|
78
|
+
"#{info[:prefix]}/.info/#{info[:job]}.yaml"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.load_info(t)
|
82
|
+
filename = t.name
|
83
|
+
info_filename = info_file(filename)
|
84
|
+
|
85
|
+
if File.exists? info_filename
|
86
|
+
YAML.load(File.open(info_filename))
|
87
|
+
else
|
88
|
+
{}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.save_info(t, info = {})
|
93
|
+
filename = t.name
|
94
|
+
info_filename = info_file(filename)
|
95
|
+
|
96
|
+
FileUtils.mkdir_p File.dirname(info_filename) unless File.exists? File.dirname(info_filename)
|
97
|
+
File.open(info_filename,'w'){|file|
|
98
|
+
file.write YAML.dump info
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
@@steps = []
|
104
|
+
|
105
|
+
def steps
|
106
|
+
@@steps
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
NON_ASCII_PRINTABLE = /[^\x20-\x7e\s]/
|
111
|
+
def is_binary?(file)
|
112
|
+
binary = file.read(1024) =~ NON_ASCII_PRINTABLE
|
113
|
+
file.rewind
|
114
|
+
binary
|
115
|
+
end
|
116
|
+
|
117
|
+
def step_descriptions
|
118
|
+
Rake::Pipeline::Step.step_descriptions
|
119
|
+
end
|
120
|
+
|
121
|
+
def step_dependencies(*args)
|
122
|
+
Rake::Pipeline::Step.step_dependencies(*args)
|
123
|
+
end
|
124
|
+
|
125
|
+
def step_dir(step = nil, filename = nil)
|
126
|
+
filename ||= @@current_task.name
|
127
|
+
info = Rake::Pipeline::Step.parse_filename(filename)
|
128
|
+
"%s/%s" % [info[:prefix], step || info[:step]]
|
129
|
+
end
|
130
|
+
|
131
|
+
def input_filename(task = nil, step = nil)
|
132
|
+
task ||= @@current_task
|
133
|
+
if step.nil?
|
134
|
+
task.prerequisites.first
|
135
|
+
else
|
136
|
+
File.join(step_dir(step, task.name), job_name)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def output_filename(task = nil)
|
141
|
+
task ||= @@current_task
|
142
|
+
task.name
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def infile(task, step = nil, &block)
|
147
|
+
filename = input_filename(task, step)
|
148
|
+
File.open(filename) do |f|
|
149
|
+
block.call(f)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def outfile(task, &block)
|
154
|
+
filename = output_filename(task)
|
155
|
+
File.open(filename, 'w') do |f|
|
156
|
+
block.call(f)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def load_input(task, step = nil)
|
161
|
+
infile(task, step) do |f|
|
162
|
+
if is_binary?(f)
|
163
|
+
Marshal.load(f)
|
164
|
+
else
|
165
|
+
f.read
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
def save_output(task, output)
|
172
|
+
case
|
173
|
+
when output.nil?
|
174
|
+
nil
|
175
|
+
when String === output
|
176
|
+
outfile(task){|f| f.write output }
|
177
|
+
else
|
178
|
+
outfile(task){|f| f.write Marshal.dump(output) }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# We cannot load the input variable before the block.call, so we need another method
|
183
|
+
|
184
|
+
# Load the input data from the previous step
|
185
|
+
def input(step = nil)
|
186
|
+
load_input(@@current_task, step) if @@current_task
|
187
|
+
end
|
188
|
+
|
189
|
+
if defined? SimpleWS::Jobs
|
190
|
+
def method_missing(symbol, *args)
|
191
|
+
if $_current_job.methods.include? symbol.to_s
|
192
|
+
$_current_job.send(symbol, *args)
|
193
|
+
else
|
194
|
+
raise "Method #{ symbol } not found"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
else
|
198
|
+
# Announce steps
|
199
|
+
def step(state, message ="")
|
200
|
+
puts "#{ state }: #{ message }"
|
201
|
+
end
|
202
|
+
|
203
|
+
# Add values to the info file
|
204
|
+
def info(values = {})
|
205
|
+
info = Rake::Pipeline::Info.load_info(@@current_task)
|
206
|
+
info = info.merge values
|
207
|
+
Rake::Pipeline::Info.save_info(@@current_task, info)
|
208
|
+
info
|
209
|
+
end
|
210
|
+
|
211
|
+
def job_name
|
212
|
+
File.basename(@@current_task.name)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Define a new step, it depends on the previously defined by default. It
|
217
|
+
# saves the output of the block so it can be loaded by the input method of
|
218
|
+
# the next step
|
219
|
+
def step_def(name, dependencies = nil, &block)
|
220
|
+
@@steps << name
|
221
|
+
rule step_dependencies(name, dependencies) do |t|
|
222
|
+
|
223
|
+
# Save the task object to be able to load the input
|
224
|
+
@@current_task = t
|
225
|
+
|
226
|
+
output = block.call(t)
|
227
|
+
|
228
|
+
save_output(t, output)
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
if __FILE__ == $0
|
235
|
+
|
236
|
+
p Rake::Pipeline::Info.info_file('work/diseases/t')
|
237
|
+
end
|
data/lib/simplews.rb
ADDED
@@ -0,0 +1,425 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
#gem 'soap4r'
|
3
|
+
gem 'soap4r-ruby1.9'
|
4
|
+
require 'soap/rpc/standaloneServer'
|
5
|
+
require 'builder'
|
6
|
+
|
7
|
+
|
8
|
+
# SimpleWS is a class that wraps SOAP::RPC::StandaloneServer to ease the
|
9
|
+
# implementation of Web Services, specifically the generation of the
|
10
|
+
# +WSDL+ file. It provides a particular syntax that allows to specify
|
11
|
+
# the methods that are going to be served along with the types of the
|
12
|
+
# parameters and of the response so that the +WSDL+ can be generated
|
13
|
+
# accordingly. Actual Servers can be instances of this class where
|
14
|
+
# methods are assigned dynamically or of classes that extend SimpleWS.
|
15
|
+
# class TestWS < SimpleWS
|
16
|
+
# def hi(name)
|
17
|
+
# "Hi #{name}, how are you?"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def initialize(*args)
|
21
|
+
# super(*args)
|
22
|
+
# serve :hi, %w(name), :name => :string, :return => :string
|
23
|
+
#
|
24
|
+
# serve :bye, %w(name), :name => :string, :return => :string do |name|
|
25
|
+
# "Bye bye #{name}. See you soon."
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Creating a server and starting it
|
32
|
+
#
|
33
|
+
# server = TestWS.new("TestWS", "Greeting Services", 'localhost', '1984')
|
34
|
+
# server.wsdl("TestWS.wsdl")
|
35
|
+
#
|
36
|
+
# Thread.new do
|
37
|
+
# server.start
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Client code. This could be run in another process.
|
41
|
+
#
|
42
|
+
# driver = SimpleWS::get_driver('http://localhost:1984', "TestWS")
|
43
|
+
# puts driver.hi('Gladis') #=> "Hi Gladis, how are you?"
|
44
|
+
# puts driver.bye('Gladis') #=> "Bye bye Gladis. See you soon."
|
45
|
+
#
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
class SimpleWS < SOAP::RPC::StandaloneServer
|
50
|
+
# Saves method defined in the class to be served by the instances
|
51
|
+
INHERITED_METHODS = {} unless defined? INHERITED_METHODS
|
52
|
+
|
53
|
+
# This is a helper function for clients. Given the +url+ where the
|
54
|
+
# server is listening, as well as the name of the server, it can
|
55
|
+
# manually access the +wsdl+ function and retrieve the complete +WSDL+
|
56
|
+
# file. This works *only* in web servers of class SimpleWS, not on
|
57
|
+
# the general SOAP::RPC::StandaloneServer or any other type of web
|
58
|
+
# server.
|
59
|
+
def self.get_wsdl(url, name)
|
60
|
+
require 'soap/rpc/driver'
|
61
|
+
driver = SOAP::RPC::Driver.new(url, "urn:#{ name }")
|
62
|
+
driver.add_method('wsdl')
|
63
|
+
driver.wsdl
|
64
|
+
end
|
65
|
+
|
66
|
+
# Builds on the get_wsdl function to provide the client with a
|
67
|
+
# reference to the driver. Again, only works with SimpleWS servers.
|
68
|
+
def self.get_driver(url, name)
|
69
|
+
require 'soap/wsdlDriver'
|
70
|
+
require 'fileutils'
|
71
|
+
|
72
|
+
tmp = File.open("/tmp/simpleWS.wsdl",'w')
|
73
|
+
tmp.write SimpleWS::get_wsdl(url, name)
|
74
|
+
tmp.close
|
75
|
+
driver = SOAP::WSDLDriverFactory.new("/tmp/simpleWS.wsdl").create_rpc_driver
|
76
|
+
FileUtils.rm "/tmp/simpleWS.wsdl"
|
77
|
+
|
78
|
+
driver
|
79
|
+
end
|
80
|
+
|
81
|
+
attr_accessor :description
|
82
|
+
|
83
|
+
# Creates an instance of a Server. The parameter +name+ specifies the
|
84
|
+
# +namespace+ used in the +WSDL+, +description+ is the description
|
85
|
+
# also included in the +WSDL+. The parameters +host+ and +port+,
|
86
|
+
# specify where the server will be listening, they are parameters of
|
87
|
+
# the +super+ class SOAP::RPC::StandaloneServer, they are made
|
88
|
+
# explicit here because they are included in the +WSDL+ as well.
|
89
|
+
def initialize(name=nil, description=nil, host=nil, port=nil, *args, &block)
|
90
|
+
name ||= self.class.to_s
|
91
|
+
description ||= "Web Server for #{ name }"
|
92
|
+
host ||= "localhost"
|
93
|
+
port ||= "1984"
|
94
|
+
|
95
|
+
super(description, "urn:#{ name }", host, port, *args)
|
96
|
+
|
97
|
+
@host = host
|
98
|
+
@port = port
|
99
|
+
@name = name
|
100
|
+
@description = description
|
101
|
+
@messages = []
|
102
|
+
@operations = []
|
103
|
+
@bindings = []
|
104
|
+
@method_descriptions = {}
|
105
|
+
@method_order = []
|
106
|
+
|
107
|
+
if block_given?
|
108
|
+
instance_eval &block
|
109
|
+
end
|
110
|
+
|
111
|
+
puts "Server #{ name } at #{ host }:#{ port }"
|
112
|
+
|
113
|
+
|
114
|
+
desc "Return the WSDL describing the web server"
|
115
|
+
param_desc :return => "WSDL description"
|
116
|
+
serve :wsdl, %w(), :return => :string
|
117
|
+
|
118
|
+
desc "Return HMLT table with documentation for the web service"
|
119
|
+
param_desc :return => "HTML table with documentation"
|
120
|
+
serve :documentation, %w(), :return => :string
|
121
|
+
|
122
|
+
INHERITED_METHODS.each{|name, info|
|
123
|
+
@@last_description = info[:description]
|
124
|
+
@@last_param_description = info[:param_descriptions]
|
125
|
+
serve name, info[:args], info[:types], &info[:block]
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def name=(name)
|
130
|
+
@name = name
|
131
|
+
appname = name
|
132
|
+
default_namespace = "urn:#{name}"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Add a description for the next method served.
|
136
|
+
@@last_description = nil
|
137
|
+
@@last_param_description = nil
|
138
|
+
def desc(text = "")
|
139
|
+
@@last_description = text
|
140
|
+
end
|
141
|
+
|
142
|
+
# Add descriptions for the parameters of the next method served
|
143
|
+
def param_desc(param_descriptions = {})
|
144
|
+
@@last_param_description = {}
|
145
|
+
param_descriptions.each{|param, description| @@last_param_description[param.to_s] = description}
|
146
|
+
end
|
147
|
+
|
148
|
+
# Add a description for the next method served defined in at the class level
|
149
|
+
def self.desc(text = "")
|
150
|
+
@@last_description = text
|
151
|
+
end
|
152
|
+
|
153
|
+
# Add descriptions for the parameters of the next method served at the class
|
154
|
+
# level
|
155
|
+
def self.param_desc(param_descriptions = {})
|
156
|
+
@@last_param_description = {}
|
157
|
+
param_descriptions.each{|param, description| @@last_param_description[param.to_s] = description}
|
158
|
+
end
|
159
|
+
|
160
|
+
# This method tells the server to provide a method named by the +name+
|
161
|
+
# parameter, with arguments listed in the +args+ parameter. The
|
162
|
+
# +types+ parameter specifies the types of the arguments as will be
|
163
|
+
# described in the +WSDL+ file (see the TYPES2WSDL constant). The
|
164
|
+
# actual method called will be the one with the same name. Optionally
|
165
|
+
# a block can be provided, this block will be used to define a
|
166
|
+
# function named as in name.
|
167
|
+
#
|
168
|
+
# If the method returns something, then the type of the return value
|
169
|
+
# must be specified in the +types+ parameter as :return. If not value
|
170
|
+
# for :return parameter is specified in the +types+ parameter the
|
171
|
+
# method is taken to return no value. Other than that, if a parameter
|
172
|
+
# type is omitted it is taken to be :string.
|
173
|
+
def serve(name, args=[], types={}, &block)
|
174
|
+
|
175
|
+
@method_descriptions[name] = {:args => args, :types => types, :block => block,
|
176
|
+
:description => @@last_description, :param_descriptions => @@last_param_description}
|
177
|
+
|
178
|
+
@@last_description = nil
|
179
|
+
@@last_param_description = nil
|
180
|
+
|
181
|
+
@method_order << name
|
182
|
+
if block
|
183
|
+
inline_name = "_inline_" + name.to_s
|
184
|
+
add_to_ruby(inline_name, &block)
|
185
|
+
add_method_as(self,inline_name, name.to_s,*args)
|
186
|
+
else
|
187
|
+
add_method(self,name.to_s,*args)
|
188
|
+
end
|
189
|
+
|
190
|
+
add_to_wsdl(name, args, types)
|
191
|
+
|
192
|
+
nil
|
193
|
+
end
|
194
|
+
|
195
|
+
# Saves the method to be served by the instances. The initialization of an
|
196
|
+
# instance check if there where any methods declared to be served in the class
|
197
|
+
# and add them.
|
198
|
+
def self.serve(name, args=[], types={}, &block)
|
199
|
+
INHERITED_METHODS[name] = {:args => args, :types => types, :block => block,
|
200
|
+
:description => @@last_description, :param_descriptions => @@last_param_description}
|
201
|
+
@@last_description = nil
|
202
|
+
@@last_param_description = nil
|
203
|
+
end
|
204
|
+
|
205
|
+
# If +filename+ is specified it saves the +WSDL+ file in that file. If
|
206
|
+
# no +filename+ is specified it returns a string containing the
|
207
|
+
# +WSDL+. The no parameter version is served by default, so that
|
208
|
+
# clients can use this method to access the complete +WSDL+ file, as
|
209
|
+
# used in get_wsdl and get_driver.
|
210
|
+
def wsdl(filename = nil)
|
211
|
+
wsdl = WSDL_STUB.dup
|
212
|
+
wsdl.gsub!(/\$\{MESSAGES\}/m,@messages.join("\n"))
|
213
|
+
wsdl.gsub!(/\$\{OPERATIONS\}/m,@operations.join("\n"))
|
214
|
+
wsdl.gsub!(/\$\{BINDINGS\}/m,@bindings.join("\n"))
|
215
|
+
wsdl.gsub!(/\$\{NAME\}/,@name)
|
216
|
+
wsdl.gsub!(/\$\{DESCRIPTION\}/,@description)
|
217
|
+
wsdl.gsub!(/\$\{LOCATION\}/,"http://#{ @host }:#{ @port }")
|
218
|
+
|
219
|
+
if filename
|
220
|
+
directory = File.dirname(File.expand_path(filename))
|
221
|
+
FileUtils.mkdir_p directory unless File.exists? directory
|
222
|
+
File.open(filename,'w') {|f| f.write wsdl }
|
223
|
+
nil
|
224
|
+
else
|
225
|
+
wsdl
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def documentation(filename = nil)
|
230
|
+
html_table = Builder::XmlMarkup.new(:indent => 2).table :class => "WS_documentation" do |xml|
|
231
|
+
xml.thead do
|
232
|
+
xml.tr do
|
233
|
+
xml.th "Operation", :class => "WS_operation"
|
234
|
+
xml.th "Parameters", :class => "WS_parameters"
|
235
|
+
xml.th "Documentation", :class => "WS_documentation"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
xml.tbody do
|
239
|
+
@method_order.each do |method|
|
240
|
+
desc = @method_descriptions[method][:description] || ""
|
241
|
+
case
|
242
|
+
when method.to_s == 'wsdl' || method.to_s == 'documentation'
|
243
|
+
type = 'WS_documentation'
|
244
|
+
when desc =~ /^Job management:/
|
245
|
+
type = 'WS_job_management'
|
246
|
+
when @method_descriptions[method][:args].include?( :sugested_name )
|
247
|
+
type = 'WS_task'
|
248
|
+
else
|
249
|
+
type = 'WS_normal'
|
250
|
+
end
|
251
|
+
xml.tr :class => type, :id => "WS_method_#{method}" do
|
252
|
+
xml.td method.to_s, :class => "WS_operation"
|
253
|
+
|
254
|
+
xml.td :class => "WS_parameters" do
|
255
|
+
description = @method_descriptions[method]
|
256
|
+
method_parameters = description[:args]
|
257
|
+
method_parameters += ['return'] unless description[:types][:return] == false || description[:types]['return'] == false
|
258
|
+
if method_parameters.any?
|
259
|
+
xml.dl :class => "WS_parameter_documentation" do
|
260
|
+
method_parameters.each do |param|
|
261
|
+
if param.to_s == 'return'
|
262
|
+
xml.dt param, :class => 'WS_return'
|
263
|
+
else
|
264
|
+
xml.dt param
|
265
|
+
end
|
266
|
+
|
267
|
+
if description[:param_descriptions]
|
268
|
+
xml.dd description[:param_descriptions][param.to_s] || ""
|
269
|
+
else
|
270
|
+
xml.dd
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
xml.td desc, :class => "WS_documentation"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
if filename
|
283
|
+
directory = File.dirname(File.expand_path(filename))
|
284
|
+
FileUtils.mkdir_p directory unless File.exists? directory
|
285
|
+
File.open(filename,'w') {|f| f.write html_table }
|
286
|
+
nil
|
287
|
+
else
|
288
|
+
html_table
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
private
|
293
|
+
|
294
|
+
def add_to_ruby(name, &block)
|
295
|
+
self.class.send(:define_method, name, block)
|
296
|
+
end
|
297
|
+
|
298
|
+
def add_to_wsdl(name, args, types)
|
299
|
+
description = @method_descriptions[name]
|
300
|
+
message = Builder::XmlMarkup.new(:indent => 2).message :name => "#{ name }Request" do |xml|
|
301
|
+
|
302
|
+
args.each do |param|
|
303
|
+
type = types[param.to_s] || types[param.to_sym] || :string
|
304
|
+
type = type.to_sym
|
305
|
+
|
306
|
+
xml.part :name => param, :type => TYPES2WSDL[type] do
|
307
|
+
param_descriptions = description[:param_descriptions]
|
308
|
+
|
309
|
+
if param_descriptions && param_descriptions[param.to_s]
|
310
|
+
xml.documentation param_descriptions[param.to_s]
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
end
|
316
|
+
@messages << message
|
317
|
+
|
318
|
+
message = Builder::XmlMarkup.new(:indent => 2).message :name => "#{ name }Response" do |xml|
|
319
|
+
type = [types[:return], types["return"]].compact.first
|
320
|
+
type = :string if type.nil?
|
321
|
+
if type
|
322
|
+
type = type.to_sym
|
323
|
+
xml.part :name => 'result', :type => TYPES2WSDL[type] do
|
324
|
+
param_descriptions = description[:param_descriptions]
|
325
|
+
|
326
|
+
if param_descriptions && param_descriptions['return']
|
327
|
+
xml.documentation param_descriptions['return']
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
@messages << message
|
333
|
+
|
334
|
+
operation = Builder::XmlMarkup.new(:indent => 2).operation :name => "#{ name }" do |xml|
|
335
|
+
xml.documentation description[:description] if description[:description]
|
336
|
+
xml.input :message => "tns:#{ name }Request"
|
337
|
+
xml.output :message => "tns:#{ name }Response"
|
338
|
+
end
|
339
|
+
|
340
|
+
@operations << operation
|
341
|
+
|
342
|
+
binding = Builder::XmlMarkup.new(:indent => 2).operation :name => "#{ name }" do |xml|
|
343
|
+
xml.tag! 'soap:operation'.to_sym, :soapAction => "urn:${NAME}##{name}", :style => 'rpc'
|
344
|
+
xml.input do |xml|
|
345
|
+
xml.tag! 'soap:body'.to_sym, :namespace => "urn:${NAME}", :encodingStyle => "http://schemas.xmlsoap.org/soap/encoding/", :use => "encoded"
|
346
|
+
end
|
347
|
+
xml.output do |xml|
|
348
|
+
xml.tag! 'soap:body'.to_sym, :namespace => "urn:${NAME}", :encodingStyle => "http://schemas.xmlsoap.org/soap/encoding/", :use => "encoded"
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
@bindings << binding
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
if ! defined? WSDL_STUB
|
357
|
+
WSDL_STUB =<<EOT
|
358
|
+
<?xml version="1.0"?>
|
359
|
+
<definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
360
|
+
xmlns:tns="${NAME}-NS"
|
361
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
362
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
363
|
+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
|
364
|
+
xmlns:si="http://soapinterop.org/xsd"
|
365
|
+
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
366
|
+
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
367
|
+
xmlns="http://schemas.xmlsoap.org/wsdl/"
|
368
|
+
targetNamespace="${NAME}-NS">
|
369
|
+
|
370
|
+
|
371
|
+
<types>
|
372
|
+
<schema xmlns="http://www.w3.org/2001/XMLSchema"
|
373
|
+
targetNamespace="${NAME}-NS"
|
374
|
+
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
375
|
+
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
|
376
|
+
<complexType name="ArrayOfString">
|
377
|
+
<complexContent>
|
378
|
+
<restriction base="soapenc:Array">
|
379
|
+
<attribute ref="soapenc:arrayType"
|
380
|
+
wsdl:arrayType="string[]"/>
|
381
|
+
</restriction>
|
382
|
+
</complexContent>
|
383
|
+
</complexType>
|
384
|
+
</schema>
|
385
|
+
</types>
|
386
|
+
|
387
|
+
${MESSAGES}
|
388
|
+
<portType name="${NAME}">
|
389
|
+
${OPERATIONS}
|
390
|
+
</portType>
|
391
|
+
|
392
|
+
<binding name="${NAME}Binding" type="tns:${NAME}">
|
393
|
+
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
|
394
|
+
${BINDINGS}
|
395
|
+
</binding>
|
396
|
+
<service name="${NAME}">
|
397
|
+
<documentation>${DESCRIPTION}</documentation>
|
398
|
+
|
399
|
+
<port name="${NAME}" binding="tns:${NAME}Binding">
|
400
|
+
<soap:address location="${LOCATION}"/>
|
401
|
+
</port>
|
402
|
+
</service>
|
403
|
+
|
404
|
+
</definitions>
|
405
|
+
EOT
|
406
|
+
end
|
407
|
+
|
408
|
+
if ! defined? TYPES2WSDL
|
409
|
+
TYPES2WSDL = {
|
410
|
+
:boolean => 'xsd:boolean',
|
411
|
+
:string => 'xsd:string',
|
412
|
+
:text => 'xsd:string',
|
413
|
+
:tsv => 'xsd:string',
|
414
|
+
:yaml => 'xsd:string',
|
415
|
+
:integer => 'xsd:integer',
|
416
|
+
:float => 'xsd:float',
|
417
|
+
:array => 'tns:ArrayOfString',
|
418
|
+
:binary => 'xsd:base64Binary',
|
419
|
+
}
|
420
|
+
end
|
421
|
+
|
422
|
+
|
423
|
+
end
|
424
|
+
|
425
|
+
|