t2-server 0.9.3 → 1.0.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.
@@ -0,0 +1,99 @@
1
+ # Copyright (c) 2010-2013 The University of Manchester, UK.
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # * Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # * Neither the names of The University of Manchester nor the names of its
16
+ # contributors may be used to endorse or promote products derived from this
17
+ # software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+ # Author: Robert Haines
32
+
33
+ # :stopdoc:
34
+ module T2Server
35
+
36
+ class Server
37
+
38
+ # This class is used to cache Run objects in a Server so they don't need to
39
+ # be created so often. When manipulating this cache the user credentials
40
+ # should be passed in, or the global user ":all" will be used instead.
41
+ class RunCache
42
+
43
+ def initialize(server)
44
+ @server = server
45
+ @cache = {}
46
+ end
47
+
48
+ # Add a run, or runs, to the cache.
49
+ def add_run(runs, credentials = nil)
50
+ cache = user_cache(credentials)
51
+
52
+ [*runs].each do |run|
53
+ cache[run.id] = run
54
+ end
55
+ end
56
+
57
+ # This method adds all new runs (creating instances where required) in
58
+ # the list provided AND removes any runs no longer in the list.
59
+ def refresh_all!(run_list, credentials = nil)
60
+ cache = user_cache(credentials)
61
+
62
+ # Add new runs to the user cache.
63
+ run_list.each_key do |id|
64
+ if !cache.has_key? id
65
+ cache[id] = Run.create(@server, "", credentials, run_list[id])
66
+ end
67
+ end
68
+
69
+ # Clear out the expired runs.
70
+ if cache.length > run_list.length
71
+ cache.delete_if {|key, _| !run_list.member? key}
72
+ end
73
+
74
+ cache
75
+ end
76
+
77
+ # Delete all runs objects from the cache. This does not delete runs from
78
+ # the remote server - just their locally cached instances.
79
+ def clear!(credentials = nil)
80
+ user_cache(credentials).clear
81
+ end
82
+
83
+ # Get all the specified user's runs.
84
+ def runs(credentials = nil)
85
+ user_cache(credentials)
86
+ end
87
+
88
+ private
89
+
90
+ def user_cache(credentials)
91
+ user = credentials.nil? ? :all : credentials.username
92
+ @cache[user] ||= {}
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
99
+ # :startdoc:
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2010-2012 The University of Manchester, UK.
1
+ # Copyright (c) 2010-2013 The University of Manchester, UK.
2
2
  #
3
3
  # All rights reserved.
4
4
  #
@@ -59,43 +59,46 @@ module T2Server
59
59
  attr_reader :server
60
60
 
61
61
  # :stopdoc:
