package NGCP::Panel::Utils::Journal;
use strict;
use warnings;

use Sipwise::Base;
use DBIx::Class::Exception;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::UserRole;
use JSON;
use HTTP::Status qw(:constants);
use TryCatch;
use boolean qw(true);
use Data::HAL qw();
use Data::HAL::Link qw();
use Scalar::Util 'blessed';
use Storable;
use IO::Compress::Deflate qw($DeflateError);
use IO::Uncompress::Inflate qw();
use Sereal::Decoder qw();
use Sereal::Encoder qw();
use NGCP::Panel::Utils::UserRole;

use constant CREATE_JOURNAL_OP => 'create';
use constant UPDATE_JOURNAL_OP => 'update';
use constant DELETE_JOURNAL_OP => 'delete';
use constant _JOURNAL_OPS => { CREATE_JOURNAL_OP.'' => 1, UPDATE_JOURNAL_OP.'' => 1, DELETE_JOURNAL_OP.'' => 1 };

use constant OPERATION_DEFAULT_ENABLED => 0;

use constant API_JOURNAL_RESOURCE_NAME => 'journal';
use constant API_JOURNALITEMTOP_RESOURCE_NAME => 'recent'; #empty to disable
use constant JOURNAL_RESOURCE_DEFAULT_ENABLED => 0;

use constant CONTENT_JSON_FORMAT => 'json';
use constant CONTENT_STORABLE_FORMAT => 'storable';
use constant CONTENT_JSON_DEFLATE_FORMAT => 'json_deflate';
use constant CONTENT_SEREAL_FORMAT => 'sereal';
use constant _CONTENT_FORMATS => { CONTENT_JSON_FORMAT.'' => 1, CONTENT_STORABLE_FORMAT.'' => 1, CONTENT_JSON_DEFLATE_FORMAT.'' => 1, CONTENT_SEREAL_FORMAT.'' => 1 };

use constant CONTENT_DEFAULT_FORMAT => CONTENT_JSON_FORMAT;

use constant API_JOURNAL_RELATION => 'ngcp:'.API_JOURNAL_RESOURCE_NAME;
#use constant API_JOURNALITEM_RELATION => 'ngcp:journalitem';

use constant JOURNAL_FIELDS => ['id', 'operation', 'resource_name', 'resource_id', 'timestamp', 'username'];

sub add_journal_item_hal {
    my ($controller,$c,$operation,@args) = @_;
    my $cfg = _get_api_journal_op_config($c,$controller->resource_name,$operation);
    if ($cfg->{operation_enabled}) {
        my $arg = $args[0];
        my @hal_from_item = ();
        my $params;
        my $id;
        my $id_name = 'id';
        if (ref $arg eq 'HASH') {
            $params = $arg;
            my $h = (defined $params->{hal} ? $params->{hal} : $params->{hal_from_item});
            $h //= (defined $params->{resource} ? $params->{resource} : $params->{resource_from_item});
            if (defined $h) {
                if (ref $h eq 'ARRAY') {
                    @hal_from_item = @$h;
                } else { #if (not ref $h) {
                    @hal_from_item = ( $h );
                }
            }
            $id_name = $params->{id_name} if defined $params->{id_name};
            $id = $params->{id} if defined $params->{id};
        } elsif (ref $arg eq 'ARRAY') {
            $params = {};
            @hal_from_item = @$arg;
        } else {
            $params = {};
            @hal_from_item = @args;
        }
        my $code = shift @hal_from_item;
        unshift(@hal_from_item,$c);
        unshift(@hal_from_item,$controller);
        unshift(@hal_from_item,$code);
        $params->{hal_from_item} = \@hal_from_item;
        $params->{operation} = $operation;
        $params->{resource_name} //= $controller->resource_name;
        $params->{format} //= $cfg->{format};

        my $resource = shift @hal_from_item;
        my $hal;
        if (ref $resource eq 'CODE') {
            $hal = $resource->(@hal_from_item);
        } else {
            $hal = $resource;
        }
        if (ref $hal eq Data::HAL::) {
            $resource = $hal->resource;
        }
        if (!defined $id) {
            if (ref $resource eq 'HASH') {
                $id = $resource->{$id_name};
            } elsif ((defined blessed($resource)) && $resource->can($id_name)) {
                $id = $resource->$id_name;
            }
        }
        return _create_journal($controller,$c,$id,$resource,$params);
    }
    return 1;
}

