File Coverage

blib/lib/Apache2/SSI/Notes.pm
Criterion Covered Total %
statement 115 134 85.8
branch 27 58 46.5
condition 10 24 41.6
subroutine 21 24 87.5
pod 11 14 78.5
total 184 254 72.4


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Apache2 Server Side Include Parser's Notes - ~/lib/Apache2/SSI/Notes.pm
3             ## Version v0.1.0
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/01/18
7             ## Modified 2021/01/19
8             ## All rights reserved
9             ##
10             ## This program is free software; you can redistribute it and/or modify it
11             ## under the same terms as Perl itself.
12             ##----------------------------------------------------------------------------
13             package Apache2::SSI::Notes;
14             BEGIN
15             {
16 15     15   86525 use strict;
  15         36  
  15         586  
17 15     15   79 use warnings;
  15         25  
  15         435  
18 15     15   82 use warnings::register;
  15         26  
  15         2409  
19 15     15   513 use parent qw( Module::Generic );
  15         269  
  15         128  
20             ## 512Kb
21 15     15   16913875 use constant MAX_SIZE => 524288;
  15         30  
  15         1015  
22 15     15   6776 use Apache2::SSI::SharedMem ':all';
  15         42  
  15         303  
23 15     15   8164 use Nice::Try;
  15         31  
  15         128  
24 15     15   4468614 our $VERSION = 'v0.1.0';
25             };
26              
27             sub init
28             {
29 63     63 1 1752 my $self = shift( @_ );
30 63         1002 $self->{key} = 'ap2_ssi_notes';
31 63         223 $self->{size} = MAX_SIZE;
32 63         209 $self->{_init_strict_use_sub} = 1;
33 63         390 $self->SUPER::init( @_ );
34 63 50       6228 return( $self->error( "Notes under this system $^O are unsupported." ) ) if( !Apache2::SSI::SharedMem->supported );
35             my $mem = Apache2::SSI::SharedMem->new(
36             key => ( length( $self->{key} ) ? $self->{key} : 'ap2_ssi_notes' ),
37             ## 512 Kb max
38             size => $self->{size},
39             ## Create if necessary
40 63   50     555 create => 1,
41             debug => $self->debug,
42             ) || return( $self->pass_error( Apache2::SSI::SharedMem->error ) );
43 63   50     442 my $shem = $mem->open || return( $self->pass_error( $mem->error ) );
44 63         423 $self->shem( $shem );
45 63         3001 return( $self );
46             };
47              
48 0     0 1 0 sub add { return( shift->set( @_ ) ); }
49              
50             sub clear
51             {
52 1     1 1 683 my $self = shift( @_ );
53 1         3 my $data = {};
54 1 50       4 $self->write_mem( $data ) || return;
55 1         7 return( $self );
56             }
57              
58             sub do
59             {
60 1     1 1 5 my $self = shift( @_ );
61 1         3 my $code = shift( @_ );
62 1         8 my @keys = @_;
63 1 50       9 return( $self->error( "Code provided ($code) is not actually a code reference." ) ) if( ref( $code ) ne 'CODE' );
64 1   50     15 my $data = $self->read_mem || return;
65 1 50       25 @keys = sort( keys( %$data ) ) unless( scalar( @keys ) );
66 1         14 foreach my $k ( @keys )
67             {
68 1         4 my $k_orig = $k;
69 1         3 my $v = $data->{ $k };
70 1         7 try
71 1     1   8 {
72             ## Code can modify values in-place like:
73             ## sub
74             ## {
75             ## $_[1] = 'new value' if( $_[0] eq 'some_key_name' );
76             ## }
77 1         12 $code->( $k, $v );
78             ## Store possibly updated value
79 1         18 $data->{ $k_orig } = $v;
80             }
81 1 50       24 catch( $e )
  1 50       7  
  1 50       9  
  1 0       4  
  1 50       4  
  1         7  
  1         3  
  1         7  
  1         17  
  0         0  
  0         0  
  1         4  
  1         15  
  1         8  
  1         16  
  1         19  
  0         0  
  0         0  
  0         0  
  0         0  
82 0     0   0 {
83 0         0 return( $self->error( "Callback died with error: $e" ) );
84 0 0 0     0 }
  0 0 33     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  1         12  
  0         0  
85             }
86             ## No need to bother if there was no keys in the first place
87 1 50       12 if( scalar( @keys ) )
88             {
89 1 50       13 $self->write_mem( $data ) || return;
90             }
91 1         11 return( $self );
92             }
93              
94             sub get
95             {
96 6     6 1 669 my $self = shift( @_ );
97 6         15 my $key;
98 6 100       21 if( @_ )
99             {
100 3         23 $key = shift( @_ );
101 3 50       18 return( $self->error( "Key provided to retrieve is empty." ) ) if( !length( $key ) );
102             }
103 6   50     73 my $data = $self->read_mem || return;
104             ## As it is the case for the first time, before any write
105 6 50       17 $data = {} if( !ref( $data ) );
106 6 100       30 return( $data ) if( !defined( $key ) );
107 3         43 return( $data->{ $key } );
108             }
109              
110 1     1 0 474 sub key { return( shift->_set_get_scalar( 'key', @_ ) ); }
111              
112             sub read_mem
113             {
114 10     10 1 23 my $self = shift( @_ );
115 10   50     39 my $shem = $self->shem ||
116             return( $self->error( "Oh no, the shared memory object is gone! That should not happen." ) );
117 10         189 my $data;
118 10         42 my $len = $shem->read( $data );
119 10 50       42 return( $self->pass_error( $shem->error ) ) if( !defined( $len ) );
120             ## $self->message( 3, "Data read is: ", sub{ $self->dump( $data ) } );
121 10 100       36 $data = {} unless( ref( $data ) eq 'HASH' );
122 10         47 return( $data );
123             }
124              
125             sub remove
126             {
127 1     1 0 373 my $self = shift( @_ );
128 1   50     11 my $shem = $self->shem ||
129             return( $self->error( "Oh no, the shared memory object is gone! That should not happen." ) );
130 1         26 my $rv;
131 1 50       10 if( !defined( $rv = $shem->remove ) )
132             {
133 0         0 return( $self->pass_error( $shem->error ) );
134             }
135 1         67 return( $rv );
136             }
137              
138             sub set
139             {
140 2     2 1 705 my $self = shift( @_ );
141 2   50     19 my $data = $self->read_mem || return;
142 2         8 my @callinfo = caller;
143             ## $self->message( 3, "Called from file $callinfo[1] at line $callinfo[2]" );
144 2         32 my( $key, $value ) = @_;
145             ## $self->message( 3, "Set key '$key' with value '$value'" );
146 2 50       13 return( $self->error( "Key provided to set value is empty." ) ) if( !length( $key ) );
147 2         16 $data->{ $key } = $value;
148 2 50       25 $self->write_mem( $data ) || return;
149 2         28 return( $self );
150             }
151              
152 79     79 1 678 sub shem { return( shift->_set_get_object_without_init( 'shem', 'Apache2::SSI::SharedMem', @_ ) ); }
153              
154 0     0 1 0 sub size { return( shift->_set_get_scalar( 'size', @_ ) ); }
155              
156 63     63 0 944 sub supported { return( Apache2::SSI::SharedMem->supported ); }
157              
158             sub unset
159             {
160 1     1 1 11 my $self = shift( @_ );
161 1         8 my $key = shift( @_ );
162 1 50       7 return( $self->error( "Key provided to unset value is empty." ) ) if( !length( $key ) );
163 1   50     9 my $data = $self->read_mem || return;
164 1         18 delete( $data->{ $key } );
165 1 50       14 $self->write_mem( $data ) || return;
166 1         15 return( $self );
167             }
168              
169             sub write_mem
170             {
171 5     5 1 15 my $self = shift( @_ );
172 5   50     14 my $shem = $self->shem ||
173             return( $self->error( "Oh no, the shared memory object is gone! That should not happen." ) );
174 5         110 my $data = shift( @_ );
175 5 50       15 return( $self->error( "I was expecting an hash reference and got instead '$data'" ) ) if( ref( $data ) ne 'HASH' );
176 5 50       28 if( !defined( $shem->lock( ( LOCK_EX | LOCK_NB ) ) ) )
177             {
178             ## $self->message( 3, "Error setting a non-blocking lock on the semaphore" );
179 0         0 return( $self->pass_error( $shem->error ) );
180             }
181 5         16 my $rc = $shem->write( $data );
182 5         30 $shem->unlock;
183 5 50       11 return( $self->pass_error( $shem->error ) ) if( !defined( $rc ) );
184 5         26 return( $self );
185             }
186              
187             1;
188              
189             __END__
190              
191             =encoding utf-8
192              
193             =head1 NAME
194              
195             Apache2::SSI::Notes - Apache2 Server Side Include Notes
196              
197             =head1 SYNOPSIS
198              
199             my $notes = Apache2::SSI::Notes->new(
200             # 100K
201             size => 102400,
202             debug => 3,
203             ) || die( Apache2::SSI::Notes->error );
204            
205             $notes->add( key => $val );
206            
207             $notes->clear;
208            
209             $notes->do(sub
210             {
211             # $_[0] = key
212             # $_[1] = value
213             $_[1] = Encode::decode( 'utf8', $_[1] );
214             });
215            
216             # Or specify the keys to check
217             $notes->do(sub
218             {
219             # $_[0] = key
220             # $_[1] = value
221             $_[1] = Encode::decode( 'utf8', $_[1] );
222             }, qw( first_name last_name location ) );
223              
224             my $val = $notes-get( 'name' );
225              
226             # Get all as an hash reference
227             my $hashref = $notes->get;
228              
229             $notes->set( name => 'John Doe' );
230              
231             # remove entry. This is different from $notes->set( name => undef() );
232             # equivalent to delete( $hash->{name} );
233             $notes->unset( 'name' );
234              
235             =head1 VERSION
236              
237             v0.1.0
238              
239             =head1 DESCRIPTION
240              
241             L<Apache2::SSI::Notes> provides a mean to share notes in and out of Apache/mod_perl2 environment.
242              
243             The interface is loosely mimicking L<APR::Table> on some, but not all, methods.
244              
245             So you could have in your script, outside of Apache:
246              
247             $notes->set( API_ID => 1234567 );
248              
249             And then, under mod_perl, in your file:
250              
251             <!--#if expr="note('API_ID')" -->
252              
253             Normally, the C<note> function would work only for values set and retrieved inside the Apache/mod_perl2 framework, but with L<Apache2::SSI::Notes>, you can set a note, say, in a command line script and share it with your Server-Side Includes files.
254              
255             To achieve this sharing of notes, L<Apache2::SSI::Notes> uses shared memory (see L<perlipc>) with L<Apache2::SSI::SharedMem> that does the heavy work.
256              
257             However, this only works when L<Apache2::SSI> is in charge of parsing SSI files. Apache mod_includes module will not recognise notes stored outside of Apache/mod_perl framework.
258              
259             =head1 METHODS
260              
261             =head2 new
262              
263             This instantiates a notes object. It takes the following parameters:
264              
265             =over 4
266              
267             =item I<debug>
268              
269             A debug value will enable debugging output (equal or above 3 actually)
270              
271             =item I<size>
272              
273             The fixed size of the memory allocation. It defaults to 524,288 bytes which is 512 Kb, which should be ample enough.
274              
275             =back
276              
277             An object will be returned if it successfully initiated, or undef() upon error, which can then be retrieved with C<Apache2::SSI::Notes->error>. You should always check the return value of the methods used here for their definedness.
278              
279             my $notes = Apache2::SSI::Notes->new ||
280             die( Apache2::SSI::Notes->error );
281              
282             =head2 add
283              
284             This is an alias for set.
285              
286             =head2 clear
287              
288             Empty all the notes. Beware that this will empty the notes for all the processes, since the notes are stored in a shared memory.
289              
290             =head2 do
291              
292             Provided with a callback as a code reference, and optionally an array of keys, and this will loop through all keys or the given keys if any, and call the callback passing it the key and its value.
293              
294             For example:
295              
296             $notes->do(sub
297             {
298             my( $n, $v ) = @_;
299             if( $n =~ /name/ )
300             {
301             $_[1] = Encode::decode( 'utf8', $_[1] );
302             }
303             });
304              
305             =head2 get
306              
307             Provided with a key and this retrieve its corresponding value, whatever that may be.
308              
309             my $val = $notes->get( 'name' );
310              
311             If no key is provided, it returns all the notes as an hash reference.
312              
313             my $all = $notes->get;
314             print( "API id is $all->{api}\n" );
315              
316             Or maybe
317              
318             print( "API id is ", $notes->get->{api}, "\n" );
319              
320             =head2 read_mem
321              
322             Access the shared memory and return the hash reference stored.
323              
324             If an error occurred, C<undef()> is returned and an L<Module::Generic/error> is set, which can be retrieved like:
325              
326             die( $notes->error );
327              
328             Be careful however, that L</get> may return C<undef()> not because an error would have occurred, but because this is the value you would have previously set.
329              
330             =head2 set
331              
332             Provided with a key and value pair, and this will set its entry into the notes hash accordingly.
333              
334             $notes->set( name => 'John Doe' );
335              
336             It returns the notes object to enable chaining.
337              
338             =head2 shem
339              
340             Returns the current value of the L<Apache2::SSI::SharedMem> object.
341              
342             You can also set an alternative value, but this is not advised unless you know what you are doing.
343              
344             =head2 size
345              
346             Sets or gets the shared memory block size.
347              
348             This should really not be changed. If you do want to change it, you first need to remove the shared memory.
349              
350             $notes->shem->remove;
351              
352             And then create a new L<Apache2::SSI::Notes> object with a different size parameter value.
353              
354             =head2 unset
355              
356             Remove the notes entry for the given key.
357              
358             # No more name key:
359             $notes->unset( 'name' );
360              
361             It returns the notes object to enable chaining.
362              
363             =head2 write_mem
364              
365             Provided with data, and this will write the data to the shared memory.
366              
367             =head1 AUTHOR
368              
369             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
370              
371             CPAN ID: jdeguest
372              
373             L<https://git.deguest.jp/jack/Apache2-SSI>
374              
375             =head1 SEE ALSO
376              
377             mod_include, mod_perl(3), L<APR::Finfo>, L<perlfunc/stat>
378             L<https://httpd.apache.org/docs/current/en/mod/mod_include.html>,
379             L<https://httpd.apache.org/docs/current/en/howto/ssi.html>,
380             L<https://httpd.apache.org/docs/current/en/expr.html>
381             L<https://perl.apache.org/docs/2.0/user/handlers/filters.html#C_PerlOutputFilterHandler_>
382              
383             =head1 COPYRIGHT & LICENSE
384              
385             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
386              
387             You can use, copy, modify and redistribute this package and associated
388             files under the same terms as Perl itself.
389              
390             =cut
391