File Coverage

lib/FSSM/SOAPClient.pm
Criterion Covered Total %
statement 24 26 92.3
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 32 34 94.1


line stmt bran cond sub pod time code
1             #$Id: SOAPClient.pm 596 2010-01-20 17:45:52Z maj $
2             package FSSM::SOAPClient;
3 1     1   109196 use strict;
  1         7  
  1         60  
4 1     1   9 use warnings;
  1         2  
  1         107  
5              
6             =head1 NAME
7              
8             FSSM::SOAPClient - Access the Fortinbras FSSM web service
9              
10             =head1 SYNOPSIS
11              
12             # create client
13             my $client = FSSM::SOAPClient->new();
14              
15             # set parameters
16             $client->search('none');
17             $client->predictor('subtype B SI/NSI');
18             $client->expansion('avg');
19             $client->seqtype('nt');
20             # or...
21             $client->new( search => 'align', expansion => 'avg',
22             seqtype => 'nt', predictor => 'subtype C SI/NSI' );
23              
24             # attach sequences
25             $client->attach_seqs('my.fas');
26              
27             # run query
28             my $result = $client->run;
29              
30             # parse query
31             while ( my $item = $result->next_call ) {
32             print $item->{seqid}, "\t";
33             if ($item->{predicted}) {
34             print "predicted SI\n";
35             }
36             else {
37             print "predicted NSI\n";
38             }
39             }
40              
41             =head1 DESCRIPTION
42              
43             This module allows the user to conveniently call the HIV-1 coreceptor
44             predictor web service at L and parse the
45             resulting analysis. For details about this service and its purpose,
46             please visit L.
47              
48             The external module L is required, and is available from CPAN.
49              
50             =head1 USAGE
51              
52             The basic steps are (1) create a client object, (2) set client
53             parameters, (3) attach a set of nucleotide or amino acid sequences,
54             (4) run the query to obtain a result object, (5) iterate the result
55             object to obtain the analysis for each sequence.
56              
57             =over
58              
59             =item Create a client
60              
61             The client object is a 'factory', from which you can set parameters,
62             attach sequences, and run your query.
63              
64             my $client = FSSM::SOAPClient->new();
65              
66             =item Set parameters
67              
68             Parameters for a query include:
69              
70             Parameter Function Acceptable values
71             ========= =========== =================
72             search how to find V3 none | fast | align
73             expansion handle ambiguities none | avg | full
74             seqtype residue type aa | nt | auto
75             predictor desired matrix names as given at
76             http://fortinbras.us/fssm
77              
78             To set parameters, call the corresponding method from the client:
79              
80             $client->search('none');
81             $client->predictor('subtype B SI/NSI');
82              
83             or use C:
84              
85             $client->set_parameters( search => 'none', expansion => 'avg' );
86              
87             Parameters can also be set when the client is created:
88              
89             $client->new( search => 'align', expansion => 'avg',
90             seqtype => 'nt', predictor => 'subtype C SI/NSI' );
91              
92             For details on the meaning of these parameters, see
93             L.
94              
95             If you forget the available parameters or their acceptable values, use
96             C:
97              
98             @parameter_names = $client->available_parameters;
99             @accepted_for_search = $client->available_parameters('search');
100              
101             =item Attach sequences
102              
103             To attach your sequences, call C. You may specify
104              
105             =over
106              
107             =item * a FASTA-formatted file:
108              
109             $client->attach_seqs('my.fas');
110              
111             =item * a hash reference with elements of the form C<$seq_id => $sequence>:
112              
113             $client->attach_seqs( { 'seq1' => 'ATC', 'seq2' => 'GGC' } )
114              
115             =item * an array reference with hashref elements of the form
116             C<{ seqid => $id, sequence => $sequence }>:
117              
118             @seqs = ( { seqid => $id, sequence => $sequence } );
119             $client->attach_seqs(\@seqs);
120              
121             =item * or, if you use BioPerl (L), an arrayref of
122             BioPerl sequence objects of any type:
123              
124             @seqs = $align->each_seq;
125             $client->attach_seqs( \@seqs );
126              
127             =back
128              
129             =item Running a query
130              
131             Simply call C :
132              
133             my $result = $client->run;
134              
135             =item Parsing the result
136              
137             The result is returned in another Perl object (of class
138             C). Use C from this object to
139             iterate through the analyses:
140              
141             while ( my $item = $result->next_call ) {
142             print $item->{seqid}, "\t";
143             if ($item->{predicted}) {
144             print "predicted SI\n";
145             }
146             else {
147             print "predicted NSI\n";
148             }
149             }
150              
151             To obtain an array of all items at once, use C:
152              
153             @items = $result->each_call;
154              
155             Rewind the iterator with C:
156              
157             $result->rewind;
158             # starting over...
159             while ( my $item = $result->next_call ) {
160             # ...
161             }
162              
163             Use C to obtain the date, ip-address, and predictor used
164             for the run:
165            
166             $date_run = $result->metadata->{'date'};
167             $ip = $result->metadata->{'your-ip'};
168             $predictor_used = $result->metadata->{'predictor'};
169              
170             =back
171              
172             =head1 UNDER THE HOOD
173              
174             The L client object can be retrieved with
175              
176             $soap = $client->soap_client()
177              
178             The L message can be retrieved with
179              
180             $som = $client->som;
181              
182             Request data in L format can be retrieved with
183              
184             $data = $client->request_data;
185              
186             and cleared with
187              
188             $client->clear_request;
189              
190             =head1 AUTHOR - Mark A. Jensen
191              
192             CPAN ID: MAJENSEN
193             Fortinbras Research
194             http://fortinbras.us
195              
196             =head1 COPYRIGHT
197              
198             This program is free software; you can redistribute
199             it and/or modify it under the same terms as Perl itself.
200              
201             The full text of the license can be found in the
202             LICENSE file included with this module.
203              
204             =head1 SEE ALSO
205              
206             L, L
207              
208             =head1 METHODS
209              
210             =cut
211              
212             BEGIN {
213 1     1   10 use Exporter ();
  1         4  
  1         24  
214 1     1   8 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         1  
  1         155  
215 1     1   3 $VERSION = '0.012';
216 1         17 @ISA = qw(Exporter);
217              
218 1         3 @EXPORT = qw();
219 1         5 @EXPORT_OK = qw();
220 1         29 %EXPORT_TAGS = ();
221 1     1   8 use lib '..';
  1         8  
  1         21  
222 1     1   549 use FSSM::SOAPClient::Config;
  1         3  
  1         236  
223             }
224              
225 0           use SOAP::Lite +autodispatch => uri => 'FSSMService',
226 1     1   413 proxy => $SERVICE_URL;
  0            