sub get_api_journal_action_config {
    my ($path_part,$action_template,$journal_methods) = @_;
    my %journal_actions_found = map { $_ => 1 } @$journal_methods;
    if (exists $journal_actions_found{'handle_item_base_journal'}) {
        my @result = ();
        if (exists $journal_actions_found{'handle_journals_get'}) {
            my $action_config = Storable::dclone($action_template);
            $action_config->{Chained} = 'handle_item_base_journal';
            $action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
            $action_config->{Args} = 0;
            $action_config->{Method} = 'GET';
            push(@result,$action_config,'handle_journals_get');
        }
        if (exists $journal_actions_found{'handle_journals_options'}) {
            my $action_config = Storable::dclone($action_template);
            $action_config->{Chained} = 'handle_item_base_journal';
            $action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
            $action_config->{Args} = 0;
            $action_config->{Method} = 'OPTIONS';
            push(@result,$action_config,'handle_journals_options');
        }
        if (exists $journal_actions_found{'handle_journals_head'}) {
            my $action_config = Storable::dclone($action_template);
            $action_config->{Chained} = 'handle_item_base_journal';
            $action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
            $action_config->{Args} = 0;
            $action_config->{Method} = 'HEAD';
            push(@result,$action_config,'handle_journals_head');
        }
        if (exists $journal_actions_found{'handle_journalsitem_get'}) {
            my $action_config = Storable::dclone($action_template);
            $action_config->{Chained} = 'handle_item_base_journal';
            $action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
            $action_config->{Args} = 1;
            $action_config->{Method} = 'GET';
            push(@result,$action_config,'handle_journalsitem_get');
        }
        if (exists $journal_actions_found{'handle_journalsitem_options'}) {
            my $action_config = Storable::dclone($action_template);
            $action_config->{Chained} = 'handle_item_base_journal';
            $action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
            $action_config->{Args} = 1;
            $action_config->{Method} = 'OPTIONS';
            push(@result,$action_config,'handle_journalsitem_options');
        }
        if (exists $journal_actions_found{'handle_journalsitem_head'}) {
            my $action_config = Storable::dclone($action_template);
            $action_config->{Chained} = 'handle_item_base_journal';
            $action_config->{PathPart} //= API_JOURNAL_RESOURCE_NAME;
            $action_config->{Args} = 1;
            $action_config->{Method} = 'HEAD';
            push(@result,$action_config,'handle_journalsitem_head');
        }
        if ((scalar @result) > 0) {
            push(@result,{
                Chained => '/',
                PathPart => $path_part,
                CaptureArgs => 1,
            },'handle_item_base_journal');
            @result = reverse @result;
            return \@result;
        }
    }
    return [];
}

sub get_api_journal_query_params {
    my ($query_params) = @_;
    my @params = (defined $query_params ? @$query_params : ());
    push(@params,{
                param => 'operation',
                description => 'Filter for journal items by a specific CRUD operation ("create", "update" or "delete")',
                query => {
                    first => sub {
                        my $q = shift;
                        { 'operation' => $q };
                    },
                    second => sub { },
                },
            },{
                param => 'username',
                description => 'Filter for journal items by the name of the user who performed the operation',
                query => {
                    first => sub {
                        my $q = shift;
                        { 'username' => $q };
                    },
                    second => sub { },
                },
            },{
                param => 'from',
                description => 'Filter for journal items recorded after or at the specified time stamp',
                query => {
                    first => sub {
                        my $q = shift;
                        my $dt = NGCP::Panel::Utils::DateTime::from_string($q);
                        return { 'timestamp' => { '>=' => $dt->epoch  } };
                    },
                    second => sub { },
                },
            },{
                param => 'to',
                description => 'Filter for journal items recorded before or at the specified time stamp',
                query => {
                    first => sub {
                        my $q = shift;
                        my $dt = NGCP::Panel::Utils::DateTime::from_string($q);
                        return { 'timestamp' => { '<=' => $dt->epoch  } };
                    },
                    second => sub { },
                },
            });
    return \@params;
}