62
- XPaths = {
62
+ XPATHS = {
63
63
  # Run XPath queries
64
- :run_desc => XML::Methods.xpath_compile("/nsr:runDescription"),
65
- :dir => XML::Methods.xpath_compile("//nss:dir"),
66
- :file => XML::Methods.xpath_compile("//nss:file"),
67
- :expiry => XML::Methods.xpath_compile("//nsr:expiry"),
68
- :workflow => XML::Methods.xpath_compile("//nsr:creationWorkflow"),
69
- :status => XML::Methods.xpath_compile("//nsr:status"),
70
- :createtime => XML::Methods.xpath_compile("//nsr:createTime"),
71
- :starttime => XML::Methods.xpath_compile("//nsr:startTime"),
72
- :finishtime => XML::Methods.xpath_compile("//nsr:finishTime"),
73
- :wdir => XML::Methods.xpath_compile("//nsr:workingDirectory"),
74
- :inputs => XML::Methods.xpath_compile("//nsr:inputs"),
75
- :output => XML::Methods.xpath_compile("//nsr:output"),
76
- :securectx => XML::Methods.xpath_compile("//nsr:securityContext"),
77
- :listeners => XML::Methods.xpath_compile("//nsr:listeners"),
78
- :baclava => XML::Methods.xpath_compile("//nsr:baclava"),
79
- :inputexp => XML::Methods.xpath_compile("//nsr:expected"),
64
+ :run_desc => "/nsr:runDescription",
65
+ :dir => "//nss:dir",
66
+ :file => "//nss:file",
67
+ :expiry => "//nsr:expiry",
68
+ :workflow => "//nsr:creationWorkflow",
69
+ :status => "//nsr:status",
70
+ :createtime => "//nsr:createTime",
71
+ :starttime => "//nsr:startTime",
72
+ :finishtime => "//nsr:finishTime",
73
+ :wdir => "//nsr:workingDirectory",
74
+ :inputs => "//nsr:inputs",
75
+ :output => "//nsr:output",
76
+ :securectx => "//nsr:securityContext",
77
+ :listeners => "//nsr:listeners",
78
+ :baclava => "//nsr:baclava",
79
+ :inputexp => "//nsr:expected",
80
+ :name => "//nsr:name",
81
+ :feed => "//nsr:interaction",
80
82
 
81
83
  # Port descriptions XPath queries
82
- :port_in => XML::Methods.xpath_compile("//port:input"),
83
- :port_out => XML::Methods.xpath_compile("//port:output"),
84
+ :port_in => "//port:input",
85
+ :port_out => "//port:output",
84
86
 
85
87
  # Run security XPath queries
86
- :sec_creds => XML::Methods.xpath_compile("//nsr:credentials"),
87
- :sec_perms => XML::Methods.xpath_compile("//nsr:permissions"),
88
- :sec_trusts => XML::Methods.xpath_compile("//nsr:trusts"),
89
- :sec_perm =>
90
- XML::Methods.xpath_compile("/nsr:permissionsDescriptor/nsr:permission"),
91
- :sec_uname => XML::Methods.xpath_compile("nsr:userName"),
92
- :sec_uperm => XML::Methods.xpath_compile("nsr:permission"),
93
- :sec_cred => XML::Methods.xpath_compile("/nsr:credential"),
94
- :sec_suri => XML::Methods.xpath_compile("nss:serviceURI"),
95
- :sec_trust =>
96
- XML::Methods.xpath_compile("/nsr:trustedIdentities/nsr:trust")
88
+ :sec_creds => "//nsr:credentials",
89
+ :sec_perms => "//nsr:permissions",
90
+ :sec_trusts => "//nsr:trusts",
91
+ :sec_perm => "/nsr:permissionsDescriptor/nsr:permission",
92
+ :sec_uname => "nsr:userName",
93
+ :sec_uperm => "nsr:permission",
94
+ :sec_cred => "/nsr:credential",
95
+ :sec_suri => "nss:serviceURI",
96
+ :sec_trust => "/nsr:trustedIdentities/nsr:trust"
97
97
  }
98
98
 
99
+ @@xpaths = XML::XPathCache.instance
100
+ @@xpaths.register_xpaths XPATHS
101
+
99
102
  # The name to be used internally for retrieving results via baclava
100
103
  BACLAVA_FILE = "out.xml"
101
104
 
@@ -110,6 +113,9 @@ module T2Server
110
113
 
111
114
  @credentials = credentials
112
115
 
116
+ # Has this Run object been deleted from the server?
117
+ @deleted = false
118
+
113
119
  # The following three fields hold cached data about the run that is only
114
120
  # downloaded the first time it is requested.
115
121
  @run_doc = nil
@@ -119,6 +125,9 @@ module T2Server
119
125
  # initialize ports lists to nil as an empty list means no inputs/outputs
120
126
  @input_ports = nil
121
127
  @output_ports = nil
128
+
129
+ # The interaction reader to use for this run, if required.
130
+ @interaction_reader = nil
122
131
  end
123
132
  # :startdoc:
124
133
 
@@ -131,10 +140,10 @@ module T2Server
131
140
  # Create a new run in the :initialized state. The run will be created on
132
141
  # the server with address supplied by _server_. This can either be a
133
142
  # String of the form <tt>http://example.com:8888/blah</tt> or an already
134
- # created instance of T2Server::Server. The _workflow_ must also be
135
- # supplied as a string in t2flow or scufl format. User credentials and
136
- # connection parameters can be supplied if required but are both optional.
137
- # If _server_ is an instance of T2Server::Server then
143
+ # created instance of T2Server::Server. The _workflow_ may be supplied
144
+ # as a string in t2flow format, a filename or a File or IO object. User
145
+ # credentials and connection parameters can be supplied if required but
146
+ # are both optional. If _server_ is an instance of T2Server::Server then
138
147
  # _connection_parameters_ will be ignored.
139
148
  #
140
149
  # This method will _yield_ the newly created Run if a block is given.
@@ -154,36 +163,59 @@ module T2Server
154
163
  end
155
164
  end
156
165
 
157
- if server.class != Server
158
- server = Server.new(server, conn_params)
159
- end
166
+ # If server is not a Server object, get one.
167
+ server = Server.new(server, conn_params) if server.class != Server
160
168
 
161
- if uri.nil?
162
- uri = server.initialize_run(workflow, credentials)
163
- end
169
+ # If we are not given a URI to a run then we know we need to create one.
170
+ uri ||= server.initialize_run(workflow, credentials)
164
171
 
172
+ # Create the run object and yield it if necessary.
165
173
  run = new(server, uri, credentials)
166
174
  yield(run) if block_given?
167
175
  run
168
176
  end
169
177
 
170
- # :stopdoc:
171
- def uuid
172
- warn "[DEPRECATION] 'uuid' is deprecated and will be removed in 1.0. " +
173
- "Please use Run#id or Run#identifier instead."
174
- @identifier
175
- end
176
- # :startdoc:
177
-
178
178
  # :call-seq:
179
- # owner -> String
179
+ # owner -> string
180
180
  #
181
181
  # Get the username of the owner of this run. The owner is the user who
182
182
  # created the run on the server.
183
183
  def owner
184
- @owner = _get_run_owner if @owner.nil?
184
+ @owner ||= _get_run_owner
185
+ end
185
186
 
186
- @owner
187
+ # :call-seq:
188
+ # name -> String
189
+ #
190
+ # Get the name of this run.
191
+ #
192
+ # Initially this name is derived by Taverna Server from the name
193
+ # annotation in the workflow file and the time at which the run was
194
+ # initialized. It can be set with the <tt>name=</tt> method.
195
+ #
196
+ # For Taverna Server versions prior to version 2.5.0 this is a no-op and
197
+ # the empty string is returned for consistency.
198
+ def name
199
+ return "" if links[:name].nil?
200
+ @server.read(links[:name], "text/plain", @credentials)
201
+ end
202
+
203
+ # :call-seq:
204
+ # name = new_name -> bool
205
+ #
206
+ # Set the name of this run. +true+ is returned upon success. The maximum
207
+ # length of names supported by the server is 48 characters. Anything
208
+ # longer than 48 characters will be truncated before upload.
209
+ #
210
+ # Initially this name is derived by Taverna Server from the name
211
+ # annotation in the workflow file and the time at which the run was
212
+ # initialized.
213
+ #
214
+ # For Taverna Server versions prior to version 2.5.0 this is a no-op but
215
+ # +true+ is still returned for consistency.
216
+ def name=(name)
217
+ return true if links[:name].nil?
218
+ @server.update(links[:name], name[0...48], "text/plain", @credentials)
187
219
  end
188
220
 
189
221
  # :call-seq:
@@ -192,44 +224,27 @@ module T2Server
192
224
  # Delete this run from the server.
193
225
  def delete
194
226
  @server.delete(@uri, @credentials)
227
+ @deleted = true
195
228
  end
196
229
 
197
- # :stopdoc:
198
- def inputs
199
- warn "[DEPRECATION] 'inputs' is deprecated and will be removed in 1.0."
200
- links[:inputs]
201
- end
202
-
203
- def set_input(input, value)
204
- warn "[DEPRECATION] 'Run#set_input' is deprecated and will be removed " +
205
- "in 1.0. Input ports are set directly instead. The most direct " +
206
- "replacement for this method is: 'Run#input_port(input).value = value'"
207
-
208
- input_port(input).value = value
209
- end
210
-
211
- def set_input_file(input, filename)
212
- warn "[DEPRECATION] 'Run#set_input_file' is deprecated and will be " +
213
- "removed in 1.0. Input ports are set directly instead. The most " +
214
- "direct replacement for this method is: " +
215
- "'Run#input_port(input).remote_file = filename'"
216
-
217
- input_port(input).remote_file = filename
230
+ # :call-seq:
231
+ # deleted? -> true or false
232
+ #
233
+ # Has this run been deleted from the server?
234
+ def deleted?
235
+ @deleted
218
236
  end
219
- # :startdoc:
220
237
 
221
238
  # :call-seq:
222
- # input_ports -> Hash
239
+ # input_ports -> hash
223
240
  #
224
241
  # Return a hash (name, port) of all the input ports this run expects.
225
242
  def input_ports
226
- @input_ports = _get_input_port_info if @input_ports.nil?
227
-
228
- @input_ports
243
+ @input_ports ||= _get_input_port_info
229
244
  end
230
245
 
231
246
  # :call-seq:
232
- # input_port(port) -> Port
247
+ # input_port(port) -> port
233
248
  #
234
249
  # Get _port_.
235
250
  def input_port(port)
@@ -237,12 +252,12 @@ module T2Server
237
252
  end
238
253
 
239
254
  # :call-seq:
240
- # output_ports -> Hash
255
+ # output_ports -> hash
241
256
  #
242
257
  # Return a hash (name, port) of all the output ports this run has. Until
243
258
  # the run is finished this method will return _nil_.
244
259
  def output_ports
245
- if finished? and @output_ports.nil?
260
+ if finished? && @output_ports.nil?
246
261
  @output_ports = _get_output_port_info
247
262
  end
248
263
 
@@ -250,34 +265,13 @@ module T2Server
250
265
  end
251
266
 
252
267
  # :call-seq:
253
- # output_port(port) -> Port
268
+ # output_port(port) -> port
254
269
  #
255
270
  # Get output port _port_.
256
271
  def output_port(port)
257
272
  output_ports[port] if finished?
258
273
  end
259
274
 
260
- # :stopdoc:
261
- def get_output_ports
262
- warn "[DEPRECATION] 'get_output_ports' is deprecated and will be " +
263
- "removed in 1.0. Please use 'Run#output_ports' instead."
264
- lists, items = _ls_ports("out")
265
- items + lists
266
- end
267
-
268
- def get_output(output, refs=false)
269
- warn "[DEPRECATION] 'get_output' is deprecated and will be removed " +
270
- "in 1.0. Please use 'Run#output_port(port).values' instead."
271
- _get_output(output, refs)
272
- end
273
-
274
- def get_output_refs(output)
275
- warn "[DEPRECATION] 'get_output_refs' is deprecated and will be " +
276
- "removed in 1.0. Please use 'Run#output_port(port).data' instead."
277
- _get_output(output, true)
278
- end
279
- # :startdoc:
280
-
281
275
  # :call-seq:
282
276
  # expiry -> string
283
277
  #
@@ -287,7 +281,7 @@ module T2Server
287
281
  end
288
282
 
289
283
  # :call-seq:
290
- # expiry=(time) -> bool
284
+ # expiry = time -> true or false
291
285
  #
292
286
  # Set the expiry time of this run to _time_. _time_ should either be a Time
293
287
  # object or something that the Time class can parse. If the value given
@@ -323,13 +317,15 @@ module T2Server
323
317
  # Get the status of this run. Status can be one of :initialized,
324
318
  # :running or :finished.
325
319
  def status
326
- text_to_state(@server.read(links[:status], "text/plain", @credentials))
320
+ return :deleted if @deleted
321
+ Status.to_sym(@server.read(links[:status], "text/plain", @credentials))
327
322
  end
328
323
 
329
324
  # :call-seq:
330
- # start
325
+ # start -> true or false
331
326
  #
332
- # Start this run on the server.
327
+ # Start this run on the server. Returns true if the run was started, false
328
+ # otherwise.
333
329
  #
334
330
  # Raises RunStateError if the run is not in the :initialized state.
335
331
  def start
@@ -339,8 +335,12 @@ module T2Server
339
335
  # set all the inputs
340
336
  _check_and_set_inputs unless baclava_input?
341
337
 
342
- @server.update(links[:status], state_to_text(:running), "text/plain",
343
- @credentials)
338
+ begin
339
+ @server.update(links[:status], Status.to_text(:running), "text/plain",
340
+ @credentials)
341
+ rescue ServerAtCapacityError => sace
342
+ false
343
+ end
344
344
  end
345
345
 
346
346
  # :call-seq:
@@ -349,23 +349,11 @@ module T2Server
349
349
  # Wait (block) for this run to finish. How often (in seconds) the run is
350
350
  # tested for completion can be specified with check_interval.
351
351
  #
352
- # Raises RunStateError if the run is still in the :initialised state.
353
- def wait(*params)
352
+ # Raises RunStateError if the run is still in the :initialized state.
353
+ def wait(interval = 1)
354
354
  state = status
355
355
  raise RunStateError.new(state, :running) if state == :initialized
356
356
 
357
- interval = 1
358
- params.each do |param|
359
- case param
360
- when Hash
361
- warn "[DEPRECATION] 'Run#wait(params={})' is deprecated and will " +
362
- "be removed in 1.0. Please use Run#wait(check_interval) instead."
363
- interval = param[:interval] || 1
364
- when Integer
365
- interval = param
366
- end
367
- end
368
-
369
357
  # wait
370
358
  until finished?
371
359
  sleep(interval)
@@ -373,7 +361,7 @@ module T2Server
373
361
  end
374
362
 
375
363
  # :call-seq:
376
- # exitcode -> integer
364
+ # exitcode -> fixnum
377
365
  #
378
366
  # Get the return code of the run. Zero indicates success.
379
367
  def exitcode
@@ -397,7 +385,32 @@ module T2Server
397
385
  end
398
386
 
399
387
  # :call-seq:
400
- # mkdir(dir) -> bool
388
+ # log -> string
389
+ # log(filename) -> fixnum
390
+ # log(stream) -> fixnum
391
+ # log {|chunk| ...}
392
+ #
393
+ # Get the internal Taverna Server log from this run.
394
+ #
395
+ # Calling this method with no parameters will simply return a text string.
396
+ # Providing a filename will stream the data directly to that file and
397
+ # return the number of bytes written. Passing in an object that has a
398
+ # +write+ method (for example, an instance of File or IO) will stream the
399
+ # text directly to that object and return the number of bytes that were
400
+ # streamed. Passing in a block will allow access to the underlying data
401
+ # stream:
402
+ # run.log do |chunk|
403
+ # print chunk
404
+ # end
405
+ def log(param = nil, &block)
406
+ raise ArgumentError,
407
+ 'both a parameter and block given for baclava_output' if param && block
408
+
409
+ download_or_stream(param, links[:logfile], "text/plain", &block)
410
+ end
411
+
412
+ # :call-seq:
413
+ # mkdir(dir) -> true or false
401
414
  #
402
415
  # Create a directory in the run's working directory on the server. This
403
416
  # could be used to store input data.
@@ -426,7 +439,7 @@ module T2Server
426
439
  end
427
440
 
428
441
  # :call-seq:
429
- # upload_data(data, remote_name, remote_directory = "") -> bool
442
+ # upload_data(data, remote_name, remote_directory = "") -> true or false
430
443
  #
431
444
  # Upload data to the server and store it in <tt>remote_file</tt>. The
432
445
  # remote directory to put this file in can also be specified, but if it is
@@ -436,19 +449,8 @@ module T2Server
436
449
  @server.upload_data(data, remote_name, location_uri, @credentials)
437
450
  end
438
451
 
439
- # :stopdoc:
440
- def upload_input_file(input, filename, params={})
441
- warn "[DEPRECATION] 'Run#upload_input_file' is deprecated and will be " +
442
- "removed in 1.0. Input ports are set directly instead. The most " +
443
- "direct replacement for this method is: " +
444
- "'Run#input_port(input).file = filename'"
445
-
446
- input_port(input).file = filename
447
- end
448
- # :startdoc:
449
-
450
452
  # :call-seq:
451
- # baclava_input=(filename) -> bool
453
+ # baclava_input = filename -> true or false
452
454
  #
453
455
  # Use a baclava file for the workflow inputs.
454
456
  def baclava_input=(filename)
@@ -463,22 +465,8 @@ module T2Server
463
465
  result
464
466
  end
465
467
 
466
- # :stopdoc:
467
- def upload_baclava_input(filename)
468
- warn "[DEPRECATION] 'upload_baclava_input' is deprecated and will be " +
469
- "removed in 1.0. Please use 'Run#baclava_input=' instead."
470
- self.baclava_input = filename
471
- end
472
-
473
- def upload_baclava_file(filename)
474
- warn "[DEPRECATION] 'upload_baclava_file' is deprecated and will be " +
475
- "removed in 1.0. Please use 'Run#baclava_input=' instead."
476
- self.baclava_input = filename
477
- end
478
- # :startdoc:
479
-
480
468
  # :call-seq:
481
- # request_baclava_output -> bool
469
+ # request_baclava_output -> true or false
482
470
  #
483
471
  # Set the server to save the outputs of this run in baclava format. This
484
472
  # must be done before the run is started.
@@ -491,16 +479,8 @@ module T2Server
491
479
  @credentials)
