The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Search::OpenSearch::Server::Plack;

use warnings;
use strict;
use base qw( Search::OpenSearch::Server Plack::Component );
use Carp;
use Search::OpenSearch;
use Search::OpenSearch::Result;
use Plack::Request;
use Data::Dump qw( dump );
use JSON;
use Scalar::Util qw( weaken );
use Time::HiRes qw( time );

our $VERSION = '0.26';

sub prepare_app {
    my $self = shift;
    $self->setup_engine();
}

sub setup_engine {
    my $self = shift;
    if ( defined $self->engine ) {
        return 1;
    }
    if ( defined $self->engine_config ) {

        # this appears to correctly defer creation
        # till each child is created, regardless of -L Delayed
        # So nice when code Just Works.
        #warn "[$$] engine created";

        $self->engine(
            Search::OpenSearch->engine(
                logger => $self,
                %{ $self->engine_config },
            )
        );
        return 1;
    }
    croak "engine() or engine_config() required";
}

sub log {
    my $self = shift;
    my $req  = $self->{_this_req};
    my $msg  = shift or croak "No logger message supplied";
    my $lvl  = shift || 'debug';
    if ( $req->can('logger') and $req->logger ) {
        $req->logger->( { level => $lvl, message => $msg } );
    }
}

sub call {
    my ( $self, $env ) = @_;
    my $request = Plack::Request->new($env);

    # stash this request object for log() to work
    $self->{_this_req} = $request;
    weaken( $self->{_this_req} );

    my $path = $request->path;
    if ( $request->method eq 'GET' and length $path == 1 ) {
        return $self->do_search( $request, $request->new_response() );
    }
    elsif ( $request->method eq 'GET'
        and $self->engine->has_rest_api )
    {
        return $self->do_rest_api( $request, $request->new_response() );
    }
    if ( !$self->engine->has_rest_api && $request->method eq 'POST' ) {
        return $self->do_search( $request, $request->new_response() );
    }
    elsif ( $self->engine->has_rest_api ) {
        return $self->do_rest_api( $request, $request->new_response() );
    }
    else {
        return $self->handle_no_query( $request, $request->new_response() )
            ->finalize();
    }
}

sub do_search {
    my ( $self, $req, $response ) = @_;
    $self->SUPER::do_search( $req, $response );
    return $response->finalize();
}

sub do_rest_api {
    my ( $self, $req, $response ) = @_;
    $self->SUPER::do_rest_api( $req, $response );
    return $response->finalize();
}

1;

__END__

=head1 NAME

Search::OpenSearch::Server::Plack - serve OpenSearch results with Plack

=head1 SYNOPSIS

 # write a PSGI application in yourapp.psgi
 use strict;
 use warnings;
 use Plack::Builder;
 use Search::OpenSearch::Server::Plack;
 
 my $engine_config = {
    type   => 'Lucy',
    index  => ['path/to/your/index'],
    facets => {
        names       => [qw( topics people places orgs author )],
        sample_size => 10_000,
    },
    fields => [qw( topics people places orgs author )],
 };

 my $app = Search::OpenSearch::Server::Plack->new( 
    engine_config => $engine_config,
    stats_logger  => MyStats->new(),
 )->to_app;

 builder {
    mount '/' => $app;
 };

 
 # run the app
 % plackup yourapp.psgi
 
=head1 DESCRIPTION

Search::OpenSearch::Server::Plack is a L<Plack::Component> application.
This module implements a HTTP-ready L<Search::OpenSearch> server using L<Plack>.

=head1 METHODS

This class inherits from Search::OpenSearch::Server and Plack::Component. Only
new or overridden methods are documented here.

=head2 new( I<params> )

Inherits from Plack::Component. I<params> can be:

=over

=item engine

A Search::OpenSearch::Engine instance. Either this or B<engine_config> is required.

=item engine_config

A hashref passed to the Search::OpenSearch->engine method.
Either this or B<engine> is required.

=item stats_logger

An object that implements at least one method called B<log>. The object's
B<log> method is invoked with 2 arguments: the Plack::Request object,
and either the Search::OpenSearch::Response object or the REST response
hashref, on each request.

=back

=head2 call

Implements the required Middleware method. The default behavior is to
instantiate a L<Plack::Request> and pass it into do_search().

=head2 log( I<msg>, I<level> )

Passes I<msg> on to the Plack::Request->logger object, if any.

=head2 prepare_app

Calls setup_engine().

=head2 setup_engine

Instantiates the Search::OpenSearch::Engine, if necessary, using
the values set in engine_config().

=head2 do_rest_api( I<request>, I<response> )

Overrides base Server method to call finalize() on I<response>.

=head2 do_search( I<request>, I<response> )

Overrides base Server method to call finalize() on I<response>.

=head1 AUTHOR

Peter Karman, C<< <karman at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-search-opensearch-server at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Search-OpenSearch-Server>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Search::OpenSearch::Server


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Search-OpenSearch-Server>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Search-OpenSearch-Server>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Search-OpenSearch-Server>

=item * Search CPAN

L<http://search.cpan.org/dist/Search-OpenSearch-Server/>

=back

=head1 COPYRIGHT & LICENSE

Copyright 2010 Peter Karman.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

=cut