The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package OX::Application;
BEGIN {
  $OX::Application::AUTHORITY = 'cpan:STEVAN';
}
{
  $OX::Application::VERSION = '0.05';
}
use Moose 2.0200;
use namespace::autoclean;
# ABSTRACT: base class for OX applications

use Bread::Board;
use Moose::Util::TypeConstraints 'match_on_type';
use Plack::Middleware::HTTPExceptions;
use Plack::Util;
use Try::Tiny;

use OX::Types;

extends 'Bread::Board::Container';


has name => (
    is      => 'rw',
    isa     => 'Str',
    lazy    => 1,
    default => sub { shift->meta->name },
);

sub BUILD {
    my $self = shift;

    container $self => as {
        service Middleware => (
            block => sub {
                my $s = shift;
                $self->build_middleware($s);
            },
            dependencies => $self->middleware_dependencies,
        );

        service App => (
            block => sub {
                my $s = shift;

                my $app = $self->build_app($s);

                my @middleware = (
                    sub {
                        my ($app) = @_;

                        return sub {
                            my $env = shift;

                            my $res = $app->($env);

                            Plack::Util::response_cb(
                                $res,
                                sub {
                                    return sub {
                                        my $content = shift;

                                        # flush all services that are
                                        # request-scoped after the response is
                                        # returned
                                        $self->_flush_request_services
                                            unless defined $content;

                                        return $content;
                                    };
                                }
                            );

                            return $res;
                        };
                    },
                    @{ $s->param('Middleware') },
                    Plack::Middleware::HTTPExceptions->new(rethrow => 1),
                );

                for my $middleware (reverse @middleware) {
                    match_on_type $middleware => (
                        'CodeRef' => sub {
                            $app = $middleware->($app);
                        },
                        'OX::Types::MiddlewareClass' => sub {
                            $app = $middleware->wrap($app);
                        },
                        'Plack::Middleware' => sub {
                            $app = $middleware->wrap($app);
                        },
                        sub {
                            warn "not applying middleware $middleware!";
                        },
                    );
                }

                $app;
            },
            dependencies => {
                Middleware => 'Middleware',
                %{ $self->app_dependencies },
            }
        );
    };
}


sub build_middleware { [] }


sub middleware_dependencies { {} }


sub build_app {
    my $self = shift;
    confess(blessed($self) . " must implement the build_app method");
}


sub app_dependencies { {} }


sub to_app {
    my $self = shift;
    return $self->resolve(service => 'App');
}

sub _flush_request_services {
    my $self = shift;

    for my $service ($self->get_service_list) {
        my $injection = $self->get_service($service);
        if ($injection->does('Bread::Board::LifeCycle::Request')) {
            $injection->flush_instance;
        }
    }
}

__PACKAGE__->meta->make_immutable;


1;

__END__
=pod

=head1 NAME

OX::Application - base class for OX applications

=head1 VERSION

version 0.05

=head1 SYNOPSIS

  package MyApp;
  use Moose;
  extends 'OX::Application';

  sub build_app {
      return sub { [ 200, [], ["Hello world"] ] };
  }

  MyApp->new->to_app; # returns the PSGI coderef

=head1 DESCRIPTION

This class provides the base set of functionality for OX applications.
OX::Application is a subclass of L<Bread::Board::Container>, so all
L<Bread::Board> functionality is available through it.

By default, the container holds two services:

=over 4

=item Middleware

This service provides an arrayref of middleware to be applied, in order, to the
app (the first middleware in the array will be the outermost middleware in the
final built app). It is built via the C<build_middleware> and
C<middleware_dependencies> methods, described below.

Middleware can be specified as either a coderef (which is expected to accept an
app coderef as an argument and return a new app coderef), the name of a
subclass of L<Plack::Middleware>, or a L<Plack::Middleware> instance.

=item App

This service provides the actual L<PSGI> coderef for the application. It is
built via the C<build_app> and C<app_dependencies> methods described below, and
applies the middleware from the C<Middleware> service afterwards. It also
applies L<Plack::Middleware::HTTPExceptions> as the innermost middleware, so
your app can throw L<HTTP::Throwable> or L<HTTP::Exception> objects and have
them work properly.

=back

You can add any other services or subcontainers you like, and can use them in
the construction of your app by overriding C<build_middleware>,
C<middleware_dependencies>, C<build_app>, and C<app_dependencies>.

=head1 METHODS

=head2 build_middleware($service)

This method can be overridden in your app to provide an arrayref of middleware
to be applied to the final application. It is passed the C<Middleware> service
object, so that you can access the resolved dependencies you specify in
C<middleware_dependencies>.

=head2 middleware_dependencies

This method returns a hashref of dependencies, as described in L<Bread::Board>.
The arrayref form of dependency specification is not currently supported. These
dependencies can be accessed in the C<build_middleware> method.

=head2 build_app($service)

This method must be overridden by your app to return a L<PSGI> coderef. It is
passed the C<App> service object, so that you can access the resolved
dependencies you specify in C<app_dependencies>.

=head2 app_dependencies

This method returns a hashref of dependencies, as described in L<Bread::Board>.
The arrayref form of dependency specification is not currently supported. These
dependencies can be accessed in the C<build_app> method.

=head2 to_app

This method returns the final L<PSGI> application, after all middleware have
been applied. This method is just a shortcut for
C<< $app->resolve(service => 'App') >>.

=for Pod::Coverage BUILD

=head1 AUTHORS

=over 4

=item *

Stevan Little <stevan.little at iinteractive.com>

=item *

Jesse Luehrs <doy at cpan dot org>

=back

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Infinity Interactive.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut