simplews 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ == 1.1.6 2008-11-24
2
+
3
+ * 1 minor enhancement:
4
+ * Added documentation
5
+
6
+
1
7
  == 1.1.5 2008-10-28
2
8
 
3
9
  * 1 major enhancement:
@@ -1,13 +1,98 @@
1
1
  require File.join(File.dirname(File.dirname(__FILE__)) + '/simplews')
2
2
 
3
3
  require 'yaml'
4
+
5
+ # This class extends SimpleWS and allows provides with a framework to
6
+ # run asynchronous jobs. Job are initiated and can be latter be accessed
7
+ # using a unique job id. The status can be queried, and when finished,
8
+ # the results can be gathered. Jobs are also able to save their states
9
+ # between executions of the server.
10
+ #
11
+ # The class provides a instance function start_job, that receives a
12
+ # block of code and creates a new thread to run this block of code. It
13
+ # also provides some helper functions to set the state of the job, as
14
+ # well as messages or to abort of report errors.
15
+ #
16
+ # Each job can produce a number of results saved into files that can
17
+ # latter be accessed by the client. At any time the code inside the
18
+ # block can access any of the following helper functions: step, read,
19
+ # write, abort_process, job_name and save. As in the following example.
20
+ # (+process+ will be a method served by the server):
21
+ #
22
+ # def process(name)
23
+ # start_job(name, %w(test.txt)) do
24
+ # begin
25
+ # write('test.txt', job_name)
26
+ # step(:init)
27
+ # sleep 2
28
+ # step(:step1, "Step1")
29
+ # sleep 2
30
+ # step(:step2, "Step2")
31
+ # sleep 2
32
+ # step(:step2, "Step3")
33
+ # rescue
34
+ # error($!.message)
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # Note that any file specified in the write or read method is relative
40
+ # to the workdir directory. The optional array passed as second argument
41
+ # to start_job lists the results the job will be generating and that
42
+ # will be accessible latter to the clients, one could have also done
43
+ # this:
44
+ #
45
+ # def process(name)
46
+ # actual_name = start_job(name) do
47
+ # begin
48
+ # write(job_name + ".txt", "Hi")
49
+ # step(:init)
50
+ # sleep 2
51
+ # step(:step1, "Step1")
52
+ # sleep 2
53
+ # step(:step2, "Step2")
54
+ # sleep 2
55
+ # step(:step2, "Step3")
56
+ # rescue
57
+ # error($!.message)
58
+ # end
59
+ # end
60
+ #
61
+ # set_results(actual_name, [ actual_name + ".txt" ])
62
+ #
63
+ # actual_name
64
+ # end
65
+ #
66
+ # Note in this example that the name provided to start_job is only a
67
+ # suggestion, it a job is already available with that name a number will
68
+ # be prepended to it. The actual name used is return by the start_job
69
+ # method and is the one that must be used in the set_results method.
70
+ # Also note that the +process+ method in the example has to return the
71
+ # actual name of the job so it will be accessible to the client.
72
+ #
73
+ #
74
+ # The clients have a number of methods available to query and modify the
75
+ # state of the job, as well as to gather results: status, messages,
76
+ # done, error, aborted, abort, results and result.
77
+ #
78
+ #
79
+ # While reading the documentation please note that there are three types
80
+ # of methods here, methods used inside the web server methods, methods
81
+ # used inside code blocks executed by Job Threads, and methods that are
82
+ # exported by the web server. Its important to call each method in the
83
+ # right context.
84
+ #
4
85
  class SimpleWS::Asynchronous < SimpleWS
5
86
 
87
+
6
88
  class JobNotFound < Exception; end
7
89
  class JobAborted < Exception; end
8
90
 
9
91
 
10
- class Job < Thread
92
+ class Job < Thread # :nodoc:
93
+
94
+ private
95
+
11
96
  def self.random_name(s="", num=20)
