SL::DB::BackgroundJob: refactoring von set_data für mehr programmatische Sicherheit
[kivitendo-erp.git] / SL / DB / BackgroundJob.pm
1 package SL::DB::BackgroundJob;
2
3 use strict;
4
5 use DateTime::Event::Cron;
6 use English qw(-no_match_vars);
7
8 use Rose::DB::Object::Helpers qw(as_tree);
9
10 use SL::DB::MetaSetup::BackgroundJob;
11 use SL::DB::Manager::BackgroundJob;
12
13 use SL::System::Process;
14
15 __PACKAGE__->meta->initialize;
16
17 __PACKAGE__->before_save('_before_save_set_next_run_at');
18
19 sub _before_save_set_next_run_at {
20   my ($self) = @_;
21
22   $self->update_next_run_at if !$self->next_run_at;
23   return 1;
24 }
25
26 sub update_next_run_at {
27   my $self = shift;
28
29   my $cron = DateTime::Event::Cron->new_from_cron($self->cron_spec || '* * * * *');
30   $self->update_attributes(next_run_at => $cron->next(DateTime->now_local));
31   return $self;
32 }
33
34 sub run {
35   my $self = shift;
36
37   my $package = "SL::BackgroundJob::" . $self->package_name;
38   my $run_at  = DateTime->now_local;
39   my $history;
40
41   require SL::DB::BackgroundJobHistory;
42
43   my $ok = eval {
44     eval "require $package" or die $@;
45     my $result = $package->new->run($self);
46
47     $history = SL::DB::BackgroundJobHistory
48       ->new(package_name => $self->package_name,
49             run_at       => $run_at,
50             status       => SL::DB::BackgroundJobHistory::SUCCESS(),
51             result       => $result,
52             data         => $self->data);
53     $history->save;
54
55     1;
56   };
57
58   if (!$ok) {
59     my $error = $EVAL_ERROR;
60     $history = SL::DB::BackgroundJobHistory
61       ->new(package_name => $self->package_name,
62             run_at       => $run_at,
63             status       => SL::DB::BackgroundJobHistory::FAILURE(),
64             error_col    => $error,
65             data         => $self->data);
66     $history->save;
67
68     $::lxdebug->message(LXDebug->WARN(), "BackgroundJob ID " . $self->id . " execution error (first three lines): " . join("\n", (split(m/\n/, $error))[0..2]));
69   }
70
71   $self->assign_attributes(last_run_at => $run_at)->update_next_run_at;
72
73   return $history;
74 }
75
76 sub data_as_hash {
77   my $self = shift;
78   return {}                        if !$self->data;
79   return $self->data               if ref($self->{data}) eq 'HASH';
80   return YAML::Load($self->{data}) if !ref($self->{data});
81   return {};
82 }
83
84 sub set_data {
85   my ($self, %data) = @_;
86
87   $self->data(YAML::Dump({
88     %{ $self->data_as_hash },
89     %data,
90   }));
91
92   $self;
93 }
94
95 sub validate {
96   my ($self) = @_;
97
98   my @errors;
99
100   push @errors, $::locale->text('The execution type is invalid.') if ($self->type         || '') !~ m/^(?: once | interval )$/x;
101
102   if (   (($self->package_name || '') !~ m/^ [A-Z][A-Za-z0-9]+ $/x)
103       || ! -f (SL::System::Process::exe_dir() . "/SL/BackgroundJob/" . $self->package_name . ".pm")) {
104     push @errors, $::locale->text('The package name is invalid.');
105   }
106
107   eval {
108     DateTime::Event::Cron->new_from_cron($self->cron_spec || '* * * * *')->next(DateTime->now_local);
109     1;
110   } or push @errors, $::locale->text('The execution schedule is invalid.');
111
112   return @errors;
113 }
114
115 1;