]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/FlatFielder.pm
Merge branch 'master' of git.evergreen-ils.org:Evergreen-DocBook into doc_consolidati...
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / FlatFielder.pm
1 package OpenILS::WWW::FlatFielder;
2
3 use strict;
4 use warnings;
5
6 use Apache2::Log;
7 use Apache2::Const -compile => qw(
8     OK HTTP_NOT_ACCEPTABLE HTTP_PAYMENT_REQUIRED HTTP_INTERNAL_SERVER_ERROR :log
9 );
10 use XML::LibXML;
11 use XML::LibXSLT;
12 use Text::Glob;
13 use CGI qw(:all -utf8);
14
15 use OpenSRF::Utils::JSON;
16 use OpenSRF::AppSession;
17 use OpenSRF::Utils::SettingsClient;
18
19 use OpenILS::Application::AppUtils;
20 my $U = 'OpenILS::Application::AppUtils';
21
22
23 my $_parser = new XML::LibXML;
24 my $_xslt = new XML::LibXSLT;
25
26 # BEGIN package globals
27
28 # We'll probably never need this fanciness for autosuggest, but
29 # you can add handlers for different requested content-types here, and
30 # you can weight them to control what matches requests for things like
31 # 'application/*'
32
33
34 sub html_ish_output {
35     my ($r, $args, $xslt) = @_;
36     $args->{'stylesheet'} =
37         OpenSRF::Utils::SettingsClient->new->config_value(dirs => 'xsl') . '/' . $xslt;
38     print data_to_xml($args);
39     return Apache2::Const::OK;
40 }
41
42 my $_output_handler_dispatch = {
43     "text/csv" => {
44         "prio" => 0,
45         "code" => sub {
46             my ($r, $args) = @_;
47             $r->headers_out->set("Content-Disposition" => "attachment; filename=FlatSearch.csv");
48             # Anecdotally, IE 8 needs name= here to provoke downloads, where
49             # other browswers respect filename= in Content-Disposition.  Also,
50             # we might want to make the filename choosable by CGI param later?
51             # Or vary it by timestamp?
52             $r->content_type('text/csv; name=FlatSearch.csv; charset=utf-8');
53             print_data_as_csv($args, \*STDOUT);
54             return Apache2::Const::OK;
55         }
56     },
57     "text/html" => {
58         "prio" => 0,
59         "code" => sub {
60             $_[0]->content_type("text/html; charset=utf-8");
61             print html_ish_output( @_, 'FlatFielder2HTML.xsl' );
62             return Apache2::Const::OK;
63         }
64     },
65     "application/xml" => {
66         "prio" => 0,
67         "code" => sub {
68             my ($r, $args) = @_;
69             $r->content_type("application/xml; charset=utf-8");
70             print data_to_xml($args);
71             return Apache2::Const::OK;
72         }
73     },
74     "application/json" => {
75         "prio" => 1,
76         "code" => sub {
77             my ($r, $args) = @_;
78             $r->content_type("application/json; charset=utf-8");
79             print data_to_json($args);
80             return Apache2::Const::OK;
81         }
82     }
83 };
84
85 my @_output_handler_types = sort {
86     $_output_handler_dispatch->{$a}->{prio} <=>
87         $_output_handler_dispatch->{$b}->{prio}
88 } keys %$_output_handler_dispatch;
89
90 # END package globals
91
92 =comment
93
94 <FlatSearch hint='foo' identifier='bar' label='Foo Bar' FS_key='ad1awe43a3a2a3ra32a23ra32ra23rar23a23r'>
95   <row ordinal='1'>
96     <column name='fiz'>YAY!</column>
97     <column name='faz'>boooo</column>
98   </row>
99   <row ordinal='2'>
100     <column name='fiz'>WHEEE!</column>
101     <column name='faz'>noooo</column>
102   </row>
103 </FlatSearch>
104
105 =cut
106
107 sub data_to_xml {
108     my ($args) = @_;
109
110     my $dom = new XML::LibXML::Document("1.0", "UTF-8");
111     my $fs = $dom->createElement("FlatSearch");
112     $fs->setAttribute("hint", $args->{hint}) if $args->{hint};
113     $fs->setAttribute("identifier", $args->{id_field}) if $args->{id_field};
114     $fs->setAttribute("label", $args->{label_field}) if $args->{label_field};
115     $fs->setAttribute("FS_key", $args->{key}) if $args->{key};
116     $dom->setDocumentElement($fs);
117
118     my $rownum = 1;
119     for my $i (@{$$args{data}}) {
120         my $item = $dom->createElement("row");
121         $item->setAttribute('ordinal', $rownum);
122         $rownum++;
123         for my $k (keys %$i) {
124             my $val = $dom->createElement('column');
125             $val->setAttribute('name', $k);
126             $val->appendText($i->{$k});
127             $item->addChild($val);
128         }
129         $fs->addChild($item);
130     }
131
132     # XML::LibXML::Document::toString() returns an encoded byte string, which
133     # is why we don't need to binmode STDOUT, ':utf8'.
134
135     return $_xslt->parse_stylesheet(
136         $_parser->parse_file( $$args{stylesheet} )
137     )->transform(
138         $dom
139     )->toString if ($$args{stylesheet}); # configured transform, early return
140
141     return $dom->toString();
142 }
143
144 sub print_data_as_csv {
145     my ($args, $fh) = @_;
146
147     my @keys = sort keys %{ $$args{data}[0] };
148     return unless @keys;
149
150     my $csv = new Text::CSV({ always_quote => 1, eol => "\r\n" });
151
152     $csv->print($fh, \@keys);
153
154     for my $row (@{$$args{data}}) {
155         $csv->print($fh, [map { $row->{$_} } @keys]);
156     }
157 }
158
159 sub data_to_json {
160     my ($args) = @_;
161
162     # Turns out we don't want the data structure you'd use to initialize an
163     # itemfilereadstore or similar. We just want rows.
164
165 #    return OpenSRF::Utils::JSON->perl2JSON({
166 #        ($$args{hint} ? (hint => $$args{hint}) : ()),
167 #        ($$args{id_field} ? (identifier => $$args{id_field}) : ()),
168 #        ($$args{label_field} ? (label => $$args{label_field}) : ()),
169 #        ($$args{key} ? (FS_key => $$args{key}) : ()),
170 #        items => $$args{data}
171 #    });
172     return OpenSRF::Utils::JSON->perl2JSON($args->{data});
173 }
174
175 # Given data and the Apache request object, this sub picks a sub from a
176 # dispatch table based on the list of content-type encodings that the client
177 # has indicated it will accept, and calls that sub, which will deliver
178 # a response of appropriately encoded data.
179 sub output_handler {
180     my ($r, $args) = @_;
181
182     my @types = split /,/, $r->headers_in->{Accept};
183
184     if ($$args{format}) {
185         unshift @types, $$args{format};
186     }
187
188     foreach my $media_range (@types) {
189         $media_range =~ s/;.+$//; # keep type, subtype. lose parameters.
190
191         my ($match) = grep {
192             Text::Glob::match_glob($media_range, $_)
193         } @_output_handler_types;
194
195         if ($match) {
196             return $_output_handler_dispatch->{$match}{code}->($r, $args);
197         }
198     }
199
200     return Apache2::Const::HTTP_NOT_ACCEPTABLE;
201 }
202
203 sub handler {
204     my $r = shift;
205     my $cgi = new CGI;
206
207     my %args;
208     $args{format} = $cgi->param('format');
209     $args{auth} = $cgi->param('ses');
210     $args{hint} = $cgi->param('hint');
211     $args{map} = OpenSRF::Utils::JSON->JSON2perl($cgi->param('map'));
212     $args{where} = OpenSRF::Utils::JSON->JSON2perl($cgi->param('where'));
213     $args{slo} = OpenSRF::Utils::JSON->JSON2perl($cgi->param('slo'));
214     $args{key} = $cgi->param('key');
215     $args{id_field} = $cgi->param('identifier');
216     $args{label_field} = $cgi->param('label');
217
218     my $fielder = OpenSRF::AppSession->create('open-ils.fielder');
219     if ($args{map}) {
220         $args{data} = $fielder->request(
221             'open-ils.fielder.flattened_search.atomic',
222             @args{qw/auth hint map where slo/}
223         )->gather(1);
224     } else {
225         $args{data} = $fielder->request(
226             'open-ils.fielder.flattened_search.execute.atomic',
227             @args{qw/auth key where slo/}
228         )->gather(1);
229
230         if (ref $args{data} and $args{data}[0] and
231             $U->event_equals($args{data}[0], 'CACHE_MISS')) {
232
233             # You have to pay the cache! I kill me.
234             return Apache2::Const::HTTP_PAYMENT_REQUIRED;
235         }
236     }
237
238     return output_handler( $r, \%args );
239
240 }
241
242 1;