12
97
  num.times{
13
98
  r = rand
@@ -40,6 +125,10 @@ class SimpleWS::Asynchronous < SimpleWS
40
125
  end
41
126
  end
42
127
 
128
+ public
129
+
130
+ # Returns the job specified in the +name+. If it is not currently
131
+ # found but it has a saved state, then it is loaded.
43
132
  def self.job(name)
44
133
  if @@jobs[name].nil?
45
134
  if File.exists?(File.join(@@workdir, ".save/#{name}.yaml"))
@@ -47,7 +136,7 @@ class SimpleWS::Asynchronous < SimpleWS
47
136
  job.load
48
137
  @@jobs[name] = job
49
138
  else
50
- raise JobNotFound, "Job with name '#{ name }' was not found"
139
+ raise SimpleWS::Asynchronous::JobNotFound, "Job with name '#{ name }' was not found"
51
140
  end
52
141
  end
53
142
  @@jobs[name]
@@ -74,7 +163,7 @@ class SimpleWS::Asynchronous < SimpleWS
74
163
  def self.workdir=(workdir)
75
164
  @@workdir = workdir
76
165
  begin
77
- FileUtils.mkdir(@@workdir) unless File.exist?( @@workdir)
166
+ FileUtils.mkdir_p(@@workdir) unless File.exist?( @@workdir)
78
167
  FileUtils.mkdir(File.join(@@workdir,'.save')) unless File.exist?( File.join(@@workdir,'.save'))
79
168
  rescue
80
169
  puts "Error creating work directory:"
@@ -139,9 +228,9 @@ class SimpleWS::Asynchronous < SimpleWS
139
228
  @name
140
229
  end
141
230
 
142
- def step(status, message="")
231
+ def step(status, message= nil)
143
232
  @status = status
144
- @messages << message if message != ""
233
+ @messages << message if message && message != ""
145
234
  end
146
235
 
147
236
  def read(path)
@@ -204,18 +293,37 @@ class SimpleWS::Asynchronous < SimpleWS
204
293
  ############################################################################################3
205
294
  #{{{ Server
206
295
 
296
+ # SERVER METHOD:
297
+ #
298
+ # Sets the workdir for the jobs
207
299
  def set_workdir(workdir)
208
300
  Job::workdir = workdir
209
301
  end
210
302
 
303
+ # SERVER METHOD:
304
+ #
305
+ # Returns the workdir that any jobs created will be using
211
306
  def workdir
212
307
  Job::workdir
213
308
  end
214
309
 
310
+ # SERVER METHOD:
311
+ #
312
+ # Starts a new job. The parameter +name+ is just a suggestion, if it
313
+ # is not available a number will be prepended to it. Results are a
314
+ # list of relative paths to files where results are expected to be
315
+ # stored. All paths are relative to the jobs workdir. Finally, block
316
+ # is the block of code to be executed by the Job Thread.
317
+ #
318
+ # Returns the actual name used as unique identifier for the job
215
319
  def start_job(name, results = [], &block)
216
320
  Job.start(name, results, &block)
217
321
  end
218
322
 
323
+ # SERVER METHOD:
324
+ #
325
+ # Sets the list of paths to results for a particular job. This
326
+ # overwrites the ones specified by start_job.
219
327
  def set_results(name, results)
220
328
  Job.job(name).results = results
221
329
  end
@@ -223,68 +331,154 @@ class SimpleWS::Asynchronous < SimpleWS
223
331
 
224
332
 
225
333
  #{{{ Thread helper methods
334
+
335
+
336
+ # CODE HELPER FUNCTION:
337
+ #
338
+ # Saves the job to it can survive server restarts. This is
339
+ # automatically called when the code is finished.
226
340
  def save
227
341
  Thread.current.save
228
342
  end
229
343
 
230
-
231
- def write(*args)
232
- Thread.current.write(*args)
344
+ # CODE HELPER FUNCTION:
345
+ #
346
+ # Writes +content+ into the file specified by +path+. The +path+`is
347
+ # taken relative to the job's workdir.
348
+ def write(path, content)
349
+ Thread.current.write(path, content)
233
350
  end
234
351
 
235
- def step(*args)
236
- Thread.current.step(*args)
352
+ # CODE HELPER FUNCTION:
353
+ #
354
+ # Reads the content of the file specified by +path+ relative to the
355
+ # job's workdir.
356
+ def read(path)
357
+ Thread.current.read(path)
237
358
  end
238
359
 
239
- def read(*args)
240
- Thread.current.read(*args)
360
+
361
+ # CODE HELPER FUNCTION:
362
+ #
363
+ # Sets the status of the job and adds a message to the message queue
364
+ # if the message is specified, and is different from an empty string.
365
+ # This method is called automatically in the following situations.
366
+ #
367
+ # * The block has finished executing:
368
+ # step(:done)
369
+ # * The job recieves an untreated SimpleWS::Asynchronous::JobAborted exception
370
+ # step(:aborted, $!.message)
371
+ # * The job recieves any other exception
372
+ # step(:error, $!.message)
373
+ #
374
+ def step(status, message=nil)
375
+ Thread.current.step(status, message)
241
376
  end
242
377
 
378
+
379
+ # CODE HELPER FUNCTION:
380
+ #
381
+ # Returns the actual name of the job running the code.
243
382
  def job_name
244
383
  Thread.current.name
245
384
  end
246
385
 
386
+ # CODE HELPER FUNCTION:
387
+ #
388
+ # Aborts the job raising a SimpleWS::Asynchronous::JobAborted
389
+ # exception.
247
390
  def abort_process
248
391
  Thread.current.abort
249
392
  end
250
393
 
251
394
 
252
-
253
395
  #{{{ WS Methods
254
396
 
397
+ # CLIENT METHOD SERVED BY DEFAULT:
398
+ #
399
+ # +name+ is the unique job identifier.
400
+ #
401
+ # Returns job status of the job.
255
402
  def status(name)
256
403
  Job.job(name).status
257
404
  end
258
405
 
406
+ # CLIENT METHOD SERVED BY DEFAULT:
407
+ #
408
+ # +name+ is the unique job identifier.
409
+ #
410
+ # Raises a SimpleWS::Asynchronous::JobAborted exception in the job
259
411
  def abort(name)
260
412
  Job.job(name).abort
261
413
  end
262
414
 
415
+ # CLIENT METHOD SERVED BY DEFAULT:
416
+ #
417
+ # +name+ is the unique job identifier.
418
+ #
419
+ # Checks if the job is still running or has finished, either normally
420
+ # or after been aborted or suffering an error.
263
421
  def done(name)
264
422
  Job.job(name).done?
265
423
  end
266
424
 
425
+ # CLIENT METHOD SERVED BY DEFAULT:
426
+ #
427
+ # +name+ is the unique job identifier.
428
+ #
429
+ # Checks if the job has been aborted.
267
430
  def aborted(name)
268
431
  Job.job(name).aborted?
269
432
  end
270
433
 
434
+ # CLIENT METHOD SERVED BY DEFAULT:
435
+ #
436
+ # +name+ is the unique job identifier.
437
+ #
438
+ # Checks if the job has suffered an error
271
439
  def error(name)
272
440
  Job.job(name).error?
273
441
  end
274
442
 
443
+ # CLIENT METHOD SERVED BY DEFAULT:
444
+ #
445
+ # +name+ is the unique job identifier.
446
+ #
447
+ # Returns the list of messages of the job.
275
448
  def messages(name)
276
449
  Job.job(name).messages
277
450
  end
278
451
 
452
+ # CLIENT METHOD SERVED BY DEFAULT:
453
+ #
454
+ # +name+ is the unique job identifier.
455
+ #
456
+ # Returns a list of unique results identifiers, one for each of the
457
+ # results of the job.
279
458
  def results(name)
280
459
  Job.job(name).results
281
460
  end
282
461
 
462
+ # CLIENT METHOD SERVED BY DEFAULT:
463
+ #
464
+ # +name+ is the unique job identifier.
465
+ # +result+ is the unique result identifier as returned by the results
466
+ # method.
467
+ #
468
+ # Retrieves a result of a given job.
283
469
  def result(name, result)
284
470
  Job.job(name).result(result)
285
471
  end
286
472
 
287
473
 
474
+ # Creates an instance. All the parameters are as in SimpleWS, except
475
+ # for the additional workdir parameter. It the workdir is not provided
476
+ # a directory in the /tmp directory with the name of the server will
477
+ # be used.
478
+ #
479
+ # It serves by default the methods: abort, messages, status, done,
480
+ # error, abort, aborted, results and result, as well as the +wsdl+
481
+ # method from SimpleWS.
288
482
  def initialize(name="WS", description="", host="localhost", port="1984", workdir=nil, *args)
289
483
  super(name, description, host, port, *args)
290
484
  @results = {}
@@ -2,7 +2,7 @@ module Simplews
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
4
  MINOR = 1
5
- TINY = 5
5
+ TINY = 6
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  self
data/lib/simplews.rb CHANGED
@@ -2,8 +2,55 @@ require 'soap/rpc/standaloneServer'
2
2
  require 'builder'
3
3
 
4
4
 
5
+ # SimpleWS is a class that wraps SOAP::RPC::StandaloneServer to ease the
6
+ # implementation of Web Services, specifically the generation of the
7
+ # +WSDL+ file. It provides a particular syntax that allows to specify
8
+ # the methods that are going to be served along with the types of the
9
+ # parameters and of the response so that the +WSDL+ can be generated
10
+ # accordingly. Actual Servers can be instances of this class where
11
+ # methods are assigned dynamically or of classes that extend SimpleWS.
12
+ # class TestWS < SimpleWS
13
+ # def hi(name)
14
+ # "Hi #{name}, how are you?"
15
+ # end
16
+ #
17
+ # def initialize(*args)
18
+ # super(*args)
19
+ # serve :hi, %w(name), :name => :string, :return => :string
20
+ #
21
+ # serve :bye, %w(name), :name => :string, :return => :string do |name|
22
+ # "Bye bye #{name}. See you soon."
23
+ # end
24
+ #
25
+ # end
26
+ # end
27
+ #
28
+ # # Creating a server and starting it
29
+ #
30
+ # server = TestWS.new("TestWS", "Greeting Services", 'localhost', '1984')
31
+ # server.wsdl("TestWS.wsdl")
32
+ #
33
+ # Thread.new do
34
+ # server.start
35
+ # end
36
+ #
37
+ # # Client code. This could be run in another process.
38
+ #
39
+ # driver = SimpleWS::get_driver('http://localhost:1984', "TestWS")
40
+ # puts driver.hi('Gladis') #=> "Hi Gladis, how are you?"
41
+ # puts driver.bye('Gladis') #=> "Bye bye Gladis. See you soon."
42
+ #
43
+
44
+
45
+
5
46
  class SimpleWS < SOAP::RPC::StandaloneServer
6
47
 
48
+ # This is a helper function for clients. Given the +url+ where the
49
+ # server is listening, as well as the name of the server, it can
50
+ # manually access the +wsdl+ function and retrieve the complete +WSDL+
51
+ # file. This works *only* in web servers of class SimpleWS, not on
52
+ # the general SOAP::RPC::StandaloneServer or any other type of web
53
+ # server.
7
54
  def self.get_wsdl(url, name)
8
55
  require 'soap/rpc/driver'
9
56
  driver = SOAP::RPC::Driver.new(url, "urn:#{ name }")
@@ -11,6 +58,8 @@ class SimpleWS < SOAP::RPC::StandaloneServer
11
58
  driver.wsdl
12
59
  end
13
60
 
61
+ # Builds on the get_wsdl function to provide the client with a
62
+ # reference to the driver. Again, only works with SimpleWS servers.
14
63
  def self.get_driver(url, name)
15
64
  require 'soap/wsdlDriver'
16
65
  require 'fileutils'
@@ -24,6 +73,12 @@ class SimpleWS < SOAP::RPC::StandaloneServer
24
73
  driver
25
74
  end
26
75
 
76
+ # Creates an instance of a Server. The parameter +name+ specifies the
77
+ # +namespace+ used in the +WSDL+, +description+ is the description
78
+ # also included in the +WSDL+. The parameters +host+ and +port+,
79
+ # specify where the server will be listening, they are parameters of
80
+ # the +super+ class SOAP::RPC::StandaloneServer, they are made
81
+ # explicit here because they are included in the +WSDL+ as well.
27
82
  def initialize(name="WS", description="", host="localhost", port="1984", *args)
28
83
  super(description, "urn:#{ name }", host, port, *args)
29
84
  @host = host
@@ -37,11 +92,20 @@ class SimpleWS < SOAP::RPC::StandaloneServer
37
92
  serve :wsdl, %w(), :return => :string
38
93
  end
39
94
 
40
- def name
41
- @name
42
- end
43
-
44
95
 
96
+ # This method tells the server to provide a method named by the +name+
97
+ # parameter, with arguments listed in the +args+ parameter. The
98
+ # +types+ parameter specifies the types of the arguments as will be
99
+ # described in the +WSDL+ file (see the TYPES2WSDL constant). The
100
+ # actual method called will be the one with the same name. Optionally
101
+ # a block can be provided, this block will be used to define a
102
+ # function named as in name.
103
+ #
104
+ # If the method returns something, then the type of the return value
105
+ # must be specified in the +types+ parameter as :return. If not value
106
+ # for :return parameter is specified in the +types+ parameter the
107
+ # method is taken to return no value. Other than that, if a parameter
108
+ # type is omitted it is taken to be :string.
45
109
  def serve(name, args=[], types={}, &block)
46
110
 
47
111
  if block
@@ -54,6 +118,11 @@ class SimpleWS < SOAP::RPC::StandaloneServer
54
118
  nil
55
119
  end
56
120
 
121
+ # If +filename+ is specified it saves the +WSDL+ file in that file. If
122
+ # no +filename+ is specified it returns a string containing the
123
+ # +WSDL+. The no parameter version is served by default, so that
124
+ # clients can use this method to access the complete +WSDL+ file, as
125
+ # used in get_wsdl and get_driver.
57
126
  def wsdl(filename = nil)
58
127
  wsdl = WSDL_STUB.dup
59
128
  wsdl.gsub!(/\$\{MESSAGES\}/m,@messages.join("\n"))
@@ -83,7 +152,7 @@ class SimpleWS < SOAP::RPC::StandaloneServer
83
152
  args.each{|param|
84
153
  type = types[param.to_s] || types[param.to_sym] || :string
85
154
  type = type.to_sym
86
- xml.part :name => param, :type => @@type2wsdl[type]
155
+ xml.part :name => param, :type => TYPES2WSDL[type]
87
156
  }
88
157
  end
89
158
  @messages << message
@@ -91,7 +160,7 @@ class SimpleWS < SOAP::RPC::StandaloneServer
91
160
  type = types[:return] || types["return"]
92
161
  if type
93
162
  type = type.to_sym
94
- xml.part :name => 'return', :type => @@type2wsdl[type]
163
+ xml.part :name => 'return', :type => TYPES2WSDL[type]
95
164
  end
96
165
  end
97
166
  @messages << message
@@ -117,6 +186,7 @@ class SimpleWS < SOAP::RPC::StandaloneServer
117
186
 
118
187
  end
119
188
 
189
+
120
190
  WSDL_STUB =<<EOT
121
191
  <?xml version="1.0"?>
122
192
  <definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
@@ -167,7 +237,7 @@ ${BINDINGS}
167
237
  </definitions>
168
238
  EOT
169
239
 
170
- @@type2wsdl = {
240
+ TYPES2WSDL = {
171
241
  :boolean => 'xsd:boolean',
172
242
  :string => 'xsd:string',
173
243
  :integer => 'xsd:integer',
@@ -9,7 +9,7 @@ class TestSimpleWS < Test::Unit::TestCase
9
9
  end
10
10
 
11
11
  def process(name)
12
- name = start_job(name, %w(test.txt)) do
12
+ actual_name = start_job(name, %w(test.txt)) do
13
13
  begin
14
14
  write('test.txt', job_name)
15
15
  step(:init)
@@ -20,12 +20,11 @@ class TestSimpleWS < Test::Unit::TestCase
20
20
  sleep 2
21
21
  step(:step2, "Step3")
22
22
  rescue
23
- puts $!.message
24
- raise $!
23
+ error($!.message)
25
24
  end
26
25
  end
27
26
 
28
- name
27
+ actual_name
29
28
  end
30
29
 
31
30
  def initialize(*args)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplews
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Vazquez