myGrid-taverna-scufl 0.6.0

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,153 @@
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 workflow.
45
+ def beanshells
46
+ return get_beanshells(self, [])
47
+ end
48
+
49
+ private
50
+
51
+ def get_beanshells(given_model, beans_collected) # :nodoc:
52
+ wf_procs = given_model.processors.select { |x| x.type == "workflow" }
53
+ wf_procs.each { |x| get_beanshells(x.model, beans_collected) }
54
+
55
+ bean_procs = given_model.processors.select { |b| b.type == "beanshell" }
56
+ bean_procs.each { |a| beans_collected << a }
57
+
58
+ return beans_collected
59
+ end
60
+ end
61
+
62
+
63
+
64
+ # This is the (shim) object within the workflow. This can be a beanshell,
65
+ # a webservice, a workflow, etc...
66
+ class Processor
67
+ # A string containing name of the processor.
68
+ attr_accessor :name
69
+
70
+ # A string containing the description of the processor if available.
71
+ # Returns nil otherwise.
72
+ attr_accessor :description
73
+
74
+ # A string for the type of processor, e.g. beanshell, workflow, webservice, etc...
75
+ attr_accessor :type
76
+
77
+ # For processors that have type == "workflow", model is the the workflow
78
+ # definition. For all other processor types, model is nil.
79
+ attr_accessor :model
80
+
81
+ # This only has a value in beanshell processors. This is the actual script
82
+ # embedded with the processor which does all the "work"
83
+ attr_accessor :script
84
+
85
+ # This is a list of inputs that the processor can take in.
86
+ attr_accessor :inputs
87
+
88
+ # This is a list of outputs that the processor can produce.
89
+ attr_accessor :outputs
90
+ end
91
+
92
+
93
+
94
+ # This contains basic descriptive information about the workflow model.
95
+ class WorkflowDescription
96
+ # The author of the workflow.
97
+ attr_accessor :author
98
+
99
+ # The name/title of the workflow.
100
+ attr_accessor :title
101
+
102
+ # A small piece of descriptive text for the workflow.
103
+ attr_accessor :description
104
+ end
105
+
106
+
107
+
108
+ # This represents a connection between any of the following pair of entities:
109
+ # {processor -> processor}, {workflow -> workflow}, {workflow -> processor},
110
+ # and {processor -> workflow}.
111
+ class Link
112
+ # The name of the source (the starting point of the connection).
113
+ attr_accessor :source
114
+
115
+ # The name of the sink (the endpoint of the connection).
116
+ attr_accessor :sink
117
+ end
118
+
119
+
120
+
121
+ # This is a representation of the 'Run after...' function in Taverna
122
+ # where the selected processor or workflow is set to run after another.
123
+ class Coordination
124
+ # The name of the processor/workflow which is to run first.
125
+ attr_accessor :controller
126
+
127
+ # The name of the processor/workflow which is to run after the controller.
128
+ attr_accessor :target
129
+ end
130
+
131
+
132
+
133
+ # This is the start node of a Link. Each source has a name and a port
134
+ # which is seperated by a colon; ":".
135
+ # This is represented as "source of a processor:port_name".
136
+ # A string that does not contain a colon can often be returned, signifiying
137
+ # a workflow source as opposed to that of a processor.
138
+ class Source
139
+ attr_accessor :name, :description
140
+ end
141
+
142
+
143
+
144
+ # This is the start node of a Link. Each sink has a name and a port
145
+ # which is seperated by a colon; ":".
146
+ # This is represented as "sink of a processor:port_name".
147
+ # A string that does not contain a colon can often be returned, signifiying
148
+ # a workflow sink as opposed to that of a processor.
149
+ class Sink
150
+ attr_accessor :name, :description
151
+ end
152
+
153
+ end
@@ -0,0 +1,162 @@
1
+ require "rexml/document"
2
+
3
+ module Scufl
4
+
5
+ class Parser
6
+ # Returns the model for the given t2flow_file.
7
+ # The method accepts objects of classes File and String only.
8
+ # ===Usage
9
+ # foo = ... # stuff to initialize foo here
10
+ # bar = Scufl::Parser.new.parse(foo)
11
+ def parse(scufl)
12
+ document = REXML::Document.new(scufl)
13
+
14
+ root = document.root
15
+ raise "Doesn't appear to be a workflow!" if root.name != "scufl"
16
+ version = root.attribute('version').value
17
+
18
+ create_model(root, version)
19
+ end
20
+
21
+ def create_model(element, version) # :nodoc:
22
+ model = Model.new
23
+
24
+ element.each_element('s:workflowdescription') { |description| set_description(model, description, version)}
25
+ element.each_element('s:processor') { |processor| add_processor(model, processor, version)}
26
+ element.each_element('s:link') { |link| add_link(model, link, version)}
27
+ element.each_element('s:source') { |source| add_source(model, source, version)}
28
+ element.each_element('s:sink') { |sink| add_sink(model, sink, version)}
29
+ element.each_element('s:coordination') { |coordination| add_coordination(model, coordination, version)}
30
+
31
+ return model
32
+ end
33
+
34
+ def add_coordination(model, element, version) # :nodoc:
35
+ coordination = Coordination.new
36
+
37
+ element.each_element('s:condition') do |condition|
38
+ condition.each_element('s:target') {|target| coordination.controller = target.text}
39
+ end
40
+ element.each_element('s:action') do |action|
41
+ action.each_element('s:target') {|target| coordination.target = target.text}
42
+ end
43
+
44
+ model.coordinations.push coordination
45
+ end
46
+
47
+ def add_link(model, element, version) # :nodoc:
48
+ link = Link.new
49
+
50
+ if version == '0.1'
51
+ element.each_element('s:input') { |input| link.sink = input.text}
52
+ element.each_element('s:output') { |output| link.source = output.text}
53
+ else
54
+ source = element.attribute('source')
55
+ link.source = source.value if source
56
+
57
+ sink = element.attribute('sink')
58
+ link.sink = sink.value if sink
59
+ end
60
+
61
+ model.links.push link
62
+ end
63
+
64
+ def add_source(model, element, version) # :nodoc:
65
+ source = Source.new
66
+
67
+ if version == '0.1'
68
+ name = element.text
69
+ source.name = name.value if name
70
+ else
71
+ name = element.attribute('name')
72
+ source.name = name.value if name
73
+ element.each_element('s:metadata') { |metadata|
74
+ metadata.each_element('s:description') {|description| source.description = description.text}
75
+ }
76
+ end
77
+
78
+ model.sources.push source
79
+ end
80
+
81
+ def add_sink(model, element, version) # :nodoc:
82
+ sink = Sink.new
83
+
84
+ if version == '0.1'
85
+ name = element.text
86
+ sink.name = name.value if name
87
+ else
88
+ name = element.attribute('name')
89
+ sink.name = name.value if name
90
+ element.each_element('s:metadata') { |metadata|
91
+ metadata.each_element('s:description') {|description| sink.description = description.text}
92
+ }
93
+ end
94
+
95
+ model.sinks.push sink
96
+ end
97
+
98
+ def add_processor(model, element, version) # :nodoc:
99
+ processor = Processor.new
100
+
101
+ name = element.attribute('name')
102
+ processor.name = name.value if name
103
+
104
+ element.each_element() do |e|
105
+ case e.name
106
+ when 'description'
107
+ processor.description = e.text
108
+ when'beanshell'
109
+ processor.type = e.name
110
+ e.each_element do |bean|
111
+ case bean.name
112
+ when "scriptvalue"
113
+ processor.script = bean.text
114
+ when "beanshellinputlist"
115
+ bean.each_element do |input|
116
+ if input.name == "beanshellinput"
117
+ processor.inputs = [] if processor.inputs.nil?
118
+ processor.inputs << input.text
119
+ end # if
120
+ end # bean.each_element
121
+ when "beanshelloutputlist"
122
+ bean.each_element do |output|
123
+ if output.name == "beanshelloutput"
124
+ processor.outputs = [] if processor.outputs.nil?
125
+ processor.outputs << output.text
126
+ end # if
127
+ end # bean.each_element
128
+ when "dependencies"
129
+ bean.each_element do |dep|
130
+ model.dependencies = [] if model.dependencies.nil?
131
+ model.dependencies << dep.text unless dep.text =~ /^\s*$/
132
+ end # bean.each_element
133
+ end # case bean.name
134
+ end # e.each_element
135
+ when 'mergemode'
136
+ when 'defaults'
137
+ when 'iterationstrategy'
138
+ else
139
+ if Dot.is_processor? e.name
140
+ processor.type = e.name
141
+ if processor.type == 'workflow'
142
+ e.each_element('s:scufl') {|e| processor.model = create_model(e, version)}
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ model.processors.push processor
149
+ end
150
+
151
+ def set_description(model, element, version) # :nodoc:
152
+ author = element.attribute('author')
153
+ title = element.attribute('title')
154
+
155
+ model.description.author = author.value if author
156
+ model.description.title = title.value if title
157
+ model.description.description = element.text
158
+ end
159
+
160
+ end
161
+
162
+ end