Plack-App-Catmandu-SRU

 view release on metacpan or  search on metacpan

lib/Plack/App/Catmandu/SRU.pm  view on Meta::CPAN

package Plack::App::Catmandu::SRU;

our $VERSION = '0.02';

use Catmandu::Sane;
use Catmandu;
use Catmandu::Fix;
use Catmandu::Exporter::Template;
use URI;
use SRU::Request;
use SRU::Response;
use Types::Standard qw(Str ArrayRef HashRef ConsumerOf);
use Types::Common::String qw(NonEmptyStr);
use Types::Common::Numeric qw(PositiveInt);
use Moo;
use Plack::Request;
use namespace::clean;
use feature qw(signatures);
no warnings qw(experimental::signatures);

has store_name => (
    is => 'ro',
    isa => Str,
    init_arg => 'store',
);

has bag_name => (
    is => 'ro',
    isa => Str,
    init_arg => 'bag',
);

has content_type => (
    is => 'ro',
    isa => Str,
    default => sub { 'text/xml'; },
);

has cql_filter => (
    is => 'ro',
    isa => NonEmptyStr,
);

has default_record_schema => (
    is => 'ro',
    isa => NonEmptyStr,
    required => 1,
);

has record_schemas => (
    is => 'ro',
    isa => ArrayRef[HashRef],
    default => sub { []; },
);

has title => (
    is => 'ro',
    isa => NonEmptyStr,
);

has description => (
    is => 'ro',
    isa => NonEmptyStr,
);

has template_options => (
    is => 'ro',
    isa => HashRef,

lib/Plack/App/Catmandu/SRU.pm  view on Meta::CPAN

    <database>$database</database>
    </serverInfo>
    $database_info
    $index_info
    $schema_info
    $config_info
</explain>
XML
            ));
            return $self->render_sru_response($response);
        }
        elsif ($operation eq 'searchRetrieve') {
            my $request  = SRU::Request::SearchRetrieve->new($params->flatten);
            my $response = SRU::Response->newFromRequest($request);
            if (@{$response->diagnostics}) {
                return $self->render_sru_response($response);
            }

            my $schema = $record_schema_map->{$request->recordSchema || $self->default_record_schema};
            unless ($schema) {
                $response->addDiagnostic(SRU::Response::Diagnostic->newFromCode(66));
                return $self->render_sru_response($response);
            }
            my $identifier  = $schema->{identifier};
            my $fix         = $schema->{fix};
            my $template    = $schema->{template};
            my $layout      = $schema->{layout};
            my $cql         = $params->get('query');
            if ($self->cql_filter) {
                # space before the filter is to circumvent a bug in the Solr
                # 3.6 edismax parser
                $cql = "( ".$self->cql_filter.") and ( $cql)";
            }

            my $first = $request->startRecord // 1;
            my $limit = $request->maximumRecords // $default_limit;
            if ($limit > $maximum_limit) {
                $limit = $maximum_limit;
            }

            my $hits = eval {
                $bag->search(
                    %{$self->default_search_params},
                    cql_query    => $cql,
                    sru_sortkeys => $request->sortKeys,
                    limit        => $limit,
                    start        => $first - 1,
                );
            } or do {
                my $e = $@;
                if (index($e, 'cql error') == 0) {
                    $response->addDiagnostic(SRU::Response::Diagnostic->newFromCode(10));
                    return $self->render_sru_response($response);
                }
                Catmandu::Error->throw($e);
            };

            $hits->each(sub {
                my $data     = $_[0];
                my $metadata = "";
                my $exporter = Catmandu::Exporter::Template->new(
                    %$template_options,
                    template => $template,
                    file     => \$metadata
                );
                $exporter->add($fix ? $fix->fix($data) : $data);
                $exporter->commit;
                $response->addRecord(SRU::Response::Record->new(
                    recordSchema => $identifier,
                    recordData   => $metadata,
                ));
            });
            $response->numberOfRecords($hits->total);
            return $self->render_sru_response($response);
        }
        else {
            my $request  = SRU::Request::Explain->new($params->flatten);
            my $response = SRU::Response->newFromRequest($request);
            $response->addDiagnostic(SRU::Response::Diagnostic->newFromCode(6));
            return $self->render_sru_response($response);
        }
    };
}

