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.
@@ -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
  #
@@ -32,6 +32,7 @@
32
32
 
33
33
  require 'base64'
34
34
  require 'uri'
35
+ require 't2-server/run-cache'
35
36
 
36
37
  module T2Server
37
38
 
@@ -45,21 +46,22 @@ module T2Server
45
46
  # endpoints.
46
47
  REST_ENDPOINT = "rest/"
47
48
 
48
- XPaths = {
49
+ XPATHS = {
49
50
  # Server top-level XPath queries
50
- :server => XML::Methods.xpath_compile("//nsr:serverDescription"),
51
- :policy => XML::Methods.xpath_compile("//nsr:policy"),
52
- :run => XML::Methods.xpath_compile("//nsr:run"),
53
- :runs => XML::Methods.xpath_compile("//nsr:runs"),
51
+ :server => "//nsr:serverDescription",
52
+ :policy => "//nsr:policy",
53
+ :run => "//nsr:run",
54
+ :runs => "//nsr:runs",
54
55
 
55
56
  # Server policy XPath queries
56
- :runlimit => XML::Methods.xpath_compile("//nsr:runLimit"),
57
- :permwkf => XML::Methods.xpath_compile("//nsr:permittedWorkflows"),
58
- :permlstn => XML::Methods.xpath_compile("//nsr:permittedListeners"),
59
- :permlstt => XML::Methods.xpath_compile("//nsr:permittedListenerTypes"),
60
- :notify =>
61
- XML::Methods.xpath_compile("//nsr:enabledNotificationFabrics")
57
+ :runlimit => "//nsr:runLimit",
58
+ :permworkflows => "//nsr:permittedWorkflows",
59
+ :permlisteners => "//nsr:permittedListenerTypes",
60
+ :notifications => "//nsr:enabledNotificationFabrics"
62
61
  }
62
+
63
+ @@xpaths = XML::XPathCache.instance
64
+ @@xpaths.register_xpaths XPATHS
63
65
  # :startdoc:
64
66
 
65
67
  # :call-seq:
@@ -72,16 +74,9 @@ module T2Server
72
74
  #
73
75
  # It will _yield_ itself if a block is given.
74
76
  def initialize(uri, params = nil)
75
- # we want to use URIs here but strings can be passed in
76
- unless uri.is_a? URI
77
- uri = URI.parse(Util.strip_path_slashes(uri))
78
- end
79
-
80
- # strip username and password from the URI if present
81
- if uri.user != nil
82
- uri = URI::HTTP.new(uri.scheme, nil, uri.host, uri.port, nil,
83
- uri.path, nil, nil, nil);
84
- end
77
+ # Convert strings to URIs and strip any credentials that have been given
78
+ # in the URI. We do not want to store credentials in this class.
79
+ uri, creds = Util.strip_uri_credentials(uri)
85
80
 
86
81
  # setup connection
87
82
  @connection = ConnectionFactory.connect(uri, params)
@@ -93,20 +88,12 @@ module T2Server
93
88
  @version_components = nil
94
89
  @links = nil
95
90
 
96
- # initialize run object cache
97
- @runs = {}
91
+ # Initialize the run object cache.
92
+ @run_cache = RunCache.new(self)
98
93
 
99
94
  yield(self) if block_given?
100
95
  end
101
96
 
102
- # :stopdoc:
103
- def Server.connect(uri, username="", password="")
104
- warn "[DEPRECATION] 'Server#connect' is deprecated and will be " +
105
- "removed in 1.0."
106
- new(uri)
107
- end
108
- # :startdoc:
109
-
110
97
  # :call-seq:
111
98
  # administrator(credentials = nil) -> Administrator
112
99
  # administrator(credentials = nil) {|admin| ...}
@@ -126,47 +113,61 @@ module T2Server
126
113
  #
127
114
  # Create a run on this server using the specified _workflow_.
128
115
  # This method will _yield_ the newly created Run if a block is given.
116
+ #
117
+ # The _workflow_ parameter may be the workflow itself, a file name or a
118
+ # File or IO object.
129
119
  def create_run(workflow, credentials = nil)
130
- id = initialize_run(workflow, credentials)
131
- run = Run.create(self, "", credentials, id)
120
+ uri = initialize_run(workflow, credentials)
121
+ run = Run.create(self, "", credentials, uri)
132
122
 
133
- # cache newly created run object - this must be done per user
134
- user = credentials.nil? ? :all : credentials.username
135
- @runs[user] = {} unless @runs[user]
136
- @runs[user][id] = run
123
+ # Add the newly created run object to the user's run cache
124
+ @run_cache.add_run(run, credentials)
137
125
 
138
126
  yield(run) if block_given?
139
127
  run
140
128
  end
141
129
 
142
- # :call-seq:
143
- # initialize_run(workflow, credentials = nil) -> string
130
+ # :stopdoc:
131
+ # Create a run on this server using the specified _workflow_ and return
132
+ # the URI to it.
144
133
  #
145
- # Create a run on this server using the specified _workflow_ but do not
146
- # return it as a Run instance. Return its identifier instead.
134
+ # We need to catch AccessForbiddenError here to be compatible with Server
135
+ # versions pre 2.4.2. When we no longer support them we can remove the
136
+ # rescue clause of this method.
147
137
  def initialize_run(workflow, credentials = nil)
148
- # set up the run object cache - this must be done per user
149
- user = credentials.nil? ? :all : credentials.username
150
- @runs[user] = {} unless @runs[user]
138
+ # If workflow is a String, it might be a filename! If so, stream it.
139
+ if (workflow.instance_of? String) && (File.file? workflow)
140
+ return File.open(workflow, "r") do |file|
141
+ create(links[:runs], file, "application/vnd.taverna.t2flow+xml",
142
+ credentials)
143
+ end
144
+ end
151
145
 
152
- @connection.POST(links[:runs], workflow,
153
- "application/vnd.taverna.t2flow+xml", credentials)
146
+ # If we get here then workflow could either be a String containing a
147
+ # workflow or a File or IO object.
148
+ create(links[:runs], workflow, "application/vnd.taverna.t2flow+xml",
149
+ credentials)
150
+ rescue AccessForbiddenError => afe
151
+ (major, minor, patch) = version_components
152
+ if minor == 4 && patch >= 2
153
+ # Need to re-raise as it's a real error for later versions.
154
+ raise afe
155
+ else
156
+ raise ServerAtCapacityError.new
157
+ end
154
158
  end
159
+ # :startdoc:
155
160
 
156
161
  # :call-seq:
157
- # version -> String
162
+ # version -> string
158
163
  #
159
164
  # The version string of the remote Taverna Server.
160
165
  def version
161
- if @version.nil?
162
- @version = _get_version
163
- end
164
-
165
- @version
166
+ @version ||= _get_version
166
167
  end
167
168
 
168
169
  # :call-seq:
169
- # version_components -> Array
170
+ # version_components -> array
170
171
  #
171
172
  # An array of the major, minor and patch version components of the remote
172
173
  # Taverna Server.
@@ -188,7 +189,7 @@ module T2Server
188
189
  end
189
190
 
190
191
  # :call-seq:
191
- # run_limit(credentials = nil) -> num
192
+ # run_limit(credentials = nil) -> fixnum
192
193
  #
193
194
  # The maximum number of runs that this server will allow at any one time.
194
195
  # Runs in any state (+Initialized+, +Running+ and +Finished+) are counted
@@ -213,26 +214,6 @@ module T2Server
213
214
  get_runs(credentials)[identifier]
214
215
  end
215
216
 
216
- # :stopdoc:
217
- def delete_run(run, credentials = nil)
218
- warn "[DEPRECATION] 'Server#delete_run' is deprecated and will be " +
219
- "removed in 1.0. Please use 'Run#delete' to delete a run."
220
-
221
- # get the identifier from the run if that is what is passed in
222
- if run.instance_of? Run
223
- run = run.identifier
224
- end
225
-
226
- run_uri = Util.append_to_uri_path(links[:runs], run)
227
- if delete(run_uri, credentials)
228
- # delete cached run object - this must be done per user
229
- user = credentials.nil? ? :all : credentials.username
230
- @runs[user].delete(run) if @runs[user]
231
- true
232
- end
233
- end
234
- # :startdoc:
235
-
236
217
  # :call-seq:
237
218
  # delete_all_runs(credentials = nil)
238
219
  #
@@ -240,56 +221,31 @@ module T2Server
240
221
  # only those runs that the provided credentials have permission to delete
241
222
  # will be deleted.
242
223
  def delete_all_runs(credentials = nil)
243
- # first refresh run list
224
+ # Refresh run list, delete everything, clear the user's run cache.
244
225
  runs(credentials).each {|run| run.delete}
226
+ @run_cache.clear!(credentials)
245
227
  end
246
228
 
247
229
  # :stopdoc:
248
- def set_run_input(run, input, value, credentials = nil)
249
- warn "[DEPRECATION] 'Server#set_run_input' is deprecated and will be " +
250
- "removed in 1.0. Input ports are set directly instead. The most " +
251
- "direct replacement for this method is: " +
252
- "'Run#input_port(input).value = value'"
253
-
254
- # get the run from the identifier if that is what is passed in
255
- if not run.instance_of? Run
256
- run = run(run, credentials)
257
- end
258
-
259
- run.input_port(input).value = value
260
- end
261
-
262
- def set_run_input_file(run, input, filename, credentials = nil)
263
- warn "[DEPRECATION] 'Server#set_run_input_file' is deprecated and " +
264
- "will be removed in 1.0. Input ports are set directly instead. The " +
265
- "most direct replacement for this method is: " +
266
- "'Run#input_port(input).remote_file = filename'"
267
-
268
- # get the run from the identifier if that is what is passed in
269
- if not run.instance_of? Run
270
- run = run(run, credentials)
271
- end
272
-
273
- run.input_port(input).remote_file = filename
274
- end
275
-
276
230
  def mkdir(uri, dir, credentials = nil)
277
231
  @connection.POST(uri, XML::Fragments::MKDIR % dir, "application/xml",
278
232
  credentials)
279
233
  end
280
234
 
281
- def make_run_dir(run, root, dir, credentials = nil)
282
- warn "[DEPRECATION] 'Server#make_run_dir' is deprecated and will be " +
283
- "removed in 1.0. Please use 'Run#mkdir' instead."
284
-
285
- create_dir(run, root, dir, credentials)
286
- end
287
-
288
235
  def upload_file(filename, uri, remote_name, credentials = nil)
289
- contents = IO.read(filename)
236
+ # Different Server versions support different upload methods
237
+ (major, minor, patch) = version_components
238
+
290
239
  remote_name = filename.split('/')[-1] if remote_name == ""
291
240
 
292
- upload_data(contents, remote_name, uri, credentials)
241
+ if minor == 4 && patch >= 1
242
+ File.open(filename, "rb") do |file|
243
+ upload_data(file, remote_name, uri, credentials)
244
+ end
245
+ else
246
+ contents = IO.read(filename)
247
+ upload_data(contents, remote_name, uri, credentials)
248
+ end
293
249
  end
294
250
 
295
251
  def upload_data(data, remote_name, uri, credentials = nil)
@@ -307,14 +263,6 @@ module T2Server
307
263
  end
308
264
  end
309
265
 
310
- def upload_run_file(run, filename, location, rename, credentials = nil)
311
- warn "[DEPRECATION] 'Server#upload_run_file' is deprecated and will " +
312
- "be removed in 1.0. Please use 'Run#upload_file' or " +
313
- "'Run#input_port(input).file = filename' instead."
314
-
315
- upload_file(run, filename, location, rename, credentials)
316
- end
317
-
318
266
  def is_resource_writable?(uri, credentials = nil)
319
267
  headers = @connection.OPTIONS(uri, credentials)
320
268
  headers["allow"][0].split(",").include? "PUT"
@@ -324,7 +272,7 @@ module T2Server
324
272
  @connection.POST(uri, value, type, credentials)
325
273
  end
326
274
 
327
- def read(uri, type, *rest)
275
+ def read(uri, type, *rest, &block)
328
276
  credentials = nil
329
277
  range = nil
330
278
 
@@ -340,7 +288,7 @@ module T2Server
340
288
  end
341
289
 
342
290
  begin
343
- @connection.GET(uri, type, range, credentials)
291
+ @connection.GET(uri, type, range, credentials, &block)
344
292
  rescue ConnectionRedirectError => cre
345
293
  # We've been redirected so save the new connection object with the new
346
294
  # server URI and try again with the new URI.
@@ -350,21 +298,51 @@ module T2Server
350
298
  end
351
299
  end
352
300
 
301
+ # An internal helper to write streamed data directly to another stream.
302
+ # The number of bytes written to the stream is returned. The stream passed
303
+ # in may be anything that provides a +write+ method; instances of IO and
304
+ # File, for example.
305
+ def read_to_stream(stream, uri, type, *rest)
306
+ raise ArgumentError,
307
+ "Stream passed in must provide a write method" unless
308
+ stream.respond_to? :write
309
+
310
+ bytes = 0
311
+
312
+ read(uri, type, *rest) do |chunk|
313
+ bytes += stream.write(chunk)
314
+ end
315
+
316
+ bytes
317
+ end
318
+
319
+ # An internal helper to write streamed data straight to a file.
320
+ def read_to_file(filename, uri, type, *rest)
321
+ File.open(filename, "wb") do |file|
322
+ read_to_stream(file, uri, type, *rest)
323
+ end
324
+ end
325
+
353
326
  def update(uri, value, type, credentials = nil)
354
327
  @connection.PUT(uri, value, type, credentials)
355
328
  end
356
329
 
357
330
  def delete(uri, credentials = nil)
358
331
  @connection.DELETE(uri, credentials)
332
+ rescue AttributeNotFoundError => ane
333
+ # Ignore this. Delete is idempotent so deleting something that has
334
+ # already been deleted, or is for some other reason not there, should
335
+ # happen silently. Return true here because when deleting it's enough to
336
+ # know that it's no longer there rather than whether it was deleted
337
+ # *this time* or not.
338
+ true
359
339
  end
360
340
  # :startdoc:
361
341
 
362
342
  private
363
343
 
364
344
  def links
365
- @links = _get_server_links if @links.nil?
366
-
367
- @links
345
+ @links ||= _get_server_links
368
346
  end
369
347
 
370
348
  def _get_server_description
@@ -378,15 +356,14 @@ module T2Server
378
356
 
379
357
  def _get_version
380
358
  doc = _get_server_description
381
- version = xpath_attr(doc, XPaths[:server], "serverVersion")
359
+ version = xpath_attr(doc, @@xpaths[:server], "serverVersion")
382
360
  if version == nil
383
361
  raise RuntimeError.new("Taverna Servers prior to version 2.3 " +
384
362
  "are no longer supported.")
385
363
  else
386
- # Remove SNAPSHOT tag if it's there.
387
- if version.end_with? "-SNAPSHOT"
388
- version.gsub!("-SNAPSHOT", "")
389
- end
364
+ # Remove extra version tags if present.
365
+ version.gsub!("-SNAPSHOT", "")
366
+ version.gsub!(/alpha[0-9]*/, "")
390
367
 
391
368
  # Add .0 if we only have a major and minor component.
392
369
  if version.split(".").length == 2
@@ -399,23 +376,11 @@ module T2Server
399
376
 
400
377
  def _get_server_links
401
378
  doc = _get_server_description
402
- links = {}
403
- links[:runs] = URI.parse(xpath_attr(doc, XPaths[:runs], "href"))
379
+ links = get_uris_from_doc(doc, [:runs, :policy])
404
380
 
405
- links[:policy] = URI.parse(xpath_attr(doc, XPaths[:policy], "href"))
406
381
  doc = xml_document(read(links[:policy], "application/xml"))
407
-
408
- links[:permlisteners] =
409
- URI.parse(xpath_attr(doc, XPaths[:permlstt], "href"))
410
- links[:notifications] =
411
- URI.parse(xpath_attr(doc, XPaths[:notify], "href"))
412
-
413
- links[:runlimit] =
414
- URI.parse(xpath_attr(doc, XPaths[:runlimit], "href"))
415
- links[:permworkflows] =
416
- URI.parse(xpath_attr(doc, XPaths[:permwkf], "href"))
417
-
418
- links
382
+ links.merge get_uris_from_doc(doc,
383
+ [:permlisteners, :notifications, :runlimit, :permworkflows])
419
384
  end
420
385
 
421
386
  def get_runs(credentials = nil)
@@ -425,29 +390,15 @@ module T2Server
425
390
 
426
391
  # get list of run identifiers
427
392
  run_list = {}
428
- xpath_find(doc, XPaths[:run]).each do |run|
393
+ xpath_find(doc, @@xpaths[:run]).each do |run|
429
394
  uri = URI.parse(xml_node_attribute(run, "href"))
430
395
  id = xml_node_content(run)
431
396
  run_list[id] = uri
432
397
  end
433
398
 
434
- # cache run objects - this must be done per user
435
- user = credentials.nil? ? :all : credentials.username
436
- @runs[user] = {} unless @runs[user]
437
-
438
- # add new runs to the user cache
439
- run_list.each_key do |id|
440
- if !@runs[user].has_key? id
441
- @runs[user][id] = Run.create(self, "", credentials, run_list[id])
442
- end
443
- end
444
-
445
- # clear out the expired runs
446
- if @runs[user].length > run_list.length
447
- @runs[user].delete_if {|key, val| !run_list.member? key}
448
- end
449
-
450
- @runs[user]
399
+ # Refresh the user's cache and return the runs in it.
400
+ @run_cache.refresh_all!(run_list, credentials)
451
401
  end
402
+
452
403
  end
453
404
  end
@@ -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
  #
@@ -30,23 +30,25 @@
30
30
  #
31
31
  # Author: Robert Haines
32
32
 
33
+ # :stopdoc:
34
+ # This comment is needed to stop the above licence from being included in the
35
+ # documentation multiple times. Sigh.
33
36
  module T2Server
37
+ # :startdoc:
34
38
 
35
39
  # This module contains various utility methods that the library uses
36
40
  # internally.
37
41
  module Util
38
42
 
39
43
  # :call-seq:
40
- # Util.strip_uri_credentials(uri) -> URI, Credentials
44
+ # Util.strip_uri_credentials(uri) -> URI, HttpBasic
41
45
  #
42
46
  # Strip user credentials from an address in URI or String format and return
43
- # a tuple of the URI minus the credentials and a T2Server::Credentials
47
+ # a tuple of the URI minus the credentials and a T2Server::HttpBasic
44
48
  # object.
45
49
  def self.strip_uri_credentials(uri)
46
50
  # we want to use URIs here but strings can be passed in
47
- unless uri.is_a? URI
48
- uri = URI.parse(Util.strip_path_slashes(uri))
49
- end
51
+ uri = URI.parse(Util.strip_path_slashes(uri)) unless uri.is_a? URI
50
52
 
51
53
  creds = nil
52
54
 
@@ -62,7 +64,7 @@ module T2Server
62
64
  end
63
65
 
64
66
  # :call-seq:
65
- # Util.strip_path_slashes(path) -> String
67
+ # Util.strip_path_slashes(path) -> string
66
68
  #
67
69
  # Returns a new String with one leading and one trailing slash
68
70
  # removed from the ends of _path_ (if present).
@@ -95,8 +97,8 @@ module T2Server
95
97
  new_uri
96
98
  end
97
99
 
98
- # :call_seq:
99
- # Util.get_path_leaf_from_uri(uri) -> String
100
+ # :call-seq:
101
+ # Util.get_path_leaf_from_uri(uri) -> string
100
102
  #
101
103
  # Get the final component from the path of a URI. This method returns the
102
104
  # empty string (not _nil_ ) if the URI does not have a path.