Replace old backend with new backend
Change-Id: I62640f31a05f9d90718950808662b8124155ef58
diff --git a/Changes b/Changes
index 618fb05..e4fa86b 100755
--- a/Changes
+++ b/Changes
@@ -1,3 +1,12 @@
+0.30 2018-10-24
+ - Rewrote backend:
+ - Removed dependency of Mojolicious::Plugin::Search.
+ - Removed abstract API.
+ - Improved backend error handling.
+ - Improved backend test suite.
+ - Removed MMap cache from default configuration
+ and rely on in-memory cache.
+
0.29 2018-10-05
- Deprecated Kalamar.api configuration key
in favor of Kalamar.api_path.
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 1fa6c52..83cea41 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -7,7 +7,7 @@
use Mojo::Util qw/url_escape/;
# Minor version - may be patched from package.json
-our $VERSION = '0.29';
+our $VERSION = '0.30';
# Supported version of Backend API
our $API_VERSION = '1.0';
@@ -142,7 +142,6 @@
# Load plugins
foreach (
- 'Search', # Abstract Search framework
'TagHelpers::MailToChiffre', # Obfuscate email addresses
'KalamarHelpers', # Specific Helpers for Kalamar
'KalamarErrors', # Specific Errors for Kalamar
@@ -208,7 +207,7 @@
});
# Base query route
- $r->get('/')->to('search2#query')->name('index');
+ $r->get('/')->to('search#query')->name('index');
# Documentation routes
$r->get('/doc')->to('documentation#page', page => 'korap')->name('doc_start');
@@ -221,10 +220,10 @@
# Match route
# Corpus route
- my $corpus = $r->get('/corpus')->to('Search2#corpus_info')->name('corpus');
+ my $corpus = $r->get('/corpus')->to('search#corpus_info')->name('corpus');
my $doc = $r->route('/corpus/:corpus_id/:doc_id');
- my $text = $doc->get('/:text_id')->to('search2#text_info')->name('text');
- my $match = $doc->get('/:text_id/:match_id')->to('search2#match_info')->name('match');
+ my $text = $doc->get('/:text_id')->to('search#text_info')->name('text');
+ my $match = $doc->get('/:text_id/:match_id')->to('search#match_info')->name('match');
# User Management
my $user = $r->any('/user')->to(controller => 'User');
diff --git a/lib/Kalamar/API.pm b/lib/Kalamar/API.pm
deleted file mode 100644
index c8f9d25..0000000
--- a/lib/Kalamar/API.pm
+++ /dev/null
@@ -1,839 +0,0 @@
-package Kalamar::API;
-use Mojo::Base 'Mojolicious::Plugin';
-use Scalar::Util qw/blessed weaken/;
-use Mojo::JSON qw/true false/;
-use strict;
-use warnings;
-
-# KorAP Search engine for Mojolicious::Plugin::Search
-
-
-# TODO:
-# This contains a lot of legacy code that is not
-# necessary anymore. Quite a lot of endpoints are
-# only proxies and should simply be piped through
-# the backend (including auth code)
-
-# TODO: Add fixtures
-# TODO: Support search in corpus and virtualcollection
-# TODO: Support caching everywhere!
-# TODO: Correct use of stash info everywhere!
-# TODO: Alot is now underneath "meta"
-
-# TODO:
-# Rewrite to use promises!
-
-
-# Register the plugin
-sub register {
- my ($plugin, $mojo, $index_class, $param) = @_;
- $param ||= {};
-
- # Add attributes to the index class
- $index_class->attr(api => $param->{api});
- $index_class->attr([qw/cutoff
- query_language
- time_exceeded
- api_request
- authorized
- _api_cache
- api_response
- benchmark
- query_jsonld
- collection
- collection_jsonld/]);
- $index_class->attr(no_cache => 0);
- $index_class->attr(status => 200);
-};
-
-
-# Search the index
-sub search {
- my $self = shift;
- my $index = shift;
-
- # Get controller
- my $c = $index->controller;
-
- # If there is a callback, do async
- my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
-
- # No query defined
- unless ($index->query) {
- return $cb->($index) if $cb;
- return;
- };
-
- # Get query url
- my $url = _query_url($index, @_);
-
- # Cache based on URL
- $index->_api_cache('total-' . $url->to_string);
-
- my %param = @_;
-
- # Set context based on parameter
- # base/s:p
- $url->query({ context => $param{'context'} // '40-t,40-t' });
- # 'base/s:p'/'paragraph'
-
- # Set path to search
- $url->path('search');
-
- # Check cache for total results
- my $total_results;
-
- # In case the user is not known, it is assumed, the user is not logged in
- my $user = $c->stash('user') // 'not_logged_in';
-
- if (!$index->no_cache &&
- defined ($total_results = $c->chi->get($user . $index->_api_cache))) {
-
- # Set total results from cache
- $index->total_results($total_results);
- $c->app->log->debug('Get total result from cache');
-
- # Set cutoff unless already set
- $url->query({cutoff => 'true'}) unless defined $index->cutoff;
- };
-
- # Set api request for debugging
- $index->api_request($url->to_string);
-
- # Create new user agent and set timeout to 2 minutes
- #my $ua = $c->user->ua;
- #$tx = $plugin->ua->start($tx);
-
- #$ua->inactivity_timeout(120);
-
- # Debugging
- $c->app->log->debug('Search for ' . $index->api_request);
-
- # Search non-blocking
- if ($cb) {
- $c->user->auth_request(
- get => $url => sub {
- my $tx = pop;
- $self->_process_response('matches', $index, $tx);
- weaken $index;
- return $cb->($index);
- });
- }
-
- # Search blocking
- else {
- my $tx = $c->user->auth_request(get => $url);
- $self->_process_response('matches', $index, $tx);
- return $index;
- };
-};
-
-
-# Trace query serialization
-sub trace {
- my $self = shift;
- my $index = shift;
-
- # Get controller
- my $c = $index->controller;
-
- # If there is a callback, do async
- my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
-
- my %param = @_;
-
- # No query defined
- unless ($index->query(delete $param{query})) {
- return $cb->($index) if $cb;
- return;
- };
-
- # Get query url
- my $url = _query_url($index, @_);
-
- $url->path('search');
-
- # Create new user agent and set timeout to 30 seconds
- my $ua = $c->user->ua; # Mojo::UserAgent->new;
- $ua->inactivity_timeout(30);
-
- # Build transaction
- my $tx = $ua->build_tx(TRACE => $url);
-
- # non-blocking
- if ($cb) {
- weaken $index;
-
- # Trace non-blocking
- $ua->start(
- $tx => sub {
- $self->_process_response('trace', $index, pop);
- return $cb->($index);
- });
- }
- # Trace blocking
- else {
- my $tx = $ua->start($url);
- return $self->_process_response('trace', $index, $tx);
- };
-};
-
-
-# Get match info
-sub match {
- my $self = shift;
- my $index = shift;
-
- # Get controller
- my $c = $index->controller;
-
- # If there is a callback, do async
- my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
-
- my %param = @_;
-
- my $url = Mojo::URL->new($index->api);
-
- # Legacy: In old versions, doc_id contained text_id
- # $param{doc_id} .= '.' . $param{text_id} if $param{text_id};
-
- # Use hash slice to create path
- $url->path(join('/', 'corpus', @param{qw/corpus_id doc_id text_id match_id/}, 'matchInfo'));
-
- # Build match id
- # $match = 'match-' . $corpus . '!' . $corpus . '_' . $doc . '-' . $match;
-
- my %query;
- $query{foundry} = $param{foundry};
- $query{layer} = $param{layer} if defined $param{layer};
- $query{spans} = $param{spans} ? 'true' : 'false';
-
- # Add query
- $url->query(\%query);
-
- $c->app->log->debug('Match info: ' . $url);
-
- # Create new user agent and set timeout to 30 seconds
- # my $ua = $c->user->ua; # Mojo::UserAgent->new;
- # $ua->inactivity_timeout(30);
-
- # non-blocking
- if ($cb) {
- # $c->u
- $c->user->auth_request(get =>
- # $ua->get(
- $url => sub {
- my $tx = pop;
- $self->_process_response('match', $index, $tx);
- weaken $index;
- return $cb->($index);
- });
- }
-
- # Match info blocking
- else {
- my $tx = $c->user->auth_request(get => $url);
- # my $tx = $ua->get($url);
- return $self->_process_response('match', $index, $tx);
- };
-};
-
-
-# Get text info
-sub text {
- my $self = shift;
- my $index = shift;
-
- # Get controller
- my $c = $index->controller;
-
- # If there is a callback, do async
- my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
-
- my %param = @_;
-
- my $url = Mojo::URL->new($index->api);
-
- # Use hash slice to create path
- $url->path(join('/', 'corpus', @param{qw/corpus_id doc_id text_id/}));
-
- my %query;
- $query{fields} = $param{fields};
-
- # Add query
- $url->query(\%query);
-
- $c->app->log->debug('Text info: ' . $url);
-
- # non-blocking
- if ($cb) {
- $c->user->auth_request(
- get =>
- $url => sub {
- my $tx = pop;
- $self->_process_response('text', $index, $tx);
- weaken $index;
- return $cb->($index);
- });
- }
-
- # Text info blocking
- else {
- my $tx = $c->user->auth_request(get => $url);
- return $self->_process_response('text', $index, $tx);
- };
-};
-
-
-# Get corpus statistics
-sub statistics {
- my $self = shift;
- my $index = shift;
-
- # Get controller
- my $c = $index->controller;
-
- # If there is a callback, do async
- my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
-
- my %param = @_;
-
- my $url = Mojo::URL->new($index->api);
-
- # Use hash slice to create path
- $url->path('statistics');
-
- my %query;
- $query{corpusQuery} = $param{cq};
-
- # Add query
- $url->query(\%query);
-
- $c->stash('search._resource_cache' => $url->to_string);
-
- $c->app->log->debug('Statistics info: ' . $url);
-
- # non-blocking
- if ($cb) {
- $c->user->auth_request(
- get =>
- $url => sub {
- my $tx = pop;
- $self->_process_response('resource', $index, $tx);
- weaken $index;
- return $cb->($index);
- });
- }
-
- # Statistics info blocking
- else {
- my $tx = $c->user->auth_request(get => $url);
- return $self->_process_response('resource', $index, $tx);
- };
-};
-
-
-
-# Get resource information
-sub resource {
- my $self = shift;
- my $index = shift;
-
- # Get controller
- my $c = $index->controller;
-
- my $user = $c->stash('user') // 'not_logged_in';
-
- # If there is a callback, do async
- my $cb = pop if ref $_[-1] && ref $_[-1] eq 'CODE';
-
- my %param = @_;
-
- # Rename info endpoints regarding resource
- my $type = $param{type} // 'collection';
- $type = 'virtualcollection' if $type eq 'collection';
-
- # Create resource URL
- my $url = Mojo::URL->new($index->api)->path($type);
-
- # Debugging
- $c->app->log->debug('Get resource info on '. $url);
-
- # Check for cached information
- if (my $json = $c->chi->get($user . $url->to_string)) {
-
- # TODO: That's unfortunate, as it prohibits caching of multiple resources
- $c->app->log->debug('Get resource info from cache');
- $c->stash('search.resource' => $json);
- return $cb->($index) if $cb;
- return $json;
- };
-
- $c->stash('search._resource_cache' => $url->to_string);
-
- # Create new user agent and set timeout to 30 seconds
- #my $ua = $c->ua; # Mojo::UserAgent->new;
- #$ua->inactivity_timeout(30);
-
- # Get resource information async
- if ($cb) {
- $c->user->auth_request(get =>
- $url => sub {
- $self->_process_response('resource', $index, pop);
- weaken $index;
- return $cb->($index);
- })
- }
-
- # Get resource information blocking
- else {
- my $tx = $c->user->auth_request(get => $url);
- $self->_process_response('resource', $index, $tx);
- };
-};
-
-
-# Process response - especially error messages etc.
-sub _process_response {
- my ($self, $type, $index, $tx) = @_;
- my $c = $index->controller;
-
- my $json;
- my $res = $tx->res;
-
- # Json failure
- unless ($json = $res->json) {
- $c->notify(error => 'JSON response is invalid');
- $index->status(0);
- return;
- };
-
- # Set api response as jsonld
- $index->api_response($json);
-
- # An error has occurded
- if (my $e = $tx->error) {
-
- # Send error
- $self->_notify_on_error($c, 1, $tx->res);
-
- # $c->notify(
- # error =>
- # ($e->{code} ? $e->{code} . ': ' : '') .
- # $e->{message} . ' for ' . $type . ' (remote)'
- # );
- $index->status($e->{code} // 0);
- return;
- };
-
-
- # Response was fine
- if ($res->is_success) {
-
- # expected response for matches
- if ($type eq 'matches') {
- $self->_process_response_matches($index, $json);
- }
- elsif ($type eq 'trace') {
- $self->_process_response_trace($index, $json);
- }
- elsif ($type eq 'match') {
- $self->_process_response_match($index, $json);
- }
- elsif ($type eq 'resource') {
- $self->_process_response_resource($index, $json);
- }
- elsif ($type eq 'text') {
- $self->_process_response_text($index, $json);
- };
-
- return 1 if ref $json ne 'HASH';
-
- $self->_notify_on_warnings($c, $json);
- $self->_notify_on_error($c, 0, $json);
- }
-
- # Request failed
- else {
- $index->status(0);
- $self->_notify_on_error($c, 1, $tx->res);
- };
- return 1;
-};
-
-
-# Handle match results
-sub _process_response_matches {
- my ($self, $index, $json) = @_;
-
- # Process meta
- my $meta = $json->{meta};
-
- # Reformat benchmark counter
- my $benchmark = $meta->{benchmark};
- if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
- $benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
- };
-
- # Set benchmark
- $index->benchmark($benchmark);
-
- # Set time exceeded
- if ($meta->{timeExceeded} && $meta->{timeExceeded} eq Mojo::JSON::true) {
- $index->time_exceeded(1);
- };
-
- # Set result values
- $index->items_per_page($meta->{itemsPerPage});
-
- # Set authorization
- $index->authorized($meta->{authorized}) if $meta->{authorized};
-
- # Bouncing query
- # if ($json->{query}) {
- # $index->query_jsonld($json->{query});
- # };
-
- # Legacy
- # elsif ($json->{request}->{query}) {
- # $index->query_jsonld($json->{request}->{query});
- # };
-
- # Bouncing collection query
- if ($json->{collection}) {
- $index->collection_jsonld($json->{collection});
- }
-
- # Legacy
- # elsif ($json->{request}->{collection}) {
- # $index->collection_jsonld($json->{request}->{collection});
- # };
-
- $index->results(_map_matches($json->{matches}));
-
- # Total results not set by stash
- if ($index->total_results == -1) {
-
- if ($meta->{totalResults} && $meta->{totalResults} > -1) {
- my $c = $index->controller;
-
- # TODO: Cache on auth_keys!
- my $user = $c->stash('user') // 'not_logged_in';
-
- $c->app->log->debug('Cache total result');
- $c->chi->set($user . $index->_api_cache => $meta->{totalResults}, '120min');
- $index->total_results($meta->{totalResults});
- };
- };
-};
-
-
-# Process query serialization response
-sub _process_response_match {
- my ($self, $index, $json) = @_;
- $index->results(_map_match($json));
-};
-
-
-# Process query serialization response
-sub _process_response_text {
- my ($self, $index, $json) = @_;
- $index->results($json);
-};
-
-
-# Process trace response
-sub _process_response_trace {
- my ($self, $index, $json) = @_;
- $index->query_jsonld($json);
-};
-
-
-# Process resource response
-sub _process_response_resource {
- my ($self, $index, $json) = @_;
- my $c = $index->controller;
-
- my $user = $c->stash('user') // 'not_logged_in';
-
- # TODO: That's unfortunate, as it prohibits multiple resources
- $c->stash('search.resource' => $json);
- $c->app->log->debug('Cache resource info');
- $c->chi->set($user . $c->stash('search._resource_cache') => $json, '24 hours');
-};
-
-
-# Parse error messages and forward them to the user
-sub _notify_on_error {
- my ($self, $c, $failure, $res) = @_;
- my $json = $res;
-
- my $log = $c->app->log;
-
- # Check if the response is already json
- if (blessed $res) {
- $json = $res->json if blessed $res ne 'Mojo::JSON';
- };
-
- # Check json response error message
- if ($json) {
-
- # Legacy, but still in use by Kustvakt
- if ($json->{error}) {
-
- # Temp
- $json->{error} =~ s/;\s+null$//;
- $c->notify(error => $json->{error});
- return;
- }
-
- # New error messages
- elsif ($json->{errstr}) {
- # Temp
- $json->{errstr} =~ s/;\s+null$//;
- $c->notify(error => $json->{errstr});
- return;
- }
-
- elsif ($json->{errors}) {
- my $errors = $json->{errors};
- # TODO: Check for ref!
- foreach (@$errors) {
- $c->notify(
- error =>
- ($_->[0] ? $_->[0] . ': ' : '') .
- ($_->[1] || 'Unknown')
- );
- };
- }
-
- # policy service error messages
- elsif ($json->{status}) {
- $c->notify(error => 'Middleware error ' . $json->{status});
- return;
- };
- };
-
- # Doesn't matter what - there is a failure!
- if ($failure) {
- $c->notify(error => (
- ($res->{code} ? $res->{code} . ': ' : '') .
- ($res->{message} ? $res->{message} : 'Unknown error') .
- ' (remote)'
- ));
- };
-};
-
-
-sub _notify_on_warnings {
- my ($self, $c, $json) = @_;
-
- # Add warnings (Legacy)
- if ($json->{warning}) {
- $json->{warning} =~ s/;\s+null$//;
- $c->notify(warn => $json->{warning});
- }
-
- # Add warnings
- elsif ($json->{warnings}) {
-
- my $warnings = $json->{warnings};
- # TODO: Check for ref!
- foreach (@$warnings) {
- $c->notify(
- warn =>
- ($_->[0] ? $_->[0] . ': ' : '') .
- $_->[1]
- );
- };
- };
-};
-
-
-# Cleanup array of matches
-sub _map_matches {
- return () unless $_[0];
- map { _map_match($_) } @{ shift() };
-};
-
-
-# Cleanup single match
-sub _map_match {
- my $x = shift or return;
-
- # legacy match id
- if ($x->{matchID}) {
- $x->{matchID} =~ s/^match\-(?:[^!]+!|[^_]+_)[^\.]+?\.[^-]+?-// or
- $x->{matchID} =~ s!^match\-(?:[^\/]+\/){2}[^-]+?-!!;
- };
-
- (
- $x->{corpusID},
- $x->{docID},
- $x->{textID}
- ) = ($x->{textSigle} =~ /^([^_]+?)_+([^\.]+?)\.(.+?)$/);
-
- # $x->{docID} =~ s/^[^_]+_//;
- # Legacy: In old versions the text_id was part of the doc_id
- # unless ($x->{textID}) {
- # ($x->{docID}, $x->{textID}) = split '\.', $x->{docID};
- # };
-
- $x;
-};
-
-# Build query url
-sub _query_url {
- my ($index, %param) = @_;
-
- # Set cutoff from param
- $index->cutoff(delete $param{cutoff});
-
- # Set collection from param
- $index->collection(delete $param{collection});
-
- # Set query language
- $index->query_language(delete $param{query_language} // 'poliqarp');
-
- # Should results be cached? Defaults to "yes"
- $index->no_cache(1) if $param{no_cache};
-
- # Init the query with stuff coming from the index
- my %query;
- $query{q} = $index->query;
- $query{ql} = $index->query_language;
- $query{page} = $index->start_page if $index->start_page;
- $query{count} = $index->items_per_page if $index->items_per_page;
- $query{cq} = $index->collection if $index->collection;
- $query{cutoff} = 'true' if $index->cutoff;
-
- # Create query url
- my $url = Mojo::URL->new($index->api);
- $url->query(\%query);
- return $url;
-};
-
-
-1;
-
-
-__END__
-
-=pod
-
-=encoding utf8
-
-=head1 NAME
-
-Kalamar::API
-
-=head1 DESCRIPTION
-
-L<Kalamar::API> is a search engine class for L<Mojolicious::Plugin::Search>
-that uses the KorAP Web API.
-
-B<The Web API as well as L<Mojolicious::Plugin::Search> are not stable yet,
-so this class is expected to change in the near future. Do not rely on its API!>
-
-
-=head1 METHODS
-
-L<Kalamar::API> inherits all methods from L<Mojolicious::Plugin> and
-implements the following new ones.
-
-
-=head2 register
-
-See L<Mojolicious::Plugin::Search> for registering search engines.
-In addition to the mentioned query parameters, the following parameters are supported:
-
-
-=over 2
-
-=item B<query_language>
-
-One of the supported query languages, like C<poliqarp> or C<annis>.
-
-
-=item B<cutoff>
-
-Cut off results following the current page (i.e. don't count the number of results).
-
-
-=item B<no_cache>
-
-Do not cache search results. Defaults to C<0>.
-
-
-=back
-
-In addition to the mentioned index attributes, the following attributes are supported:
-
-=over 2
-
-=item B<api>
-
-The API address.
-
-
-=item B<time_exceeded>
-
-Report on time outs, that may mean, not all results were retrieved.
-
-
-=item B<api_request>
-
-Report the whole API request.
-
-
-=item B<api_response>
-
-Report the whole API response (a KoralQuery object).
-
-
-=item B<benchmarks>
-
-Report on processing time for benchmarking.
-
-
-=item B<query_jsonld>
-
-The KoralQuery realization of the C<query> object.
-
-=back
-
-=head2 search
-
-Search the index.
-
-=head2 trace
-
-Trace query serializations.
-
-=head2 match
-
-Get match information.
-
-=head2 resource
-
-Get resource information.
-
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (C) 2015-2018, L<IDS Mannheim|http://www.ids-mannheim.de/>
-Author: L<Nils Diewald|http://nils-diewald.de/>
-
-Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
-Corpus Analysis Platform at the
-L<Institute for the German Language (IDS)|http://ids-mannheim.de/>,
-member of the
-L<Leibniz-Gemeinschaft|http://www.leibniz-gemeinschaft.de/en/about-us/leibniz-competition/projekte-2011/2011-funding-line-2/>
-and supported by the L<KobRA|http://www.kobra.tu-dortmund.de> project,
-funded by the
-L<Federal Ministry of Education and Research (BMBF)|http://www.bmbf.de/en/>.
-
-Kalamar is free software published under the
-L<BSD-2 License|https://raw.githubusercontent.com/KorAP/Kalamar/master/LICENSE>.
-
-=cut
diff --git a/lib/Kalamar/Controller/Search.pm b/lib/Kalamar/Controller/Search.pm
index 968402d..2359274 100644
--- a/lib/Kalamar/Controller/Search.pm
+++ b/lib/Kalamar/Controller/Search.pm
@@ -1,230 +1,464 @@
package Kalamar::Controller::Search;
use Mojo::Base 'Mojolicious::Controller';
-use Mojo::Util qw/deprecated/;
+use Mojo::Collection 'c';
+use Mojo::ByteStream 'b';
+use POSIX 'ceil';
+
+has no_cache => 0;
+
+has items_per_page => 25;
+
+# TODO:
+# Support server timing API
+
+# TODO:
+# Add match_info template for HTML
+#
+# TODO:
+# Support search in corpus and virtualcollection
+#
+# TODO:
+# set caches with timing like '120min'
-# Query the KorAP backends and render a template
+
+# Query endpoint
sub query {
my $c = shift;
+
+ # Validate user input
my $v = $c->validation;
- $v->optional('q');
- $v->optional('ql');
- $v->optional('collection');
- $v->optional('action');
- $v->optional('snippet');
- $v->optional('cutoff');
- $v->optional('count');
- $v->optional('p');
+ $v->optional('q', 'trim');
+ $v->optional('ql')->in(qw/poliqarp cosmas2 annis cql fcsql/);
+ $v->optional('collection', 'trim'); # Legacy
+ $v->optional('cq', 'trim');
+ # $v->optional('action'); # action 'inspect' is no longer valid
+ # $v->optional('snippet');
+ $v->optional('cutoff')->in(qw/true false/);
+ $v->optional('count')->num(1, undef);
+ $v->optional('p', 'trim')->num(1, undef); # Start page
+ $v->optional('o', 'trim')->num(1, undef); # Offset
+ $v->optional('context');
- #my $tx = $ua->build_tx(TRACE => $url . 'search?cq=corpusAuthor+%3D+%22Baum%22');
- #{"@context":"http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld","errors":[[301,"You did not specify a query!"]],"collection":{"@type":"koral:doc","key":"corpusAuthor","value":"Baum","match":"match:eq"}}
-
+ # Get query
my $query = $v->param('q');
+ # TODO:
+ # Check for validation errors!
+
# No query
unless ($query) {
return $c->render($c->loc('Template_intro', 'intro'));
};
- # Base parameters for remote access
- my %param = (
- query_language => scalar $v->param('ql'),
- query => $query,
- collection => scalar $v->param('collection')
- );
+ my %query = ();
+ $query{q} = $query;
+ $query{ql} = $v->param('ql') // 'poliqarp';
+ $query{count} = $v->param('count') // $c->items_per_page;
+ $query{cq} = $v->param('cq') // $v->param('collection');
+ $query{cutoff} = $v->param('cutoff');
+ # Before: 'base/s:p'/'paragraph'
+ $query{context} = $v->param('context') // '40-t,40-t';
- # May be not relevant
- my $inspect = (scalar $v->param('action') // '') eq 'inspect' ? 1 : 0;
+ # Start page
+ my $page = $v->param('p') // 1;
- # Just check the serialization non-blocking
- if ($inspect) {
- $c->search->trace(
- %param => sub {
- return $c->render(template => 'query_info');
- }
- );
- return;
+ $c->stash(query => $query);
+ $c->stash(ql => $query{ql});
+
+ my $items_per_page = $c->items_per_page;
+
+ # Set count
+ if ($query{count} && $query{count} <= $c->items_per_page ) {
+ $items_per_page = delete $query{count};
+ $query{count} = $items_per_page;
};
- # Choose the snippet based on the parameter
- my $template = scalar $v->param('snippet') ? 'snippet' : 'search';
-
- # Search non-blocking
- my $tx = $c->render_later->tx;
+ $c->stash(items_per_page => $items_per_page);
# TODO:
- # This should be simplified to use Promises only
- Mojo::IOLoop->delay(
+ # if ($v->param('action') eq 'inspect') use trace!
+
+ # Set offset
+ # From Mojolicious::Plugin::Search::Index
+ $query{offset} = $v->param('o') || ((($page // 1) - 1) * ($items_per_page || 1));
+
+ # Create remote request URL
+ my $url = Mojo::URL->new($c->korap->api);
+ $url->path('search');
+ # $url->query(%query);
+ $url->query(map { $_ => $query{$_}} sort keys %query);
+
+ # In case the user is not known, it is assumed, the user is not logged in
+ my $total_cache_str;
+
+ # Check if total results information is cached
+ my $total_results = -1;
+ unless ($c->no_cache) {
+
+ # Create cache string
+ my $user = $c->user->handle;
+ my $cache_url = $url->clone;
+ $cache_url->query->remove('context')->remove('count')->remove('cutoff')->remove('offset');
+ $total_cache_str = "total-$user-" . $cache_url->to_string;
+
+ $c->app->log->debug('Check for total results: ' . $total_cache_str);
+
+ # Get total results value
+ $total_results = $c->chi->get($total_cache_str);
+
+ # Set stash if cache exists
+ if (defined $total_results) {
+ $c->stash(total_results => $total_results);
+
+ $c->app->log->debug('Get total result from cache: ' . $total_results);
+
+ # Set cutoff unless already set
+ $url->query({cutoff => 'true'});
+ };
+ };
+
+ # Wait for rendering
+ $c->render_later;
+
+ # Fetch resource
+ $c->cached_koral_p('get', $url)->then(
+
+ # Process response
sub {
- my $delay = shift;
+ my $json = shift;
- # Search with a callback (async)
- $c->search(
- cutoff => scalar $v->param('cutoff'),
- count => scalar $v->param('count'),
- start_page => scalar $v->param('p'),
- cb => $delay->begin,
- %param
- ) if $query;
+ #######################
+ # Cache total results #
+ #######################
+ # The stash is set in case the total results value is from the cache,
+ # so in that case, it does not need to be cached again
+ my $total_results = $c->stash('total_results');
- # Search resource (async)
- # $c->search->resource(
- # type => 'collection',
- # $delay->begin
- # );
- },
+ unless (defined $total_results) {
- # Collected search
- sub {
+ # There are results to remember
+ if ($json->{meta}->{totalResults} >= 0) {
- # Render to the template
- return $c->render(template => $template);
+ # Remove cutoff requirement again
+ # $url->query([cutoff => 'true']);
+
+ $total_results = $json->{meta}->{totalResults};
+ $c->stash(total_results => $total_results);
+
+ $c->app->log->debug('Set for total results: ' . $total_cache_str);
+
+ # Set cache
+ $c->chi->set($total_cache_str => $total_results);
+ }
+
+ # Undefined total results
+ else {
+ $c->stash(total_results => -1);
+ };
+ };
+
+
+ $c->stash(total_pages => 0);
+
+ # Set total pages
+ # From Mojolicious::Plugin::Search::Index
+ if ($total_results > 0) {
+ $c->stash(
+ total_pages => ceil($total_results / ($c->stash('items_per_page') || 1))
+ );
+ };
+
+ # Process meta
+ my $meta = $json->{meta};
+
+ # TODO:
+ # Set benchmark in case of development mode only.
+ # Use server timing API
+ #
+ # Reformat benchmark counter
+ # my $benchmark = $meta->{benchmark};
+ # if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
+ # $benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
+ # };
+ #
+ # # Set benchmark
+ # $self->stash(benchmark => $benchmark);
+
+ # Set time exceeded
+ if ($meta->{timeExceeded} &&
+ $meta->{timeExceeded} eq Mojo::JSON::true) {
+ $c->stash(time_exceeded => 1);
+ };
+
+ # Set result values
+ $c->stash(items_per_page => $meta->{itemsPerPage});
+
+ # Bouncing collection query
+ if ($json->{corpus} || $json->{collection}) {
+ $c->stash(corpus_jsonld => ($json->{corpus} || $json->{collection}));
+ };
+
+ # TODO:
+ # scalar $v->param('snippet') ? 'snippet' : 'search';
+
+ # Render result
+ return $c->render(
+ q => $c->stash('query'),
+ ql => $c->stash('ql'),
+ start_page => $page,
+ start_index => $json->{meta}->{startIndex},
+ results => _map_matches($json->{matches}),
+ template => 'search'
+ );
}
- )->catch(sub { $c->helpers->reply->exception(pop) and undef $tx })->wait;
+
+ # Deal with errors
+ )->catch(
+ sub {
+ my $err_msg = shift;
+
+ # Only raised in case of connection errors
+ if ($err_msg) {
+ $c->stash('err_msg' => 'backendNotAvailable');
+ $c->notify(error => { src => 'Backend' } => $err_msg)
+ };
+
+ # $c->_notify_on_errors(shift);
+ return $c->render(
+ q => $c->stash('query'),
+ ql => $c->stash('ql'),
+ template => 'failure'
+ );
+ }
+ )
+
+ # Start IOLoop
+ ->wait;
+
+ return 1;
};
-# Get meta data of a text
+# Corpus info endpoint
+# This replaces the collections endpoint
+sub corpus_info {
+ my $c = shift;
+
+ # Input validation
+ my $v = $c->validation;
+ $v->optional('cq');
+
+ my $url = Mojo::URL->new($c->korap->api);
+
+ # Use hash slice to create path
+ $url->path('statistics');
+
+ # Add query
+ $url->query(corpusQuery => $v->param('cq'));
+
+ $c->app->log->debug("Statistics info: $url");
+
+ # Async
+ $c->render_later;
+
+ # Request koral, maybe cached
+ $c->cached_koral_p('get', $url)
+
+ # Process response
+ ->then(
+ sub {
+ my $json = shift;
+ return $c->render(
+ json => $c->notifications(json => $json),
+ status => 200
+ );
+ }
+ )
+
+ # Deal with errors
+ ->catch(
+ sub {
+ return $c->render(
+ json => $c->notifications('json')
+ )
+ }
+ )
+
+ # Start IOLoop
+ ->wait;
+
+ return 1;
+};
+
+
+# Text info endpoint
sub text_info {
my $c = shift;
- my %query = (fields => '*');
- if ($c->param('fields')) {
- $query{fields} = $c->param('fields')
- };
+ # Input validation
+ my $v = $c->validation;
+ $v->optional('fields');
+ my %query = (fields => '@all');
+ $query{fields} = $v->param('fields') if $v->param('fields');
+
+ my $url = Mojo::URL->new($c->korap->api);
+
+ # Use hash slice to create path
+ $url->path(
+ join('/', (
+ 'corpus',
+ $c->stash('corpus_id'),
+ $c->stash('doc_id'),
+ $c->stash('text_id')
+ ))
+ );
+ $url->query(%query);
+
+ # Async
$c->render_later;
- # Use the API for fetching matching information non-blocking
- $c->search->text(
- corpus_id => $c->stash('corpus_id'),
- doc_id => $c->stash('doc_id'),
- text_id => $c->stash('text_id'),
- %query,
+ # Request koral, maybe cached
+ $c->cached_koral_p('get', $url)
- # Callback for async search
+ # Process response
+ ->then(
sub {
- my $index = shift;
- return $c->respond_to(
-
- # Render json if requested
- json => sub {
- # Add notifications to the matching json
- # TODO: There should be a special notification engine doing that!
- my $notes = $c->notifications(json => $index->results->[0]);
- $c->render(
- json => $notes,
- status => $index->status
- );
- }
+ my $json = shift;
+ return $c->render(
+ json => $c->notifications(json => $json),
+ status => 200
);
}
- );
+ )
+
+ # Deal with errors
+ ->catch(
+ sub {
+ return $c->render(
+ json => $c->notifications('json')
+ )
+ }
+ )
+
+ # Start IOLoop
+ ->wait;
+
+ return 1;
};
-
-# Get information about a match
-# Note: This is called 'match_info' as 'match' is reserved
+# Match info endpoint
sub match_info {
my $c = shift;
+ # Validate user input
+ my $v = $c->validation;
+ $v->optional('foundry');
+ $v->optional('layer');
+ $v->optional('spans')->in(qw/true false/);
+
# Old API foundry/layer usage
my $foundry = '*';
my %query = (foundry => '*');
- if ($c->param('foundry')) {
- $query{foundry} = scalar $c->param('foundry');
- if ($c->param('layer')) {
- $query{layer} = scalar $c->param('layer');
- };
- if ($c->param('spans')) {
- $query{spans} = 'true';
- };
+ if ($v->param('foundry')) {
+ $query{foundry} = $v->param('foundry');
+ $query{layer} = $v->param('layer') if $v->param('layer');
+ $query{spans} = $v->param('spans') if $v->param('spans');
};
- # Async
+ # Create new request API
+ my $url = Mojo::URL->new($c->korap->api);
+
+ # Use stash information to create url path
+ $url->path(
+ join('/', (
+ 'corpus',
+ $c->stash('corpus_id'),
+ $c->stash('doc_id'),
+ $c->stash('text_id'),
+ $c->stash('match_id'),
+ 'matchInfo'
+ ))
+ );
+
+ # Set query parameters in order
+ $url->query(map { $_ => $query{$_}} sort keys %query);
+
$c->render_later;
- # Use the API for fetching matching information non-blocking
- $c->search->match(
- corpus_id => $c->stash('corpus_id'),
- doc_id => $c->stash('doc_id'),
- text_id => $c->stash('text_id'),
- match_id => $c->stash('match_id'),
- %query,
-
- # Callback for async search
+ $c->cached_koral_p('get', $url)->then(
sub {
- my $index = shift;
- return $c->respond_to(
+ my $json = shift;
- # Render json if requested
- json => sub {
- # Add notifications to the matching json
- # TODO: There should be a special notification engine doing that!
- my $notes = $c->notifications(json => $index->results->[0]);
- $c->render(
- json => $notes,
- status => $index->status
- );
- },
+ # Process results
+ $json = _map_match($json);
+ $c->stash(results => $json);
- # Render html if requested
- html => sub {
- return $c->render(
- layout => 'default',
- template => 'match_info'
- )
- }
+ return $c->render(
+ json => $c->notifications(json => $json),
+ status => 200
);
+
+ return $json;
}
- );
-};
+ )
-
-# Get information about collections
-sub corpus_info {
- my $c = shift;
- my $v = $c->validation;
-
- $v->optional('cq');
-
- # Async
- $c->render_later;
-
- $c->search->statistics(
- cq => $v->param('cq'),
+ # Deal with errors
+ ->catch(
sub {
- my $notes = $c->notifications(json => $c->stash('search.resource'));
- return $c->render(json => $notes);
+ return $c->render(
+ json => $c->notifications('json')
+ )
}
- );
+ )
+
+ # Start IOLoop
+ ->wait;
+
+ return 1;
};
-sub collections {
- my $c = shift;
- # Async
- $c->render_later;
-
- deprecated 'collections() is deprecated in favour of corpus_info';
-
- # Get resource (for all)
- $c->search->resource(
- type => 'collection',
- sub {
- my $notes = $c->notifications(json => $c->stash('search.resource'));
- return $c->render(json => $notes);
- }
- );
+# Cleanup array of matches
+sub _map_matches {
+ return c() unless $_[0];
+ c(map { _map_match($_) } @{ shift() });
};
+
+# Cleanup single match
+sub _map_match {
+ my $match = shift or return;
+
+ # Legacy match id
+ if ($match->{matchID}) {
+ $match->{matchID} =~ s/^match\-(?:[^!]+!|[^_]+_)[^\.]+?\.[^-]+?-// or
+ $match->{matchID} =~ s!^match\-(?:[^\/]+\/){2}[^-]+?-!!;
+ };
+
+ return unless $match->{textSigle};
+
+ # Set IDs based on the sigle
+ (
+ $match->{corpusID},
+ $match->{docID},
+ $match->{textID}
+ ) = ($match->{textSigle} =~ /^([^_]+?)_+([^\.]+?)\.(.+?)$/);
+
+ return $match;
+};
+
+
1;
__END__
+__END__
+
=pod
=encoding utf8
@@ -306,12 +540,42 @@
B<This parameter is directly forwarded to the API and may not be supported in the future.>
+=item B<o>
+
+If set, the matches will offset to the given match in the result set.
+Will default to 0.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+=item B<context>
+
+The context of the snippets to retrieve. Defaults to C<40-t,40-t>.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+=item B<cq>
+
+The corpus query to limit the search to.
+
=back
+=head2 corpus
+
+ /corpus?cq=corpusSigle+%3D+%22GOE%22
+
+Returns statistics information for a virtual corpus.
+
+=head2 text
+
+ /corpus/:corpus_id/:doc_id/:text_id
+
+Returns meta data information for a specific text.
+
+
=head2 match
- /:corpus_id/:doc_id/:text_id/:match_id?foundry=*
+ /corpus/:corpus_id/:doc_id/:text_id/:match_id?foundry=*
Returns information to a match either as a C<JSON> or an C<HTML> document.
The path defines the concrete match, by corpus identifier, document identifier,
@@ -348,33 +612,6 @@
=back
-In addition to the given parameters, the following path values are expected.
-
-=over 2
-
-=item B<corpus_id>
-
-The corpus sigle as defined by DeReKo.
-
-
-=item B<doc_id>
-
-The document sigle as defined by DeReKo.
-
-
-=item B<text_id>
-
-The text sigle as defined by DeReKo.
-
-
-=item B<match_id>
-
-The ID of the match, normally generated by the search backend.
-This contains the span of the match in the text and possibly further
-information (like highlights).
-
-=back
-
=head1 COPYRIGHT AND LICENSE
diff --git a/lib/Kalamar/Controller/Search2.pm b/lib/Kalamar/Controller/Search2.pm
deleted file mode 100644
index f071fbf..0000000
--- a/lib/Kalamar/Controller/Search2.pm
+++ /dev/null
@@ -1,634 +0,0 @@
-package Kalamar::Controller::Search2;
-use Mojo::Base 'Mojolicious::Controller';
-use Data::Dumper;
-use Mojo::Collection 'c';
-use Mojo::ByteStream 'b';
-use POSIX 'ceil';
-
-has no_cache => 0;
-
-has items_per_page => 25;
-
-# TODO:
-# Support server timing API
-
-# TODO:
-# Add match_info template for HTML
-#
-# TODO:
-# Support search in corpus and virtualcollection
-#
-# TODO:
-# set caches with timing like '120min'
-
-
-
-# Query endpoint
-sub query {
- my $c = shift;
-
- # Validate user input
- my $v = $c->validation;
-
- $v->optional('q', 'trim');
- $v->optional('ql')->in(qw/poliqarp cosmas2 annis cql fcsql/);
- $v->optional('collection', 'trim'); # Legacy
- $v->optional('cq', 'trim');
- # $v->optional('action'); # action 'inspect' is no longer valid
- # $v->optional('snippet');
- $v->optional('cutoff')->in(qw/true false/);
- $v->optional('count')->num(1, undef);
- $v->optional('p', 'trim')->num(1, undef); # Start page
- $v->optional('o', 'trim')->num(1, undef); # Offset
- $v->optional('context');
-
- # Get query
- my $query = $v->param('q');
-
- # TODO:
- # Check for validation errors!
-
- # No query
- unless ($query) {
- return $c->render($c->loc('Template_intro', 'intro'));
- };
-
- my %query = ();
- $query{q} = $query;
- $query{ql} = $v->param('ql') // 'poliqarp';
- $query{count} = $v->param('count') // $c->items_per_page;
- $query{cq} = $v->param('cq') // $v->param('collection');
- $query{cutoff} = $v->param('cutoff');
- # Before: 'base/s:p'/'paragraph'
- $query{context} = $v->param('context') // '40-t,40-t';
-
- # Start page
- my $page = $v->param('p') // 1;
-
- $c->stash(query => $query);
- $c->stash(ql => $query{ql});
-
- my $items_per_page = $c->items_per_page;
-
- # Set count
- if ($query{count} && $query{count} <= $c->items_per_page ) {
- $items_per_page = delete $query{count};
- $query{count} = $items_per_page;
- };
-
- $c->stash(items_per_page => $items_per_page);
-
- # TODO:
- # if ($v->param('action') eq 'inspect') use trace!
-
- # Set offset
- # From Mojolicious::Plugin::Search::Index
- $query{offset} = $v->param('o') || ((($page // 1) - 1) * ($items_per_page || 1));
-
- # Create remote request URL
- my $url = Mojo::URL->new($c->korap->api);
- $url->path('search');
- # $url->query(%query);
- $url->query(map { $_ => $query{$_}} sort keys %query);
-
- # In case the user is not known, it is assumed, the user is not logged in
- my $total_cache_str;
-
- # Check if total results information is cached
- my $total_results = -1;
- unless ($c->no_cache) {
-
- # Create cache string
- my $user = $c->user->handle;
- my $cache_url = $url->clone;
- $cache_url->query->remove('context')->remove('count')->remove('cutoff')->remove('offset');
- $total_cache_str = "total-$user-" . $cache_url->to_string;
-
- $c->app->log->debug('Check for total results: ' . $total_cache_str);
-
- # Get total results value
- $total_results = $c->chi->get($total_cache_str);
-
- # Set stash if cache exists
- if (defined $total_results) {
- $c->stash(total_results => $total_results);
-
- $c->app->log->debug('Get total result from cache: ' . $total_results);
-
- # Set cutoff unless already set
- $url->query({cutoff => 'true'});
- };
- };
-
- # Wait for rendering
- $c->render_later;
-
- # Fetch resource
- $c->cached_koral_p('get', $url)->then(
-
- # Process response
- sub {
- my $json = shift;
-
- #######################
- # Cache total results #
- #######################
- # The stash is set in case the total results value is from the cache,
- # so in that case, it does not need to be cached again
- my $total_results = $c->stash('total_results');
-
- unless (defined $total_results) {
-
- # There are results to remember
- if ($json->{meta}->{totalResults} >= 0) {
-
- # Remove cutoff requirement again
- # $url->query([cutoff => 'true']);
-
- $total_results = $json->{meta}->{totalResults};
- $c->stash(total_results => $total_results);
-
- $c->app->log->debug('Set for total results: ' . $total_cache_str);
-
- # Set cache
- $c->chi->set($total_cache_str => $total_results);
- }
-
- # Undefined total results
- else {
- $c->stash(total_results => -1);
- };
- };
-
-
- $c->stash(total_pages => 0);
-
- # Set total pages
- # From Mojolicious::Plugin::Search::Index
- if ($total_results > 0) {
- $c->stash(
- total_pages => ceil($total_results / ($c->stash('items_per_page') || 1))
- );
- };
-
- # Process meta
- my $meta = $json->{meta};
-
- # TODO:
- # Set benchmark in case of development mode only.
- # Use server timing API
- #
- # Reformat benchmark counter
- # my $benchmark = $meta->{benchmark};
- # if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
- # $benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
- # };
- #
- # # Set benchmark
- # $self->stash(benchmark => $benchmark);
-
- # Set time exceeded
- if ($meta->{timeExceeded} &&
- $meta->{timeExceeded} eq Mojo::JSON::true) {
- $c->stash(time_exceeded => 1);
- };
-
- # Set result values
- $c->stash(items_per_page => $meta->{itemsPerPage});
-
- # Bouncing collection query
- if ($json->{corpus} || $json->{collection}) {
- $c->stash(corpus_jsonld => ($json->{corpus} || $json->{collection}));
- };
-
- # TODO:
- # scalar $v->param('snippet') ? 'snippet' : 'search2';
-
- # Render result
- return $c->render(
- q => $c->stash('query'),
- ql => $c->stash('ql'),
- start_page => $page,
- start_index => $json->{meta}->{startIndex},
- results => _map_matches($json->{matches}),
- template => 'search2'
- );
- }
-
- # Deal with errors
- )->catch(
- sub {
- my $err_msg = shift;
-
- # Only raised in case of connection errors
- if ($err_msg) {
- $c->stash('err_msg' => 'backendNotAvailable');
- $c->notify(error => { src => 'Backend' } => $err_msg)
- };
-
- # $c->_notify_on_errors(shift);
- return $c->render(
- q => $c->stash('query'),
- ql => $c->stash('ql'),
- template => 'failure'
- );
- }
- )
-
- # Start IOLoop
- ->wait;
-
- return 1;
-};
-
-
-# Corpus info endpoint
-# This replaces the collections endpoint
-sub corpus_info {
- my $c = shift;
-
- # Input validation
- my $v = $c->validation;
- $v->optional('cq');
-
- my $url = Mojo::URL->new($c->korap->api);
-
- # Use hash slice to create path
- $url->path('statistics');
-
- # Add query
- $url->query(corpusQuery => $v->param('cq'));
-
- $c->app->log->debug("Statistics info: $url");
-
- # Async
- $c->render_later;
-
- # Request koral, maybe cached
- $c->cached_koral_p('get', $url)
-
- # Process response
- ->then(
- sub {
- my $json = shift;
- return $c->render(
- json => $c->notifications(json => $json),
- status => 200
- );
- }
- )
-
- # Deal with errors
- ->catch(
- sub {
- return $c->render(
- json => $c->notifications('json')
- )
- }
- )
-
- # Start IOLoop
- ->wait;
-
- return 1;
-};
-
-
-# Text info endpoint
-sub text_info {
- my $c = shift;
-
- # Input validation
- my $v = $c->validation;
- $v->optional('fields');
-
- my %query = (fields => '@all');
- $query{fields} = $v->param('fields') if $v->param('fields');
-
- my $url = Mojo::URL->new($c->korap->api);
-
- # Use hash slice to create path
- $url->path(
- join('/', (
- 'corpus',
- $c->stash('corpus_id'),
- $c->stash('doc_id'),
- $c->stash('text_id')
- ))
- );
- $url->query(%query);
-
- # Async
- $c->render_later;
-
- # Request koral, maybe cached
- $c->cached_koral_p('get', $url)
-
- # Process response
- ->then(
- sub {
- my $json = shift;
- return $c->render(
- json => $c->notifications(json => $json),
- status => 200
- );
- }
- )
-
- # Deal with errors
- ->catch(
- sub {
- return $c->render(
- json => $c->notifications('json')
- )
- }
- )
-
- # Start IOLoop
- ->wait;
-
- return 1;
-};
-
-
-# Match info endpoint
-sub match_info {
- my $c = shift;
-
- # Validate user input
- my $v = $c->validation;
- $v->optional('foundry');
- $v->optional('layer');
- $v->optional('spans')->in(qw/true false/);
-
- # Old API foundry/layer usage
- my $foundry = '*';
- my %query = (foundry => '*');
- if ($v->param('foundry')) {
- $query{foundry} = $v->param('foundry');
- $query{layer} = $v->param('layer') if $v->param('layer');
- $query{spans} = $v->param('spans') if $v->param('spans');
- };
-
- # Create new request API
- my $url = Mojo::URL->new($c->korap->api);
-
- # Use stash information to create url path
- $url->path(
- join('/', (
- 'corpus',
- $c->stash('corpus_id'),
- $c->stash('doc_id'),
- $c->stash('text_id'),
- $c->stash('match_id'),
- 'matchInfo'
- ))
- );
-
- # Set query parameters in order
- $url->query(map { $_ => $query{$_}} sort keys %query);
-
- $c->render_later;
-
- $c->cached_koral_p('get', $url)->then(
- sub {
- my $json = shift;
-
- # Process results
- $json = _map_match($json);
- $c->stash(results => $json);
-
- return $c->render(
- json => $c->notifications(json => $json),
- status => 200
- );
-
- return $json;
- }
- )
-
- # Deal with errors
- ->catch(
- sub {
- return $c->render(
- json => $c->notifications('json')
- )
- }
- )
-
- # Start IOLoop
- ->wait;
-
- return 1;
-};
-
-
-# Cleanup array of matches
-sub _map_matches {
- return c() unless $_[0];
- c(map { _map_match($_) } @{ shift() });
-};
-
-
-# Cleanup single match
-sub _map_match {
- my $match = shift or return;
-
- # Legacy match id
- if ($match->{matchID}) {
- $match->{matchID} =~ s/^match\-(?:[^!]+!|[^_]+_)[^\.]+?\.[^-]+?-// or
- $match->{matchID} =~ s!^match\-(?:[^\/]+\/){2}[^-]+?-!!;
- };
-
- return unless $match->{textSigle};
-
- # Set IDs based on the sigle
- (
- $match->{corpusID},
- $match->{docID},
- $match->{textID}
- ) = ($match->{textSigle} =~ /^([^_]+?)_+([^\.]+?)\.(.+?)$/);
-
- return $match;
-};
-
-
-1;
-
-
-__END__
-
-__END__
-
-=pod
-
-=encoding utf8
-
-=head1 NAME
-
-Kalamar::Controller::Search
-
-
-=head1 DESCRIPTION
-
-L<Kalamar::Controller::Search> is the controller class for
-search related endpoints in Kalamar. Actions are released when routes
-match.
-
-
-=head1 METHODS
-
-L<Kalamar::Controller::Search> inherits all methods from
-L<Mojolicious::Controller> and implements the following new ones.
-
-=head2 query
-
- GET /?q=Baum&ql=poliqarp
-
-Action for all queries to the system. Returns C<HTML> only for the moment.
-
-The following parameters are supported.
-
-
-=over 2
-
-=item B<q>
-
-The query string. This may any query written in a supported query language.
-
-
-=item B<ql>
-
-The query language. This may be any query language supported by the system,
-written as the API expects the string.
-
-
-=item B<action>
-
-May be C<inspect>. In that case, the serialized request is mirrored instead of
-processed.
-
-B<This switch is experimental and may change without warnings!>
-
-
-=item B<snippet>
-
-If set, the query is returned in the snippet view template.
-
-B<This parameter is experimental and may change without warnings!>
-
-
-=item B<cutoff>
-
-If set, the query will be cut off after the matches.
-
-B<This parameter is directly forwarded to the API and may not be supported in the future.>
-
-
-=item B<count>
-
-If set, the query will be only return the given number of matches,
-in case the API supports it. Will fallback to the default number of matches defined
-by the API or the backend.
-
-B<This parameter is directly forwarded to the API and may not be supported in the future.>
-
-
-=item B<p>
-
-If set, the query will page to the given number of pages in the result set.
-Will default to 1.
-
-B<This parameter is directly forwarded to the API and may not be supported in the future.>
-
-=item B<o>
-
-If set, the matches will offset to the given match in the result set.
-Will default to 0.
-
-B<This parameter is directly forwarded to the API and may not be supported in the future.>
-
-=item B<context>
-
-The context of the snippets to retrieve. Defaults to C<40-t,40-t>.
-
-B<This parameter is directly forwarded to the API and may not be supported in the future.>
-
-=item B<cq>
-
-The corpus query to limit the search to.
-
-=back
-
-
-=head2 corpus
-
- /corpus?cq=corpusSigle+%3D+%22GOE%22
-
-Returns statistics information for a virtual corpus.
-
-=head2 text
-
- /corpus/:corpus_id/:doc_id/:text_id
-
-Returns meta data information for a specific text.
-
-
-=head2 match
-
- /corpus/:corpus_id/:doc_id/:text_id/:match_id?foundry=*
-
-Returns information to a match either as a C<JSON> or an C<HTML> document.
-The path defines the concrete match, by corpus identifier, document identifier,
-text identifier (all information as given by DeReKo), and match identifier
-(essentially the position of the match in the document, including highlight information).
-
-The following parameters are supported.
-
-
-=over 2
-
-=item B<foundry>
-
-Expects a foundry definition for retrieved information.
-If not given, returns all annotations for the match.
-If given, returns only given layer information for the defined foundry.
-
-B<This parameter is experimental and may change without warnings!>
-
-
-=item B<layer>
-
-Expects a layer definition for retrieved information.
-If not given, returns all annotations for the foundry.
-If given, returns only given layer information for the defined foundry.
-
-B<This parameter is experimental and may change without warnings!>
-
-
-=item B<spans>
-
-Boolean value - either C<true> or C<false> - indicating, whether span information
-(i.e. for tree structures) should be retrieved.
-
-=back
-
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (C) 2015-2018, L<IDS Mannheim|http://www.ids-mannheim.de/>
-Author: L<Nils Diewald|http://nils-diewald.de/>
-
-Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
-Corpus Analysis Platform at the
-L<Institute for the German Language (IDS)|http://ids-mannheim.de/>,
-member of the
-L<Leibniz-Gemeinschaft|http://www.leibniz-gemeinschaft.de/en/about-us/leibniz-competition/projekte-2011/2011-funding-line-2/>
-and supported by the L<KobRA|http://www.kobra.tu-dortmund.de> project,
-funded by the
-L<Federal Ministry of Education and Research (BMBF)|http://www.bmbf.de/en/>.
-
-Kalamar is free software published under the
-L<BSD-2 License|https://raw.githubusercontent.com/KorAP/Kalamar/master/LICENSE>.
-
-=cut
diff --git a/lib/Kalamar/Plugin/KalamarHelpers.pm b/lib/Kalamar/Plugin/KalamarHelpers.pm
index 6e0b228..295a04f 100644
--- a/lib/Kalamar/Plugin/KalamarHelpers.pm
+++ b/lib/Kalamar/Plugin/KalamarHelpers.pm
@@ -243,7 +243,7 @@
# Establish 'search_results' taghelper
# This is based on Mojolicious::Plugin::Search
$mojo->helper(
- search_results2 => sub {
+ search_results => sub {
my $c = shift;
# This is a tag helper for templates
diff --git a/lib/Kalamar/Plugin/KalamarUser.pm b/lib/Kalamar/Plugin/KalamarUser.pm
index 19bb2a7..4cf7642 100644
--- a/lib/Kalamar/Plugin/KalamarUser.pm
+++ b/lib/Kalamar/Plugin/KalamarUser.pm
@@ -401,10 +401,3 @@
__END__
-# Failure
-entity {
- "errors":[
- [204,"authentication token is expired","eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0MSIsImlzcyI6Imh0dHA6IiwiZXhwIjoxNDUyOTY2NzAxOTYxfQ.W_rJjJ8i82Srw7MiSPRGeIBLE-rMPmSPK9BA7Dt_7Yc"]
- ]
-}
-
diff --git a/package.json b/package.json
index 8ee1b7f..27c14f7 100755
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "Kalamar",
"description": "Mojolicious-based Frontend for KorAP",
"license": "BSD-2-Clause",
- "version": "0.29.2",
+ "version": "0.30.0",
"pluginVersion": "0.1",
"repository" : {
"type": "git",
diff --git a/templates/failure.html.ep b/templates/failure.html.ep
index d9673b6..509333a 100644
--- a/templates/failure.html.ep
+++ b/templates/failure.html.ep
@@ -2,7 +2,7 @@
<div id="resultinfo"><p class="found"></p></div>
-%= include 'query2'
+%= include 'query'
<p class="no-results"><%= loc('notIssued') %></p>
% if (stash('err_msg')) {
diff --git a/templates/query.html.ep b/templates/query.html.ep
index b3e52fd..fa9980c 100644
--- a/templates/query.html.ep
+++ b/templates/query.html.ep
@@ -1,8 +1,8 @@
% use Mojo::JSON 'encode_json';
-% if (search->api_response) {
+% if (stash('api_response')) {
%= javascript begin
-% my $kq_hash = search->api_response;
+% my $kq_hash = stash('api_response');
% $kq_hash->{matches} = ["..."];
KorAP.koralQuery = <%= b(encode_json($kq_hash))->decode %>;
% end
diff --git a/templates/query2.html.ep b/templates/query2.html.ep
deleted file mode 100644
index fa9980c..0000000
--- a/templates/query2.html.ep
+++ /dev/null
@@ -1,9 +0,0 @@
-% use Mojo::JSON 'encode_json';
-
-% if (stash('api_response')) {
-%= javascript begin
-% my $kq_hash = stash('api_response');
-% $kq_hash->{matches} = ["..."];
- KorAP.koralQuery = <%= b(encode_json($kq_hash))->decode %>;
-% end
-% };
diff --git a/templates/search.html.ep b/templates/search.html.ep
index 21589ee..be95990 100644
--- a/templates/search.html.ep
+++ b/templates/search.html.ep
@@ -1,17 +1,17 @@
-% layout 'main', title => loc('searchtitle', q => search->query, ql => search->query_language), schematype => 'SearchResultsPage';
+% layout 'main', title => loc('searchtitle', q => stash('q'), ql => stash('ql')), schematype => 'SearchResultsPage';
-<div id="resultinfo" <% if (search->results->size) { %> class="found"<%} %>>
- <div id="pagination"><%= pagination(search->start_page, search->total_pages, url_with->query(['p' => '{page}'])) =%></div>
-% my $found = search->total_results;
+<div id="resultinfo" <% if (stash('results')->size) { %> class="found"<%} %>>
+ <div id="pagination"><%= pagination(stash('start_page'), stash('total_pages'), url_with->query(['p' => '{page}'])) =%></div>
+% my $found = stash('total_results') // 0;
<p class="found">\
% if ($found != -1) {
% my $found_text = loc('numf', number => $found);
-% if (search->time_exceeded) {
+% if (stash('time_exceeded')) {
% $found_text = '> ' . $found_text;
% };
<span id="total-results"><%= $found_text %></span> <%= loc('matchCount', found => $found) %>\
-%# <% if (search->benchmark) { %> (~ <%= search->benchmark %>)<% } %>
-% } elsif (search->start_index == 0 && search->results->size == 0) {
+%# <% if (stash('benchmark')) { %> (~ <%= stash('benchmark') %>)<% } %>
+% } elsif (stash('start_index') == 0 && stash('results')->size == 0) {
<span id="total-results">0</span> <%= loc('matchCount', found => $found) %>\
% };
</p>
@@ -20,13 +20,13 @@
%= include 'query'
<div id="search">
-% if (search->total_results != 0 && search->results->size) {
+% if (stash('results')->size && stash('total_results') != 0) {
<ol class="align-left">
%= search_results begin
%= include 'match', match => $_
% end
</ol>
-% } elsif (search->results->size == 0) {
-<p id="no-results"><%= loc 'noMatches', q => search->query, ql => search->query_language %></p>
+% } elsif (stash('results')->size == 0) {
+<p class="no-results"><%= loc 'noMatches', q => stash('q'), ql => stash('ql') %></p>
% }
</div>
diff --git a/templates/search2.html.ep b/templates/search2.html.ep
deleted file mode 100644
index 907cc03..0000000
--- a/templates/search2.html.ep
+++ /dev/null
@@ -1,32 +0,0 @@
-% layout 'main', title => loc('searchtitle', q => stash('q'), ql => stash('ql')), schematype => 'SearchResultsPage';
-
-<div id="resultinfo" <% if (stash('results')->size) { %> class="found"<%} %>>
- <div id="pagination"><%= pagination(stash('start_page'), stash('total_pages'), url_with->query(['p' => '{page}'])) =%></div>
-% my $found = stash('total_results') // 0;
- <p class="found">\
-% if ($found != -1) {
-% my $found_text = loc('numf', number => $found);
-% if (stash('time_exceeded')) {
-% $found_text = '> ' . $found_text;
-% };
-<span id="total-results"><%= $found_text %></span> <%= loc('matchCount', found => $found) %>\
-%# <% if (stash('benchmark')) { %> (~ <%= stash('benchmark') %>)<% } %>
-% } elsif (stash('start_index') == 0 && stash('results')->size == 0) {
-<span id="total-results">0</span> <%= loc('matchCount', found => $found) %>\
-% };
-</p>
-</div>
-
-%= include 'query2'
-
-<div id="search">
-% if (stash('results')->size && stash('total_results') != 0) {
- <ol class="align-left">
-%= search_results2 begin
-%= include 'match', match => $_
-% end
- </ol>
-% } elsif (stash('results')->size == 0) {
-<p class="no-results"><%= loc 'noMatches', q => stash('q'), ql => stash('ql') %></p>
-% }
-</div>