sub handle_api_item_base_journal {
    my ($controller,$c,$id) = @_;

    if ($c->user->id == $id || $c->user->is_system || $c->user->is_superuser) {
        $c->stash->{item_id_journal} = $id;
        return;
    }

    return;
}

sub handle_api_journals_get {
    my ($controller,$c) = @_;
    my $page = $c->request->params->{page} // 1;
    my $rows = $c->request->params->{rows} // 10;
    {
        my $item_id = $c->stash->{item_id_journal};
        last unless $controller->valid_id($c, $item_id);

        my $journals = get_journal_rs($controller,$c,$item_id);
        (my $total_count, $journals, my $journals_rows ) = $controller->paginate_order_collection($c,$journals);

        my (@embedded, @links);
        for my $journal(@$journals_rows) {
            my $hal = hal_from_journal($controller,$c,$journal);
            $hal->_forcearray(1);
            push @embedded,$hal;
            my $link = get_journal_relation_link($journal,$c,$item_id,$journal->id);
            $link->_forcearray(1);
            push @links,$link;
        }
        push @links,
            Data::HAL::Link->new(
                relation => 'curies',
                href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
                name => 'ngcp',
                templated => true,
            ),
            Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
            $controller->collection_nav_links($c, $page, $rows, $total_count, $c->request->path, $c->request->query_params);

        my $hal = Data::HAL->new(
            embedded => [@embedded],
            links => [@links],
        );
        $hal->resource({
            total_count => $total_count,
        });
        my $response = HTTP::Response->new(HTTP_OK, undef,
            HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json);
        $c->response->headers($response->headers);
        $c->response->body($response->content);
        return;

    }
    return;
}



sub handle_api_journalsitem_get {
    my ($controller,$c,$id) = @_;
    my $guard = $c->model('DB')->txn_scope_guard;
    {
        my $item_id = $c->stash->{item_id_journal};
        last unless $controller->valid_id($c, $item_id);
        my $journal = undef;
        if (API_JOURNALITEMTOP_RESOURCE_NAME and $id eq API_JOURNALITEMTOP_RESOURCE_NAME) {
            $journal = get_top_journalitem($controller,$c,$item_id);
        } elsif ($controller->valid_id($c, $id)) {
            $journal = get_journalitem($c,$id);
        } else {
            last;
        }

        last unless $controller->resource_exists($c, journal => $journal);

        if ($journal->resource_id != $item_id) {
            $c->log->error("Journal item '" . $id . "' does not belong to '" . $controller->resource_name . '/' . $item_id . "'");
            $controller->error($c, HTTP_NOT_FOUND, "Entity 'journal' not found.");
            last;
        }

        my $hal = hal_from_journal($controller,$c,$journal);
        $guard->commit;

        #my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
        #    (map { # XXX Data::HAL must be able to generate links with multiple relations
        #        s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|;
        #        s/rel=self/rel="item self"/;
        #        $_
        #    } $hal->http_headers),
        #), $hal->as_json);
        $c->response->headers(HTTP::Headers->new($hal->http_headers));
        $c->response->body($hal->as_json);
        return;
    }
    return;
}

sub handle_api_journals_options {
    my ($controller, $c, $id) = @_;
    my @allowed_methods = ('OPTIONS');
    my %journal_actions_found = map { $_ => 1 } @{ $controller->get_journal_methods };
    if (exists $journal_actions_found{'handle_journals_get'}) {
        push(@allowed_methods,'GET');
        if (exists $journal_actions_found{'handle_journals_head'}) {
            push(@allowed_methods,'HEAD');
        }
    }
    $c->response->headers(HTTP::Headers->new(
        Allow => join(', ',@allowed_methods),
    ));
    $c->response->content_type('application/json');
    $c->response->body(JSON::to_json({ methods => \@allowed_methods })."\n");
    return;
}

