1 package Koha::SearchEngine::Elasticsearch::Indexer;
3 # Copyright 2013 Catalyst IT
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 use List::Util qw(any);
24 use base qw(Koha::SearchEngine::Elasticsearch);
27 # For now just marc, but we can do anything here really
28 use Catmandu::Importer::MARC;
29 use Catmandu::Store::ElasticSearch;
34 Koha::SearchEngine::Elasticsearch::Indexer->mk_accessors(qw( store ));
38 Koha::SearchEngine::Elasticsearch::Indexer - handles adding new records to the index
42 my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new(
43 { index => Koha::SearchEngine::BIBLIOS_INDEX } );
44 $indexer->drop_index();
45 $indexer->update_index(\@biblionumbers, \@records);
52 =item C<Koha::SearchEngine::Elasticsearch::Indexer::INDEX_STATUS_OK>
54 Represents an index state where index is created and in a working state.
56 =item C<Koha::SearchEngine::Elasticsearch::Indexer::INDEX_STATUS_REINDEX_REQUIRED>
58 Not currently used, but could be useful later, for example if can detect when new field or mapping added.
60 =item C<Koha::SearchEngine::Elasticsearch::Indexer::INDEX_STATUS_RECREATE_REQUIRED>
62 Representings an index state where index needs to be recreated and is not in a working state.
70 INDEX_STATUS_REINDEX_REQUIRED => 1,
71 INDEX_STATUS_RECREATE_REQUIRED => 2,
76 =head2 update_index($biblionums, $records)
79 $self->update_index($biblionums, $records);
81 die("Something went wrong trying to update index:" . $_[0]);
84 Converts C<MARC::Records> C<$records> to Elasticsearch documents and performs
85 an update request for these records on the Elasticsearch index.
91 Arrayref of biblio numbers for the C<$records>, the order must be the same as
92 and match up with C<$records>.
96 Arrayref of C<MARC::Record>s.
103 my ($self, $biblionums, $records) = @_;
105 my $conf = $self->get_elasticsearch_params();
106 my $elasticsearch = $self->get_elasticsearch();
107 my $documents = $self->marc_records_to_documents($records);
110 for (my $i=0; $i < scalar @$biblionums; $i++) {
111 my $id = $biblionums->[$i];
112 my $document = $documents->[$i];
118 push @body, $document;
121 my $response = $elasticsearch->bulk(
122 index => $conf->{index_name},
123 type => 'data', # is just hard coded in Indexer.pm?
127 # TODO: handle response
131 =head2 set_index_status_ok
133 Convenience method for setting index status to C<INDEX_STATUS_OK>.
137 sub set_index_status_ok {
139 $self->index_status(INDEX_STATUS_OK);
142 =head2 is_index_status_ok
144 Convenience method for checking if index status is C<INDEX_STATUS_OK>.
148 sub is_index_status_ok {
150 return $self->index_status == INDEX_STATUS_OK;
153 =head2 set_index_status_reindex_required
155 Convenience method for setting index status to C<INDEX_REINDEX_REQUIRED>.
159 sub set_index_status_reindex_required {
161 $self->index_status(INDEX_STATUS_REINDEX_REQUIRED);
164 =head2 is_index_status_reindex_required
166 Convenience method for checking if index status is C<INDEX_STATUS_REINDEX_REQUIRED>.
170 sub is_index_status_reindex_required {
172 return $self->index_status == INDEX_STATUS_REINDEX_REQUIRED;
175 =head2 set_index_status_recreate_required
177 Convenience method for setting index status to C<INDEX_STATUS_RECREATE_REQUIRED>.
181 sub set_index_status_recreate_required {
183 $self->index_status(INDEX_STATUS_RECREATE_REQUIRED);
186 =head2 is_index_status_recreate_required
188 Convenience method for checking if index status is C<INDEX_STATUS_RECREATE_REQUIRED>.
192 sub is_index_status_recreate_required {
194 return $self->index_status == INDEX_STATUS_RECREATE_REQUIRED;
197 =head2 index_status($status)
199 Will either set the current index status to C<$status> and return C<$status>,
200 or return the current index status if called with no arguments.
206 Optional argument. If passed will set current index status to C<$status> if C<$status> is
207 a valid status. See L</CONSTANTS>.
214 my ($self, $status) = @_;
215 my $key = 'ElasticsearchIndexStatus_' . $self->index;
217 if (defined $status) {
218 unless (any { $status == $_ } (
220 INDEX_STATUS_REINDEX_REQUIRED,
221 INDEX_STATUS_RECREATE_REQUIRED,
224 Koha::Exceptions::Exception->throw("Invalid index status: $status");
226 C4::Context->set_preference($key, $status);
230 return C4::Context->preference($key);
234 =head2 update_mappings
236 Generate Elasticsearch mappings from mappings stored in database and
237 perform a request to update Elasticsearch index mappings. Will throw an
238 error and set index status to C<INDEX_STATUS_RECREATE_REQUIRED> if update
243 sub update_mappings {
245 my $conf = $self->get_elasticsearch_params();
246 my $elasticsearch = $self->get_elasticsearch();
247 my $mappings = $self->get_elasticsearch_mappings();
249 foreach my $type (keys %{$mappings}) {
251 my $response = $elasticsearch->indices->put_mapping(
252 index => $conf->{index_name},
255 $type => $mappings->{$type}
259 $self->set_index_status_recreate_required();
260 my $reason = $_[0]->{vars}->{body}->{error}->{reason};
261 Koha::Exceptions::Exception->throw(
262 error => "Unable to update mappings for index \"$conf->{index_name}\". Reason was: \"$reason\". Index needs to be recreated and reindexed",
266 $self->set_index_status_ok();
269 =head2 update_index_background($biblionums, $records)
271 This has exactly the same API as C<update_index> however it'll
272 return immediately. It'll start a background process that does the adding.
274 If it fails to add to Elasticsearch then it'll add to a queue that will cause
275 it to be updated by a regular index cron job in the future.
279 # TODO implement in the future - I don't know the best way of doing this yet.
280 # If fork: make sure process group is changed so apache doesn't wait for us.
282 sub update_index_background {
284 $self->update_index(@_);
287 =head2 delete_index($biblionums)
289 C<$biblionums> is an arrayref of biblionumbers to delete from the index.
294 my ($self, $biblionums) = @_;
296 if ( !$self->store ) {
297 my $params = $self->get_elasticsearch_params();
299 Catmandu::Store::ElasticSearch->new(
301 index_settings => $self->get_elasticsearch_settings(),
302 index_mappings => $self->get_elasticsearch_mappings(),
306 $self->store->bag->delete("$_") foreach @$biblionums;
307 $self->store->bag->commit;
310 =head2 delete_index_background($biblionums)
312 Identical to L</delete_index($biblionums)>
316 # TODO: Should be made async
317 sub delete_index_background {
319 $self->delete_index(@_);
324 Drops the index from the Elasticsearch server.
330 if ($self->index_exists) {
331 my $conf = $self->get_elasticsearch_params();
332 my $elasticsearch = $self->get_elasticsearch();
333 $elasticsearch->indices->delete(index => $conf->{index_name});
334 $self->set_index_status_recreate_required();
340 Creates the index (including mappings) on the Elasticsearch server.
346 my $conf = $self->get_elasticsearch_params();
347 my $settings = $self->get_elasticsearch_settings();
348 my $elasticsearch = $self->get_elasticsearch();
349 $elasticsearch->indices->create(
350 index => $conf->{index_name},
352 settings => $settings
355 $self->update_mappings();
360 Checks if index has been created on the Elasticsearch server. Returns C<1> or the
361 empty string to indicate whether index exists or not.
367 my $conf = $self->get_elasticsearch_params();
368 my $elasticsearch = $self->get_elasticsearch();
369 return $elasticsearch->indices->exists(
370 index => $conf->{index_name},
382 =item Chris Cormack C<< <chrisc@catalyst.net.nz> >>
384 =item Robin Sheat C<< <robin@catalyst.net.nz> >>