Foundations of Action/Triger-based telephony,
[working/Evergreen.git] / Open-ILS / src / support-scripts / action_trigger_runner.pl
1 #!/usr/bin/perl
2 # ---------------------------------------------------------------
3 # Copyright (C) 2009 Equinox Software, Inc
4 # Author: Bill Erickson <erickson@esilibrary.com>
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 # ---------------------------------------------------------------
16 use strict;
17 use warnings;
18 use Getopt::Long;
19 use OpenSRF::System;
20 use OpenSRF::AppSession;
21 use OpenSRF::Utils::JSON;
22 use OpenSRF::EX qw(:try);
23 use OpenILS::Utils::Fieldmapper;
24
25 # DEFAULT values
26
27 my $opt_lockfile      = '/tmp/action-trigger-LOCK';
28 my $opt_osrf_config   = '/openils/conf/opensrf_core.xml';
29 my $opt_custom_filter = '/openils/conf/action_trigger_filters.json';
30 my $opt_max_sleep     = 3600;  # default to 1 hour
31 my $opt_run_pending   = 0;
32 my $opt_debug_stdout  = 0;
33 my $opt_help          = 0;
34 my $opt_verbose;
35 my $opt_hooks;
36 my $opt_process_hooks = 0;
37 my $opt_granularity   = undef;
38
39 (-f $opt_custom_filter) or undef($opt_custom_filter);   # discard default if no file exists
40
41 GetOptions(
42     'max-sleep'        => \$opt_max_sleep,
43     'osrf-config=s'    => \$opt_osrf_config,
44     'run-pending'      => \$opt_run_pending,
45     'hooks=s'          => \$opt_hooks,
46     'granularity=s'    => \$opt_granularity,
47     'process-hooks'    => \$opt_process_hooks,
48     'debug-stdout'     => \$opt_debug_stdout,
49     'custom-filters=s' => \$opt_custom_filter,
50     'lock-file=s'      => \$opt_lockfile,
51     'verbose'          => \$opt_verbose,
52     'help'             => \$opt_help,
53 );
54
55 my $max_sleep = $opt_max_sleep;
56
57 # typical passive hook filters
58 my $hook_handlers = {
59
60     # default overdue circulations
61     'checkout.due' => {
62         context_org => 'circ_lib',
63         filter => {
64             checkin_time => undef, 
65             '-or' => [
66                 {stop_fines => ['MAXFINES', 'LONGOVERDUE']}, 
67                 {stop_fines => undef}
68             ]
69         }
70     }
71 };
72
73 if ($opt_custom_filter) {
74     if (open FILTERS, $opt_custom_filter) {
75         $hook_handlers = OpenSRF::Utils::JSON->JSON2perl(join('',(<FILTERS>)));
76         close FILTERS;
77     } else {
78         die "Cannot read filter file '$opt_custom_filter'";
79     }
80 }
81
82 sub help {
83     print <<HELP;
84
85 $0 : Create and process action/trigger events
86
87     --osrf-config=<config_file>
88         OpenSRF core config file.  Defaults to:
89             /openils/conf/opensrf_core.xml
90
91     --custom-filters=<filter_file>
92         File containing a JSON Object which describes any hooks that should
93         use a user-defined filter to find their target objects.  Defaults to:
94             /openils/conf/action_trigger_filters.json
95
96     --run-pending
97         Run pending events
98
99     --process-hooks
100         Create hook events
101
102     --max-sleep=<seconds>
103         When in process-hooks mode, wait up to <seconds> for the lock file to
104         go away.  Defaults to 3600 (1 hour).
105
106     --hooks=hook1[,hook2,hook3,...]
107         Define which hooks to create events for.  If none are defined,
108         it defaults to the list of hooks defined in the --custom-filters option.
109
110     --granularity=<label>
111         Run events with {label} granularity setting, or no granularity setting
112
113     --debug-stdout
114         Print server responses to stdout (as JSON) for debugging
115
116     --lock-file=<file_name>
117         Lock file
118
119     --help
120         Show this help
121
122     Examples:
123
124         # To run all pending events.  This is what you tell CRON to run at
125         # regular intervals
126         perl $0 --osrf-config /openils/conf/opensrf_core.xml --run-pending
127
128         # To batch create all "checkout.due" events
129         perl $0 --osrf-config /openils/conf/opensrf_core.xml --hooks checkout.due
130
131 HELP
132 }
133
134
135 # create events for the specified hooks using the configured filters and context orgs
136 sub process_hooks {
137     $opt_verbose and print "process_hooks: " . ($opt_process_hooks ? '(start)' : 'SKIPPING') . "\n";
138     return unless $opt_process_hooks;
139
140     my @hooks = ($opt_hooks) ? split(',', $opt_hooks) : keys(%$hook_handlers);
141     my $ses = OpenSRF::AppSession->create('open-ils.trigger');
142
143     for my $hook (@hooks) {
144         my $config = $$hook_handlers{$hook};
145         $opt_verbose and print "process_hooks: $hook " . ($config ? ($opt_granularity || '') : ' NO HANDLER') . "\n";
146         $config or next;
147
148         my $method = 'open-ils.trigger.passive.event.autocreate.batch';
149         $method =~ s/passive/active/ if $config->{active};
150         
151         my $req = $ses->request($method, $hook, $config->{context_org}, $config->{filter}, $opt_granularity);
152         while(my $resp = $req->recv(timeout => 1800)) {
153             if($opt_debug_stdout) {
154                 print OpenSRF::Utils::JSON->perl2JSON($resp->content) . "\n";
155             }
156         }
157     }
158 }
159
160 sub run_pending {
161     $opt_verbose and print "run_pending: " .
162         ($opt_run_pending ? ($opt_granularity || 'ALL granularity') : 'SKIPPING') . "\n";
163     return unless $opt_run_pending;
164     my $ses = OpenSRF::AppSession->create('open-ils.trigger');
165     my $req = $ses->request('open-ils.trigger.event.run_all_pending' => $opt_granularity);
166
167     my $check_lockfile = 1;
168     while(my $resp = $req->recv(timeout => 7200)) {
169         if ($check_lockfile && -e $opt_lockfile) {
170             open LF, $opt_lockfile;
171             my $contents = <LF>;
172             close LF;
173             unlink $opt_lockfile if ($contents == $$);
174             $check_lockfile = 0;
175         }
176         if($opt_debug_stdout) {
177             print OpenSRF::Utils::JSON->perl2JSON($resp->content) . "\n";
178         }
179     }
180 }
181
182 help() and exit if $opt_help;
183 help() and exit unless ($opt_run_pending or $opt_process_hooks);
184
185 # check the lockfile
186 if (-e $opt_lockfile) {
187     die "I'm already running with lockfile $opt_lockfile\n" if (!$opt_process_hooks);
188     # sleeping loop if we're in --process-hooks mode
189     do { last unless ( -e $opt_lockfile ); $max_sleep--; } while ($max_sleep >= 0 && sleep(1));
190 }
191
192 # there's a tiny race condition here ... oh well
193 die "Someone else has been holding the lockfile $opt_lockfile for at least $opt_max_sleep. Giving up now ...\n" if (-e $opt_lockfile);
194
195 # set the lockfile
196 open(F, ">$opt_lockfile") or die "Unable to open lockfile $opt_lockfile for writing\n";
197 print F $$;
198 close F;
199
200 try {
201         OpenSRF::System->bootstrap_client(config_file => $opt_osrf_config);
202         Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
203     process_hooks();
204     run_pending();
205 } otherwise {
206     my $e = shift;
207     warn "$e\n";
208 };
209
210 if (-e $opt_lockfile) {
211     open LF, $opt_lockfile;
212     my $contents = <LF>;
213     close LF;
214     unlink $opt_lockfile if ($contents == $$);
215 }