sub handle_api_journalsitem_options {
    my ($controller, $c, $id) = @_;
    my @allowed_methods = ('OPTIONS');
    my %journal_actions_found = map { $_ => 1 } @{ $controller->get_journal_methods };
    if (exists $journal_actions_found{'handle_journalsitem_get'}) {
        push(@allowed_methods,'GET');
        if (exists $journal_actions_found{'handle_journalsitem_head'}) {
            push(@allowed_methods,'HEAD');
        }
    }
    $c->response->headers(HTTP::Headers->new(
        Allow => join(', ',@allowed_methods),
    ));
    $c->response->content_type('application/json');
    $c->response->body(JSON::to_json({ methods => \@allowed_methods })."\n");
    return;
}

sub _has_journal_method {

    my ($controller, $action) = @_;
    my %journal_actions_found = map { $_ => 1 } @{ $controller->get_journal_methods };
    return exists $journal_actions_found{$action};

}

sub handle_api_journals_head {
    my ($controller, $c) = @_;
    if (_has_journal_method($controller,'handle_journals_get')) {
        $c->forward('handle_journals_get');
        $c->response->body(q());
        return;
    } else {
        $c->log->error("journals_get action not implemented: " . ref $controller);
        $c->response->status(HTTP_METHOD_NOT_ALLOWED);
        $c->response->body(q());
        return;
    }
}

sub handle_api_journalsitem_head {
    my ($controller, $c) = @_;
    if (_has_journal_method($controller,'handle_journalsitem_get')) {
        $c->forward('handle_journalsitem_get');
        $c->response->body(q());
        return;
    } else {
        $c->log->error("journalsitem_get not implemented: " . ref $controller);
        $c->response->status(HTTP_METHOD_NOT_ALLOWED);
        $c->response->body(q());
        return;
    }
}

sub get_journal_rs {
    my ($controller,$c,$resource_id,$all_columns) = @_;
    my $rs = $c->model('DB')->resultset('journals')
        ->search({
            'resource_name' => $controller->resource_name,
            'resource_id' => $resource_id,
        },($all_columns ? undef : {
            columns => JOURNAL_FIELDS,
            #alias => 'me',
        }));

    if ($controller->can('journal_query_params')) {
        return $controller->apply_query_params($c,$controller->journal_query_params,$rs);
    }

    return $rs;
}

sub get_journalitem {
    my ($c,$id) = @_;
    my $journal = $c->model('DB')->resultset('journals')
        ->find({
            'id' => $id,
        });

    return $journal;
}

sub get_top_journalitem {
    my ($controller,$c,$resource_id) = @_;
    return get_top_n_journalitems($controller,$c,$resource_id,1,1)->[0];
}

sub get_top_n_journalitems {
    my ($controller,$c,$resource_id,$n,$all_columns) = @_;
    my @journals = get_journal_rs($controller,$c,$resource_id,$all_columns)->search(undef,{
        page => 1,
        rows => $n,
        order_by => {-desc => "id"},
    })->all;
    return \@journals;
}

sub hal_from_journal {
    my ($controller,$c,$journal) = @_;

    my %resource = $journal->get_inflated_columns;
    {
        if (exists $resource{content}) {
            try {
                $resource{content} = _deserialize_content($journal->content_format,$journal->content);
            } catch($e) {
                $resource{content} = undef;
                $c->log->error("Failed to de-serialize content snapshot of journal item '" . $journal->id . "': $e");
                #$controller->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
                #return;
                last;
            };
            #delta stuff...
        }
    }

    my $hal = Data::HAL->new(
        links => [
            Data::HAL::Link->new(
                relation => 'curies',
                href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
                name => 'ngcp',
                templated => true,
            ),
            get_journal_relation_link($journal,$c,$journal->resource_id,undef,'collection'),
            Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
            get_journal_relation_link($journal,$c,$journal->resource_id,$journal->id,'self'),
            Data::HAL::Link->new(relation => sprintf('ngcp:%s',$journal->resource_name),
                href => sprintf('/api/%s/%d', $journal->resource_name, $journal->resource_id)),
        ],
        relation => API_JOURNAL_RELATION, #API_JOURNALITEM_RELATION,
    );

    my $datetime_fmt = DateTime::Format::Strptime->new(
        pattern => '%F %T',
    );

    $resource{timestamp} = $datetime_fmt->format_datetime($resource{timestamp});
    delete $resource{resource_name};
    delete $resource{resource_id};
    delete $resource{content_format};
    $hal->resource({%resource});

    return $hal;
}

