File Coverage

blib/lib/Alien/Taco/Server.pm
Criterion Covered Total %
statement 137 189 72.4
branch 18 52 34.6
condition 3 12 25.0
subroutine 29 31 93.5
pod 14 14 100.0
total 201 298 67.4


line stmt bran cond sub pod time code
1             # Taco Perl server module.
2             # Copyright (C) 2013-2014 Graham Bell
3             #
4             # This program is free software: you can redistribute it and/or modify
5             # it under the terms of the GNU General Public License as published by
6             # the Free Software Foundation, either version 3 of the License, or
7             # (at your option) any later version.
8             #
9             # This program is distributed in the hope that it will be useful,
10             # but WITHOUT ANY WARRANTY; without even the implied warranty of
11             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12             # GNU General Public License for more details.
13             #
14             # You should have received a copy of the GNU General Public License
15             # along with this program. If not, see <http://www.gnu.org/licenses/>.
16              
17             =head1 NAME
18              
19             Alien::Taco::Server - Taco Perl server module
20              
21             =head1 SYNOPSIS
22              
23             use Alien::Taco::Server;
24             my $server = new Alien::Taco::Server();
25             $server->run();
26              
27             =head1 DESCRIPTION
28              
29             This module provides a Perl implementation of the actions
30             required of a Taco server.
31              
32             =cut
33              
34             package Alien::Taco::Server;
35              
36 3     3   1053597 use Scalar::Util qw/blessed/;
  3         27  
  3         399  
37              
38 3     3   1379 use Alien::Taco::Transport;
  3         8  
  3         104  
39 3     3   1399 use Alien::Taco::Util qw/filter_struct/;
  3         9  
  3         185  
40              
41 3     3   19 use strict;
  3         6  
  3         3923  
