Skip Menu |

Preferred bug tracker

Please visit the preferred bug tracker to report your issue.

This queue is for tickets about the Workflow CPAN distribution.

Report information
The Basics
Id: 18265
Status: resolved
Priority: 0/
Queue: Workflow

People
Owner: jonasbn [...] cpan.org
Requestors: andrewo [...] oriel.com.au
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in: (no value)
Fixed in:
  • 1.33_2
  • 1.33



Subject: [PATCH] enable dynamic loading of config files
I was unable to subclass Workflow::Factory to allow me to dynamically load configs at runtime so here's a patch to the core that allows just that. I've added a new section to the docs and added an _initialize_workflow_config function which calls a user-specified callback located in the factory accessor "get_workflow_config_func". Example usage in the pod. I haven't added full details to the function list nor have I added an external "initialize_workflow_config" wrapper as I'm not sure if this is useful or wanted. And no, there is no test for this yet :) Comments, flames and requests for changes welcome. Thanks for the module!
Subject: dynamically_loaded_configs.0.17.diff
diff -rBbu Workflow-0.17.orig/lib/Workflow/Factory.pm Workflow-0.17/lib/Workflow/Factory.pm --- Workflow-0.17.orig/lib/Workflow/Factory.pm 2006-03-16 13:03:23.000000000 +1100 +++ Workflow-0.17/lib/Workflow/Factory.pm 2006-03-21 18:56:49.000000000 +1100 @@ -7,6 +7,7 @@ use DateTime; use Log::Log4perl qw( get_logger ); use Workflow::Exception qw( configuration_error workflow_error ); +use Oriel::WF::Config (); $Workflow::Factory::VERSION = sprintf("%d.%02d", q$Revision: 1.16 $ =~ /(\d+)\.(\d+)/); @@ -51,7 +52,7 @@ my $INITIAL_STATE = 'INITIAL'; -my @FIELDS = qw(); +my @FIELDS = qw(get_workflow_config_func); __PACKAGE__->mk_accessors( @FIELDS ); sub new { @@ -80,6 +81,30 @@ my %CONFIG = ( 'Workflow::Config' => 1 ); +#################################### +#### Oriel functions start here #### +#################################### + +sub _dump_instances { + my $class = shift; + $class = ref($class) || $class; + require Data::Dumper; + warn Data::Dumper::Dumper( $class, \%INSTANCES ); +} +sub _initialize_workflow_config { + my $self = shift; + my $wf_type = shift; + $log ||= get_logger(); + if ( ref($self->get_workflow_config_func) eq 'CODE' ) { + my $args = &{ $self->get_workflow_config_func }( $wf_type ); + $self->add_config_from_file( %$args ) if $args && %$args; + } +} + +################################## +#### Oriel functions end here #### +################################## + sub add_config_from_file { my ( $self, %params ) = @_; return unless ( scalar keys %params ); @@ -321,6 +346,7 @@ sub _get_workflow_config { my ( $self, $wf_type ) = @_; + $self->_initialize_workflow_config( $wf_type ) unless $self->{_workflow_config}{ $wf_type }; return $self->{_workflow_config}{ $wf_type }; } @@ -803,6 +829,39 @@ Returns: nothing +=head1 DYNAMIC CONFIG LOADING + +If you have either a large set of config files or a set of large +config files then you may not want to incur the overhead of loading +each and every one on startup if you cannot predict which set you will +use in that instance of your application. + +To combat this you can specify a callback that the factory will use to +retrieve batched hashes of config_declarations. Whenever an unknown +workflow name is encountered the factory will first try to load your +config declarations then continue. + +The callback takes one argument which is the workflow type. It should +return a reference to a hash of arguments in a form suitable for +C<add_config_from_file>. + +For example: + + use Workflow::Factory qw(FACTORY); + use My::Config::System; + + sub my_application_init { + my $self = shift; + + FACTORY->get_workflow_config_func( + sub { + my $wf_type = shift; + my %ret = My::Config::System->get_files_for_wf( $wf_type ) || (); + return \%ret; + } + ); + } + =head1 SUBCLASSING =head2 Implementation and Usage
From: jonasbn [...] cpan.org
On Tue Mar 21 03:11:58 2006, guest wrote: Show quoted text
> I was unable to subclass Workflow::Factory to allow me to dynamically > load configs at runtime so here's a patch to the core that allows just that. > > I've added a new section to the docs and added an > _initialize_workflow_config function which calls a user-specified > callback located in the factory accessor "get_workflow_config_func". > Example usage in the pod. > > I haven't added full details to the function list nor have I added an > external "initialize_workflow_config" wrapper as I'm not sure if this is > useful or wanted. And no, there is no test for this yet :) > > Comments, flames and requests for changes welcome. > > Thanks for the module!
Hello, I am very keen on the feature, but do you have some tests to accompagny this? jonasbn
See attached for an updated patch against 1.32. This new patch includes documentation and tests but does not modify the Changes file. Hope this is helpful!
diff -rNu Workflow-1.32.orig/MANIFEST Workflow-1.32/MANIFEST --- Workflow-1.32.orig/MANIFEST 2009-01-27 02:07:31.000000000 +1100 +++ Workflow-1.32/MANIFEST 2009-01-28 18:50:30.000000000 +1100 @@ -92,6 +92,7 @@ t/context.t t/exception.t t/factory.t +t/factory_callback_config.t t/factory_subclass.t t/FactorySubclass.pm t/history.t @@ -112,6 +113,7 @@ t/TestApp/Action/TicketCreate.pm t/TestApp/Action/TicketCreateType.pm t/TestApp/Action/TicketUpdate.pm +t/TestApp/Condition/AlwaysTrue.pm t/TestApp/Condition/HasUser.pm t/TestApp/Condition/HasUserType.pm t/TestApp/Ticket.pm @@ -129,14 +131,17 @@ t/workflow.xml t/workflow_action.perl t/workflow_action.xml +t/workflow_action_callback.xml t/workflow_action_type.perl t/workflow_action_type.xml t/workflow_autorun.xml t/workflow_cached_condition.xml t/workflow_cached_condition_action.xml t/workflow_cached_condition_condition.xml +t/workflow_callback.xml t/workflow_condition.perl t/workflow_condition.xml +t/workflow_condition_callback.xml t/workflow_condition_type.perl t/workflow_condition_type.xml t/workflow_errorprone.perl diff -rNu Workflow-1.32.orig/lib/Workflow/Factory.pm Workflow-1.32/lib/Workflow/Factory.pm --- Workflow-1.32.orig/lib/Workflow/Factory.pm 2009-01-27 02:07:31.000000000 +1100 +++ Workflow-1.32/lib/Workflow/Factory.pm 2009-01-28 17:18:42.000000000 +1100 @@ -56,7 +56,7 @@ my $INITIAL_STATE = 'INITIAL'; -my @FIELDS = qw(); +my @FIELDS = qw(config_callback); __PACKAGE__->mk_accessors(@FIELDS); sub new { @@ -405,8 +405,19 @@ $wf->add_observer($_) for ( @{$observers} ); } +sub _initialize_workflow_config { + my $self = shift; + my $wf_type = shift; + $log ||= get_logger(); + if ( ref($self->config_callback) eq 'CODE' ) { + my $args = &{ $self->config_callback }( $wf_type ); + $self->add_config_from_file( %$args ) if $args && %$args; + } +} + sub _get_workflow_config { my ( $self, $wf_type ) = @_; + $self->_initialize_workflow_config( $wf_type ) unless $self->{_workflow_config}{ $wf_type }; return $self->{_workflow_config}{$wf_type}; } @@ -1001,6 +1012,44 @@ L</instance> should be called or the imported 'FACTORY' should be utilized. +=head1 DYNAMIC CONFIG LOADING + +If you have either a large set of config files or a set of very large +config files then you may not want to incur the overhead of loading +each and every one on startup if you cannot predict which set you will +use in that instance of your application. + +This approach doesn't make much sense in a persistent environment such +as mod_perl but it may lower startup costs if you have regularly +scheduled scripts that may not need to touch all possible types of +workflow. + +To do this you can specify a callback that the factory will use to +retrieve batched hashes of config declarations. Whenever an unknown +workflow name is encountered the factory will first try to load your +config declarations then continue. + +The callback takes one argument which is the workflow type. It should +return a reference to a hash of arguments in a form suitable for +C<add_config_from_file>. + +For example: + + use Workflow::Factory qw(FACTORY); + use My::Config::System; + + sub init { + my $self = shift; + + FACTORY->config_callback( + sub { + my $wf_type = shift; + my %ret = My::Config::System->get_files_for_wf( $wf_type ) || (); + return \%ret; + } + ); + } + =head1 SUBCLASSING =head2 Implementation and Usage diff -rNu Workflow-1.32.orig/t/TestApp/Condition/AlwaysTrue.pm Workflow-1.32/t/TestApp/Condition/AlwaysTrue.pm --- Workflow-1.32.orig/t/TestApp/Condition/AlwaysTrue.pm 1970-01-01 10:00:00.000000000 +1000 +++ Workflow-1.32/t/TestApp/Condition/AlwaysTrue.pm 2009-01-28 17:41:33.000000000 +1100 @@ -0,0 +1,19 @@ +package TestApp::Condition::AlwaysTrue; + +# $Id$ + +use strict; +use base qw( Workflow::Condition ); +use Log::Log4perl qw( get_logger ); +use Workflow::Exception qw( condition_error ); + +$TestApp::Condition::AlwaysTrue::VERSION = '0.01'; + +sub evaluate { + my ( $self, $wf ) = @_; + my $log = get_logger(); + $log->debug( "Trying to execute condition ", ref( $self ) ); + $log->debug( 'Condition met ok' ); +} + +1; diff -rNu Workflow-1.32.orig/t/factory_callback_config.t Workflow-1.32/t/factory_callback_config.t --- Workflow-1.32.orig/t/factory_callback_config.t 1970-01-01 10:00:00.000000000 +1000 +++ Workflow-1.32/t/factory_callback_config.t 2009-01-29 09:39:39.000000000 +1100 @@ -0,0 +1,39 @@ +# -*-perl-*- + +# $Id$ + +use strict; +use lib 't'; +use TestUtil; +use Test::More tests => 7; +use Test::Exception; + +require_ok( 'Workflow::Factory' ); + +my $factory = Workflow::Factory->instance(); +my $wf; + +lives_ok { TestUtil->init_mock_persister } + 'Loading test persister succeeds'; + +dies_ok { $wf = $factory->create_workflow( 'CallbackTest' ) } + 'Attempt to create new workflow without loading its config first dies'; + +can_ok( $factory, 'config_callback' ); + +lives_ok { $factory->config_callback( sub { + my $type = shift; + if ($type eq 'CallbackTest') { + return { workflow => 'workflow_callback.xml', + action => 'workflow_action_callback.xml', + condition => 'workflow_condition_callback.xml' }; + } + return {}; + } ) } + 'Setting callback function succeeds'; + +dies_ok { $wf = $factory->create_workflow( 'CallbackTestBogus' ) } + 'Attempt to create unknown workflow still dies'; + +lives_ok { $wf = $factory->create_workflow( 'CallbackTest' ) } + 'Attempt to create known workflow via callback succeeds'; diff -rNu Workflow-1.32.orig/t/workflow_action_callback.xml Workflow-1.32/t/workflow_action_callback.xml --- Workflow-1.32.orig/t/workflow_action_callback.xml 1970-01-01 10:00:00.000000000 +1000 +++ Workflow-1.32/t/workflow_action_callback.xml 2009-01-28 18:18:45.000000000 +1100 @@ -0,0 +1,8 @@ +<actions> + <type>CallbackTest</type> + <description>Actions for the CallbackTest workflow only</description> + + <action name="CALLBACK_CLOSE" class="Workflow::Action::Null"> + <description>Dummy close a callback test workflow</description> + </action> +</actions> \ No newline at end of file diff -rNu Workflow-1.32.orig/t/workflow_callback.xml Workflow-1.32/t/workflow_callback.xml --- Workflow-1.32.orig/t/workflow_callback.xml 1970-01-01 10:00:00.000000000 +1000 +++ Workflow-1.32/t/workflow_callback.xml 2009-01-29 09:40:17.000000000 +1100 @@ -0,0 +1,17 @@ +<workflow> + <type>CallbackTest</type> + <description>This is a sample workflow used to test loading config on demand</description> + <persister>TestPersister</persister> + <state name="INITIAL"> + <description>This is the state the workflow enters when + instantiated. It's like a 'state zero' but since we're + using names rather than IDs we cannot assume</description> + <action name="CALLBACK_CLOSE" resulting_state="Callback_Closed"> + <condition name="CallbackAlwaysTrue"/> + </action> + </state> + + <state name="Callback_Closed"> + <description>Placeholder closed state</description> + </state> +</workflow> diff -rNu Workflow-1.32.orig/t/workflow_condition_callback.xml Workflow-1.32/t/workflow_condition_callback.xml --- Workflow-1.32.orig/t/workflow_condition_callback.xml 1970-01-01 10:00:00.000000000 +1000 +++ Workflow-1.32/t/workflow_condition_callback.xml 2009-01-28 17:38:24.000000000 +1100 @@ -0,0 +1,4 @@ +<conditions> + <type>CallbackTest</type> + <condition name="CallbackAlwaysTrue" class="TestApp::Condition::AlwaysTrue"/> +</conditions>
This patch is currently under evaluation, please see release 1.33_2 jonasbn