sub get_journal_relation_link {
    my ($resource, $c, $item_id, $id, $relation) = @_;
    my $resource_name = undef;
    my $controller = undef;
    my $action_name = $id ? 'handle_journalsitem_get' : 'handle_journals_get';
    if (ref $resource eq 'HASH') {
        $resource_name = $resource->{resource_name};
    } elsif ((defined blessed($resource)) && $resource->can('resource_name')) { #both controllers and journal rows
        $resource_name = $resource->resource_name;
        if ($resource->can('get_config') and exists $resource->get_config('action')->{$action_name}) {
            $controller = $resource;
        } 
    } elsif (!ref $resource) {
        $resource_name = $resource;
    }
    if (defined $resource_name) {
        my $allowed_roles = [];
        if (!$controller) {
            $controller = NGCP::Panel::Utils::API::get_module_by_resource($c, $resource_name, $item_id);
        }
        if ($controller->can('get_config')) {
            if (exists $controller->get_config('action')->{$action_name}) {
                $allowed_roles = $controller->get_config('action')->{$action_name}->{AllowedRole};
            }
        }
        if (grep { $_ eq $c->user->roles } @$allowed_roles) {
            if (defined $id) {
                return Data::HAL::Link->new(
                    relation => ($relation // API_JOURNAL_RELATION), #API_JOURNALITEM_RELATION),
                    href     => sprintf('/api/%s/%d/%s/%d', $resource_name, $item_id,API_JOURNAL_RESOURCE_NAME,$id),
                );
            } else {
                return Data::HAL::Link->new(
                    relation => ($relation // API_JOURNAL_RELATION),
                    href     => sprintf('/api/%s/%d/%s/', $resource_name, $item_id, API_JOURNAL_RESOURCE_NAME),
                );
            }
        }
    }
    return ();
}

sub get_api_journal_op_config {
    my ($config,$resource_name,$operation) = @_;
    return _get_journal_op_config($config->{api_journal},undef,$resource_name,$operation);
}

sub _get_api_journal_op_config {
    return _get_journal_op_config($_[0]->config->{api_journal},@_);
}

sub _get_journal_op_config {
    my ($cfg,$c,$resource_name,$operation) = @_;
    #my $cfg = $c->config->{$section};
    my $format = CONTENT_DEFAULT_FORMAT;
    my $operation_enabled = OPERATION_DEFAULT_ENABLED;
    if (defined $cfg && exists $cfg->{$resource_name}) {
        $cfg = $cfg->{$resource_name};
        if (ref $cfg eq 'HASH') {
            if (ref $cfg->{operations} eq 'ARRAY') {
                foreach my $op (@{ $cfg->{operations} }) {
                    if (exists _JOURNAL_OPS->{$op}) {
                        if ($operation eq $op) {
                            $operation_enabled = 1;
                            last;
                        }
                    } else {
                        $c->log->error("Invalid '$resource_name' operation to log in journals: $op") if defined $c;
                    }
                }
            }
            if (defined $cfg->{format}) {
                if (exists _CONTENT_FORMATS->{$cfg->{format}}) {
                    $format = $cfg->{format};
                } else {
                    $c->log->error("Invalid journal content format for resource '$resource_name': $cfg->{format}") if defined $c;
                }
            }
        }
    }
    return { format => $format, operation_enabled => $operation_enabled};

}

sub get_journal_resource_config {
    my ($config,$resource_name) = @_;
    my $cfg = $config->{api_journal};
    my $journal_resource_enabled = JOURNAL_RESOURCE_DEFAULT_ENABLED;
    if (defined $cfg && exists $cfg->{$resource_name}) {
        $cfg = $cfg->{$resource_name};
        if (ref $cfg eq 'HASH') {
            if (defined $cfg->{enabled}) {
                if ($cfg->{enabled}) {
                    $journal_resource_enabled = 1;
                } else {
                    $journal_resource_enabled = 0;
                }
            }
        }
    }
    return { journal_resource_enabled => $journal_resource_enabled };

}

sub _serialize_content { #run this in eval only, deflate somehow inflicts a segfault in subsequent catalyst action when not consuming all args there.
    my ($format,$data) = @_;
    if (defined $format && defined $data) {
        if ($format eq CONTENT_JSON_FORMAT) {
            return JSON::to_json($data, { canonical => 1, pretty => 1, utf8 => 1 });
        } elsif ($format eq CONTENT_JSON_DEFLATE_FORMAT) {
            my $json = JSON::to_json($data, { canonical => 1, pretty => 1, utf8 => 1 });
            my $buf = '';
            IO::Compress::Deflate::deflate(\$json,\$buf) or die($DeflateError);
            return $buf;
        } elsif ($format eq CONTENT_STORABLE_FORMAT) {
            return Storable::nfreeze($data);
        } elsif ($format eq CONTENT_SEREAL_FORMAT) {
            return Sereal::Encoder::encode_sereal($data);
        }
    }
    return;
}

sub _deserialize_content {
    my ($format,$serialized) = @_;
    if (defined $format && defined $serialized) {
        if ($format eq CONTENT_JSON_FORMAT) {
            return JSON::from_json($serialized);
        } elsif ($format eq CONTENT_JSON_DEFLATE_FORMAT) {
            my $buf = '';
            IO::Uncompress::Inflate::inflate(\$serialized,\$buf) or die($DeflateError);
            return JSON::from_json($buf);
        } elsif ($format eq CONTENT_STORABLE_FORMAT) {
            return Storable::thaw($serialized);
        } elsif ($format eq CONTENT_SEREAL_FORMAT) {
            return Sereal::Decoder::decode_sereal($serialized);
        }
    }
    return;
}

sub _create_journal {
    my ($controller, $c, $id, $resource, $params) = @_;
    {
        my $content_format = ((defined $params->{format} and exists _CONTENT_FORMATS->{$params->{format}}) ? $params->{format} : CONTENT_DEFAULT_FORMAT);
        my $operation = ((defined $params->{operation} and exists _JOURNAL_OPS->{$params->{operation}}) ? $params->{operation} : undef);
        if (!defined $operation) {
            $c->log->error("Invalid '$params->{resource_name}' operation to log in journals: $operation");
            last;
        }
        my %journal = (
                    operation => $operation,
                    resource_name => $params->{resource_name},
                    #resource_id => $id,
                    timestamp => NGCP::Panel::Utils::DateTime::current_local->hires_epoch,
                    content_format => $content_format,
                );
        if (defined $id) {
            $journal{resource_id} = $id;
        }
        if (defined $params->{user}) {
            $journal{username} = $params->{user};
        } elsif (defined $c->user) {
            $journal{user_id} = $c->user->id;
            if($c->user->roles eq 'admin' || $c->user->roles eq 'reseller') {
                $journal{username} = $c->user->login;
            } elsif($c->user->roles eq 'subscriber' || $c->user->roles eq 'subscriberadmin') {
                $journal{username} = $c->user->webusername . '@' . $c->user->domain->domain;
            }
        }


        $journal{reseller_id} = $resource->{reseller_id} // $c->user->reseller_id;
        $journal{role_id} = NGCP::Panel::Utils::UserRole::resolve_role_id($c, $c->user);
        $journal{tx_id} = $c->stash->{api_request_tx_id};

        try {
            $journal{content} = _serialize_content($content_format, $resource);
            #my $test = 1 / 0;
        } catch($e) {
            $c->log->error("Failed to serialize journal content snapshot of '" . $params->{resource_name} . '/' . $id . "': $e");
            #$controller->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
            #return;
            last;
        };
        try {
            return $c->model('DB')->resultset('journals')->create(\%journal);
        } catch($e) {
            $c->log->error("Failed to create journal item for '" . $params->{resource_name} . '/' . $id . "': $e");
            #$controller->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
            #return;
            last;
        };
    }
    return;
}

1;

