ensembl-hive  2.5
HivePipeline.pm
Go to the documentation of this file.
1 =head1 LICENSE
2 
3 Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
4 Copyright [2016-2022] EMBL-European Bioinformatics Institute
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10  http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 
18 =cut
19 
20 package Bio::EnsEMBL::Hive::HivePipeline;
21 
22 use strict;
23 use warnings;
24 
27 use Bio::EnsEMBL::Hive::Utils ('stringify', 'destringify', 'throw');
31 
32  # needed for offline graph generation:
35 
36 
37 sub hive_dba { # The adaptor for HivePipeline objects
38  my $self = shift @_;
39 
40  if(@_) {
41  $self->{'_hive_dba'} = shift @_;
42  $self->{'_hive_dba'}->hive_pipeline($self) if $self->{'_hive_dba'};
43  }
44  return $self->{'_hive_dba'};
45 }
46 
47 
48 sub display_name {
49  my $self = shift @_;
50 
51  if(my $dbc = $self->hive_dba && $self->hive_dba->dbc) {
52  return $dbc->dbname . '@' .($dbc->host||'');
53  } else {
54  return '(unstored '.$self->hive_pipeline_name.')';
55  }
56 }
57 
58 
59 sub unambig_key { # based on DBC's URL if present, otherwise on pipeline_name
60  my $self = shift @_;
61 
62  if(my $dbc = $self->hive_dba && $self->hive_dba->dbc) {
63  return Bio::EnsEMBL::Hive::Utils::URL::hash_to_unambig_url( $dbc->to_url_hash );
64  } else {
65  return 'unstored:'.$self->hive_pipeline_name;
66  }
67 }
68 
69 
70 sub collection_of {
71  my $self = shift @_;
72  my $type = shift @_;
73 
74  if (@_) {
75  $self->{'_cache_by_class'}->{$type} = shift @_;
76  } elsif (not $self->{'_cache_by_class'}->{$type}) {
77 
78  if( (my $hive_dba = $self->hive_dba) and ($type ne 'NakedTable') and ($type ne 'Accumulator') and ($type ne 'Job') and ($type ne 'AnalysisJob')) {
79  my $adaptor = $hive_dba->get_adaptor( $type );
80  my $all_objects = $adaptor->fetch_all();
81  if(@$all_objects and UNIVERSAL::can($all_objects->[0], 'hive_pipeline') ) {
82  $_->hive_pipeline($self) for @$all_objects;
83  }
84  $self->{'_cache_by_class'}->{$type} = Bio::EnsEMBL::Hive::Utils::Collection->new( $all_objects );
85 # warn "initialized collection_of($type) by loading all ".scalar(@$all_objects)."\n";
86  } else {
87  $self->{'_cache_by_class'}->{$type} = Bio::EnsEMBL::Hive::Utils::Collection->new();
88 # warn "initialized collection_of($type) as an empty one\n";
89  }
90  }
91 
92  return $self->{'_cache_by_class'}->{$type};
93 }
94 
95 
96 sub find_by_query {
97  my $self = shift @_;
98  my $query_params = shift @_;
99  my $no_die = shift @_;
100 
101  if(my $object_type = delete $query_params->{'object_type'}) {
102  my $object;
103 
104  if($object_type eq 'Accumulator' or $object_type eq 'NakedTable') {
105 
106  unless($object = $self->collection_of($object_type)->find_one_by( %$query_params )) {
107 
108  my @specific_adaptor_params = ($object_type eq 'NakedTable')
109  ? ('table_name' => $query_params->{'table_name'},
110  $query_params->{'insertion_method'}
111  ? ('insertion_method' => $query_params->{'insertion_method'})
112  : ()
113  )
114  : ();
115  ($object) = $self->add_new_or_update( $object_type, # NB: add_new_or_update returns a list
116  %$query_params,
117  $self->hive_dba ? ('adaptor' => $self->hive_dba->get_adaptor($object_type, @specific_adaptor_params)) : (),
118  );
119  }
120  } elsif($object_type eq 'AnalysisJob' or $object_type eq 'Semaphore') {
121  my $id_name = { 'AnalysisJob' => 'job_id', 'Semaphore' => 'semaphore_id' }->{$object_type};
122  my $dbID = $query_params->{$id_name};
123  my $coll = $self->collection_of($object_type);
124  unless($object = $coll->find_one_by( 'dbID' => $dbID )) {
125 
126  my $adaptor = $self->hive_dba->get_adaptor( $object_type );
127  if( $object = $adaptor->fetch_by_dbID( $dbID ) ) {
128  $coll->add( $object );
129  }
130  }
131  } else {
132  $object = $self->collection_of($object_type)->find_one_by( %$query_params );
133  }
134 
135  return $object if $object || $no_die;
136  throw("Could not find an '$object_type' object from query ".stringify($query_params)." in ".$self->display_name);
137 
138  } else {
139  throw("Could not find or guess the object_type from the query ".stringify($query_params)." , so could not find the object");
140  }
141 }
142 
143 sub test_connections {
144  my $self = shift;
145 
146  my @warnings;
147 
148  foreach my $dft ($self->collection_of('DataflowTarget')->list) {
149  my $analysis_url = $dft->to_analysis_url;
150  if ($analysis_url =~ m{^\w+$}) {
151  my $heir_analysis = $self->collection_of('Analysis')->find_one_by('logic_name', $analysis_url)
152  or push @warnings, "Could not find a local analysis named '$analysis_url' (dataflow from analysis '".($dft->source_dataflow_rule->from_analysis->logic_name)."')";
153  }
154  }
155 
156  foreach my $cf ($self->collection_of('AnalysisCtrlRule')->list) {
157  my $analysis_url = $cf->condition_analysis_url;
158  if ($analysis_url =~ m{^\w+$}) {
159  my $heir_analysis = $self->collection_of('Analysis')->find_one_by('logic_name', $analysis_url)
160  or push @warnings, "Could not find a local analysis named '$analysis_url' (control-flow for analysis '".($cf->ctrled_analysis->logic_name)."')";
161  }
162 
163  }
164 
165  if (@warnings) {
166  push @warnings, '', 'Please fix these before running the pipeline';
167  warn join("\n", '', '# ' . '-' x 26 . '[WARNINGS]' . '-' x 26, '', @warnings), "\n";
168  }
169 }
170 
171 
172 sub new { # construct an attached or a detached Pipeline object
173  my $class = shift @_;
174 
175  my $self = bless {}, $class;
176 
177  my %dba_flags = @_;
178  my $existing_dba = delete $dba_flags{'-dba'};
179 
180  if(%dba_flags) {
181  my $hive_dba = Bio::EnsEMBL::Hive::DBSQL::DBAdaptor->new( %dba_flags );
182  $self->hive_dba( $hive_dba );
183  } elsif ($existing_dba) {
184  $self->hive_dba( $existing_dba );
185  } else {
186 # warn "Created a standalone pipeline";
187  }
188 
189  Bio::EnsEMBL::Hive::TheApiary->pipelines_collection->add( $self );
190 
191  return $self;
192 }
193 
194 
195  # If there is a DBAdaptor, collection_of() will fetch a collection on demand:
196 sub invalidate_collections {
197  my $self = shift @_;
198 
199  delete $self->{'_cache_by_class'};
200  return;
201 }
202 
203 
204 sub save_collections {
205  my $self = shift @_;
206 
207  my $hive_dba = $self->hive_dba();
208 
210 
211  foreach my $AdaptorType (reverse @adaptor_types) {
212  my $adaptor = $hive_dba->get_adaptor( $AdaptorType );
213  my $coll = $self->collection_of( $AdaptorType );
214  if( my $dark_collection = $coll->dark_collection) {
215  foreach my $obj_to_be_deleted ( $coll->dark_collection->list ) {
216  $adaptor->remove( $obj_to_be_deleted );
217 # warn "Deleted ".(UNIVERSAL::can($obj_to_be_deleted, 'toString') ? $obj_to_be_deleted->toString : stringify($obj_to_be_deleted))."\n";
218  }
219  $coll->dark_collection( undef );
220  }
221  }
222 
223  foreach my $AdaptorType (@adaptor_types) {
224  my $adaptor = $hive_dba->get_adaptor( $AdaptorType );
225  my $class = 'Bio::EnsEMBL::Hive::'.$AdaptorType;
226  my $coll = $self->collection_of( $AdaptorType );
227  foreach my $storable_object ( $coll->list ) {
228  $adaptor->store_or_update_one( $storable_object, $class->unikey() );
229 # warn "Stored/updated ".$storable_object->toString()."\n";
230  }
231  }
232 
233  my $job_adaptor = $hive_dba->get_AnalysisJobAdaptor;
234  foreach my $analysis ( $self->collection_of( 'Analysis' )->list ) {
235  if(my $our_jobs = $analysis->jobs_collection ) {
236  $job_adaptor->store( $our_jobs );
237 # foreach my $job (@$our_jobs) {
238 # warn "Stored ".$job->toString()."\n";
239 # }
240  }
241  }
242 }
243 
244 
245 sub add_new_or_update {
246  my $self = shift @_;
247  my $type = shift @_;
248 
249  # $verbose is an extra optional argument that sits between the type and the object hash
250  my $verbose = scalar(@_) % 2 ? shift : 0;
251 
252  my $class = 'Bio::EnsEMBL::Hive::'.$type;
253  my $coll = $self->collection_of( $type );
254 
255  my $object;
256  my $newly_made = 0;
257 
258  if( my $unikey_keys = $class->unikey() ) {
259  my %other_pairs = @_;
260  my %unikey_pairs;
261  @unikey_pairs{ @$unikey_keys} = delete @other_pairs{ @$unikey_keys };
262 
263  if( $object = $coll->find_one_by( %unikey_pairs ) ) {
264  my $found_display = $verbose && (UNIVERSAL::can($object, 'toString') ? $object->toString : stringify($object));
265  if(keys %other_pairs) {
266  print "Updating $found_display with (".stringify(\%other_pairs).")\n" if $verbose;
267  if( ref($object) eq 'HASH' ) {
268  @$object{ keys %other_pairs } = values %other_pairs;
269  } else {
270  while( my ($key, $value) = each %other_pairs ) {
271  $object->$key($value);
272  }
273  }
274  } else {
275  print "Found a matching $found_display\n" if $verbose;
276  }
277  } elsif( my $dark_coll = $coll->dark_collection) {
278  if( my $shadow_object = $dark_coll->find_one_by( %unikey_pairs ) ) {
279  $dark_coll->forget( $shadow_object );
280  my $found_display = $verbose && (UNIVERSAL::can($shadow_object, 'toString') ? $shadow_object->toString : stringify($shadow_object));
281  print "Undeleting $found_display\n" if $verbose;
282  }
283  }
284  } else {
285  warn "$class doesn't redefine unikey(), so unique objects cannot be identified";
286  }
287 
288  unless( $object ) {
289  $object = $class->can('new') ? $class->new( @_ ) : { @_ };
290  $newly_made = 1;
291 
292  $coll->add( $object );
293 
294  $object->hive_pipeline($self) if UNIVERSAL::can($object, 'hive_pipeline');
295 
296  my $found_display = $verbose && (UNIVERSAL::can($object, 'toString') ? $object->toString : 'naked entry '.stringify($object));
297  print "Created a new $found_display\n" if $verbose;
298  }
299 
300  return ($object, $newly_made);
301 }
302 
303 
304 =head2 get_source_analyses
305 
306  Description: returns a listref of analyses that do not have local inflow ("source analyses")
307 
308 =cut
309 
310 sub get_source_analyses {
311  my $self = shift @_;
312 
313  my %analyses_to_discard = map {scalar($_->to_analysis) => 1} $self->collection_of( 'DataflowTarget' )->list;
314 
315  return [grep {!$analyses_to_discard{"$_"}} $self->collection_of( 'Analysis' )->list];
316 }
317 
318 
319 =head2 _meta_value_by_key
320 
321  Description: getter/setter for a particular meta_value from 'MetaParameters' collection given meta_key
322 
323 =cut
324 
325 sub _meta_value_by_key {
326  my $self = shift @_;
327  my $meta_key= shift @_;
328 
329  my $hash = $self->collection_of( 'MetaParameters' )->find_one_by( 'meta_key', $meta_key );
330 
331  if(@_) {
332  my $new_value = shift @_;
333 
334  if($hash) {
335  $hash->{'meta_value'} = $new_value;
336  } else {
337  ($hash) = $self->add_new_or_update( 'MetaParameters',
338  'meta_key' => $meta_key,
339  'meta_value' => $new_value,
340  );
341  }
342  }
343 
344  return $hash && $hash->{'meta_value'};
345 }
346 
347 
348 =head2 hive_use_param_stack
349 
350  Description: getter/setter via MetaParameters. Defines which one of two modes of parameter propagation is used in this pipeline
351 
352 =cut
353 
354 sub hive_use_param_stack {
355  my $self = shift @_;
356 
357  return $self->_meta_value_by_key('hive_use_param_stack', @_) // 0;
358 }
359 
360 
361 =head2 hive_pipeline_name
362 
363  Description: getter/setter via MetaParameters. Defines the symbolic name of the pipeline.
364 
365 =cut
366 
367 sub hive_pipeline_name {
368  my $self = shift @_;
369 
370  return $self->_meta_value_by_key('hive_pipeline_name', @_) // '';
371 }
372 
373 
374 =head2 hive_auto_rebalance_semaphores
375 
376  Description: getter/setter via MetaParameters. Defines whether beekeeper should attempt to rebalance semaphores on each iteration.
377 
378 =cut
379 
380 sub hive_auto_rebalance_semaphores {
381  my $self = shift @_;
382 
383  return $self->_meta_value_by_key('hive_auto_rebalance_semaphores', @_) // '0';
384 }
385 
386 
387 =head2 hive_use_triggers
388 
389  Description: getter via MetaParameters. Defines whether SQL triggers are used to automatically update AnalysisStats counters
390 
391 =cut
392 
393 sub hive_use_triggers {
394  my $self = shift @_;
395 
396  if(@_) {
397  throw('HivePipeline::hive_use_triggers is not settable, it is only a getter');
398  }
399 
400  return $self->_meta_value_by_key('hive_use_triggers') // '0';
401 }
402 
403 =head2 hive_default_max_retry_count
404 
405  Description: getter/setter via MetaParameters. Defines the default value for analysis_base.max_retry_count
406 
407 =cut
408 
409 sub hive_default_max_retry_count {
410  my $self = shift @_;
411 
412  return $self->_meta_value_by_key('hive_default_max_retry_count', @_) // 0;
413 }
414 
415 
416 =head2 list_all_hive_tables
417 
418  Description: getter via MetaParameters. Lists the (MySQL) table names used by the HivePipeline
419 
420 =cut
421 
422 sub list_all_hive_tables {
423  my $self = shift @_;
424 
425  if(@_) {
426  throw('HivePipeline::list_all_hive_tables is not settable, it is only a getter');
427  }
428 
429  return [ split /,/, ($self->_meta_value_by_key('hive_all_base_tables') // '') ];
430 }
431 
432 
433 =head2 list_all_hive_views
434 
435  Description: getter via MetaParameters. Lists the (MySQL) view names used by the HivePipeline
436 
437 =cut
438 
439 sub list_all_hive_views {
440  my $self = shift @_;
441 
442  if(@_) {
443  throw('HivePipeline::list_all_hive_views is not settable, it is only a getter');
444  }
445 
446  return [ split /,/, ($self->_meta_value_by_key('hive_all_views') // '') ];
447 }
448 
449 
450 =head2 hive_sql_schema_version
451 
452  Description: getter via MetaParameters. Defines the Hive SQL schema version of the database if it has been stored
453 
454 =cut
455 
456 sub hive_sql_schema_version {
457  my $self = shift @_;
458 
459  if(@_) {
460  throw('HivePipeline::hive_sql_schema_version is not settable, it is only a getter');
461  }
462 
463  return $self->_meta_value_by_key('hive_sql_schema_version') // 'N/A';
464 }
465 
466 
467 =head2 params_as_hash
468 
469  Description: returns the destringified contents of the 'PipelineWideParameters' collection as a hash
470 
471 =cut
472 
473 sub params_as_hash {
474  my $self = shift @_;
475 
476  my $collection = $self->collection_of( 'PipelineWideParameters' );
477  return { map { $_->{'param_name'} => destringify($_->{'param_value'}) } $collection->list() };
478 }
479 
480 
481 =head2 get_cached_hive_current_load
482 
483  Description: Proxy for RoleAdaptor::get_hive_current_load() that caches the last value.
484 
485 =cut
486 
487 sub get_cached_hive_current_load {
488  my $self = shift @_;
489 
490  if (not exists $self->{'_cached_hive_load'}) {
491  if ($self->hive_dba) {
492  $self->{'_cached_hive_load'} = $self->hive_dba->get_RoleAdaptor->get_hive_current_load();
493  } else {
494  $self->{'_cached_hive_load'} = 0;
495  }
496  }
497  return $self->{'_cached_hive_load'};
498 }
499 
500 
501 =head2 invalidate_hive_current_load
502 
503  Description: Method that forces the next get_cached_hive_current_load() call to fetch a fresh value from the database
504 
505 =cut
506 
507 sub invalidate_hive_current_load {
508  my $self = shift @_;
509 
510  delete $self->{'_cached_hive_load'};
511 }
512 
513 
514 =head2 print_diagram
515 
516  Description: prints a "Unicode art" textual representation of the pipeline's flow diagram
517 
518 =cut
519 
520 sub print_diagram {
521  my $self = shift @_;
522 
523  print ''.('─'x20).'[ '.$self->display_name.' ]'.('─'x20)."\n";
524 
525  my %seen = ();
526  foreach my $source_analysis ( @{ $self->get_source_analyses } ) {
527  print "\n";
528  $source_analysis->print_diagram_node($self, '', \%seen);
529  }
530  foreach my $cyclic_analysis ( $self->collection_of( 'Analysis' )->list ) {
531  next if $seen{$cyclic_analysis};
532  print "\n";
533  $cyclic_analysis->print_diagram_node($self, '', \%seen);
534  }
535 }
536 
537 
538 =head2 apply_tweaks
539 
540  Description: changes attributes of Analyses|ResourceClasses|ResourceDescriptions or values of pipeline/analysis parameters
541 
542 =cut
543 
544 sub apply_tweaks {
545  my $self = shift @_;
546  my $tweaks = shift @_;
547 
548  my $need_write = 0;
549 
550  foreach my $tweak (@$tweaks) {
551  print "\nTweak.Request\t$tweak\n";
552 
553  if($tweak=~/^pipeline\.param\[(\w+)\](\?|#|=(.+))$/) {
554  my ($param_name, $operator, $new_value_str) = ($1, $2, $3);
555 
556  my $pwp_collection = $self->collection_of( 'PipelineWideParameters' );
557  my $hash_pair = $pwp_collection->find_one_by('param_name', $param_name);
558 
559  if($operator eq '?') {
560  print "Tweak.Show \tpipeline.param[$param_name] ::\t"
561  . ($hash_pair ? $hash_pair->{'param_value'} : '(missing_value)') . "\n";
562  } elsif($operator eq '#') {
563  if ($hash_pair) {
564  $need_write = 1;
565  $pwp_collection->forget_and_mark_for_deletion( $hash_pair );
566  print "Tweak.Deleting\tpipeline.param[$param_name] ::\t".stringify($hash_pair->{'param_value'})." --> (missing value)\n";
567  } else {
568  print "Tweak.Deleting\tpipeline.param[$param_name] skipped (does not exist)\n";
569  }
570  } else {
571  $need_write = 1;
572  my $new_value = destringify( $new_value_str );
573  $new_value_str = stringify($new_value);
574 
575  if($hash_pair) {
576  print "Tweak.Changing\tpipeline.param[$param_name] ::\t$hash_pair->{'param_value'} --> $new_value_str\n";
577 
578  $hash_pair->{'param_value'} = $new_value_str;
579  } else {
580  print "Tweak.Adding \tpipeline.param[$param_name] ::\t(missing value) --> $new_value_str\n";
581 
582  $self->add_new_or_update( 'PipelineWideParameters',
583  'param_name' => $param_name,
584  'param_value' => $new_value_str,
585  );
586  }
587  }
588 
589  } elsif($tweak=~/^pipeline\.(\w+)(\?|=(.+))$/) {
590  my ($attrib_name, $operator, $new_value_str) = ($1, $2, $3);
591 
592  if($self->can($attrib_name)) {
593  my $old_value = stringify( $self->$attrib_name() );
594 
595  if($operator eq '?') {
596  print "Tweak.Show \tpipeline.$attrib_name ::\t$old_value\n";
597  } else {
598  print "Tweak.Changing\tpipeline.$attrib_name ::\t$old_value --> $new_value_str\n";
599 
600  $self->$attrib_name( $new_value_str );
601  $need_write = 1;
602  }
603 
604  } else {
605  print "Tweak.Error \tCould not find the pipeline-wide '$attrib_name' method\n";
606  }
607 
608  } elsif($tweak=~/^analysis\[([^\]]+)\]\.param\[(\w+)\](\?|#|=(.+))$/) {
609  my ($analyses_pattern, $param_name, $operator, $new_value_str) = ($1, $2, $3, $4);
610 
611  my $analyses = $self->collection_of( 'Analysis' )->find_all_by_pattern( $analyses_pattern );
612  print "Tweak.Found \t".scalar(@$analyses)." analyses matching the pattern '$analyses_pattern'\n";
613 
614  my $new_value = destringify( $new_value_str );
615  $new_value_str = stringify( $new_value );
616 
617  foreach my $analysis (@$analyses) {
618  my $analysis_name = $analysis->logic_name;
619 
620  my $old_value = $analysis->parameters;
621 
622  my $param_hash = destringify( $old_value );
623 
624  if($operator eq '?') {
625  print "Tweak.Show \tanalysis[$analysis_name].param[$param_name] ::\t"
626  . (exists($param_hash->{ $param_name }) ? stringify($param_hash->{ $param_name }) : '(missing value)')
627  ."\n";
628  } elsif($operator eq '#') {
629  print "Tweak.Deleting\tanalysis[$analysis_name].param[$param_name] ::\t".stringify($param_hash->{ $param_name })." --> (missing value)\n";
630 
631  delete $param_hash->{ $param_name };
632  $analysis->parameters( stringify($param_hash) );
633  $need_write = 1;
634  } else {
635  if(exists($param_hash->{ $param_name })) {
636  print "Tweak.Changing\tanalysis[$analysis_name].param[$param_name] ::\t".stringify($param_hash->{ $param_name })." --> $new_value_str\n";
637  } else {
638  print "Tweak.Adding \tanalysis[$analysis_name].param[$param_name] ::\t(missing value) --> $new_value_str\n";
639  }
640 
641  $param_hash->{ $param_name } = $new_value;
642  $analysis->parameters( stringify($param_hash) );
643  $need_write = 1;
644  }
645  }
646 
647  } elsif($tweak=~/^analysis\[([^\]]+)\]\.(wait_for|flow_into)(\?|#|\+?=(.+))$/) {
648  my ($analyses_pattern, $attrib_name, $operation, $new_value_str) = ($1, $2, $3, $4);
649  $operation=~/^(\?|#|\+?=)/;
650  my $operator = $1;
651 
652  my $analyses = $self->collection_of( 'Analysis' )->find_all_by_pattern( $analyses_pattern );
653  print "Tweak.Found \t".scalar(@$analyses)." analyses matching the pattern '$analyses_pattern'\n";
654 
655  my $new_value = destringify( $new_value_str );
656 
657  foreach my $analysis (@$analyses) {
658 
659  my $analysis_name = $analysis->logic_name;
660 
661  if( $attrib_name eq 'wait_for' ) {
662 
663  my $cr_collection = $self->collection_of( 'AnalysisCtrlRule' );
664  my $acr_collection = $analysis->control_rules_collection;
665 
666  if($operator eq '?') {
667  print "Tweak.Show \tanalysis[$analysis_name].wait_for ::\t[".join(', ', map { $_->condition_analysis_url } @$acr_collection )."]\n";
668  }
669 
670  if($operator eq '#' or $operator eq '=') { # delete the existing rules
671  foreach my $c_rule ( @$acr_collection ) {
672  $cr_collection->forget_and_mark_for_deletion( $c_rule );
673  $need_write = 1;
674 
675  print "Tweak.Deleting\t".$c_rule->toString." --> (missing value)\n";
676  }
677  }
678 
679  if($operator eq '=' or $operator eq '+=') { # create new rules
680  Bio::EnsEMBL::Hive::Utils::PCL::parse_wait_for($self, $analysis, $new_value);
681  foreach my $c_rule ( @{$analysis->control_rules_collection} ) {
682  print "Tweak.Adding\t".$c_rule->toString."\n";
683  }
684  $need_write = 1;
685  }
686 
687  } elsif( $attrib_name eq 'flow_into' ) {
688 
689  if($operator eq '?') {
690  # FIXME: should not recurse
691  $analysis->print_diagram_node($self, '', {});
692  }
693 
694  if($operator eq '#' or $operator eq '=') { # delete the existing rules
695  my $dfr_collection = $self->collection_of( 'DataflowRule' );
696  my $dft_collection = $self->collection_of( 'DataflowTarget' );
697 
698  foreach my $group ( @{$analysis->get_grouped_dataflow_rules} ) {
699  my ($funnel_dfr, $fan_dfrs, $funnel_df_targets) = @$group;
700 
701  foreach my $df_rule (@$fan_dfrs, $funnel_dfr) {
702 
703  foreach my $df_target ( @{$df_rule->get_my_targets} ) {
704  $dft_collection->forget_and_mark_for_deletion( $df_target );
705 
706  print "Tweak.Deleting\t".$df_target->toString." --> (missing value)\n";
707  }
708  $dfr_collection->forget_and_mark_for_deletion( $df_rule );
709  $need_write = 1;
710 
711  print "Tweak.Deleting\t".$df_rule->toString." --> (missing value)\n";
712  }
713  }
714  }
715 
716  if($operator eq '=' or $operator eq '+=') { # create new rules
717  $need_write = 1;
718  Bio::EnsEMBL::Hive::Utils::PCL::parse_flow_into($self, $analysis, $new_value );
719  }
720  }
721  }
722 
723  } elsif($tweak=~/^analysis\[([^\]]+)\]\.(\w+)(\?|#|=(.+))$/) {
724  my ($analyses_pattern, $attrib_name, $operator, $new_value_str) = ($1, $2, $3, $4);
725 
726  my $analyses = $self->collection_of( 'Analysis' )->find_all_by_pattern( $analyses_pattern );
727  print "Tweak.Found \t".scalar(@$analyses)." analyses matching the pattern '$analyses_pattern'\n";
728 
729  my $new_value = destringify( $new_value_str );
730 
731  foreach my $analysis (@$analyses) {
732 
733  my $analysis_name = $analysis->logic_name;
734 
735  if( $attrib_name eq 'resource_class' ) {
736 
737  if($operator eq '?') {
738  my $old_value = $analysis->resource_class;
739  print "Tweak.Show \tanalysis[$analysis_name].resource_class ::\t".$old_value->name."\n";
740  } elsif($operator eq '#') {
741  print "Tweak.Error \tDeleting of an Analysis' resource-class is not supported\n";
742  } else {
743 
744  my $old_value = $analysis->resource_class;
745  print "Tweak.Changing\tanalysis[$analysis_name].resource_class ::\t".$old_value->name." --> $new_value_str\n";
746 
747  my $resource_class;
748  if($resource_class = $self->collection_of( 'ResourceClass' )->find_one_by( 'name', $new_value )) {
749  print "Tweak.Found \tresource_class[$new_value_str]\n";
750  $analysis->resource_class( $resource_class );
751  $need_write = 1;
752  } else {
753  print "Tweak.Error \t'$new_value_str' is not a known resource-class\n";
754 
755  }
756  }
757 
758  } elsif( $attrib_name eq 'is_excluded' ) {
759  my $analysis_stats = $analysis->stats();
760  if($operator eq '?') {
761  print "Tweak.Show \tanalysis[$analysis_name].is_excluded ::\t".$analysis_stats->is_excluded()."\n";
762  } elsif($operator eq '#') {
763  print "Tweak.Error \tDeleting of excluded status is not supported\n";
764  } else {
765  if(!($new_value =~ /^[01]$/)) {
766  print "Tweak.Error \tis_excluded can only be 0 (no) or 1 (yes)\n";
767  } elsif ($new_value == $analysis_stats->is_excluded()) {
768  print "Tweak.Info \tanalysis[$analysis_name].is_excluded is already $new_value, leaving as is\n";
769  } else {
770  print "Tweak.Changing\tanalysis[$analysis_name].is_excluded ::\t" .
771  $analysis_stats->is_excluded() . " --> $new_value_str\n";
772  $analysis_stats->is_excluded($new_value);
773  $need_write = 1;
774  }
775  }
776  } elsif( $attrib_name eq 'dbID' ) {
777  print "Tweak.Error \tChanging the dbID of an Analysis is not supported\n";
778 
779  } elsif($analysis->can($attrib_name)) {
780  my $old_value = stringify($analysis->$attrib_name());
781 
782  if($operator eq '?') {
783  print "Tweak.Show \tanalysis[$analysis_name].$attrib_name ::\t$old_value\n";
784  } elsif($operator eq '#') {
785  print "Tweak.Error \tDeleting of Analysis attributes is not supported\n";
786  } else {
787  print "Tweak.Changing\tanalysis[$analysis_name].$attrib_name ::\t$old_value --> ".stringify($new_value)."\n";
788 
789  $analysis->$attrib_name( $new_value );
790  $need_write = 1;
791  }
792  } else {
793  print "Tweak.Error \tAnalysis does not support '$attrib_name' attribute\n";
794  }
795  }
796 
797  } elsif($tweak=~/^resource_class\[([^\]]+)\]\.(\w+)(\?|=(.+))$/) {
798  my ($rc_pattern, $meadow_type, $operator, $new_value_str) = ($1, $2, $3, $4);
799 
800  my $resource_classes = $self->collection_of( 'ResourceClass' )->find_all_by_pattern( $rc_pattern );
801  print "Tweak.Found \t".scalar(@$resource_classes)." resource_classes matching the pattern '$rc_pattern'\n";
802 
803  if($operator eq '?') {
804  foreach my $rc (@$resource_classes) {
805  my $rc_name = $rc->name;
806 
807  if(my $rd = $self->collection_of( 'ResourceDescription' )->find_one_by('resource_class', $rc, 'meadow_type', $meadow_type)) {
808  my ($submission_cmd_args, $worker_cmd_args) = ($rd->submission_cmd_args, $rd->worker_cmd_args);
809  print "Tweak.Show \tresource_class[$rc_name].$meadow_type ::\t".stringify([$submission_cmd_args, $worker_cmd_args])."\n";
810  } else {
811  print "Tweak.Show \tresource_class[$rc_name].$meadow_type ::\t(missing values)\n";
812  }
813  }
814 
815  } else {
816 
817  # Auto-vivification of the ResourceClass
818  unless (@$resource_classes) {
819  print "Tweak.Adding \tresource_class[$rc_pattern]\n";
820  my ($resource_class) = $self->add_new_or_update( 'ResourceClass', # NB: add_new_or_update returns a list
821  'name' => $rc_pattern,
822  );
823  push @$resource_classes, $resource_class;
824  $need_write = 1;
825  }
826 
827  my $new_value = destringify( $new_value_str );
828  my ($new_submission_cmd_args, $new_worker_cmd_args) = (ref($new_value) eq 'ARRAY') ? @$new_value : ($new_value, '');
829 
830  foreach my $rc (@$resource_classes) {
831  my $rc_name = $rc->name;
832 
833  if(my $rd = $self->collection_of( 'ResourceDescription' )->find_one_by('resource_class', $rc, 'meadow_type', $meadow_type)) {
834  my ($submission_cmd_args, $worker_cmd_args) = ($rd->submission_cmd_args, $rd->worker_cmd_args);
835  print "Tweak.Changing\tresource_class[$rc_name].$meadow_type :: "
836  .stringify([$submission_cmd_args, $worker_cmd_args])." --> "
837  .stringify([$new_submission_cmd_args, $new_worker_cmd_args])."\n";
838 
839  $rd->submission_cmd_args( $new_submission_cmd_args );
840  $rd->worker_cmd_args( $new_worker_cmd_args );
841  } else {
842  print "Tweak.Adding \tresource_class[$rc_name].$meadow_type :: (missing values) --> "
843  .stringify([$new_submission_cmd_args, $new_worker_cmd_args])."\n";
844 
845  my ($rd) = $self->add_new_or_update( 'ResourceDescription', # NB: add_new_or_update returns a list
846  'resource_class' => $rc,
847  'meadow_type' => $meadow_type,
848  'submission_cmd_args' => $new_submission_cmd_args,
849  'worker_cmd_args' => $new_worker_cmd_args,
850  );
851  }
852  $need_write = 1;
853  }
854  }
855 
856  } else {
857  print "Tweak.Error \tFailed to parse the tweak\n";
858  }
859  }
860  return $need_write;
861 }
862 
863 1;