t2-server 0.9.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.