492
480
  end
493
481
 
494
- # :stopdoc:
495
- def set_baclava_output(name="")
496
- warn "[DEPRECATION] 'set_baclava_output' is deprecated and will be " +
497
- "removed in 1.0. Please use 'Run#request_baclava_output' instead."
498
- self.request_baclava_output
499
- end
500
- # :startdoc:
501
-
502
482
  # :call-seq:
503
- # baclava_input? -> bool
483
+ # baclava_input? -> true or false
504
484
  #
505
485
  # Have the inputs to this run been set by a baclava document?
506
486
  def baclava_input?
@@ -508,7 +488,7 @@ module T2Server
508
488
  end
509
489
 
510
490
  # :call-seq:
511
- # baclava_output? -> bool
491
+ # baclava_output? -> true or false
512
492
  #
513
493
  # Has this run been set to return results in baclava format?
514
494
  def baclava_output?
@@ -517,43 +497,74 @@ module T2Server
517
497
 
518
498
  # :call-seq:
519
499
  # baclava_output -> string
500
+ # baclava_output(filename) -> fixnum
501
+ # baclava_output(stream) -> fixnum
502
+ # baclava_output {|chunk| ...}
520
503
  #
521
504
  # Get the outputs of this run in baclava format. This can only be done if
522
505
  # the output has been requested in baclava format by #set_baclava_output
