<commit_before>## OpenXPKI::Workflow::Config
##
package OpenXPKI::Workflow::Config;

use strict;
use warnings;

use Workflow 1.39;
use OpenXPKI::Exception;
use OpenXPKI::Debug;
use OpenXPKI::Server::Context qw( CTX );
use Data::Dumper;

use Moose;

has '_workflow_config' => (
    is => 'rw',
    isa => 'HashRef',
    required => 0,
    reader => 'workflow_config',
    builder => '_build_workflow_config'
);

has '_config' => (
    is => 'rw',
    isa => 'Object',
    required => 0,
    lazy => 1, # This is fundamental! (race condition within loader -> empty ref)
    default => sub { return CTX('config'); }
);

has 'logger' => (
    is => 'ro',
    isa => 'Object',
    required => 0,
    lazy => 1,
    default => sub { return CTX('log')->workflow(); }
);

sub _build_workflow_config {

    my $self = shift;

    my $conn = $self->_config();

    # Fetch serialized workflow definition from config layer
    if (!$conn->exists('workflow.def')) {
        my $pki_realm = CTX('session')->data->pki_realm;
        OpenXPKI::Exception->throw(
            message => 'No workflow configuration found for current realm',
            params => {
                realm => $pki_realm,
                config_path => "realm.$pki_realm.workflow.def",
            },
        );
    }

    # Init structure
    $self->_workflow_config({ condition => [], validator => [], action =>[], workflow => [], persister => [] });

    # Add persisters
    my @persister = $conn->get_keys('workflow.persister');
    foreach my $persister (@persister) {
        my $conf = $conn->get_hash(['workflow','persister', $persister]);
        $conf->{name} = $persister;
        push @{$self->_workflow_config()->{persister}}, $conf;
    }

    # Add workflows
    my @workflow_def = $conn->get_keys('workflow.def');
    foreach my $wf_name (@workflow_def) {
        # The main workflow definiton (the flow rules)
        $self->__process_workflow($wf_name);

        # Add workflow defined actions
        my @action_names = $conn->get_keys(['workflow','def', $wf_name, 'action']);
        foreach my $action_name (@action_names) {
            $self->__process_action(['workflow','def', $wf_name, 'action', $action_name ]);
        }

        # Add workflow defined conditions
        my @condition_names = $conn->get_keys(['workflow','def', $wf_name, 'condition']);
        foreach my $condition_name (@condition_names) {
            $self->__process_condition(['workflow','def', $wf_name, 'condition', $condition_name ]);
        }

        # Add workflow defined validators
        my @validator_names = $conn->get_keys(['workflow','def', $wf_name, 'validator']);
        foreach my $validator_name (@validator_names) {
            $self->__process_validator(['workflow','def', $wf_name, 'validator', $validator_name ]);
        }

    }

    # Finally add the global actions
    my @action_names = $conn->get_keys(['workflow','global', 'action']);
    foreach my $action_name (@action_names) {
        $self->__process_action(['workflow','global', 'action', $action_name]);
    }

    # And conditions
    my @condition_names = $conn->get_keys(['workflow','global', 'condition']);
    foreach my $condition_name (@condition_names) {
        $self->__process_condition(['workflow','global', 'condition', $condition_name ]);
    }

    # Validators
    my @validator_names = $conn->get_keys(['workflow','global', 'validator']);
    foreach my $validator_name (@validator_names) {
        $self->__process_validator(['workflow','global', 'validator', $validator_name ]);
    }

    push @{$self->_workflow_config()->{validator}}, {
        name => '_internal_basic_field_type',
        class => 'OpenXPKI::Server::Workflow::Validator::BasicFieldType'
    };


    foreach my $class (('action','condition','validator')) {
        my %defined;
        foreach my $item (@{$self->_workflow_config()->{$class}}) {
            my $name = $item->{name};
            OpenXPKI::Exception->throw(
                message => 'Item name defined twice - unable to load workflow configuration',
                params => { name => $name, class => $class }
            ) if (defined $defined{$name});
            $defined{$name} = 1;
        }
    }

    return $self->_workflow_config

}

