File Coverage

blib/lib/Data/Conveyor/Ticket.pm
Criterion Covered Total %
statement 34 212 16.0
branch 0 30 0.0
condition 0 11 0.0
subroutine 12 51 23.5
pod 37 37 100.0
total 83 341 24.3


line stmt bran cond sub pod time code
1 1     1   3166 use 5.008;
  1         5  
  1         855  
2 1     1   13 use strict;
  1         2  
  1         112  
3 1     1   7 use warnings;
  1         1  
  1         70  
4              
5             package Data::Conveyor::Ticket;
6             BEGIN {
7 1     1   24 $Data::Conveyor::Ticket::VERSION = '1.103130';
8             }
9             # ABSTRACT: Stage-based conveyor-belt-like ticket handling system
10              
11 1     1   7 use Data::Miscellany 'is_defined';
  1         2  
  1         69  
12 1     1   7 use Error::Hierarchy;
  1         2  
  1         20  
13 1     1   135 use Error::Hierarchy::Util qw/assert_defined assert_is_integer assert_getopt/;
  1         2  
  1         61  
14 1     1   6 use Data::Dumper; # needed for service method 'data_dump'
  1         1  
  1         62  
15 1     1   8 use Error ':try';
  1         2  
  1         7  
16 1     1   1100 use Hash::Flatten;
  1         2630  
  1         40  
17 1     1   712 use Class::Value::Exception::NotWellFormedValue;
  1         3838  
  1         16  
18 1     1   50 use parent 'Class::Scaffold::Storable';
  1         2  
  1         4  