523
506
  # before starting the run.
524
- def baclava_output
507
+ #
508
+ # Calling this method with no parameters will simply return a blob of
509
+ # XML data. Providing a filename will stream the data directly to that
510
+ # file and return the number of bytes written. Passing in an object that
511
+ # has a +write+ method (for example, an instance of File or IO) will
512
+ # stream the XML data directly to that object and return the number of
513
+ # bytes that were streamed. Passing in a block will allow access to the
514
+ # underlying data stream:
515
+ # run.baclava_output do |chunk|
516
+ # print chunk
517
+ # end
518
+ #
519
+ # Raises RunStateError if the run has not finished running.
520
+ def baclava_output(param = nil, &block)
521
+ raise ArgumentError,
522
+ 'both a parameter and block given for baclava_output' if param && block
523
+
525
524
  state = status
526
525
  raise RunStateError.new(state, :finished) if state != :finished
527
526
 
528
527
  raise AccessForbiddenError.new("baclava output") if !@baclava_out
529
528
 
530
529
  baclava_uri = Util.append_to_uri_path(links[:wdir], BACLAVA_FILE)
531
- @server.read(baclava_uri, "*/*", @credentials)
530
+ download_or_stream(param, baclava_uri, "*/*", &block)
532
531
  end
533
532
 
