File Coverage

blib/lib/Alien/Taco/Server.pm
Criterion Covered Total %
statement 124 167 74.2
branch 18 52 34.6
condition 3 12 25.0
subroutine 25 27 92.5
pod 12 12 100.0
total 182 270 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 .
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   812216 use Scalar::Util qw/blessed/;
  3         15  
  3         755  
37              
38 3     3   3016 use Alien::Taco::Transport;
  3         11  
  3         102  
39 3     3   2657 use Alien::Taco::Util qw/filter_struct/;
  3         33  
  3         192  
40              
41 3     3   18 use strict;
  3         6  
  3         6590  
42              
43             our $VERSION = '0.001';
44              
45             =head1 SUBROUTINES
46              
47             =head2 Main Methods
48              
49             =over 4
50              
51             =item new()
52              
53             Set up a L object communicating via
54             C and C.
55              
56             C is selected as the current stream to try to avoid
57             any subroutine or method calls printing to C 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   711 my $self = shift;
89 1         2 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         12 }],
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   2105 my $message = shift;
144              
145 10         18 my @param = ();
146              
147 10 100       29 if (defined $message->{'args'}) {
148 5         7 @param = @{$message->{'args'}};
  5         14  
149             }
150              
151 10 100       28 if (defined $message->{'kwargs'}) {
152 5         9 @param = (@param, %{$message->{'kwargs'}});
  5         21  
153             }
154              
155 10         42 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 10     10   206 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   452 my $self = shift;
177             filter_struct(shift, sub {
178 3     3   6 my $x = shift;
179 3 50       34 blessed($x) and not JSON::is_bool($x);
180             },
181             sub {
182 3     3   11 my $nn = my $n = ++ $self->{'nobject'};
183 3         11 $self->{'objects'}->{$nn} = shift;
184 3         21 return {_Taco_Object_ => $n};
185 3         33 });
186             }
187              
188             # _delete_object($number)
189             #
190             # Delete an object from the cache.
191              
192             sub _delete_object {
193 2     2   444 my $self = shift;
194 2         4 my $n = shift;
195 2         8 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   706 my $self = shift;
204 8         12 my $n = shift;
205 8         39 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.
218              
219             =cut
220              
221             sub call_class_method {
222 1     1 1 17 my $self = shift;
223 1         2 my $message = shift;
224              
225 1         17 my $c = $message->{'class'};
226 1         2 my $f = $message->{'name'};
227 1         2 my @param = _get_param($message);
228              
229 1         1 my $result = undef;
230 1 50 33     12 unless (defined $message->{'context'}
    50          
    50          
    50          
231             and $message->{'context'} ne 'scalar') {
232 0         0 $result = $c->$f(@param);
233             }
234             elsif ($message->{'context'} eq 'list') {
235 0         0 my @result = $c->$f(@param);
236 0         0 $result = \@result;
237             }
238             elsif ($message->{'context'} eq 'map') {
239 0         0 my %result = $c->$f(@param);
240 0         0 $result = \%result;
241             }
242             elsif ($message->{'context'} eq 'void') {
243 1         6 $c->$f(@param);
244             }
245             else {
246 0         0 die 'unknown context: ' . $message->{'context'};
247             }
248              
249 1         6 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 followed by the I 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 2 my $self = shift;
271 1         1 my $message = shift;
272              
273 1         2 my $f = \&{$message->{'name'}};
  1         4  
274 1         3 my @param = _get_param($message);
275              
276 1         1 my $result = undef;
277 1 50 33     9 unless (defined $message->{'context'}
    0          
    0          
    0          
278             and $message->{'context'} ne 'scalar') {
279 1         3 $result = $f->(@param);
280             }
281             elsif ($message->{'context'} eq 'list') {
282 0         0 my @result = $f->(@param);
283 0         0 $result = \@result;
284             }
285             elsif ($message->{'context'} eq 'map') {
286 0         0 my %result = $f->(@param);
287 0         0 $result = \%result;
288             }
289             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         6 return _make_result($result);
297             }
298              
299             =item call_method($message)
300              
301             Call an object method, similarly to C.
302              
303             =cut
304              
305             sub call_method {
306 2     2 1 4 my $self = shift;
307 2         5 my $message = shift;
308              
309 2         3 my $number = $message->{'number'};
310 2         5 my $name = $message->{'name'};
311 2         5 my @param = _get_param($message);
312              
313 2         13 my $object = $self->_get_object($number);
314              
315 2         3 my $result = undef;
316 2 50 33     21 unless (defined $message->{'context'}
    100          
    50          
    0          
317             and $message->{'context'} ne 'scalar') {
318 0         0 $result = $object->$name(@param);
319             }
320             elsif ($message->{'context'} eq 'list') {
321 1         6 my @result = $object->$name(@param);
322 1         7 $result = \@result;
323             }
324             elsif ($message->{'context'} eq 'map') {
325 1         5 my %result = $object->$name(@param);
326 1         18 $result = \%result;
327             }
328             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 1 my $self = shift;
346 1         2 my $message = shift;
347              
348 1         2 my $c = $message->{'class'};
349 1         3 my @param = _get_param($message);
350              
351 1         4 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 381 my $self = shift;
362 1         2 my $message = shift;
363              
364 1         2 my $n = $message->{'number'};
365 1         6 $self->_delete_object($n);
366              
367 1         5 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 2 my $self = shift;
381 1         1 my $message = shift;
382              
383 1         3 my $number = $message->{'number'};
384 1         2 my $name = $message->{'name'};
385              
386 1         16 my $object = $self->_get_object($number);
387              
388 1 50       6 die 'object is not a hash' unless $object->isa('HASH');
389              
390 1         14 return _make_result($object->{$name});
391             }
392              
393             =item get_value($message)
394              
395             Try to read the given variable. The variable name should begin
396             with the appropriate sigil (C<$> / C<@> / C<%>).
397              
398             =cut
399              
400             sub get_value {
401 1     1 1 2 my $self = shift;
402 1         2 my $message = shift;
403              
404 1         2 my $name = $message->{'name'};
405              
406 3     3   27 no strict 'refs';
  3         4  
  3         1424  
407 1 50       10 if ($name =~ s/^\$//) {
    0          
    0          
408 1         5 return _make_result($$name);
409             }
410             elsif ($name =~ s/^\@//) {
411 0         0 return _make_result(\@{$name});
  0         0  
412             }
413             elsif ($name =~ s/^\%//) {
414 0         0 return _make_result(\%{$name});
  0         0  
415             }
416             else {
417 0         0 die 'unknown sigil';
418             }
419             }
420              
421             =item import_module($message)
422              
423             Convert the supplied module name to a path by replacing C<::> with C
424             and appending C<.pm>. Then require the resulting module file and
425             call its C subroutine. Any parameters provided are passed
426             to C.
427              
428             =cut
429              
430             sub import_module {
431 1     1 1 2 my $self = shift;
432 1         2 my $message = shift;
433 1         4 my @param = _get_param($message);
434              
435 1         4 my $m = $message->{'name'};
436 1         2 my $f = $m; $f =~ s/::/\//g;
  1         3  
437              
438 1         12 require $f . '.pm';
439 1         7 $m->import(@param);
440              
441 1         8 return $null_result;
442             }
443              
444             =item set_attribute($message)
445              
446             Attempt to set an attribute of an object, but see the notes for
447             C above.
448              
449             =cut
450              
451             sub set_attribute {
452 1     1 1 2 my $self = shift;
453 1         3 my $message = shift;
454              
455 1         2 my $number = $message->{'number'};
456 1         3 my $name = $message->{'name'};
457 1         1 my $value = $message->{'value'};
458              
459 1         13 my $object = $self->_get_object($number);
460              
461 1 50       6 die 'object is not a hash' unless $object->isa('HASH');
462              
463 1         3 $object->{$name} = $value;
464              
465 1         3 return $null_result;
466             }
467              
468             =item set_value($message)
469              
470             Assign to the given variable. The variable name should begin
471             with the appropriate sigil (C<$> / C<@> / C<%>).
472              
473             =cut
474              
475             sub set_value {
476 1     1 1 2 my $self = shift;
477 1         12 my $message = shift;
478              
479 1         7 my $name = $message->{'name'};
480 1         6 my $value = $message->{'value'};
481              
482 3     3   17 no strict 'refs';
  3         156  
  3         448  
483 1 50       8 if ($name =~ s/^\$//) {
    0          
    0          
484 1         3 $$name = $value;
485             }
486             elsif ($name =~ s/^\@//) {
487 0         0 @$name = @$value;
488             }
489             elsif ($name =~ s/^\%//) {
490 0         0 %{$name} = %$value;
  0         0  
491             }
492             else {
493 0         0 die 'unknown sigil';
494             }
495              
496 1         3 return $null_result;
497             }
498              
499             1;
500              
501             =back
502              
503             =cut