42              
43             our $VERSION = '0.003';
44              
45             =head1 SUBROUTINES
46              
47             =head2 Main Methods
48              
49             =over 4
50              
51             =item new()
52              
53             Set up a L<Alien::Taco::Transport> object communicating via
54             C<STDIN> and C<STDOUT>.
55              
56             C<STDERR> is selected as the current stream to try to avoid
57             any subroutine or method calls printing to C<STDOUT> which would
58             corrupt communications with the client.
59              
60             =cut
61              
62             sub new {
63 0     0 1 0 my $class = shift;
64              
65             # Create cache of objects held on the server side for which an
66             # object number is passed to the client.
67              
68 0         0 my $self = bless {
69             nobject => 0,
70             objects => {},
71             }, $class;
72              
73             # Select STDERR as current file handle so that if a function is
74             # called which in turn prints something, it doesn't go into the
75             # transport stream.
76 0         0 select(STDERR);
77              
78 0         0 $self->{'xp'} = $self->_construct_transport(*STDIN, *STDOUT);
79              
80 0         0 return $self;
81             }
82              
83             # _construct_transport
84             #
85             # Implements construction of the Alien::Taco::Transport object.
86              
87             sub _construct_transport {
88 1     1   704 my $self = shift;
89 1         3 my $in = shift;
90 1         2 my $out = shift;
91              
92             return new Alien::Taco::Transport(in => $in, out => $out,
93             filter_single => ['_Taco_Object_' => sub {
94 1     1   4 return $self->_get_object(shift);
95 1         11 }],
96             );
97             }
98              
99             =item run()
100              
101             Enter the message handling loop, which exits on failure to read from
102             the transport.
103              
104             =cut
105              
106             sub run {
107 0     0 1 0 my $self = shift;
108 0         0 my $xp = $self->{'xp'};
109              
110 0         0 while (1) {
111 0         0 my $message = $xp->read();
112 0 0       0 last unless defined $message;
113              
114 0         0 my $act = $message->{'action'};
115 0         0 my $res = undef;
116              
117 0 0 0     0 if ($act !~ /^_/ and $self->can($act)) {
118 0         0 $res = eval {$self->$act($message)};
  0         0  
119              
120 0 0       0 $res = {
121             action => 'exception',
122             message => 'exception caught: ' . $@,
123             } unless defined $res;
124             }
125             else {
126 0         0 $res = {
127             action => 'exception',
128             message => 'unknown action: ' . $act,
129             };
130             }
131              
132 0         0 $self->_replace_objects($res);
133 0         0 $xp->write($res);
134             }
135             }
136              
137             # _get_param(\%message)
138             #
139             # Read subroutine / method parameters from a Taco message and
140             # return them as a list suitable for passing to Perl subroutines.
141              
142             sub _get_param {
143 10     10   1974 my $message = shift;
144              
145 10         19 my @param = ();
146              
147 10 100       33 if (defined $message->{'args'}) {
148 5         7 @param = @{$message->{'args'}};
  5         15  
149             }
150              
151 10 100       31 if (defined $message->{'kwargs'}) {
152 5         10 @param = (@param, %{$message->{'kwargs'}});
  5         19  
153             }
154              
155 10         38 return @param;
156             }
157              
158             # _make_result($value)
159             #
160             # Construct a Taco result message containing the given value.
161              
162             sub _make_result {
163             return {
164 11     11   89 action => 'result',
165             result => shift,
166             };
167             }
168              
169             my $null_result = _make_result(undef);
170              
171             # _replace_objects(\%message)
172             #
173             # Replace objects in the given message with Taco object number references.
174              
175             sub _replace_objects {
176 3     3   714 my $self = shift;
177             filter_struct(shift, sub {
178 3     3   6 my $x = shift;
179 3 50       25 blessed($x) and not JSON::is_bool($x);
180             },
181             sub {
182 3     3   11 my $nn = my $n = ++ $self->{'nobject'};
183 3         8 $self->{'objects'}->{$nn} = shift;
184 3         18 return {_Taco_Object_ => $n};
185 3         26 });
186             }
187              
188             # _delete_object($number)
189             #
190             # Delete an object from the cache.
191              
192             sub _delete_object {
193 2     2   385 my $self = shift;
194 2         4 my $n = shift;
195 2         7 delete $self->{'objects'}->{$n};
196             }
197              
198             # _get_object($number)
199             #
200             # Fetch an object from the cache.
201              
202             sub _get_object {
203 8     8   579 my $self = shift;
204 8         13 my $n = shift;
205 8         30 return $self->{'objects'}->{$n};
206             }
207              
208             =back
209              
210             =head2 Taco Action Handlers
211              
212             =over 4
213              
214             =item call_class_method($message)
215              
216             Call the class method specified in the message, similarly to
217             C<call_function>.
218              
219             =cut
220              
221             sub call_class_method {
222 1     1 1 24 my $self = shift;
223 1         2 my $message = shift;
224              
225 1         2 my $c = $message->{'class'};
226 1         3 my $f = $message->{'name'};
227 1         3 my @param = _get_param($message);
228              
229 1         4 my $result = undef;
230 1 50 33     14 unless (defined $message->{'context'}
    50          
    50          
    50          
231             and $message->{'context'} ne 'scalar') {
232 0         0 $result = $c->$f(@param);
233             }
234 0         0 elsif ($message->{'context'} eq 'list') {
235 0         0 my @result = $c->$f(@param);
236 0         0 $result = \@result;
237             }
238 0         0 elsif ($message->{'context'} eq 'map') {
239 0         0 my %result = $c->$f(@param);
240 0         0 $result = \%result;
241             }
242 0         0 elsif ($message->{'context'} eq 'void') {
243 1         4 $c->$f(@param);
244             }
245             else {
246 0         0 die 'unknown context: ' . $message->{'context'};
247             }
248              
249 1         7 return _make_result($result);
250             }
251              
252              
253              
254             =item call_function($message)
255              
256             Call the function specified in the message. The function is called
257             in the requested context (void / scalar / list) if specified. A
258             context of "map" can also be specified to avoid the client having
259             to convert a list to a hash in cases where the function returns
260             a hash directly.
261              
262             The function is called with an argument list consisting of the
263             I<args> followed by the I<kwargs> in list form. To supply a
264             hash reference to the function, a hash should be placed inside
265             one of the arguments paramters of the message.
266              
267             =cut
268              
269             sub call_function {
270 1     1 1 3 my $self = shift;
271 1         2 my $message = shift;
272              
273 1         2 my $f = \&{$message->{'name'}};
  1         4  
274 1         4 my @param = _get_param($message);
275              
276 1         3 my $result = undef;
277 1 0 33     8 unless (defined $message->{'context'}
    0          
    0          
    50          
278             and $message->{'context'} ne 'scalar') {
279 1         3 $result = $f->(@param);
280             }
281 0         0 elsif ($message->{'context'} eq 'list') {
282 0         0 my @result = $f->(@param);
283 0         0 $result = \@result;
284             }
285 0         0 elsif ($message->{'context'} eq 'map') {
286 0         0 my %result = $f->(@param);
287 0         0 $result = \%result;
288             }
289 0         0 elsif ($message->{'context'} eq 'void') {
290 0         0 $f->(@param);
291             }
292             else {
293 0         0 die 'unknown context: ' . $message->{'context'};
294             }
295              
296 1         9 return _make_result($result);
297             }
298              
299             =item call_method($message)
300              
301             Call an object method, similarly to C<call_function>.
302              
303             =cut
304              
305             sub call_method {
306 2     2 1 6 my $self = shift;
307 2         3 my $message = shift;
308              
309 2         5 my $number = $message->{'number'};
310 2         3 my $name = $message->{'name'};
311 2         6 my @param = _get_param($message);
312              
313 2         9 my $object = $self->_get_object($number);
314              
315 2         5 my $result = undef;
316 2 0 33     17 unless (defined $message->{'context'}
    50          
    100          
    50          
317             and $message->{'context'} ne 'scalar') {
318 0         0 $result = $object->$name(@param);
319             }
320 0         0 elsif ($message->{'context'} eq 'list') {
321 1         5 my @result = $object->$name(@param);
322 1         10 $result = \@result;
323             }
324 0         0 elsif ($message->{'context'} eq 'map') {
325 1         4 my %result = $object->$name(@param);
326 1         16 $result = \%result;
327             }
328 0         0 elsif ($message->{'context'} eq 'void') {
329 0         0 $object->$name(@param);
330             }
331             else {
332 0         0 die 'unknown context: ' . $message->{'context'};
333             }
334              
335 2         5 return _make_result($result);
336             }
337              
338             =item construct_object($message)
339              
340             Call an object constructor.
341              
342             =cut
343              
344             sub construct_object {
345 1     1 1 3 my $self = shift;
346 1         3 my $message = shift;
347              
348 1         3 my $c = $message->{'class'};
349 1         3 my @param = _get_param($message);
350              
351 1         5 return _make_result($c->new(@param));
352             }
353              
354             =item destroy_object($message)
355              
356             Remove an object from the cache.
357              
358             =cut
359              
360             sub destroy_object {
361 1     1 1 375 my $self = shift;
362 1         2 my $message = shift;
363              
364 1         3 my $n = $message->{'number'};
365 1         6 $self->_delete_object($n);
366              
367 1         4 return $null_result;
368             }
369              
370             =item get_attribute($message)
371              
372             Attempt to read an object attribute, but this depends on the object
373             being a blessed HASH reference. If so then the named HASH entry
374             is returned. Typically, however, Perl object values will be
375             accessed by calling the corresponding method on the object instead.
376              
377             =cut
378              
379             sub get_attribute {
380 1     1 1 4 my $self = shift;
381 1         2 my $message = shift;
382              
383 1         2 my $number = $message->{'number'};
384 1         2 my $name = $message->{'name'};
385              
386 1         4 my $object = $self->_get_object($number);
387              
388 1 50       17 die 'object is not a hash' unless $object->isa('HASH');
389              
390 1         4 return _make_result($object->{$name});
391             }
392              
393             =item get_class_attribute($message)
394              
395             Attempt the read a variable from the given class's package.
396             The attribute name should begin with the appropriate sigil
397             (C<$> / C<@> / C<%>).
398              
399             =cut
400              
401             sub get_class_attribute {
402 1     1 1 3 my $self = shift;
403 1         2 my $message = shift;
404              
405 1         2 my $name = $message->{'name'};
406              
407             # Construct full name from sigil + class + '::' + attribute.
408             return $self->_get_attr_or_value(
409 1         9 substr($name, 0, 1) . $message->{'class'} . '::' . substr($name, 1));
410             }
411              
412             =item get_value($message)
413              
414             Try to read the given variable. The variable name should begin
415             with the appropriate sigil (C<$> / C<@> / C<%>).
416              
417             =cut
418              
419             sub get_value {
420 1     1 1 3 my $self = shift;
421 1         2 my $message = shift;
422              
423 1         4 return $self->_get_attr_or_value($message->{'name'});
424             }
425              
426             # _get_attr_or_value($name)
427             #
428             # Internal method to get a value based on its sigil.
429              
430             sub _get_attr_or_value {
431 2     2   4 my $self = shift;
432 2         4 my $name = shift;
433              
434 3     3   24 no strict 'refs';
  3         13  
  3         1458  
435 2 50       14 if ($name =~ s/^\$//) {
    0          
    0          
436 2         7 return _make_result($$name);
437             }
438             elsif ($name =~ s/^\@//) {
439 0         0 return _make_result(\@{$name});
  0         0  
440             }
441             elsif ($name =~ s/^\%//) {
442 0         0 return _make_result(\%{$name});
  0         0  
443             }
444             else {
445 0         0 die 'unknown sigil';
446             }
447             }
448              
449             =item import_module($message)
450              
451             Convert the supplied module name to a path by replacing C<::> with C</>
452             and appending C<.pm>. Then require the resulting module file and
453             call its C<import> subroutine. Any parameters provided are passed
454             to C<import>.
455              
456             =cut
457              
458             sub import_module {
459 1     1 1 3 my $self = shift;
460 1         2 my $message = shift;
461 1         3 my @param = _get_param($message);
462              
463 1         3 my $m = $message->{'name'};
464 1         3 my $f = $m; $f =~ s/::/\//g;
  1         2  
465              
466 1         9 require $f . '.pm';
467 1         7 $m->import(@param);
468              
469 1         7 return $null_result;
470             }
471              
472             =item set_attribute($message)
473              
474             Attempt to set an attribute of an object, but see the notes for
475             C<get_attribute> above.
476              
477             =cut
478              
479             sub set_attribute {
480 1     1 1 3 my $self = shift;
481 1         2 my $message = shift;
482              
483 1         2 my $number = $message->{'number'};
484 1         3 my $name = $message->{'name'};
485 1         2 my $value = $message->{'value'};
486              
487 1         2 my $object = $self->_get_object($number);
488              
489 1 50       41 die 'object is not a hash' unless $object->isa('HASH');
490              
491 1         6 $object->{$name} = $value;
492              
493 1         3 return $null_result;
494             }
495              
496             =item set_class_attribute($message)
497              
498             Attempt to set a variable in the given class's package.
499             The attribute name should begin with the appropriate sigil
500             (C<$> / C<@> / C<%>).
501              
502             =cut
503              
504             sub set_class_attribute {
505 1     1 1 3 my $self = shift;
506 1         2 my $message = shift;
507              
508 1         3 my $name = $message->{'name'};
509              
510             # Construct full name from sigil + class + '::' + attribute.
511             $self->_set_attr_or_value(
512             substr($name, 0, 1) . $message->{'class'} . '::' . substr($name, 1),
513 1         11 $message->{'value'});
514              
515 1         3 return $null_result;
516             }
517              
518             =item set_value($message)
519              
520             Assign to the given variable. The variable name should begin
521             with the appropriate sigil (C<$> / C<@> / C<%>).
522              
523             =cut
524              
525             sub set_value {
526 1     1 1 3 my $self = shift;
527 1         1 my $message = shift;
528              
529 1         5 $self->_set_attr_or_value($message->{'name'}, $message->{'value'});
530              
531 1         3 return $null_result;
532             }
533              
534             # _set_attr_or_value($name, $value)
535             #
536             # Internal method to set a value based on its sigil.
537              
538             sub _set_attr_or_value {
539 2     2   4 my $self = shift;
540 2         3 my $name = shift;
541 2         4 my $value = shift;
542              
543 3     3   24 no strict 'refs';
  3         6  
  3         527  
544 2 50       15 if ($name =~ s/^\$//) {
    0          
    0          
545 2         6 $$name = $value;
546             }
547             elsif ($name =~ s/^\@//) {
548 0           @$name = @$value;
549             }
550             elsif ($name =~ s/^\%//) {
551 0           %{$name} = %$value;
  0            
552             }
553             else {
554 0           die 'unknown sigil';
555             }
556             }
557              
558             1;
559              
560             =back
561              
562             =cut