534
- # :stopdoc:
535
- def get_baclava_output
536
- warn "[DEPRECATION] 'get_baclava_output' is deprecated and will be " +
537
- "removed in 1.0. Please use 'Run#baclava_output' instead."
538
- baclava_output
539
- end
540
- # :startdoc:
541
-
542
533
  # :call-seq:
543
534
  # zip_output -> binary blob
535
+ # zip_output(filename) -> fixnum
536
+ # zip_output(stream) -> fixnum
537
+ # zip_output {|chunk| ...}
544
538
  #
545
539
  # Get the working directory of this run directly from the server in zip
546
540
  # format.
547
- def zip_output
541
+ #
542
+ # Calling this method with no parameters will simply return a blob of
543
+ # zipped data. Providing a filename will stream the data directly to that
544
+ # file and return the number of bytes written. Passing in an object that
545
+ # has a +write+ method (for example, an instance of File or IO) will
546
+ # stream the zip data directly to that object and return the number of
547
+ # bytes that were streamed. Passing in a block will allow access to the
548
+ # underlying data stream:
549
+ # run.zip_output do |chunk|
550
+ # print chunk
551
+ # end
552
+ #
553
+ # Raises RunStateError if the run has not finished running.
554
+ def zip_output(param = nil, port = "", &block)
555
+ raise ArgumentError,
556
+ "both a parameter and block given for zip_output" if param && block
557
+
548
558
  state = status
549
559
  raise RunStateError.new(state, :finished) if state != :finished
550
560
 
551
- output_uri = Util.append_to_uri_path(links[:wdir], "out")
552
- @server.read(output_uri, "application/zip", @credentials)
561
+ path = port.empty? ? "out" : "out/#{port}"
562
+ output_uri = Util.append_to_uri_path(links[:wdir], path)
563
+ download_or_stream(param, output_uri, "application/zip", &block)
553
564
  end
554
565
 
555
566
  # :call-seq:
556
- # initialized? -> bool
567
+ # initialized? -> true or false
557
568
  #
558
569
  # Is this run in the :initialized state?
559
570
  def initialized?
@@ -561,7 +572,7 @@ module T2Server
561
572
  end
562
573
 
563
574
  # :call-seq:
564
- # running? -> bool
575
+ # running? -> true or false
565
576
  #
566
577
  # Is this run in the :running state?
567
578
  def running?
@@ -569,13 +580,28 @@ module T2Server
569
580
  end
570
581
 
571
582
  # :call-seq:
572
- # finished? -> bool
583
+ # finished? -> true or false
573
584
  #
574
585
  # Is this run in the :finished state?
575
586
  def finished?
576
587
  status == :finished
577
588
  end
578
589
 
590
+ # :call-seq:
591
+ # error? -> true or false
592
+ #
593
+ # Are there errors in this run's outputs? Returns false if the run is not
594
+ # finished yet.
595
+ def error?
596
+ return false unless finished?
597
+
598
+ output_ports.values.each do |output|
599
+ return true if output.error?
600
+ end
601
+
602
+ false
603
+ end
604
+
579
605
  # :call-seq:
580
606
  # create_time -> string
581
607
  #
@@ -601,7 +627,7 @@ module T2Server
601
627
  end
602
628
 
603
629
  # :call-seq:
604
- # owner? -> bool
630
+ # owner? -> true or false
605
631
  #
606
632
  # Are the credentials being used to access this run those of the owner?
607
633
  # The owner of the run can give other users certain access rights to their
@@ -639,9 +665,9 @@ module T2Server
639
665
  doc = xml_document(@server.read(links[:sec_perms], "application/xml",
640
666
  @credentials))
641
667
 
642
- xpath_find(doc, XPaths[:sec_perm]).each do |p|
643
- user = xml_node_content(xpath_first(p, XPaths[:sec_uname]))
644
- perm = xml_node_content(xpath_first(p, XPaths[:sec_uperm])).to_sym
668
+ xpath_find(doc, @@xpaths[:sec_perm]).each do |p|
669
+ user = xml_node_content(xpath_first(p, @@xpaths[:sec_uname]))
670
+ perm = xml_node_content(xpath_first(p, @@xpaths[:sec_uperm])).to_sym
645
671
  perms[user] = perm
646
672
  end
647
673
 
@@ -657,11 +683,11 @@ module T2Server
657
683
  def permission(username)
658
684
  return unless owner?
659
685
 
660
- permissions[username]
686
+ permissions[username] || :none
661
687
  end
662
688
 
663
689
  # :call-seq:
664
- # revoke_permission(username) -> bool
690
+ # revoke_permission(username) -> true or false
665
691
  #
666
692
  # Revoke whatever permissions that have been granted to the user. Only the
667
693
  # owner of a run may revoke permissions on it. +nil+ is returned if a user
@@ -729,7 +755,7 @@ module T2Server
729
755
  end
730
756
 
731
757
  # :call-seq:
732
- # credentials -> Hash
758
+ # credentials -> hash
733
759
  #
734
760
  # Return a hash (service_uri => credential_uri) of all the credentials
