File Coverage

blib/lib/CouchDB/ExternalProcess.pm
Criterion Covered Total %
statement 86 108 79.6
branch 8 14 57.1
condition 5 16 31.2
subroutine 21 26 80.7
pod 7 7 100.0
total 127 171 74.2


line stmt bran cond sub pod time code
1             package CouchDB::ExternalProcess;
2              
3 6     6   149593 use strict;
  6         13  
  6         353  
4 6     6   29 use warnings;
  6         10  
  6         158  
5              
6 6     6   12971 use Attribute::Handlers;
  6         41859  
  6         38  
7 6     6   6873 use JSON::Any;
  6         160293  
  6         45  
8              
9             BEGIN {
10 6     6   45584 use Exporter ();
  6         15  
  6         158  
11 6     6   71 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  6         11  
  6         779  
12 6     6   18 $VERSION = '0.02';
13 6         139 @ISA = qw(Exporter);
14             #Give a hoot don't pollute, do not export more than needed by default
15 6         20 @EXPORT = qw();
16 6         11 @EXPORT_OK = qw();
17 6         6011 %EXPORT_TAGS = ();
18             }
19              
20             my %actions = (
21             _meta => \&_meta
22             );
23             my %metadata;
24              
25             =head1 NAME
26              
27             CouchDB::ExternalProcess - Make creating Perl-based external processs for CouchDB
28             easy
29              
30             =head1 SYNOPSIS
31              
32             In C:
33              
34             package MyProcess;
35             use base qw/CouchDB::ExternalProcess/;
36              
37             sub _before {
38             my ($self, $request) = @_;
39             # Do something with the hashref $request
40             return $request;
41             }
42              
43             sub hello_world :Action {
44             my ($self, $req) = @_;
45             my $response = {
46             body => "Hello, " . $req->{query}->{greeting_target} . "!"
47             };
48             return $response;
49             }
50              
51             sub _after {
52             my ($self,$response) = @_;
53             # Do something with the hashref $response
54             return $response;
55             }
56              
57             In CouchDB's C:
58              
59             [external]
60             my_process = perl -MMyProcess -e 'MyProcess->new->run'
61              
62             [httpd_db_handlers]
63             _my_process = {couch_httpd_external, handle_external_req, <<"my_process">>}
64              
65              
66             Now queries to the database I as:
67            
68             http://myserver/databaseName/_my_process/hello_world/?greeting_target=Sally
69              
70             Will return a document with Content-Type "text/html" and a body containing:
71              
72             Hello, Sally!
73              
74             For more information, including the request and response data structure formats,
75             see:
76              
77             L
78            
79             =head1 DESCRIPTION
80              
81             This module makes creating CouchDB External Processes simple and concise.
82              
83             =head1 USAGE
84              
85              
86             =head1 METHODS
87              
88             =cut
89              
90             =head2 new
91              
92             Create an external process, just needs C to be called to start processing
93             STDIN.
94              
95             =cut
96             sub new
97             {
98 5     5 1 200 my ($class, %parameters) = @_;
99              
100 5   33     50 my $self = bless ({}, ref ($class) || $class);
101              
102 5         54 $self->jsonParser(JSON::Any->new);
103              
104 5         18 return $self;
105             }
106              
107             =head2 run
108              
109             Run the action, read lines from STDIN and process them one by one
110              
111             Named arguments can be passed to run like run(a => 1, c => 2).
112              
113             Accepted arguments are:
114              
115             =over
116              
117             =item in_fh
118              
119             File Handle to read input from. *STDIN by default
120              
121             =item out_fh
122              
123             File handle to write output to. *STDOUT by default
124              
125             =back
126              
127             =cut
128             sub run {
129 1     1 1 4968 my $self = shift;
130 1         90 my %opts = @_;
131              
132 1   33     48 my $in_fh = $opts{in_fh} || *STDIN;
133 1   33     48 my $out_fh = $opts{out_fh} || *STDOUT;
134              
135             $SIG{__DIE__} = sub {
136 0     0   0 close($in_fh);
137 0         0 close($out_fh);
138 0         0 $self->_destroy();
139 0         0 print STDERR "Error In ExternalProcess '".(ref $self)."': @_";
140 0         0 exit();
141 1         157 };
142              
143 1         80 $self->_init;
144              
145 1         69 $| = 1;
146 1         96 while(my $reqJson = <$in_fh>) {
147 1         39 my $output = $self->_process($reqJson);
148 1         121 print $out_fh $output . $/;
149             }
150              
151 1         11 close($in_fh);
152 1         36 close($out_fh);
153              
154 1         8 $self->_destroy;
155             }
156              
157             =head2 jsonParser
158              
159             getter/setter for the JSON::Any instance used for an instance.
160              
161             All methods of an ExternalProcess class should use this processor so they can
162             share the same magical 'true' and 'false' markers.
163              
164             =cut
165             sub jsonParser {
166 23     23 1 352298 my ($self, $jp) = @_;
167 23 100       132 $self->{jsonParser} = $jp if $jp;
168 23         184 return $self->{jsonParser};
169             }
170              
171             =head1 CHILD CLASS METHODS
172              
173             These methods may be overridden by child classes to add processing to various
174             parts of the script and request handling lifecycle
175              
176             =cut
177              
178             =head2 _init
179              
180             Called at program startup before any requests are processed.
181              
182             =cut
183 1     1   247 sub _init {
184             }
185              
186             =head2 _destroy
187              
188             Called when STDIN is closed, or at program termination (if possible)
189              
190             =cut
191 0     0   0 sub _destroy {
192             }
193              
194             =head2 _before
195              
196             Receives, and can manipulate or replace, the JSON request as hash reference
197             produced by JSON::Any before the requested action is processed.
198              
199             =cut
200             sub _before {
201 4     4   11 return $_[1];
202             }
203              
204             =head2 _after
205              
206             Passed the return value of whatever action was called, as a hash reference
207             parseable by JSON::Any. May modify or replace it.
208              
209             =cut
210             sub _after {
211 4     4   11 return $_[1];
212             }
213              
214             =head2 _error
215              
216             Passed any errors that occur during processing. Returns a hash reference to be
217             used as the response.
218              
219             The default response for an error $error is:
220              
221             {
222             code => 500,
223             json => {
224             error => $error
225             }
226             }
227              
228             =cut
229              
230             sub _error {
231 1     1   3 my ($self, $error) = @_;
232             return {
233 1         5 code => 500,
234             json => {
235             error => $error
236             }
237             }
238             }
239              
240             =head2 _extract_action_name
241              
242             Extracts the name of the action to handle a request.
243              
244             Receives the request object. Defaults to:
245              
246             C<$req->{path}->[2]>
247              
248             =cut
249             sub _extract_action_name {
250 6     6   20 my ($self,$req) = @_;
251 6         22 return $req->{path}->[2];
252             }
253              
254             =head1 PROVIDED ACTIONS
255              
256             =head2 _meta
257              
258             Returns metadata about the methods we're providing
259              
260             If your module has the following Actions:
261              
262             sub foo :Action :Description("Foo!") :Args("Some data") {
263             # ...
264             }
265              
266             sub bar :Action
267             :Description("Get your Bar on!")
268             :Args({name => "Name of something", color => "RGB Color Value"})
269             {
270             # ...
271             }
272              
273             Then requesting the '_meta' action will return the following JSON:
274              
275             {
276             "foo": {
277             "description": "Foo!",
278             "args": "Some data"
279             },
280             "bar": }
281             "description": "Get your Bar on!",
282             "args": "Some data"
283             }
284             }
285              
286             =cut
287             sub _meta {
288             return {
289 0     0   0 json => \%metadata,
290             };
291             }
292              
293             =head1 INTERNAL METHODS - Ignore these!
294              
295             =head2 _process
296              
297             Process a request.
298              
299             Receives one argument, a JSON string, does all CouchDB::ExternalProcess
300             processing and returns a valid External Process response.
301              
302             =cut
303             sub _process {
304 6     6   3818 my ($self, $reqJson) = @_;
305              
306 6         35 my $req = $self->jsonParser->jsonToObj($reqJson);
307 6         463 my $response = {};
308              
309             # TODO: Strip first component off and use that as name?
310 6         48 my $actionName = $self->_extract_action_name($req);
311              
312 6         95 eval {
313             # Do we have the requested action ...
314 6 100 66     117 if(!defined($actionName) || !defined($actions{$actionName})) {
315 1         6 die("The specified action is not defined\n");
316             }
317              
318             # Run _before
319 5         51 $req = $self->_before($req);
320              
321             # Run the action
322 5         62 $response = $actions{$actionName}->($self, $req);
323              
324             # Run _after
325 5         78 $response = $self->_after($response);
326             };
327              
328 6 100       29 if($@) {
329 1         3 chomp($@);
330 1         2 my $error = $@;
331 1         1 eval {
332 1         7 $response = $self->_error($error);
333             };
334 1 50       4 if($@) {
335 0         0 $response->{code} = 500;
336 0         0 $response->{json}->{error} = [ $error, $@ ];
337             }
338             }
339              
340 6         25 return $self->jsonParser->objToJson($response);
341             }
342              
343              
344             =head2 Action
345              
346             Processes 'Action' Attribute
347              
348             =cut
349             sub Action :ATTR {
350 6     6 1 6501 my $args = attrArgs(@_);
351 6         10 my $subName = *{$args->{symbol}}{NAME};
  6         18  
352              
353 6         22 my @reservedNames = qw/
354             _meta _init _error _destroy _before _after _process new run
355             /;
356              
357 6 50       15 if(grep { $_ eq $subName} @reservedNames) {
  54         98  
358 0         0 die("'$subName' is a reserved method name and cannot be used as an action name");
359             }
360              
361 6         33 $actions{ $subName } = $args->{referent};
362 6     6   44 }
  6         11  
  6         36  