sub render_sru_response ($self, $response) {
    my $body = $response->asXML;
    utf8::encode($body);
    [200, ['Content-Type' => $self->content_type], [$body]];
}

sub not_found ($self) {
    [404, ['Content-Type' => 'text/plain'], ['not found']];
}

1;

=head1 NAME

Plack::App::Catmandu::SRU - drop in replacement for Dancer::Plugin::Catmandu::SRU

=head1 SYNOPSIS

    use Plack::Builder;
    Plack::App::Catmandu::SRU;

    builder {
        enable 'ReverseProxy';
        enable '+Dancer::Middleware::Rebase', base  => Catmandu->config->{uri_base}, strip => 1;
        mount "/sru" => Plack::App::Catmandu::SRU->new(
            store => 'search',
            bag   => 'publication',
            cql_filter => 'type = dataset',
            limit  => 100,
            maximum_limit => 500,
            record_schemas => [
                {
                    identifier => "info:srw/schema/1/mods-v3.6",
                    title => "MODS",
                    name => "mods_36",
                    template => "views/export/mods_36.tt",
                    fix => 'fixes/pub.fix'
                },
            ],
        )->to_app;
    };

=head1 CONSTRUCTOR ARGUMENTS

=over

=item store

Name of Catmandu store in your catmandu store configuration

Default: C<default>

=item bag

Name of Catmandu bag in your catmandu store configuration

Default: C<data>

This must be a bag that implements L<Catmandu::CQLSearchable>, and that configures a C<cql_mapping>

=item cql_filter

A CQL query to find all records in the database that should be made available to SRU

=item default_record_schema

default metadata schema all records are shown in, when SRU parameter C<recordSchema> is not gi en . Should be one listed in C<record_schemas>

=item limit

The default number of records to be returned in each SRU request, when SRU parameter C<maximumRecords> is not given during a searchRetrieve request.

When not provided in the constructor, it is derived from the default limit of your catmandu bag (see L<Catmandu::Searchable#default_limit>)

=item maximum_limit

The maximum value allowed for request parameter C<maximumRecords>.

When not provided in the constructor, it is derived from the maximum limit of your catmandu bag (see L<Catmandu::Searchable#maximum_limit>)

=item record_schemas

An array of all supported record schemas. Each item in the array is an object with attributes:

* identifier - The SRU identifier for the schema (see L<http://www.loc.gov/standards/sru/recordSchemas/>)

* name - A short descriptive name for the schema

* fix - Optionally an array of fixes to apply to the records before they are transformed into XML

* template - The path to a Template Toolkit file to transform your records into this format

=item template_options

An optional hash of configuration options that will be passed to L<Catmandu::Exporter::Template> or L<Template>

=item content_type

Set a custom content type header, the default is C<text/xml>.

=item title

Title shown in databaseInfo

=item description

Description shown in databaseInfo

=item default_search_params

Extra search parameters added during search in your catmandu bag:

    $bag->search(
        %{$self->default_search_params},
        cql_query    => $cql,
        sru_sortkeys => $request->sortKeys,
        limit        => $limit,
        start        => $first - 1,
    );

Must be a hash reference

Note that search parameter C<cql_query>, C<sru_sortkeys>, C<limit> and C<start> are overwritten

=back

As this is meant as a drop in replacement for L<Dancer::Plugin::Catmandu::SRU> all arguments should be the same.

So all arguments can be taken from your previous dancer plugin configuration, if necessary:

    use Dancer;
    use Catmandu;
    use Plack::Builder;
    use Plack::App::Catmandu::SRU;

    my $dancer_app = sub {
        Dancer->dance(Dancer::Request->new(env => $_[0]));
    };

    builder {
        enable 'ReverseProxy';
        enable '+Dancer::Middleware::Rebase', base  => Catmandu->config->{uri_base}, strip => 1;
    
        mount "/sru" => Plack::App::Catmandu::SRU->new(
            %{config->{plugins}->{'Catmandu::SRU'}}
        )->to_app;

        mount "/" => builder {
            # only create session cookies for dancer application
            enable "Session";
            mount '/' => $dancer_app;
        };
    };

=head1 METHODS



( run in 1.401 second using v1.01-cache-2.11-cpan-54e63673c56 )