58cbc011e7a64f87b376e9a04771c579d05cc6c7
[evergreen-equinox.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Storage / Publisher / authority.pm
1 use strict;
2 use warnings;
3
4 package OpenILS::Application::Storage::Publisher::authority;
5 use base qw/OpenILS::Application::Storage::Publisher/;
6 use vars qw/$VERSION/;
7 use OpenSRF::EX qw/:try/;
8 use OpenILS::Application::Storage::FTS;
9 use OpenILS::Utils::Fieldmapper;
10 use OpenILS::Utils::Normalize qw( naco_normalize );
11 use OpenSRF::Utils::Logger qw/:level/;
12 use OpenSRF::Utils::Cache;
13 use Data::Dumper;
14 use Digest::MD5 qw/md5_hex/;
15 use XML::LibXML;
16 use Time::HiRes qw/time sleep/;
17 use Unicode::Normalize;
18
19 my $log = 'OpenSRF::Utils::Logger';
20
21 $VERSION = 1;
22
23 my $parser = XML::LibXML->new;
24
25 sub validate_tag {
26         my $self = shift;
27         my $client = shift;
28         my %args = @_;
29         
30         my @tags = @{$args{tags}};
31         my @searches = @{$args{searches}};
32
33         my $search_table = authority::full_rec->table;
34
35         my @values;
36         my @selects;
37         for my $t ( @tags ) {
38                 for my $search ( @searches ) {
39                         my $sf = $$search{subfield};
40                         my $term = naco_normalize($$search{term}, $sf);
41
42                         push @values, $t, $sf, $term;
43
44                         push @selects,
45                                 "SELECT record FROM $search_table ".
46                                 "WHERE tag = ? AND subfield = ? AND value = ?";
47                 }
48
49                 my $sql;
50                 if ($self->api_name =~ /id_list/) {
51                         $sql = 'SELECT DISTINCT record FROM (';
52                 } else {
53                         $sql = 'SELECT COUNT(DISTINCT record) FROM (';
54                 }
55                 $sql .= 'SELECT record FROM (('.join(') INTERSECT (', @selects).')) AS x ';
56                 $sql .= "JOIN $search_table recheck USING (record) WHERE recheck.tag = ? ";
57                 $sql .= "GROUP BY 1 HAVING (COUNT(recheck.id) - ?) = 0) AS foo;";
58
59                 if ($self->api_name =~ /id_list/) {
60                         my $id_list = authority::full_rec->db_Main->selectcol_arrayref( $sql, {}, @values, $t, scalar(@searches) );
61                         return $id_list;
62                 } else {
63                         my $count = authority::full_rec->db_Main->selectcol_arrayref( $sql, {}, @values, $t, scalar(@searches) )->[0];
64                         return $count if ($count > 0);
65                 }
66         }
67
68         return 0;
69 }
70 __PACKAGE__->register_method(
71         api_name        => "open-ils.storage.authority.validate.tag",
72         method          => 'validate_tag',
73         api_level       => 1,
74 );
75
76 __PACKAGE__->register_method(
77         api_name        => "open-ils.storage.authority.validate.tag.id_list",
78         method          => 'validate_tag',
79         api_level       => 1,
80 );
81
82
83 sub find_authority_marc {
84         my $self = shift;
85         my $client = shift;
86         my %args = @_;
87         
88         my $term = NFD(lc($args{term}));
89         my $tag = $args{tag};
90         my $subfield = $args{subfield};
91         my $limit = $args{limit} || 100;
92         my $offset = $args{offset} || 0;
93
94         if ($limit) {
95                 $limit = "LIMIT $limit";
96         } else {
97                 $limit = '';
98         }
99
100         if ($offset) {
101                 $offset = "OFFSET $offset";
102         } else {
103                 $offset = '';
104         }
105
106         my $tag_where = "AND f.tag LIKE '$tag'";
107         if (ref $tag) {
108                 $tag_where = "AND f.tag IN ('".join("','",@$tag)."')";
109         }
110
111         my $sf_where = "AND f.subfield = '$subfield'";
112         if (ref $subfield) {
113                 $sf_where = "AND f.subfield IN ('".join("','",@$subfield)."')";
114         }
115
116         my $search_table = authority::full_rec->table;
117         my $marc_table = authority::record_entry->table;
118
119         my ($index_col) = authority::full_rec->columns('FTS');
120         $index_col ||= 'value';
121
122         my $fts = OpenILS::Application::Storage::FTS->compile(default => $term, 'f.value', "f.$index_col");
123
124         $term =~ s/\W+$//gso;
125         $term =~ s/'/''/gso;
126         $term =~ s/\pM//gso;
127
128         my $fts_where = $fts->sql_where_clause;
129         my $fts_words = join '%', $fts->words;
130
131     return undef unless ($fts_words);
132
133         my $fts_words_where = "f.value LIKE '$fts_words\%'";
134         my $fts_start_where = "f.value LIKE '$term\%'";
135         my $fts_eq_where = "f.value = '$term'";
136
137         my $fts_rank = join '+', $fts->fts_rank;
138
139         my $select = <<"        SQL";
140                 SELECT  a.marc, sum($fts_rank), count(f.record), first(f.value)
141                 FROM    $search_table f,
142                         $marc_table a
143                 WHERE   $fts_start_where
144                         $tag_where
145                         $sf_where
146                         AND a.id = f.record
147                         GROUP BY 1
148                         ORDER BY 2 desc, 3 desc, 4
149                         $limit
150                         $offset
151                         
152         SQL
153
154         $log->debug("Authority Search SQL :: [$select]",DEBUG);
155
156         my $recs = authority::full_rec->db_Main->selectcol_arrayref( $select );
157         
158         $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
159
160         $client->respond($_) for (@$recs);
161         return undef;
162 }
163 __PACKAGE__->register_method(
164         api_name        => "open-ils.storage.authority.search.marc",
165         method          => 'find_authority_marc',
166         api_level       => 1,
167         stream          => 1,
168         cachable        => 1,
169 );
170
171 sub _empty_check {
172         my $term = shift;
173         my $class = shift || 'metabib::full_rec';
174
175         my $table = $class->table;
176
177         my ($index_col) = $class->columns('FTS');
178         $index_col ||= 'value';
179
180         my $fts = OpenILS::Application::Storage::FTS->compile(default => $term, 'm.value', "m.$index_col");
181         my $fts_where = $fts->sql_where_clause;
182
183         my $sql = <<"   SQL";
184                 SELECT  TRUE
185                 FROM    $table m
186                 WHERE   $fts_where
187                 LIMIT 1
188         SQL
189
190         return $class->db_Main->selectcol_arrayref($sql)->[0];
191 }
192
193 my $prevtime;
194
195 sub find_see_from_controlled {
196         my $self = shift;
197         my $client = shift;
198         my $term = shift;
199         my $limit = shift;
200         my $offset = shift;
201
202         $prevtime = time;
203
204         (my $class = $self->api_name) =~ s/^.+authority.([^\.]+)\.see.+$/$1/o;
205         my $sf = 'a';
206         $sf = 't' if ($class eq 'title');
207
208         my @marc = $self->method_lookup('open-ils.storage.authority.search.marc')
209                         ->run( term => $term, tag => [400,410,411,430,450,455], subfield => $sf, limit => $limit, offset => $offset );
210
211         
212         for my $m ( @marc ) {
213                 my $doc = $parser->parse_string($m);
214                 my @nodes = $doc->documentElement->findnodes('//*[substring(@tag,1,1)="1"]/*[@code="a" or @code="d" or @code="x"]');
215                 my $list = [ map { $_->textContent } @nodes ];
216                 $client->respond( $list ) if (_empty_check(join(' ',@$list), "metabib::${class}_field_entry"));
217         }
218         return undef;
219 }
220 for my $class ( qw/title author subject keyword series identifier/ ) {
221         __PACKAGE__->register_method(
222                 api_name        => "open-ils.storage.authority.$class.see_from.controlled",
223                 method          => 'find_see_from_controlled',
224                 api_level       => 1,
225                 stream          => 1,
226                 cachable        => 1,
227         );
228 }
229
230 sub find_see_also_from_controlled {
231         my $self = shift;
232         my $client = shift;
233         my $term = shift;
234         my $limit = shift;
235         my $offset = shift;
236
237         (my $class = $self->api_name) =~ s/^.+authority.([^\.]+)\.see.+$/$1/o;
238         my $sf = 'a';
239         $sf = 't' if ($class eq 'title');
240
241         my @marc = $self->method_lookup('open-ils.storage.authority.search.marc')
242                         ->run( term => $term, tag => [500,510,511,530,550,555], subfield => $sf, limit => $limit, offset => $offset );
243         for my $m ( @marc ) {
244                 my $doc = $parser->parse_string($m);
245                 my @nodes = $doc->documentElement->findnodes('//*[substring(@tag,1,1)="1"]/*[@code="a" or @code="d" or @code="x"]');
246                 my $list = [ map { $_->textContent } @nodes ];
247                 $client->respond( $list ) if (_empty_check(join(' ',@$list), "metabib::${class}_field_entry"));
248         }
249         return undef;
250 }
251 for my $class ( qw/title author subject keyword series identifier/ ) {
252         __PACKAGE__->register_method(
253                 api_name        => "open-ils.storage.authority.$class.see_also_from.controlled",
254                 method          => 'find_see_also_from_controlled',
255                 api_level       => 1,
256                 stream          => 1,
257                 cachable        => 1,
258         );
259 }
260
261
262 1;