227             use SOAP::Transport::HTTP;
228              
229             our $AUTOLOAD;
230              
231             sub new
232             {
233             my ($class, %parameters) = @_;
234             my $self = bless ({}, ref ($class) || $class);
235             # init
236             $self->{_soap} = SOAP::Lite->new();
237             $self->set_parameters( %parameters ) if %parameters;
238            
239             return $self;
240             }
241              
242             =head3 run()
243              
244             Title : run
245             Usage : $client->run;
246             Function: run FSSM query using currently set client
247             parameters
248             Returns : result object on succesful query,
249             undef on SOAP fault (see
250             errcode() and errstr() for
251             detail)
252             Args : none
253              
254             =cut
255              
256             sub run {
257             my $self = shift;
258             unless ($self->request_data) {
259             warn "No request created; query not run";
260             return;
261             }
262             $self->{_errcode} = $self->{_errstr} = undef;
263             $self->{_som} = $self->soap_client->run($self->request_data);
264             if ( $self->som->fault) {
265             $self->{_errcode} = $self->som->faultcode;
266             $self->{_errstr} = $self->som->faultstring;
267             return;
268             }
269             return FSSM::SOAPClient::Result->new($self->som);
270             }
271              
272             =head3 attach_seqs()
273              
274             Title : attach_seqs
275             Usage :
276             Function: attach a set of sequences to the client in
277             preparation for query
278             Returns : true on sucess
279             Args : fasta file name | array of BioPerl seq objects |
280             arrayref of hashes { seqid => $id, sequence => $seq }|
281             hashref of { $id => $sequence, ... }
282              
283             =cut
284              
285             sub attach_seqs {
286             my $self = shift;
287             my $collection = shift;
288             my $seqs;
289             unless ($collection) {
290             die "attach_seqs() requires a sequence collection argument";
291             }
292             for (ref($collection)) {
293             !$_ && do {
294             # assume file name
295             unless (-e $collection) {
296             die "attach_seqs(): File '$collection' cannot be found";
297             }
298             $seqs = _parse_fasta($collection);
299             unless ($seqs) {
300             die "attach_seqs(): Could not parse file '$collection' as FASTA";
301             }
302             last;
303             };
304             $_ eq 'ARRAY' && do {
305             if (ref($collection->[0]) eq 'HASH') {
306             unless ($$collection[0]->{seqid} &&
307             $$collection[0]->{sequence}) {
308             die "attach_seqs(): Could not parse array elements";
309             }
310             $seqs = $collection;
311             last;
312             }
313             elsif (ref($collection->[0]) =~ /^Bio::/) {
314             unless ($$collection[0]->can('id') &&
315             $$collection[0]->can('seq') &&
316             $$collection[0]->can('alphabet')) {
317             die "attach_seqs(): Could not parse array elements";
318             }
319             $seqs = [];
320             foreach my $seq (@$collection) {
321             push @$seqs, { 'seqid' => $seq->id,
322             'type' => ($seq->alphabet =~ /^.na/) ?
323             'nt' : 'aa',
324             'sequence' => $seq->seq };
325             }
326             last;
327             }
328             else {
329             die "attch_seqs(): Could not parse array elements";
330             }
331             };
332             $_ eq 'HASH' && do {
333             $seqs = [];
334             foreach my $id ( keys %$collection ) {
335             push @$seqs, { 'seqid' => $id,
336             'sequence' => $$collection{$id} };
337             }
338             last;
339             };
340             do { #else
341             die "attach_seqs(): sequence collection argument not recognized";
342             };
343             }
344             $self->{_seqs} = $seqs;
345             return 1;
346             }
347              
348             =head2 Parameters
349              
350             =head3 seqtype()
351              
352             Title : seqtype
353             Usage :
354             Function: get/set sequence type [aa|nt|auto] for the client
355             Returns : scalar string
356             Args : [aa|nt|auto]
357             aa : amino acid data
358             nt : nucleotide data
359             auto : let BioPerl guess each sequence (unreliable when
360             many ambiguity symbols present)
361              
362             =cut
363              
364             sub seqtype {
365             my $self = shift;
366             my $seqtype = shift;
367             unless ($seqtype) {
368             $self->parameters_changed(0);
369             return $self->{_seqtype};
370             }
371             unless ( $seqtype =~ /^a[a|uto]|[dr]na|nt|protein$/i ) {
372             die __PACKAGE__."::seqtype(): Invalid sequence type";
373             }
374             $self->parameters_changed(1);
375             return $self->{_seqtype} = 'auto' if ($seqtype =~ /auto/i);
376             return $self->{_seqtype} = 'aa' if ($seqtype =~ /^aa|protein$/i);
377             return $self->{_seqtype} = 'nt' if ($seqtype =~ /^.na$/i);
378             }
379              
380             =head3 predictor()
381              
382             Title : predictor
383             Usage : $client->predictor('subtype B SI/NSI');
384             Function: get/set underlying predictor for client
385             Returns : scalar string
386             Args : run $client->available_parameters('predictor')
387             for a list of accepted predictors
388              
389             =head3 expansion()
390              
391             Title : expansion
392             Usage : $client->expansion('avg');
393             Function: get/set ambiguity expansion selector for client
394             Returns : scalar string
395             Args : none | avg | full
396             none : no amibiguity expansion (ambig treated like 'X')
397             avg : return average score over all possible non-ambig seqs
398             full : return individual scores for all non-ambig seqs
399             (can fail if too many)
400              
401             =head3 search()
402              
403             Title : search
404             Usage : $client->search('align');
405             Function: get/set search selector for client
406             Returns : scalar string
407             Args : none | fast | align
408             none : treat each sequence as already aligned
409             fast : find V3 loop using a regular expression heuristic
410             align: align seqs to PSSM matrix to find V3 loop
411              
412             =cut
413              
414             =head2 Parameter manipulation
415              
416             =head3 set_parameters()
417              
418             Title : set_parameters
419             Usage :
420             Function: set client parameters
421             Returns :
422             Args :
423              
424             =cut
425              
426             sub set_parameters {
427             my $self = shift;
428             my %args = @_;
429             if (@_ % 2) {
430             die "set_parameters requires named parameters";
431             }
432             foreach (keys %args) {
433             if (! grep /^$_$/, keys %PARAM_VALUES) {
434             warn "Parameter '$_' not recognized; skipping...";
435             next;
436             }
437             $self->$_($args{$_});
438             }
439             return $self->parameters_changed(1);
440             }
441              
442             =head3 get_parameters()
443              
444             Title : get_parameters
445             Usage :
446             Function: get current client parameters
447             Returns : array
448             Args : none
449              
450             =cut
451              
452             sub get_parameters {
453             my $self = shift;
454             my @ret;
455             for (keys %PARAM_VALUES) {
456             push @ret, $_, $self->$_;
457             }
458             $self->parameters_changed(0);
459             return @ret;
460             }
461              
462             =head3 reset_parameters()
463              
464             Title : reset_parameters
465             Usage :
466             Function: reset client parameters
467             Returns :
468             Args :
469              
470             =cut
471              
472             sub reset_parameters {
473             my $self = shift;
474             my %args = @_;
475             if (@_ % 2) {
476             die "set_parameters requires named parameters";
477             }
478             foreach (keys %PARAM_VALUES) {
479             undef $self->{"_$_"};
480             }
481             $self->set_parameters(%args);
482             }
483              
484             =head3 available_parameters()
485              
486             Title : available_parameters
487             Usage : @parms = $client->available_parameters;
488             @accept = $client->available_parameters('seqtype');
489             Function: list available parameters or acceptable values
490             Returns : array of scalar strings or undef
491             Args : scalar string (a valid parameter name)
492              
493             =cut
494              
495             sub available_parameters {
496             my $self = shift;
497             my $parm = shift;
498             unless ($parm) {
499             return sort keys %PARAM_VALUES;
500             }
501             return unless grep /^$parm$/, keys %PARAM_VALUES;
502             return @{$PARAM_VALUES{$parm}};
503             }
504              
505             =head3 parameters_changed()
506              
507             Title : parameters_changed
508             Usage :
509             Function: set if client parameters have been changed
510             since last parameter access
511             Returns : boolean
512             Args : new value or undef
513              
514             =cut
515              
516             sub parameters_changed {
517             my $self = shift;
518             return $self->{_parameters_changed} = shift if @_;
519             return $self->{_parameters_changed};
520             }
521              
522             =head2 Accessors/Attributes
523              
524             =head3 soap_client()
525              
526             Title : soap_client
527             Usage : $soap = $client->soap_client
528             Function: Get the SOAP::Lite client attached to this object
529             Returns : a SOAP::Lite object or undef
530             Args : none
531              
532             =cut
533              
534             sub soap_client { shift->{_soap} }
535              
536             =head3 som()
537              
538             Title : som
539             Alias : message
540             Usage : $som = $client->som
541             Function: get the current SOAP::SOM (message) object
542             attached to the client
543             Returns : a SOAP::SOM object or undef
544             Args : none
545              
546             =cut
547              
548             sub som { shift->{_som} }
549             sub message { shift->{_som} }
550              
551             =head3 request_data()
552              
553             Title : request_data
554             Usage : $data =$self->request_data
555             Function: creates/gets the SOAP::Data structure forming the
556             request
557             Returns : a SOAP::Data object
558             Args : none
559              
560             =cut
561              
562             sub request_data {
563             my $self = shift;
564             return $self->{_request_data} if $self->{_request_data};
565             my $go = 1;
566             $go &&= $_ for ( map { $self->$_ } keys %PARAM_VALUES );
567             unless ($go) {
568             warn "Missing parameters; can't create request (try get_parameters())";
569             return;
570             }
571             $go &&= $self->{_seqs};
572             unless ($go) {
573             warn "No sequences attached; can't create request (try attach_seqs())";
574             return;
575             }
576              
577             my $expand = ($self->expansion eq 'none' ? 0 : 1);
578             my @x;
579             if ($self->expansion eq 'none') {
580             push @x, SOAP::Data->name( 'ExpandQ' => 0 );
581             }
582             else {
583             push @x, (SOAP::Data->name( 'ExpandQ' => 1 ),
584             SOAP::Data->name( 'ExpandParam' =>
585             $XPND_TBL{$self->expansion}));
586             }
587             return $self->{_request_data} =
588             SOAP::Data->name('request' => \SOAP::Data->value(
589             SOAP::Data->name('Residue' => $self->seqtype),
590             SOAP::Data->name('PredictorParam' =>
591             $self->predictor),
592             SOAP::Data->name('SearchParam' =>
593             $self->search),
594             @x,
595             SOAP::Data->name(
596             'SeqSet' => \SOAP::Data->value(
597             $self->_package_seqs
598             )
599             )
600             )
601             );
602             }
603              
604             =head3 clear_request()
605              
606             Title : clear_request
607             Usage : $client->clear_request
608             Function: reset the request data
609             Returns : true
610             Args : none
611              
612             =cut
613              
614             sub clear_request { delete shift->{_request_data}; return 1 }
615              
616              
617             =head3 ok(), errcode(), errstr()()
618              
619             Title : ok(), errcode(), errstr()
620             Usage : if (!$client->ok()) { warn $client->errstr }
621             Function: test the SOAP response message for faults
622             Returns : ok() : true if success, false if fault present
623             errcode() : the SOAP fault code (scalar int)
624             errstr() : the SOAP faultstring (scalar string)
625             Args : none
626              
627             =cut
628              
629             sub errcode { my $self = shift; $self->som && $self->som->faultcode; }
630             sub errstr { my $self = shift; $self->som && $self->som->faultstring; }
631             sub ok { my $self = shift; $self->som && !$self->som->fault; }
632              
633             # package sequence collection into SOAP::Data objects
634              
635             sub _package_seqs {
636             my $self = shift;
637             return unless $self->{_seqs};
638             my @ret;
639            
640             foreach (@{$self->{_seqs}}) {
641             push @ret, SOAP::Data->name('sequence' => $_->{sequence})
642             ->attr( { seqid => $_->{seqid} } );
643             }
644             return @ret;
645             }
646              
647             sub _parse_fasta {
648             my $file = shift;
649             open (my $fh, "<", $file) or die "parse_fasta(): Input file issue : $!";
650             my $ret = [];
651             my $item;
652             my $in_seq;
653             my $i = 1;
654             my @lines = <$fh>;
655             foreach (@lines) {
656             chomp;
657             /^>/ && do {
658             if ($in_seq) {
659             push @$ret, $item;
660             }
661             my ($nm) = /^>([^[:space:]]+)/;
662             $nm ||= "seq".$i++;
663             $item = { 'seqid' => $nm,
664             'sequence' => ''};
665             $in_seq = 0;
666             next;
667             };
668             do {
669             unless (defined $in_seq) {
670             die "parse_fasta(): file does not appear to be in FASTA format";
671             }
672             $in_seq = 1;
673             s/\s//g;
674             if (/[^-~?A-Za-z]/) {
675             die "parse_fasta(): unrecognized sequence characters";
676             }
677             $item->{'sequence'} .= $_;
678             next;
679             };
680             }
681             # last one
682             push @$ret, $item;
683             return $ret;
684             }
685              
686             sub AUTOLOAD {
687             my $self = shift;
688             my $method = $AUTOLOAD;
689             $method =~ s/.*:://;
690             if (grep /^$method$/, keys %PARAM_VALUES) {
691             my $arg = shift;
692             $self->parameters_changed(0);
693             return $self->{"_$method"} unless $arg;
694             unless (grep /^$arg$/, @{$PARAM_VALUES{$method}}) {
695             die "Invalid argument '$arg' for parameter '$method' in ".__PACKAGE__;
696             }
697             $self->parameters_changed(1);
698             return $self->{"_$method"} = $arg;
699             }
700             else {
701             die "Can't locate method '$method' in ".__PACKAGE__;
702             }
703             }
704              
705             sub DESTROY {}
706              
707             1;
708              
709             package FSSM::SOAPClient::Result;
710             use strict;
711             use warnings;
712              
713             =head1 NAME
714              
715             FSSM::SOAPClient::Result - access the returned FSSM analysis
716              
717             C objects are returned by C. Use the following methods to retrieve the analysis.
718              
719             =head1 METHODS
720              
721             =cut
722              
723             sub new {
724             my $class = shift;
725             my $som = shift;
726             die "SOM object required at arg 1" unless $som and
727             ref($som) eq 'SOAP::SOM';
728             bless {
729             _som => $som,
730             _idx => 0
731             }, $class;
732             }
733              
734             =head3 next_call()
735              
736             Title : next_call
737             Usage : $item = $result->next_call
738             Function: get the FSSM call for the next submitted sequence
739             Returns : hashref of data, with the following key => value pairs:
740             seqid => the submitted sequence name/id
741             ourid => the id as modified by FSSM (for differentiating
742             among strand/frame/non-amibig translations of
743             a single submitted sequence, with symbol
744             indicating comment)
745             score => PSSM score
746             left-end => 5' or N-terminal coordinate of V3
747             right-end => 3' or C-terminal coordinate of V3
748             comment => describes a possible caveat for this sequence
749             predicted => 1 if X4/SI or dual, 0 if R5/NSI
750             plabel => predicted phenotype in this predictor's context
751             Args : none
752              
753             =cut
754              
755             sub next_call {
756             my $self = shift;
757             my $ret = ($self->{_som}->valueof("//Result/seq-result"))[$self->{_idx}];
758             return unless $ret;
759             ($self->{_idx})++;
760             return $ret;
761            
762             }
763              
764             =head3 rewind()
765              
766             Title : rewind
767             Usage : $result->rewind
768             Function: reset the next_call iterator to the beginning
769             Returns : true
770             Args : none
771              
772             =cut
773              
774             sub rewind { shift->{_idx} = 0; 1 };
775              
776             =head3 each_call()
777              
778             Title : each_call
779             Usage : @calls = $result->each_call;
780             Function: returns an array of call hashes as described
781             in next_call()
782             Returns : array of hashrefs
783             Args : none
784              
785             =cut
786              
787             sub each_call {
788             my @ret = shift->{_som}->valueof("//Result/seq-result/");
789             return @ret;
790             }
791              
792             =head3 metadata()
793              
794             Title : metadata
795             Alias : meta
796             Usage : $run_info = $result->metadata
797             Function: Obtains some data about the run
798             Returns : hashref with following key => value pairs
799             date : date/time of run
800             your-ip : ip address from which the run originated
801             predictor : predictor used
802             Args : none
803              
804             =cut
805              
806             sub metadata {
807             return shift->{_som}->valueof("//Result/meta");
808             }
809              
810             sub meta { shift->metadata }
811              
812             sub DESTROY {
813             my $self = shift;
814             delete $self->{_som};
815             }