735
761
  # provided for this run. Only the owner of a run may query its credentials.
@@ -741,8 +767,8 @@ module T2Server
741
767
  doc = xml_document(@server.read(links[:sec_creds], "application/xml",
742
768
  @credentials))
743
769
 
744
- xpath_find(doc, XPaths[:sec_cred]).each do |c|
745
- uri = URI.parse(xml_node_content(xpath_first(c, XPaths[:sec_suri])))
770
+ xpath_find(doc, @@xpaths[:sec_cred]).each do |c|
771
+ uri = URI.parse(xml_node_content(xpath_first(c, @@xpaths[:sec_suri])))
746
772
  cred_uri = URI.parse(xml_node_attribute(c, "href"))
747
773
  creds[uri] = cred_uri
748
774
  end
@@ -763,7 +789,7 @@ module T2Server
763
789
  end
764
790
 
765
791
  # :call-seq:
766
- # delete_credential(service_uri) -> bool
792
+ # delete_credential(service_uri) -> true or false
767
793
  #
768
794
  # Delete the credential that has been provided for the specified service.
769
795
  # Only the owner of a run may delete its credentials. +nil+ is returned if
@@ -775,7 +801,7 @@ module T2Server
775
801
  end
776
802
 
777
803
  # :call-seq:
778
- # delete_all_credentials -> bool
804
+ # delete_all_credentials -> true or false
779
805
  #
780
806
  # Delete all credentials associated with this workflow run. Only the owner
781
807
  # of a run may delete its credentials. +nil+ is returned if a user other
@@ -805,7 +831,7 @@ module T2Server
805
831
  end
806
832
 
807
833
  # :call-seq:
808
- # trusts -> Array
834
+ # trusts -> array
809
835
  #
810
836
  # Return a list of all the URIs of trusts that have been registered for
811
837
  # this run. At present there is no way to differentiate between trusts
@@ -819,7 +845,7 @@ module T2Server
819
845
  doc = xml_document(@server.read(links[:sec_trusts], "application/xml",
820
846
  @credentials))
821
847
 
822
- xpath_find(doc, XPaths[:sec_trust]). each do |t|
848
+ xpath_find(doc, @@xpaths[:sec_trust]). each do |t|
823
849
  t_uris << URI.parse(xml_node_attribute(t, "href"))
824
850
  end
825
851
 
@@ -827,7 +853,7 @@ module T2Server
827
853
  end
828
854
 
829
855
  # :call-seq:
830
- # delete_trust(URI) -> bool
856
+ # delete_trust(URI) -> true or false
831
857
  #
832
858
  # Delete the trust with the provided URI. Only the owner of a run may
833
859
  # delete its trusts. +nil+ is returned if a user other than the owner uses
@@ -839,7 +865,7 @@ module T2Server
839
865
  end
840
866
 
841
867
  # :call-seq:
842
- # delete_all_trusts -> bool
868
+ # delete_all_trusts -> true or false
843
869
  #
844
870
  # Delete all trusted identities associated with this workflow run. Only
845
871
  # the owner of a run may delete its trusts. +nil+ is returned if a user
@@ -854,17 +880,76 @@ module T2Server
854
880
  # Outputs are represented as a directory structure with the eventual list
855
881
  # items (leaves) as files. This method (not part of the public API)
856
882
  # downloads a file from the run's working directory.
857
- def download_output_data(uri, range = nil)
858
- @server.read(uri, "application/octet-stream", range, @credentials)
883
+ def download_output_data(uri, range = nil, &block)
884
+ @server.read(uri, "application/octet-stream", range, @credentials,
885
+ &block)
886
+ end
887
+
888
+ # Read from the run's notification feed.
889
+ def read_notification_feed
890
+ @server.read(links[:feed], "application/atom+xml", @credentials)
891
+ end
892
+
893
+ # Write to the run's notification feed.
894
+ def write_notification(entry)
895
+ @server.create(links[:feed], entry, "application/atom+xml", @credentials)
896
+ end
897
+
898
+ # Read a file from the interactions directory for this run on the server.
899
+ def read_interaction_data(name)
900
+ uri = Util.append_to_uri_path(links[:feeddir], name)
901
+ @server.read(uri, "*/*", @credentials)
902
+ end
903
+
904
+ # Write a file to the interactions directory for this run on the server.
905
+ def write_interaction_data(name, data)
906
+ uri = Util.append_to_uri_path(links[:feeddir], name)
907
+ @server.update(uri, data, "*/*", @credentials)
908
+ end
909
+
910
+ # This is a slightly unpleasant hack to help proxy notification
911
+ # communications through a third party.
912
+ def notifications_uri
913
+ links[:feed] || ""
914
+ end
915
+
916
+ # This is a slightly unpleasant hack to help proxy interaction
917
+ # communications through a third party.
918
+ def interactions_uri
919
+ links[:feeddir] || ""
859
920
  end
860
921
  # :startdoc:
861
922
 
923
+ # :call-seq:
924
+ # notifications(type = :new_requests) -> array
925
+ #
926
+ # Poll the server for notifications and return them in a list. Returns the
927
+ # empty list if there are none, or if the server does not support the
928
+ # Interaction Service.
929
+ #
930
+ # The +type+ parameter is used to select which types of notifications are
931
+ # returned as follows:
932
+ # * <tt>:requests</tt> - Interaction requests.
933
+ # * <tt>:replies</tt> - Interaction replies.
934
+ # * <tt>:new_requests</tt> - Interaction requests that are new since the
935
+ # last time they were polled (default).
936
+ # * <tt>:all</tt> - All interaction requests and replies.
937
+ def notifications(type = :new_requests)
938
+ return [] if links[:feed].nil?
939
+
940
+ @interaction_reader ||= Interaction::Feed.new(self)
941
+
942
+ if type == :new_requests
943
+ @interaction_reader.new_requests
944
+ else
945
+ @interaction_reader.notifications(type)
946
+ end
947
+ end
948
+
862
949
  private
