File Coverage

blib/lib/XAS/Lib/Stomp/Frame.pm
Criterion Covered Total %
statement 30 121 24.7
branch 0 28 0.0
condition 0 2 0.0
subroutine 10 20 50.0
pod 2 2 100.0
total 42 173 24.2


line stmt bran cond sub pod time code
1             package XAS::Lib::Stomp::Frame;
2              
3             our $VERSION = '0.03';
4              
5 1     1   793 use XAS::Constants 'CRLF LF :stomp';
  1         1  
  1         26  
6              
7             use XAS::Class
8 1         11 debug => 0,
9             version => $VERSION,
10             base => 'XAS::Base',
11             utils => 'dotid',
12             accessors => 'eol header',
13             mutators => 'command body',
14             codec => 'unicode',
15             vars => {
16             PARAMS => {
17             -body => { optional => 1, default => undef },
18             -command => { optional => 1, default => undef },
19             -headers => { optional => 1, default => undef },
20             -target => { optional => 1, default => undef, regex => STOMP_LEVELS },
21             }
22             }
23 1     1   274 ;
  1         1  
24              
25             our %ENCODE_MAP = (
26             "\r" => "\\r",
27             "\n" => "\\n",
28             ":" => "\\c",
29             "\\" => "\\\\",
30             );
31              
32             our %DECODE_MAP = reverse %ENCODE_MAP;
33              
34             #use Data::Dumper;
35             #use Data::Hexdumper;
36              
37             # ----------------------------------------------------------------------
38             # Public Methods
39             # ----------------------------------------------------------------------
40              
41             sub as_string {
42 0     0 1   my $self = shift;
43              
44             # protocol spec is unclear about the case of the command,
45             # so uppercase the command, Why, just because I can.
46              
47 0           my $frame;
48 0           my $command = uc($self->command);
49 0           my $headers = $self->header->devolve;
50 0           my $body = $self->body;
51              
52             # special handling for NOOPs
53              
54 0 0         if ($command eq 'NOOP') {
55              
56 0           $command = '';
57 0           $headers = {};
58 0           $body = '';
59              
60             }
61              
62 0 0         if ($self->target > 1.1) {
63              
64 0           $frame = encode('utf8', $command) . $self->eol;
65              
66             } else {
67              
68 0           $frame = $command . $self->eol;
69              
70             }
71              
72             # v1.0 and v1.1 is unclear about spaces between headers and values
73             # nor the case of the header.
74             #
75             # v1.2 says there should be no 'padding' in headers and values, not
76             # sure what 'padding' means. It also adds the capability to 'escape'
77             # certain values. Please see %ENCODE_MAP and %DECODE_MAP for those
78             # values.
79             #
80             # So add a space and lowercase the header. Why, just because I can.
81              
82 0 0         if (keys %{$headers}) {
  0            
83              
84 0 0         $self->_encode_headers(\$headers) if ($self->target > 1.1);
85              
86 0           while (my ($key, $value) = each(%{$headers})) {
  0            
87              
88 0 0         if (defined($value)) {
89              
90 0           $frame .= lc($key) . ': ' . $value . $self->eol();
91              
92             }
93              
94             }
95              
96             } else {
97              
98 0           $frame .= $self->eol();
99              
100             }
101              
102 0           $frame .= $self->eol();
103 0           $frame .= $body;
104 0           $frame .= STOMP_EOF;
105              
106 0           return $frame;
107              
108             }
109              
110             # ----------------------------------------------------------------------
111             # Private Methods
112             # ----------------------------------------------------------------------
113              
114             sub init {
115 0     0 1   my $class = shift;
116              
117 0           my $self = $class->SUPER::init(@_);
118            
119 0 0         unless (defined($self->{'target'})) {
120              
121 0           $self->{'target'} = $self->env->mqlevel;
122              
123             }
124              
125 0   0       my $headers = $self->headers || {};
126              
127 0 0         $self->{'eol'} = ($self->target > 1.1) ? CRLF : LF;
128              
129 0 0         $self->_decode_headers(\$headers) if ($self->target > 1.1);
130 0           $self->{'header'} = XAS::Lib::Stomp::Frame::Headers->new($headers);
131              
132 0           return $self;
133              
134             }
135              
136             sub _encode_headers {
137 0     0     my $self = shift;
138 0           my $headers = shift; # a pointer to a reference of a hash, oh my...
139              
140 0           my $ENCODE_KEYS = '['.join('', map(sprintf('\\x%02x', ord($_)), keys(%ENCODE_MAP))).']';
141              
142 0           while (my ($k, $v) = each(%$$headers)) {
143              
144 0           $k = encode('utf8', $k);
145 0           $v = encode('utf8', $v);
146              
147 0           $v =~ s/($ENCODE_KEYS)/$ENCODE_MAP{$1}/ego;
  0            
148 0           $k =~ s/($ENCODE_KEYS)/$ENCODE_MAP{$1}/ego;
  0            
149              
150 0           $$headers->{$k} = $v;
151              
152             }
153              
154             }
155              
156             sub _decode_headers {
157 0     0     my $self = shift;
158 0           my $headers = shift; # a pointer to a reference of a hash, oh my...
159              
160 0           while (my ($k, $v) = each(%$$headers)) {
161              
162 0           $k = decode('utf8', $k);
163 0           $v = decode('utf8', $v);
164              
165 0 0         if ($v =~ m/(\\.)/) {
166              
167 0 0         unless ($v =~ s/(\\.)/$DECODE_MAP{$1}/eg) {
  0            
168              
169 0           $self->throw_msg(
170             dotid($self->class) . '.decode_header.badval',
171             'stomp_badval',
172             );
173              
174             }
175              
176             }
177              
178 0 0         if ($k =~ m/(\\.)/) {
179              
180 0 0         unless ($k =~ s/(\\.)/$DECODE_MAP{$1}/eg) {
  0            
181              
182 0           $self->throw_msg(
183             dotid($self->class) . '.decode_header.badkey',
184             'stomp_badkey'
185             );
186              
187             }
188              
189             }
190              
191 0           $$headers->{$k} = $v;
192              
193             }
194              
195             }
196              
197             package # hide from pause...
198             XAS::Lib::Stomp::Frame::Headers;
199              
200             our $VERSION = '0.02';
201              
202 1     1   1335 use XAS::Lib::Set::Light;
  1         1  
  1         37  