sub __process_workflow {

    my $self = shift;
    my $wf_name = shift;

    my $workflow = {
        type => $wf_name,
        persister => 'OpenXPKI',
        observer => [
            { class => 'OpenXPKI::Server::Workflow::Observer::AddExecuteHistory' },
            { class => 'OpenXPKI::Server::Workflow::Observer::Log' },
        ],
        state => []
    };

    my $conn = $self->_config();

    my $wf_persister = $conn->get( [ 'workflow', 'def', $wf_name , 'head', 'persister' ] );
    if ($wf_persister) {
        $workflow->{persister}  = $wf_persister;
    }

    my $wf_prefix = $conn->get( [ 'workflow', 'def', $wf_name , 'head', 'prefix' ] );


    if ($wf_prefix =~ /[^a-z0-9]/) {
        OpenXPKI::Exception->throw(
            message => 'Workflow prefix must not contain other chars as a-z and 0-9',
            params => {
                name => $wf_name,
                prefix => $wf_prefix
            }
        );
    }

    my @states = $conn->get_keys( ['workflow', 'def', $wf_name, 'state' ] );

    # The FAILURE state is required for the autofail feature
    if (!grep /FAILURE/, @states) {
        push @states, 'FAILURE';
    }

    foreach my $state_name (@states) {

        # We are not interessted in eye candy, just the logic
        # action attribute has a list/scalar with a combo string
        # left hand is action name, right hand the target state<commit_msg>Enforce prefix in workflow definition<commit_after>## OpenXPKI::Workflow::Config
##
package OpenXPKI::Workflow::Config;

use strict;
use warnings;

use Workflow 1.39;
use OpenXPKI::Exception;
use OpenXPKI::Debug;
use OpenXPKI::Server::Context qw( CTX );
use Data::Dumper;

use Moose;

has '_workflow_config' => (
    is => 'rw',
    isa => 'HashRef',
    required => 0,
    reader => 'workflow_config',
    builder => '_build_workflow_config'
);

has '_config' => (
    is => 'rw',
    isa => 'Object',
    required => 0,
    lazy => 1, # This is fundamental! (race condition within loader -> empty ref)
    default => sub { return CTX('config'); }
);

has 'logger' => (
    is => 'ro',
    isa => 'Object',
    required => 0,
    lazy => 1,
    default => sub { return CTX('log')->workflow(); }
);

sub _build_workflow_config {

    my $self = shift;

    my $conn = $self->_config();

    # Fetch serialized workflow definition from config layer
    if (!$conn->exists('workflow.def')) {
        my $pki_realm = CTX('session')->data->pki_realm;
        OpenXPKI::Exception->throw(
            message => 'No workflow configuration found for current realm',
            params => {
                realm => $pki_realm,
                config_path => "realm.$pki_realm.workflow.def",
            },
        );
    }

    # Init structure
    $self->_workflow_config({ condition => [], validator => [], action =>[], workflow => [], persister => [] });

    # Add persisters
    my @persister = $conn->get_keys('workflow.persister');
    foreach my $persister (@persister) {
        my $conf = $conn->get_hash(['workflow','persister', $persister]);
        $conf->{name} = $persister;
        push @{$self->_workflow_config()->{persister}}, $conf;
    }

    # Add workflows
    my @workflow_def = $conn->get_keys('workflow.def');
    foreach my $wf_name (@workflow_def) {
        # The main workflow definiton (the flow rules)
        $self->__process_workflow($wf_name);

        # Add workflow defined actions
        my @action_names = $conn->get_keys(['workflow','def', $wf_name, 'action']);
        foreach my $action_name (@action_names) {
            $self->__process_action(['workflow','def', $wf_name, 'action', $action_name ]);
        }

        # Add workflow defined conditions
        my @condition_names = $conn->get_keys(['workflow','def', $wf_name, 'condition']);
        foreach my $condition_name (@condition_names) {
            $self->__process_condition(['workflow','def', $wf_name, 'condition', $condition_name ]);
        }

        # Add workflow defined validators
        my @validator_names = $conn->get_keys(['workflow','def', $wf_name, 'validator']);
        foreach my $validator_name (@validator_names) {
            $self->__process_validator(['workflow','def', $wf_name, 'validator', $validator_name ]);
        }

    }

    # Finally add the global actions
    my @action_names = $conn->get_keys(['workflow','global', 'action']);
    foreach my $action_name (@action_names) {
        $self->__process_action(['workflow','global', 'action', $action_name]);
    }

    # And conditions
    my @condition_names = $conn->get_keys(['workflow','global', 'condition']);
    foreach my $condition_name (@condition_names) {
        $self->__process_condition(['workflow','global', 'condition', $condition_name ]);
    }

    # Validators
    my @validator_names = $conn->get_keys(['workflow','global', 'validator']);
    foreach my $validator_name (@validator_names) {
        $self->__process_validator(['workflow','global', 'validator', $validator_name ]);
    }

    push @{$self->_workflow_config()->{validator}}, {
        name => '_internal_basic_field_type',
        class => 'OpenXPKI::Server::Workflow::Validator::BasicFieldType'
    };


    foreach my $class (('action','condition','validator')) {
        my %defined;
        foreach my $item (@{$self->_workflow_config()->{$class}}) {
            my $name = $item->{name};
            OpenXPKI::Exception->throw(
                message => 'Item name defined twice - unable to load workflow configuration',
                params => { name => $name, class => $class }
            ) if (defined $defined{$name});
            $defined{$name} = 1;
        }
    }

    return $self->_workflow_config

}

sub __process_workflow {

    my $self = shift;
    my $wf_name = shift;

    my $workflow = {
        type => $wf_name,
        persister => 'OpenXPKI',
        observer => [
            { class => 'OpenXPKI::Server::Workflow::Observer::AddExecuteHistory' },
            { class => 'OpenXPKI::Server::Workflow::Observer::Log' },
        ],
        state => []
    };

    my $conn = $self->_config();

    my $wf_persister = $conn->get( [ 'workflow', 'def', $wf_name , 'head', 'persister' ] );
    if ($wf_persister) {
        $workflow->{persister}  = $wf_persister;
    }

    my $wf_prefix = $conn->get( [ 'workflow', 'def', $wf_name , 'head', 'prefix' ] );

    if (!$wf_prefix || ($wf_prefix =~ /[^a-z0-9]/)) {
        OpenXPKI::Exception->throw(
            message => 'Workflow prefix must be set and contain no other chars as a-z and 0-9',
            params => {
                name => $wf_name,
                prefix => $wf_prefix
            }
        );
    }

    my @states = $conn->get_keys( ['workflow', 'def', $wf_name, 'state' ] );

    # The FAILURE state is required for the autofail feature
    if (!grep /FAILURE/, @states) {
        push @states, 'FAILURE';
    }

    foreach my $state_name (@states) {

        # We are not interessted in eye candy, just the logic
        # action attribute has a list/scalar with a combo string
        # left hand is action name, right hand the target state
{'fraction_non_alphanumeric': 0.16303200844178684, 'fraction_numerical': 0.0022863172704889203, 'mean_word_length': 2.7448139611458675, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 169, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6069040', 'n_tokens_mistral': 3567, 'n_tokens_neox': 3450, 'n_words': 813}