863
950
 
864
951
  def links
865
- @links = _get_run_links if @links.nil?
866
-
867
- @links
952
+ @links ||= _get_run_links
868
953
  end
869
954
 
870
955
  # Check each input to see if it requires a list input and call the
@@ -887,24 +972,18 @@ module T2Server
887
972
  input_ports.each_value do |port|
888
973
  next unless port.set?
889
974
 
975
+ uri = Util.append_to_uri_path(links[:inputs], "input/#{port.name}")
890
976
  if port.file?
891
977
  # If we're using a local file upload it first then set the port to
892
978
  # use a remote file.
893
- unless port.remote_file?
894
- file = upload_file(port.file)
895
- port.remote_file = file
896
- end
979
+ port.remote_file = upload_file(port.file) unless port.remote_file?
897
980
 
898
- xml_value = xml_text_node(port.file)
899
- uri = Util.append_to_uri_path(links[:inputs], "input/#{port.name}")
900
- @server.update(uri, XML::Fragments::RUNINPUTFILE % xml_value,
901
- "application/xml", @credentials)
981
+ xml_value = XML::Fragments::RUNINPUTFILE % xml_text_node(port.file)
902
982
  else
903
- xml_value = xml_text_node(port.value)
904
- uri = Util.append_to_uri_path(links[:inputs], "input/#{port.name}")
905
- @server.update(uri, XML::Fragments::RUNINPUTVALUE % xml_value,
906
- "application/xml", @credentials)
983
+ xml_value = XML::Fragments::RUNINPUTVALUE % xml_text_node(port.value)
907
984
  end
985
+
986
+ @server.update(uri, xml_value, "application/xml", @credentials)
908
987
  end
909
988
  end
910
989
 
@@ -950,90 +1029,6 @@ module T2Server
950
1029
  u.to_s
951
1030
  end
952
1031
 
953
- # List a directory in the run's workspace on the server. If dir is left
954
- # blank then / is listed. As there is no concept of changing into a
955
- # directory (cd) in Taverna Server then all paths passed into _ls_ports
956
- # should be full paths starting at "root". The contents of a directory are
957
- # returned as a list of two lists, "lists" and "values" respectively.
958
- def _ls_ports(dir="", top=true)
959
- dir = Util.strip_path_slashes(dir)
960
- uri = Util.append_to_uri_path(links[:wdir], dir)
961
- dir_list = @server.read(uri, "*/*", @credentials)
962
-
963
- # compile a list of directory entries stripping the
964
- # directory name from the front of each filename
965
- lists = []
966
- values = []
967
-
968
- doc = xml_document(dir_list)
969
-
970
- xpath_find(doc, XPaths[:dir]).each do |e|
971
- if top
972
- lists << xml_node_content(e).split('/')[-1]
973
- else
974
- index = (xml_node_attribute(e, 'name').to_i - 1)
975
- lists[index] = xml_node_content(e).split('/')[-1]
976
- end
977
- end
978
-
979
- xpath_find(doc, XPaths[:file]).each do |e|
980
- if top
981
- values << xml_node_content(e).split('/')[-1]
982
- else
983
- index = (xml_node_attribute(e, 'name').to_i - 1)
984
- values[index] = xml_node_content(e).split('/')[-1]
985
- end
986
- end
987
-
988
- [lists, values]
989
- end
990
-
991
- def _get_output(output, refs=false, top=true)
992
- output = Util.strip_path_slashes(output)
993
-
994
- # if at the top level we need to check if the port represents a list
995
- # or a singleton value
996
- if top
997
- lists, items = _ls_ports("out")
998
- if items.include? output
999
- if refs
1000
- return "#{@server.uri}/rest/runs/#{@identifier}/" +
1001
- "#{links[:wdir]}/out/#{output}"
1002
- else
1003
- out_uri = Util.append_to_uri_path(links[:wdir], "out/#{output}")
1004
- return @server.read(out_uri, "application/octet-stream",
1005
- @credentials)
1006
- end
1007
- end
1008
- end
1009
-
1010
- # we're not at the top level so look at the contents of the output port
1011
- lists, items = _ls_ports("out/#{output}", false)
1012
-
1013
- # build up lists of results
1014
- result = []
1015
-
1016
- # for each list recurse into it and add the items to the result
1017
- lists.each do |list|
1018
- result << _get_output("#{output}/#{list}", refs, false)
1019
- end
1020
-
1021
- # for each item, add it to the output list
1022
- items.each do |item|
1023
- if refs
1024
- result << "#{@server.uri}/rest/runs/#{@identifier}/" +
1025
- "#{links[:wdir]}/out/#{output}/#{item}"
1026
- else
1027
- out_uri = Util.append_to_uri_path(links[:wdir],
1028
- "out/#{output}/#{item}")
1029
- result << @server.read(out_uri, "application/octet-stream",
1030
- @credentials)
1031
- end
1032
- end
1033
-
1034
- result
1035
- end
1036
-
1037
1032
  def _get_input_port_info
1038
1033
  ports = {}