19             __PACKAGE__->mk_abstract_accessors(qw(request_as_string))
20             ->mk_framework_object_accessors(
21             ticket_payload => 'payload',
22             ticket_facets => 'facets',
23             value_ticket_stage => 'stage',
24             value_ticket_rc => 'rc',
25             value_ticket_status => 'status',
26             )->mk_scalar_accessors(qw(ticket_no origin type received_date));
27 0     0 1   sub key { $_[0]->ticket_no }
28              
29             sub assert_ticket_no {
30 0     0 1   my $self = shift;
31 0           local $Error::Depth = $Error::Depth + 1;
32 0           assert_defined $self->ticket_no, 'called without defined ticket number';
33             }
34              
35             sub read {
36 0     0 1   my $self = shift;
37 0           $self->assert_ticket_no;
38 0           $self->storage->ticket_read_from_object($self);
39             }
40              
41             # generates new ticket
42             sub gen_ticket_no {
43 0     0 1   my $self = shift;
44 0           my $ticket_no = $self->storage->generate_ticket_no;
45             try {
46 0     0     $self->ticket_no($ticket_no);
47             }
48             catch Class::Value::Exception::NotWellFormedValue with {
49 0     0     throw Data::Conveyor::Exception::Ticket::GenFailed(
50             ticket_no => $ticket_no);
51 0           };
52              
53             # don't return $ticket_no; the value object might have normalized the
54             # value
55 0           $self->ticket_no;
56             }
57              
58             # opens (sets the stage on 'aktiv_[% stage %]') either given ticket or
59             # the oldest ticket in given stage. sets $self->ticket_no on success
60             # or throws Data::Conveyor::Exception::Ticket::NoSuchTicket otherwise.
61             #
62             # if a ticket has been opened, it will be read.
63             #
64             # NOTE: this method commits (but respects the rollback flag).
65             #
66             # accepts one parameter:
67             # stage_name [mandatory]
68             #
69             # fails if stage isn't given.
70             sub open {
71 0     0 1   my ($self, $stage_name) = @_;
72 0           assert_defined $stage_name, 'called without stage name.';
73 0           my ($new_stage, $ticket_no) =
74             $self->storage->ticket_open($stage_name, $self->ticket_no);
75 0 0         if (is_defined($ticket_no)) {
76 0           $self->stage($new_stage);
77 0           $self->ticket_no($ticket_no);
78 0           $self->read;
79 0           $self->reset_default_rc_and_status;
80             } else {
81 0           $self->log->debug('HINT: Does the ticket have all required fields?');
82 0   0       throw Data::Conveyor::Exception::Ticket::NoSuchTicket(
83             ticket_no => $self->ticket_no || 'n/a',
84             stage => $stage_name,
85             );
86             }
87             }
88              
89             sub try_open {
90 0     0 1   my $self = shift;
91             assert_defined $self->$_, sprintf "called without %s argument.", $_
92 0           for qw/ticket_no stage rc status/;
93 0           my $ticket_no = $self->storage->ticket_set_active($self);
94 0 0 0       return unless defined $ticket_no && $ticket_no eq $self->ticket_no;
95 0           $self->read;
96 0           $self->reset_default_rc_and_status;
97 0           1;
98             }
99              
100             # stores the whole ticket.
101             sub store {
102 0     0 1   my $self = shift;
103 0           $self->assert_ticket_no;
104 0           $self->update_calculated_values;
105 0           $self->storage->ticket_store($self);
106 0           $self->storage->facets_store($self);
107             }
108              
109             # Store everything about the ticket. Used by test code when we want to make
110             # sure everything we specified in the YAML test files gets stored. Here we
111             # just store the ticket itself; subclasses can add their things.
112             sub store_full {
113 0     0 1   my $self = shift;
114 0           $self->store;
115             }
116              
117             # Writes the ticket's stage, status, and rc to the database, and ensures that
118             # the new stage is the end_* version of the current stage.
119             sub close {
120 0     0 1   my $self = shift;
121 0           $self->assert_ticket_no;
122 0           assert_defined $self->stage, 'called without set stage.';
123 0           assert_defined $self->rc, 'called without set returncode.';
124 0           assert_defined $self->status, 'called without set status.';
125 0 0         unless ($self->stage->is_active) {
126 0           throw Data::Conveyor::Exception::Ticket::InvalidStage(
127             stage => $self->stage,);
128             }
129 0           $self->stage->set_end;
130 0           $self->close_basic;
131             }
132              
133             # Sets only stage, rc and status.
134             # Low-level method that can be called instead of close() when you want to set
135             # the ticket to some other stage, rc and status than close() would mandate.
136             sub close_basic {
137 0     0 1   my $self = shift;
138 0           $self->storage->ticket_close($self);
139             }
140              
141             # Does this ticket ignore the given exception?
142             # Fails if the exception name isn't provided.
143             sub ignores_exception {
144 0     0 1   my ($self, $exception) = @_;
145              
146             # we ignore it if it is acknowledged
147 0 0 0       if (ref $exception && UNIVERSAL::can($exception, 'acknowledged')) {
148 0 0         return 1 if $exception->acknowledged;
149             }
150             }
151              
152             # XXX: could this, along with wrote_billing_lock and other methods, be
153             # forwarded directly to $self->payload->common->* ?
154             sub set_log_level {
155 0     0 1   my ($self, $log_level) = @_;
156 0           assert_is_integer($log_level);
157 0           $self->payload->common->log_level($log_level);
158             }
159              
160             sub get_log_level {
161 0     0 1   my $self = shift;
162 0 0         $self->payload->common->log_level || 1;
163             }
164              
165             # shifts a ticket to the next stage.
166             #
167             # fails if the ticket_no isn't defined.
168             sub shift_stage {
169 0     0 1   my $self = shift;
170 0           $self->assert_ticket_no;
171              
172             # Can't shift to an undefined stage -> do nothing in this case. Could
173             # happen if a ticket has RC_INTERNAL_ERROR, for example.
174             # get_next_stage() now returns an arrayref with [ stage-object,
175             # status-constant-name ] so the special stati E,D can be supported by
176             # shift at the end of the ticket lifecycle. status is undefined if
177             # nothing was specified in the memory storage's mapping.
178 0 0         if (my $transition =
179             $self->delegate->make_obj('ticket_transition')
180             ->get_next_stage($self->stage, $self->rc)) {
181 0           my $status = $transition->[1];
182 0           $self->stage($transition->[0]);
183 0 0         $self->status($self->delegate->$status) if defined $status;
184 0           $self->storage->ticket_update_stage($self);
185             }
186             }
187              
188             # service method
189             sub object_tickets {
190 0     0 1   my ($self, $object, $limit) = @_;
191 0           $self->delegate->make_obj('service_result_tabular')->set_from_rows(
192             limit => $limit,
193             fields => [
194             qw/ticket_no stage status ticket_type origin
195             real effective cdate mdate/
196             ],
197             rows => scalar $self->storage->get_object_tickets($object, $limit,),
198             );
199             }
200              
201             sub sif_dump {
202 0     0 1   my ($self, %opt) = @_;
203 0           assert_getopt $opt{ticket}, 'Called without ticket number.';
204 0           $self->ticket_no($opt{ticket});
205 0           $self->read;
206 0 0         my $dump = $opt{raw} ? Dumper($self) : scalar($self->dump_comparable);
207 0           $self->delegate->make_obj('service_result_scalar', result => $dump);
208             }
209              
210             sub sif_ydump {
211 0     0 1   my ($self, %opt) = @_;
212 0           assert_getopt $opt{ticket}, 'Called without ticket number.';
213 0           $self->ticket_no($opt{ticket});
214 0           $self->read;
215 0           $self->delegate->make_obj('service_result_scalar',
216             result => $self->yaml_dump_comparable);
217             }
218              
219             sub sif_exceptions {
220 0     0 1   my ($self, %opt) = @_;
221 0           assert_getopt $opt{ticket}, 'Called without ticket number.';
222 0           $self->ticket_no($opt{ticket});
223 0           $self->read;
224 0           local $Data::Dumper::Indent = 1;
225 0           my $container = $self->payload->get_all_exceptions;
226 0 0         $self->delegate->make_obj('service_result_scalar',
227             result => $opt{raw} ? Dumper($container) : "$container\n");
228             }
229              
230             sub sif_clear_exceptions {
231 0     0 1   my ($self, %opt) = @_;
232 0           assert_getopt $opt{ticket}, 'Called without ticket number.';
233 0           $self->ticket_no($opt{ticket});
234 0           $self->read;
235 0           $self->payload->clear_all_exceptions;
236 0           $self->store;
237 0           $self->delegate->make_obj('service_result_scalar', result => 'OK');
238             }
239              
240             sub sif_exceptions_structured {
241 0     0 1   my ($self, %args) = @_;
242 0           $self->ticket_no($args{ticket});
243 0           $self->read;
244 0           my $res = {};
245 0   0       for my $ot ($args{object} || $self->delegate->OT, 'common') {
246 0           my $item_count = 1;
247 0 0         for my $item (
248             $ot eq 'common'
249             ? $self->payload->common
250             : $self->payload->get_list_for_object_type($ot)
251             ) {
252 0           my $h_item = sprintf "%s.%s", $ot, $item_count++;
253 0           $res->{$h_item} = [];
254 0           for my $E ($item->exception_container->items) {
255 0           my $ex = {
256             class => ref $E,
257             uuid => $E->uuid,
258 0           attrs => { map { $_ => $E->$_ } $E->get_properties }
259             };
260 0           push(@{ $res->{$h_item} }, $ex);
  0            
261             }
262             }
263             }
264 0           $self->delegate->make_obj('service_result_scalar', result => $res);
265             }
266              
267             sub sif_delete_exception {
268 0     0 1   my ($self, %args) = @_;
269 0           $self->ticket_no($args{ticket});
270 0           $self->read;
271 0           $self->payload->delete_by_uuid($args{uuid});
272 0           $self->store;
273 0           $self->delegate->make_obj('service_result_scalar');
274             }
275              
276             sub sif_journal {
277 0     0 1   my ($self, %opt) = @_;
278 0           assert_getopt $opt{ticket}, 'Called without ticket number.';
279 0           $self->ticket_no($opt{ticket});
280 0           $self->delegate->make_obj('service_result_tabular')->set_from_rows(
281             rows => scalar $self->storage->get_ticket_journal($self),
282             fields => [qw/stage status rc ts osuser oshost/],
283             );
284             }
285              
286             # This is a service method, which doesn't just set the state attribute, so it
287             # gets its own method (as opposed to just setting state() from within a
288             # service interface).
289             #
290             # FIXME: Doesn't write sif log yet.
291             sub sif_set_stage {
292 0     0 1   my ($self, %opt) = @_;
293 0           assert_getopt $opt{ticket}, 'Called without ticket number.';
294 0           assert_getopt $opt{stage}, 'Called without stage.';
295 0           $self->ticket_no($opt{ticket});
296 0           $self->read;
297 0           my $prev_stage = $self->stage;
298 0           $self->stage($opt{stage});
299 0           $self->store;
300 0           $self->delegate->make_obj(
301             'service_result_scalar',
302             result =>
303             sprintf "Ticket [%s]: Previous stage [%s]\n",
304             $self->ticket_no, $prev_stage
305             );
306             }
307              
308             sub sif_get_ticket_payload {
309 0     0 1   my ($self, %args) = @_;
310 0           my $ticket = $self->delegate->make_obj('ticket',);
311 0           my $res = {};
312 0           $ticket->ticket_no($args{ticket});
313 0           $ticket->read;
314 0           for my $object_type ($self->delegate->OT) {
315 0 0         next if $object_type eq $self->delegate->OT_LOCK;
316 0 0         next if $object_type eq $self->delegate->OT_TRANSACTION;
317 0           for my $payload_item (
318             $ticket->payload->get_list_for_object_type($object_type)) {
319 0           my $pref = $payload_item->comparable(1);
320 0           $pref = Hash::Flatten::flatten $pref;
321 0           $res->{$object_type} = $pref;
322             }
323             }
324 0           foreach
325             my $facet (qw/authoritative_registrar ignore_exceptions_as_registrar/) {
326 0           $res->{facets}->{$facet} =
327             sprintf("%s", $ticket->facets->$facet->protocol_id);
328             }
329 0           $res->{protokoll_id} = $ticket->registrar->protocol_id;
330 0           $self->delegate->make_obj('service_result_scalar', result => $res);
331             }
332              
333             # rc and status are only updated from the payload; call this before storing
334             # the ticket whenever you change the payload's exception containers. This way,
335             # when you remove an exception (e.g., via a service interface), it has a
336             # direct effect on the ticket's rc and status.
337             #
338             # The ticket is passed to the payload method so it can pass it to the methods
339             # it calls; eventually the exception container will ask the ticket whether to
340             # ignore each exception it processes (cf. ignores_exception).
341             sub update_calculated_values {
342 0     0 1   my $self = shift;
343 0           $self->payload->update_transaction_stati($self);
344 0           $self->calculate_status; # calculates rc as well
345             }
346              
347             sub calculate_rc {
348 0     0 1   my $self = shift;
349 0           $self->rc($self->payload->rc($self));
350             }
351              
352             sub calculate_status {
353 0     0 1   my $self = shift;
354 0           $self->calculate_rc; # since status depends on the rc
355 0           my $status = sprintf "%s", $self->payload->status($self);
356 0 0         if ($self->stage eq $self->delegate->FINAL_TICKET_STAGE) {
357 0 0         $status =
358             $self->rc eq $self->delegate->RC_ERROR
359             ? $self->delegate->TS_ERROR
360             : $self->delegate->TS_DONE;
361             }
362 0           $self->status($status);
363             }
364              
365             sub set_default_rc {
366 0     0 1   my ($self, $rc) = @_;
367 0           assert_defined $rc, 'called without rc.';
368 0           $self->payload->common->default_rc($rc);
369             }
370              
371             sub set_default_status {
372 0     0 1   my ($self, $status) = @_;
373 0           assert_defined $status, 'called without status.';
374 0           $self->payload->common->default_status($status);
375             }
376              
377             sub reset_default_rc_and_status {
378 0     0 1   my $self = shift;
379 0           my $new_common = $self->delegate->make_obj('payload_common');
380 0           $self->payload->common->default_rc($new_common->default_rc);
381 0           $self->payload->common->default_status($new_common->default_status);
382             }
383              
384             sub check {
385 0     0 1   my $self = shift;
386 0           $self->payload->check($self);
387 0           $self->facets->check($self);
388             }
389              
390             sub filter_exceptions_by_rc {
391 0     0 1   my ($self, @filter) = @_;
392 0           $self->payload->filter_exceptions_by_rc($self, @filter);
393             }
394              
395             sub filter_exceptions_by_status {
396 0     0 1   my ($self, @filter) = @_;
397 0           $self->payload->filter_exceptions_by_status($self, @filter);
398             }
399              
400             sub delete {
401 0     0 1   my $self = shift;
402 0           $self->assert_ticket_no;
403 0           $self->storage->ticket_delete($self);
404             }
405              
406             sub store_facets {
407 0     0 1   my $self = shift;
408 0           $self->facets->store($self);
409             }
410              
411             sub read_facets {
412 0     0 1   my $self = shift;
413 0           $self->facets->read($self);
414 0           $self->facets;
415             }
416              
417             # don't call this delete_facets, because framework_object already generates a
418             # 'delete_*' method.
419             sub remove_facets {
420 0     0 1   my $self = shift;
421 0           $self->facets->delete($self);
422             }
423             1;
424              
425              
426             __END__