363              
364             =head2 Description
365              
366             Processes 'Description' Attribute
367              
368             =cut
369             sub Description :ATTR {
370 0     0 1 0 my $args = attrArgs(@_);
371 0 0       0 die(":Description attribute must specify a string describing the method")
372             unless $args->{data};
373 0         0 my $subName = *{$args->{symbol}}{NAME};
  0         0  
374 0   0     0 $metadata{ $subName } ||= {};
375 0         0 $metadata{ $subName }->{description} = $args->{data};
376 6     6   3003 }
  6         75  
  6         27  
377              
378             =head2 Args
379              
380             Processes 'Args' Attribute
381              
382             =cut
383             sub Args :ATTR {
384 0     0 1 0 my $args = attrArgs(@_);
385 0 0       0 die(":Args attribute must specify a list of arguments the method accepts")
386             unless $args->{data};
387 0         0 my $subName = *{$args->{symbol}}{NAME};
  0         0  
388 0   0     0 $metadata{ $subName } ||= {};
389 0         0 $metadata{ $subName }->{args} = $args->{data};
390 6     6   2783 }
  6         10  
  6         35  
391              
392             =head2 attrArgs
393              
394             Helper method to process Attribute::Handlers arguments
395              
396             =cut
397             sub attrArgs {
398 6     6 1 11 my %args;
399 6         46 @args{qw/ package symbol referent attr data phase filename linenum /} = @_;
400 6         23 return \%args;
401             }
402              
403             =head1 BUGS
404              
405              
406              
407             =head1 SUPPORT
408              
409              
410              
411             =head1 AUTHOR
412              
413             Mike Walker
414             CPAN ID: FANSIPANS
415             mike-cpan-couchdb-externalprocess@napkindrawing.com
416             http://napkindrawing.com/
417              
418             =head1 COPYRIGHT
419              
420             This program is free software licensed under the...
421              
422             The BSD License
423              
424             The full text of the license can be found in the
425             LICENSE file included with this module.
426              
427              
428             =head1 SEE ALSO
429              
430             perl(1).
431              
432             CouchDB ExternalProcesses L
433              
434             =cut
435              
436             #################### main pod documentation end ###################
437              
438              
439             1;
440             # The preceding line will help the module return a true value
441