1039
1034
  port_desc = @server.read(links[:inputexp], "application/xml",
@@ -1041,7 +1036,7 @@ module T2Server
1041
1036
 
1042
1037
  doc = xml_document(port_desc)
1043
1038
 
1044
- xpath_find(doc, XPaths[:port_in]).each do |inp|
1039
+ xpath_find(doc, @@xpaths[:port_in]).each do |inp|
1045
1040
  port = InputPort.new(self, inp)
1046
1041
  ports[port.name] = port
1047
1042
  end
@@ -1060,7 +1055,7 @@ module T2Server
1060
1055
 
1061
1056
  doc = xml_document(port_desc)
1062
1057
 
1063
- xpath_find(doc, XPaths[:port_out]).each do |out|
1058
+ xpath_find(doc, @@xpaths[:port_out]).each do |out|
1064
1059
  port = OutputPort.new(self, out)
1065
1060
  ports[port.name] = port
1066
1061
  end
@@ -1080,32 +1075,31 @@ module T2Server
1080
1075
  def _get_run_owner
1081
1076
  doc = _get_run_description
1082
1077
 
1083
- xpath_attr(doc, XPaths[:run_desc], "owner")
1078
+ xpath_attr(doc, @@xpaths[:run_desc], "owner")
1084
1079
  end
1085
1080
 
1086
1081
  def _get_run_links
1087
1082
  doc = _get_run_description
1088
1083
 
1089
1084
  # first parse out the basic stuff
1090
- links = {}
1085
+ links = get_uris_from_doc(doc, [:expiry, :workflow, :status,
1086
+ :createtime, :starttime, :finishtime, :wdir, :inputs, :output,
1087
+ :securectx, :listeners, :name, :feed])
1091
1088
 
1092
- [:expiry, :workflow, :status, :createtime, :starttime, :finishtime,
1093
- :wdir, :inputs, :output, :securectx, :listeners].each do |key|
1094
- links[key] = URI.parse(xpath_attr(doc, XPaths[key], "href"))
1095
- end
1089
+ # Working dir links
1090
+ _get_wdir_links(links)
1096
1091
 
1097
1092
  # get inputs
1098
1093
  inputs = @server.read(links[:inputs], "application/xml",@credentials)
1099
1094
  doc = xml_document(inputs)
1100
1095
 
1101
- links[:baclava] = URI.parse(xpath_attr(doc, XPaths[:baclava], "href"))
1102
- links[:inputexp] = URI.parse(xpath_attr(doc, XPaths[:inputexp], "href"))
1096
+ links.merge! get_uris_from_doc(doc, [:baclava, :inputexp])
1103
1097
 
1104
1098
  # set io properties
1105
1099
  links[:io] = Util.append_to_uri_path(links[:listeners], "io")
1106
- links[:stdout] = Util.append_to_uri_path(links[:io], "properties/stdout")
1107
- links[:stderr] = Util.append_to_uri_path(links[:io], "properties/stderr")
1108
- links[:exitcode] = Util.append_to_uri_path(links[:io], "properties/exitcode")
1100
+ [:stdout, :stderr, :exitcode].each do |res|
1101
+ links[res] = Util.append_to_uri_path(links[:io], "properties/#{res}")
1102
+ end
1109
1103
 
1110
1104
  # security properties - only available to the owner of a run
1111
1105
  if owner?
@@ -1113,39 +1107,62 @@ module T2Server
1113
1107
  @credentials)
1114
1108
  doc = xml_document(securectx)
1115
1109
 
1116
- [:sec_creds, :sec_perms, :sec_trusts].each do |key|
1117
- #links[key] = "#{links[:securectx]}/" + xpath_attr(doc, XPaths[key],
1118
- # "href").split('/')[-1]
1119
- links[key] = Util.append_to_uri_path(links[:securectx],
1120
- xpath_attr(doc, XPaths[key], "href").split('/')[-1])
1121
- end
1110
+ links.merge! get_uris_from_doc(doc,
1111
+ [:sec_creds, :sec_perms, :sec_trusts])
1122
1112
  end
1123
1113
 
1124
1114
  links
1125
1115
  end
1126
1116
 
1127
- # :stopdoc:
1128
- STATE2TEXT = {
1129
- :initialized => "Initialized",
1130
- :running => "Operating",
1131
- :finished => "Finished",
1132
- :stopped => "Stopped"
1133
- }
1117
+ def _get_wdir_links(links)
1118
+ # Logs directory
1119
+ links[:logdir] = Util.append_to_uri_path(links[:wdir], "logs")
1134
1120
 
1135
- TEXT2STATE = {
1136
- "Initialized" => :initialized,
1137
- "Operating" => :running,
1138
- "Finished" => :finished,
1139
- "Stopped" => :stopped
1140
- }
1141
- # :startdoc:
1121
+ # Log file
1122
+ links[:logfile] = Util.append_to_uri_path(links[:logdir], "detail.log")
1142
1123
 
1143
- def state_to_text(state)
1144
- STATE2TEXT[state.to_sym]
1124
+ # Interaction working directory, if we have a feed.
1125
+ unless links[:feed].nil?
1126
+ links[:feeddir] = Util.append_to_uri_path(links[:wdir], "interactions")
1127
+ end
1145
1128
  end
1146
1129
 
1147
- def text_to_state(text)
1148
- TEXT2STATE[text]
1130
+ def download_or_stream(param, uri, type, &block)
1131
+ if param.respond_to? :write
1132
+ @server.read_to_stream(param, uri, type, @credentials)
1133
+ elsif param.instance_of? String
1134
+ @server.read_to_file(param, uri, type, @credentials)
1135
+ else
1136
+ @server.read(uri, type, @credentials, &block)
1137
+ end
1149
1138
  end
1139
+
1140
+ # :stopdoc:
1141
+ class Status
1142
+ STATE2TEXT = {
1143
+ :initialized => "Initialized",
1144
+ :running => "Operating",
1145
+ :finished => "Finished",
1146
+ :stopped => "Stopped",
1147
+ :deleted => "Deleted"
1148
+ }
1149
+
1150
+ TEXT2STATE = {
1151
+ "Initialized" => :initialized,
1152
+ "Operating" => :running,
1153
+ "Finished" => :finished,
1154
+ "Stopped" => :stopped
1155
+ }
1156
+
1157
+ def Status.to_text(state)
1158
+ STATE2TEXT[state.to_sym]
1159
+ end
1160
+
1161
+ def Status.to_sym(text)
1162
+ TEXT2STATE[text]
1163
+ end
1164
+ end
1165
+ # :startdoc:
1166
+
1150
1167
  end
1151
1168
  end