203              
204             use XAS::Class
205 1         3 debug => 0,
206             version => $VERSION,
207             base => 'XAS::Base',
208             constants => 'REFS',
209             accessors => 'methods',
210 1     1   3 ;
  1         2  
211              
212             #use Data::Dumper;
213              
214             sub remove {
215 0     0     my ($self, $key) = @_;
216              
217 0           $self->methods->remove($key);
218              
219 0           delete($self->{$key});
220              
221 1     1   427 no warnings;
  1         2  
  1         45  
222 1     1   4 no strict REFS;
  1         2  
  1         81  
223              
224 0           *$key = undef;
225              
226             }
227              
228             sub add {
229 0     0     my ($self, $key, $value) = @_;
230              
231 0           $key =~ s/-/_/g;
232              
233 0           $self->{$key} = $value;
234 0           $self->methods->insert($key);
235              
236 1     1   3 no warnings;
  1         2  
  1         30  
237 1     1   4 no strict REFS;
  1         0  
  1         185  
238              
239             *$key = sub {
240 0     0     my $self = shift;
241 0 0         $self->{$key} = shift if @_;
242 0           return $self->{$key};
243 0           };
244              
245             }
246              
247             sub devolve {
248 0     0     my $self = shift;
249              
250 0           my $value;
251 0           my $header = {};
252              
253 0           foreach my $key ($self->methods->items()) {
254              
255 0           $value = $self->{$key};
256 0           $key =~ s/_/-/g;
257 0           $header->{$key} = $value;
258              
259             }
260              
261 0           return $header;
262              
263             }
264              
265             sub init {
266 0     0     my $self = shift;
267 0           my $configs = shift;
268              
269 0           $self->{'config'} = $configs;
270 0           $self->{'methods'} = XAS::Lib::Set::Light->new();
271              
272             # turn frame headers into mutators of there values
273              
274 0           while (my ($key, $value) = each(%$configs)) {
275              
276 0           $key =~ s/-/_/g;
277              
278 0           $self->{$key} = $value;
279 0           $self->methods->insert($key);
280              
281 1     1   4 no warnings;
  1         0  
  1         30  
282 1     1   4 no strict REFS;
  1         1  
  1         78  
283              
284             *$key = sub {
285 0     0     my $self = shift;
286 0 0         $self->{$key} = shift if @_;
287 0           return $self->{$key};
288 0           };
289              
290             }
291              
292 0           return $self;
293              
294             }
295              
296             1;
297              
298             __END__
299              
300             =head1 NAME
301              
302             XAS::Lib::Stomp::Frame - A STOMP Frame
303              
304             =head1 SYNOPSIS
305              
306             use XAS::Lib::Stomp::Frame;
307              
308             my $frame = XAS::Lib::Stomp::Frame->new(
309             -command => $command,
310             -headers => $headers,
311             -body => $body,
312             );
313              
314             ... or ...
315              
316             my $frame = XAS:::Lib::Stomp::Frame->new();
317              
318             $frame->command('MESSAGE');
319             $frame->header->add('destination', '/queue/foo');
320             $frame->body('this is the body');
321              
322             ... stringification ...
323              
324             my $string = $frame->as_string;
325              
326             =head1 DESCRIPTION
327              
328             This module encapsulates a STOMP frame. STOMP is the Streaming Text
329             Orientated Messaging Protocol (or the Protocol Briefly Known as TTMP
330             and Represented by the symbol :ttmp). It's a simple and easy to
331             implement protocol for working with Message Orientated Middleware from
332             any language.
333              
334             A STOMP frame consists of a command, a series of headers and a body.
335              
336             =head1 METHODS
337              
338             =head2 new
339              
340             Create a new XAS::Lib::Stomp::Frame object:
341              
342             my $frame = XAS::Lib::Stomp::Frame->new(
343             -command => $command,
344             -headers => $headers,
345             -body => $body,
346             );
347              
348             It can take the following parameters:
349              
350             =over 4
351              
352             =item B<-command>
353              
354             The command verb.
355              
356             =item B<-headers>
357              
358             Headers for this command. This supports the 'bytes_message' header which
359             indicates a binary body.
360              
361             =item B<-body>
362              
363             A body for the command.
364              
365             =back
366              
367             =head2 as_string
368              
369             Create a buffer from the serialized frame.
370              
371             my $buffer = $frame->as_string;
372              
373             =head2 header
374              
375             This returns a XAS::Lib::Stomp::Frame::Headers object. This object contains
376             auto generated mutators of the header fields in a STOMP frame.
377              
378             =head1 MUTATORS
379              
380             =head2 command
381              
382             This get/sets STOMP frames command verb.
383              
384             =head2 body
385              
386             This get/sets the body of the STOMP frame.
387              
388             =head1 XAS::Lib::Stomp::Frame::Headers
389              
390             This is an internal class that auto generates mutators for the headers in a
391             STOMP frame. Any dashes in the header names are converted to underscores
392             for the mutators name.
393              
394             Example, a header of:
395              
396             content-type: test/plain
397              
398             Will become the mutator content_type().
399              
400             The usual way to access the headers is as follows:
401              
402             my $type = $frame->header->content_type;
403              
404             $frame->header->content_type('text/plain');
405              
406             The following methods are also available.
407              
408             =head2 devolve
409              
410             This will create a hash with header/value pairs. Any underscores are
411             converted to dashes in the headers name. Primarily used during
412             stringification of the STOMP frame.
413              
414             =head2 methods
415              
416             Returns a L<Set::Light|Set::Light> object of available methods.
417              
418             =head2 add($name, $value)
419              
420             This will add a header.
421              
422             =over 4
423              
424             =item B<$name>
425              
426             The name of the header.
427              
428             =item B<$value>
429              
430             The value for the header.
431              
432             =back
433              
434             =head2 remove($name)
435              
436             This will remove a header.
437              
438             =over 4
439              
440             =item B<$name>
441              
442             The name of the header to remove.
443              
444             =back
445              
446             =head1 ACKNOWLEDGEMENTS
447              
448             This module is based on L<Net::Stomp::Frame|https://metacpan.org/pod/Net::Stomp::Frame> by Leon Brocard <acme@astray.com>.
449              
450             =head1 SEE ALSO
451              
452             =over 4
453              
454             =item L<XAS|XAS>
455              
456             =back
457              
458             For more information on the STOMP protocol, please refer to: L<http://stomp.github.io/> .
459              
460             =head1 AUTHOR
461              
462             Kevin L. Esteb, E<lt>kevin@kesteb.usE<gt>
463              
464             =head1 COPYRIGHT AND LICENSE
465              
466             Copyright (C) 2014 Kevin L. Esteb
467              
468             This is free software; you can redistribute it and/or modify it under
469             the terms of the Artistic License 2.0. For details, see the full text
470             of the license at http://www.perlfoundation.org/artistic_license_2_0.
471              
472             =cut