taverna-scufl 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,248 @@
1
+ module Scufl
2
+
3
+ # This class enables you to write the script will will be used by dot
4
+ # (which is part of GraphViz[http://www.graphviz.org/Download.php])
5
+ # to generate the image showing the structure of a given model.
6
+ # To get started quickly, you could try:
7
+ # out_file = File.new("path/to/file/you/want/the/dot/script/to/be/written", "w+")
8
+ # workflow = File.new("path/to/workflow/file", "r").read
9
+ # model = Scufl::Parser.new.parse(workflow)
10
+ # Scufl::Dot.new.write_dot(out_file, model)
11
+ # `dot -Tpng -o"path/to/the/output/image" #{out_file.path}`
12
+ class Dot
13
+
14
+ @@processor_colours = {
15
+ 'apiconsumer' => 'palegreen',
16
+ 'beanshell' => 'burlywood2',
17
+ 'biomart' => 'lightcyan2',
18
+ 'local' => 'mediumorchid2',
19
+ 'biomobywsdl' => 'darkgoldenrod1',
20
+ 'biomobyobject' => 'gold',
21
+ 'biomobyparser' => 'white',
22
+ 'inferno' => 'violetred1',
23
+ 'notification' => 'mediumorchid2',
24
+ 'rdfgenerator' => 'purple',
25
+ 'rserv' => 'lightgoldenrodyellow',
26
+ 'seqhound' => '#836fff',
27
+ 'soaplabwsdl' => 'lightgoldenrodyellow',
28
+ 'stringconstant' => 'lightsteelblue',
29
+ 'talisman' => 'plum2',
30
+ 'bsf' => 'burlywood2',
31
+ 'abstractprocessor' => 'lightgoldenrodyellow',
32
+ 'rshell' => 'lightgoldenrodyellow',
33
+ 'arbitrarywsdl' => 'darkolivegreen3',
34
+ 'workflow' => 'crimson'}
35
+
36
+ @@fill_colours = %w{white aliceblue antiquewhite beige}
37
+
38
+ @@ranksep = '0.22'
39
+ @@nodesep = '0.05'
40
+
41
+ # Creates a new dot object for interaction.
42
+ def initialize
43
+ # @port_style IS CURRENTLY UNUSED. IGNORE!!!
44
+ @port_style = 'none' # 'all', 'bound' or 'none'
45
+ end
46
+
47
+ # Writes to the given stream (File, StringIO, etc) the script to generate
48
+ # the image showing the internals of the given workflow model.
49
+ # === Usage
50
+ # stream = File.new("path/to/file/you/want/the/dot/script/to/be/written", "w+")
51
+ # workflow = .......
52
+ # model = Scufl::Parser.new.parse(workflow)
53
+ # Scufl::Dot.new.write_dot(stream, model)
54
+ def write_dot(stream, model)
55
+ stream.puts 'digraph scufl_graph {'
56
+ stream.puts ' graph ['
57
+ stream.puts ' style=""'
58
+ stream.puts ' labeljust="left"'
59
+ stream.puts ' clusterrank="local"'
60
+ stream.puts " ranksep=\"#@@ranksep\""
61
+ stream.puts " nodesep=\"#@@nodesep\""
62
+ stream.puts ' ]'
63
+ stream.puts
64
+ stream.puts ' node ['
65
+ stream.puts ' fontname="Helvetica",'
66
+ stream.puts ' fontsize="10",'
67
+ stream.puts ' fontcolor="black", '
68
+ stream.puts ' shape="box",'
69
+ stream.puts ' height="0",'
70
+ stream.puts ' width="0",'
71
+ stream.puts ' color="black",'
72
+ stream.puts ' fillcolor="lightgoldenrodyellow",'
73
+ stream.puts ' style="filled"'
74
+ stream.puts ' ];'
75
+ stream.puts
76
+ stream.puts ' edge ['
77
+ stream.puts ' fontname="Helvetica",'
78
+ stream.puts ' fontsize="8",'
79
+ stream.puts ' fontcolor="black",'
80
+ stream.puts ' color="black"'
81
+ stream.puts ' ];'
82
+ write_workflow(stream, model)
83
+ stream.puts '}'
84
+
85
+ stream.flush
86
+ end
87
+
88
+ def write_workflow(stream, model, prefix="", name="", depth=0) # :nodoc:
89
+ if name != ""
90
+ stream.puts "subgraph cluster_#{prefix}#{name} {"
91
+ stream.puts " label=\"#{name}\""
92
+ stream.puts ' fontname="Helvetica"'
93
+ stream.puts ' fontsize="10"'
94
+ stream.puts ' fontcolor="black"'
95
+ stream.puts ' clusterrank="local"'
96
+ stream.puts " fillcolor=\"#{@@fill_colours[depth % @@fill_colours.length]}\""
97
+ stream.puts ' style="filled"'
98
+ end
99
+ model.processors.each {|processor| write_processor(stream, processor, prefix, depth)}
100
+ write_source_cluster(stream, model.sources, prefix)
101
+ write_sink_cluster(stream, model.sinks, prefix)
102
+ model.links.each {|link| write_link(stream, link, model, prefix)}
103
+ model.coordinations.each {|coordination| write_coordination(stream, coordination, model, prefix)}
104
+ if name != ""
105
+ stream.puts '}'
106
+ end
107
+ end
108
+
109
+ def write_processor(stream, processor, prefix, depth) # :nodoc:
110
+ # nested workflows
111
+ if processor.model
112
+ write_workflow(stream, processor.model, prefix + processor.name, processor.name, depth.next)
113
+ else
114
+ stream.puts " \"#{prefix}#{processor.name}\" ["
115
+ stream.puts " fillcolor=\"#{get_colour processor.type}\","
116
+ stream.puts ' shape="box",'
117
+ stream.puts ' style="filled",'
118
+ stream.puts ' height="0",'
119
+ stream.puts ' width="0",'
120
+ stream.puts " label=\"#{processor.name}\""
121
+ stream.puts ' ];'
122
+ end
123
+ end
124
+
125
+ def write_source_cluster(stream, sources, prefix) # :nodoc:
126
+ if sources.length > 0
127
+ stream.puts " subgraph cluster_#{prefix}sources {"
128
+ stream.puts ' style="dotted"'
129
+ stream.puts ' label="Workflow Inputs"'
130
+ stream.puts ' fontname="Helvetica"'
131
+ stream.puts ' fontsize="10"'
132
+ stream.puts ' fontcolor="black"'
133
+ stream.puts ' rank="same"'
134
+ stream.puts " \"#{prefix}WORKFLOWINTERNALSOURCECONTROL\" ["
135
+ stream.puts ' shape="triangle",'
136
+ stream.puts ' width="0.2",'
137
+ stream.puts ' height="0.2",'
138
+ stream.puts ' fillcolor="brown1"'
139
+ stream.puts ' label=""'
140
+ stream.puts ' ]'
141
+ sources.each {|source| write_source(stream, source, prefix)}
142
+ stream.puts ' }'
143
+ end
144
+ end
145
+
146
+ def write_source(stream, source, prefix) # :nodoc:
147
+ stream.puts " \"#{prefix}WORKFLOWINTERNALSOURCE_#{source.name}\" ["
148
+ stream.puts ' shape="box",'
149
+ stream.puts " label=\"#{source.name}\""
150
+ stream.puts ' width="0",'
151
+ stream.puts ' height="0",'
152
+ stream.puts ' fillcolor="skyblue"'
153
+ stream.puts ' ]'
154
+ end
155
+
156
+ def write_sink_cluster(stream, sinks, prefix) # :nodoc:
157
+ if sinks.length > 0
158
+ stream.puts " subgraph cluster_#{prefix}sinks {"
159
+ stream.puts ' style="dotted"'
160
+ stream.puts ' label="Workflow Outputs"'
161
+ stream.puts ' fontname="Helvetica"'
162
+ stream.puts ' fontsize="10"'
163
+ stream.puts ' fontcolor="black"'
164
+ stream.puts ' rank="same"'
165
+ stream.puts " \"#{prefix}WORKFLOWINTERNALSINKCONTROL\" ["
166
+ stream.puts ' shape="invtriangle",'
167
+ stream.puts ' width="0.2",'
168
+ stream.puts ' height="0.2",'
169
+ stream.puts ' fillcolor="chartreuse3"'
170
+ stream.puts ' label=""'
171
+ stream.puts ' ]'
172
+ sinks.each {|sink| write_sink(stream, sink, prefix)}
173
+ stream.puts ' }'
174
+ end
175
+ end
176
+
177
+ def write_sink(stream, sink, prefix) # :nodoc:
178
+ stream.puts " \"#{prefix}WORKFLOWINTERNALSINK_#{sink.name}\" ["
179
+ stream.puts ' shape="box",'
180
+ stream.puts " label=\"#{sink.name}\""
181
+ stream.puts ' width="0",'
182
+ stream.puts ' height="0",'
183
+ stream.puts ' fillcolor="lightsteelblue2"'
184
+ stream.puts ' ]'
185
+ end
186
+
187
+ def write_link(stream, link, model, prefix) # :nodoc:
188
+ if model.sources.select{|s| s.name == link.source} != []
189
+ stream.write " \"#{prefix}WORKFLOWINTERNALSOURCE_#{link.source}\""
190
+ else
191
+ processor = model.processors.select{|p| p.name == link.source.split(':')[0]}[0]
192
+ if processor.model
193
+ stream.write " \"#{prefix}#{processor.name}WORKFLOWINTERNALSINK_#{link.source.split(':')[1]}\""
194
+ else
195
+ stream.write " \"#{prefix}#{processor.name}\""
196
+ end
197
+ end
198
+ stream.write '->'
199
+ if model.sinks.select{|s| s.name == link.sink} != []
200
+ stream.write "\"#{prefix}WORKFLOWINTERNALSINK_#{link.sink}\""
201
+ else
202
+ processor = model.processors.select{|p| p.name == link.sink.split(':')[0]}[0]
203
+ if processor.model
204
+ stream.write "\"#{prefix}#{processor.name}WORKFLOWINTERNALSOURCE_#{link.sink.split(':')[1]}\""
205
+ else
206
+ stream.write "\"#{prefix}#{processor.name}\""
207
+ end
208
+ end
209
+ stream.puts ' ['
210
+ stream.puts ' ];'
211
+ end
212
+
213
+ def write_coordination(stream, coordination, model, prefix) # :nodoc:
214
+ stream.write " \"#{prefix}#{coordination.controller}"
215
+ processor = model.processors.select{|p| p.name == coordination.controller}[0]
216
+ if processor.model
217
+ stream.write 'WORKFLOWINTERNALSINKCONTROL'
218
+ end
219
+ stream.write '"->"'
220
+ stream.write "#{prefix}#{coordination.target}\""
221
+ processor = model.processors.select{|p| p.name == coordination.target}[0]
222
+ if processor.model
223
+ stream.write 'WORKFLOWINTERNALSOURCECONTROL'
224
+ end
225
+ stream.puts ' ['
226
+ stream.puts ' color="gray",'
227
+ stream.puts ' arrowhead="odot",'
228
+ stream.puts ' arrowtail="none"'
229
+ stream.puts ' ];'
230
+ end
231
+
232
+ def get_colour(processor_name) # :nodoc:
233
+ colour = @@processor_colours[processor_name]
234
+ if colour
235
+ colour
236
+ else
237
+ 'white'
238
+ end
239
+ end
240
+
241
+ # Returns true if the given name is a processor; false otherwise
242
+ def Dot.is_processor?(processor_name)
243
+ true if @@processor_colours[processor_name]
244
+ end
245
+
246
+ end
247
+
248
+ end
@@ -0,0 +1,310 @@
1
+ # This is the module containing the Scufl model implementation i.e. the model structure/definition and all its internals.
2
+
3
+ module Scufl # :nodoc:
4
+
5
+ # The model for a given Taverna 1 workflow.
6
+ class Model
7
+ # This returns a WorkflowDescription object.
8
+ attr_reader :description
9
+
10
+ # Retrieve the list of processors specific to the workflow.
11
+ # Does not include those from nested workflows.
12
+ attr_reader :processors
13
+
14
+ # Retrieve the list of datalinks specific to the workflow.
15
+ # Does not include those from nested workflows.
16
+ attr_reader :links
17
+
18
+ # Retrieve the list of sources specific to the workflow.
19
+ # Does not include those from nested workflows.
20
+ attr_reader :sources
21
+
22
+ # Retrieve the list of sinks specific to the workflow.
23
+ # Does not include those from nested workflows.
24
+ attr_reader :sinks
25
+
26
+ # Retrieve the list of coordinations specific to the workflow.
27
+ # Does not include those from nested workflows.
28
+ attr_reader :coordinations
29
+
30
+ # The list of any dependencies that have been found inside the workflow.
31
+ # Does not include those from nested workflows.
32
+ attr_accessor :dependencies
33
+
34
+ # Creates an empty model for a Taverna 1 workflow.
35
+ def initialize
36
+ @description = WorkflowDescription.new
37
+ @processors = Array.new
38
+ @links = Array.new
39
+ @sources = Array.new
40
+ @sinks = Array.new
41
+ @coordinations = Array.new
42
+ end
43
+
44
+ # Retrieve ALL the beanshell processors WITHIN the given workflow model.
45
+ def beanshells
46
+ self.all_processors.select { |x| x.type == "beanshell" }
47
+ end
48
+
49
+ # Retrieve ALL processors of that are webservices WITHIN the model.
50
+ def web_services
51
+ self.all_processors.select { |x| x.type =~ /wsdl|soaplab|biomoby/i }
52
+ end
53
+
54
+ # Retrieve ALL local workers WITHIN the workflow
55
+ def local_workers
56
+ self.all_processors.select { |x| x.type =~ /local/i }
57
+ end
58
+
59
+ # Retrieve ALL processor objects WITHIN the given workflow model.
60
+ def all_processors
61
+ return get_processors(self, [])
62
+ end
63
+
64
+
65
+ # Retrieve ALL the links WITHIN the given workflow model.
66
+ def all_links
67
+ return get_links(self, [])
68
+ end
69
+
70
+ # Retrieve ALL the sinks(outputs) WITHIN the given workflow model.
71
+ def all_sinks
72
+ return get_sinks(self, [])
73
+ end
74
+
75
+ # Retrieve ALL the sources(inputs) WITHIN the given workflow model.
76
+ def all_sources
77
+ return get_sources(self, [])
78
+ end
79
+
80
+ # For the given dataflow, return the beanshells and/or services which
81
+ # have direct links to or from the given processor.
82
+ # == Usage
83
+ # my_processor = model.processor[0]
84
+ # linked_processors = model.get_processors_linked_to(my_processor)
85
+ # processors_feeding_into_my_processor = linked_processors.sources
86
+ # processors_feeding_from_my_processor = linked_processors.sinks
87
+ def get_processor_links(processor)
88
+ return nil unless processor
89
+ proc_links = ProcessorLinks.new
90
+
91
+ # SOURCES
92
+ sources = self.all_links.select { |x| x.sink =~ /#{processor.name}:.+/ }
93
+ proc_links.sources = []
94
+
95
+ # SINKS
96
+ sinks = self.all_links.select { |x| x.source =~ /#{processor.name}:.+/ }
97
+ proc_links.sinks = []
98
+ temp_sinks = []
99
+ sinks.each { |x| temp_sinks << x.sink }
100
+
101
+ # Match links by port into format
102
+ # my_port:name_of_link_im_linked_to:its_port
103
+ sources.each do |connection|
104
+ link = connection.sink
105
+ connected_proc_name = link.split(":")[0]
106
+ my_connection_port = link.split(":")[1]
107
+
108
+ if my_connection_port
109
+ source = my_connection_port << ":" << connection.source
110
+ proc_links.sources << source if source.split(":").size == 3
111
+ end
112
+ end
113
+
114
+ sinks.each do |connection|
115
+ link = connection.source
116
+ connected_proc_name = link.split(":")[0]
117
+ my_connection_port = link.split(":")[1]
118
+
119
+ if my_connection_port
120
+ sink = my_connection_port << ":" << connection.sink
121
+ proc_links.sinks << sink if sink.split(":").size == 3
122
+ end
123
+ end
124
+
125
+ return proc_links
126
+ end
127
+
128
+ private
129
+
130
+ def get_beanshells(given_model, beans_collected) # :nodoc:
131
+ wf_procs = given_model.processors.select { |x| x.type == "workflow" }
132
+ wf_procs.each { |x| get_beanshells(x.model, beans_collected) if x.model }
133
+
134
+ bean_procs = given_model.processors.select { |b| b.type == "beanshell" }
135
+ bean_procs.each { |a| beans_collected << a }
136
+
137
+ return beans_collected
138
+ end
139
+
140
+ def get_processors(given_model, procs_collected) # :nodoc:
141
+ wf_procs = given_model.processors.select { |x| x.type == "workflow" }
142
+ wf_procs.each { |x| get_processors(x.model, procs_collected) if x.model }
143
+
144
+ procs = given_model.processors
145
+ procs.each { |a| procs_collected << a }
146
+
147
+ return procs_collected
148
+ end
149
+
150
+ def get_links(given_model, links_collected) # :nodoc:
151
+ wf_procs = given_model.processors.select { |x| x.type == "workflow" }
152
+ wf_procs.each { |x| get_links(x.model, links_collected) if x.model }
153
+
154
+ links = given_model.links
155
+ links.each { |a| links_collected << a }
156
+
157
+ return links_collected
158
+ end
159
+
160
+ def get_sinks(given_model, sinks_collected) # :nodoc:
161
+ wf_procs = given_model.processors.select { |x| x.type == "workflow" }
162
+ wf_procs.each { |x| get_sinks(x.model, sinks_collected) if x.model }
163
+
164
+ sinks = given_model.sinks
165
+ sinks.each { |a| sinks_collected << a }
166
+
167
+ return sinks_collected
168
+ end
169
+
170
+ def get_sources(given_model, sources_collected) # :nodoc:
171
+ wf_procs = given_model.processors.select { |x| x.type == "workflow" }
172
+ wf_procs.each { |x| get_sources(x.model, sources_collected) if x.model }
173
+
174
+ sources = given_model.sources
175
+ sources.each { |a| sources_collected << a }
176
+
177
+ return sources_collected
178
+ end
179
+ end
180
+
181
+
182
+
183
+ # This is the (shim) object within the workflow. This can be a beanshell,
184
+ # a webservice, a workflow, etc...
185
+ class Processor
186
+ # A string containing name of the processor.
187
+ attr_accessor :name
188
+
189
+ # A string containing the description of the processor if available.
190
+ # Returns nil otherwise.
191
+ attr_accessor :description
192
+
193
+ # A string for the type of processor, e.g. beanshell, workflow, webservice, etc...
194
+ attr_accessor :type
195
+
196
+ # For processors that have type == "workflow", model is the the workflow
197
+ # definition. For all other processor types, model is nil.
198
+ attr_accessor :model
199
+
200
+ # This only has a value in beanshell processors. This is the actual script
201
+ # embedded with the processor which does all the "work"
202
+ attr_accessor :script
203
+
204
+ # This is a list of inputs that the processor can take in.
205
+ attr_accessor :inputs
206
+
207
+ # This is a list of outputs that the processor can produce.
208
+ attr_accessor :outputs
209
+
210
+ # For processors of type "arbitrarywsdl", this is the URI to the location
211
+ # of the wsdl file.
212
+ attr_accessor :wsdl
213
+
214
+ # For processors of type "arbitrarywsdl", this is the operation invoked.
215
+ attr_accessor :wsdl_operation
216
+
217
+ # For soaplab and biomoby services, this is the endpoint URI.
218
+ attr_accessor :endpoint
219
+
220
+ # Authority name for the biomoby service.
221
+ attr_accessor :biomoby_authority_name
222
+
223
+ # Service name for the biomoby service. This is not necessarily the same
224
+ # as the processors name.
225
+ attr_accessor :biomoby_service_name
226
+
227
+ # Category for the biomoby service.
228
+ attr_accessor :biomoby_category
229
+ end
230
+
231
+
232
+
233
+ # This object is returned after invoking model.get_processor_links(processor)
234
+ # . The object contains two lists of processors. Each element consists of:
235
+ # the input or output port the processor uses as a link, the name of the
236
+ # processor being linked, and the port of the processor used for the linking,
237
+ # all seperated by a colon (:) i.e.
238
+ # my_port:name_of_processor:processor_port
239
+ class ProcessorLinks
240
+ # The processors whose output is fed as input into the processor used in
241
+ # model.get_processors_linked_to(processor).
242
+ attr_accessor :sources
243
+
244
+ # A list of processors that are fed the output from the processor (used in
245
+ # model.get_processors_linked_to(processor) ) as input.
246
+ attr_accessor :sinks
247
+ end
248
+
249
+
250
+
251
+ # This contains basic descriptive information about the workflow model.
252
+ class WorkflowDescription
253
+ # The author of the workflow.
254
+ attr_accessor :author
255
+
256
+ # The name/title of the workflow.
257
+ attr_accessor :title
258
+
259
+ # A small piece of descriptive text for the workflow.
260
+ attr_accessor :description
261
+ end
262
+
263
+
264
+
265
+ # This represents a connection between any of the following pair of entities:
266
+ # {processor -> processor}, {workflow -> workflow}, {workflow -> processor},
267
+ # and {processor -> workflow}.
268
+ class Link
269
+ # The name of the source (the starting point of the connection).
270
+ attr_accessor :source
271
+
272
+ # The name of the sink (the endpoint of the connection).
273
+ attr_accessor :sink
274
+ end
275
+
276
+
277
+
278
+ # This is a representation of the 'Run after...' function in Taverna
279
+ # where the selected processor or workflow is set to run after another.
280
+ class Coordination
281
+ # The name of the processor/workflow which is to run first.
282
+ attr_accessor :controller
283
+
284
+ # The name of the processor/workflow which is to run after the controller.
285
+ attr_accessor :target
286
+ end
287
+
288
+
289
+
290
+ # This is the start node of a Link. Each source has a name and a port
291
+ # which is seperated by a colon; ":".
292
+ # This is represented as "source of a processor:port_name".
293
+ # A string that does not contain a colon can often be returned, signifiying
294
+ # a workflow source as opposed to that of a processor.
295
+ class Source
296
+ attr_accessor :name, :description
297
+ end
298
+
299
+
300
+
301
+ # This is the start node of a Link. Each sink has a name and a port
302
+ # which is seperated by a colon; ":".
303
+ # This is represented as "sink of a processor:port_name".
304
+ # A string that does not contain a colon can often be returned, signifiying
305
+ # a workflow sink as opposed to that of a processor.
306
+ class Sink
307
+ attr_accessor :name, :description
308
+ end
309
+
310
+ end