package Ark::Context; use Mouse; use Scalar::Util (); use Try::Tiny 0.02; use URI::WithBase; our $DETACH = 'ARK_DETACH'; our $DEFERRED = 'ARK_DEFERRED'; our $STREAMING = 'ARK_STREAMING'; extends 'Ark::Component'; has request => ( is => 'rw', isa => 'Object', required => 1, ); has response => ( is => 'rw', isa => 'Ark::Response', lazy => 1, default => sub { Ark::Response->new; }, ); has app => ( is => 'rw', isa => 'Ark::Core', required => 1, weak_ref => 1, handles => ['debug', 'log', 'get_actions', 'get_action', 'ensure_class_loaded', 'component', 'controller', 'view', 'model', 'path_to', 'config', 'router',], ); has stash => ( is => 'rw', isa => 'HashRef', lazy => 1, default => sub { {} }, ); has stack => ( is => 'rw', isa => 'ArrayRef', lazy => 1, default => sub { [] }, ); has state => ( is => 'rw', default => 0, ); has error => ( is => 'rw', isa => 'ArrayRef', lazy => 1, default => sub { [] }, ); has [qw/detached finalized/] => ( is => 'rw', isa => 'Bool', default => 0, ); { # alias no warnings 'once'; *req = \&request; *res = \&response; } sub process { my $self = shift; $self->prepare; $self->dispatch; $self->finalize unless $self->response->is_deferred; } sub prepare { my $self = shift; $self->prepare_action; $self->prepare_encoding; $self->prepare_headers; $self->prepare_body; } sub prepare_action { my $self = shift; my $req = $self->request; $req->match( $self->router->match($req->path) ); } sub prepare_headers {} sub prepare_body {} sub forward { my ($self, $target, @args) = @_; return 0 unless $target; unless (@args) { @args = @{ $self->req->captures } ? @{ $self->req->captures } : @{ $self->req->args }; } if (Scalar::Util::blessed($target)) { if ($target->isa('Ark::Action')) { $target->dispatch($self, @args); return $self->state; } elsif ($target->can('process')) { $self->execute($target, 'process', @args); return $self->state; } } else { if ($target =~ m!^/.+!) { my ($namespace, $name) = $target =~ m!^(.*/)([^/]+)$!; $namespace =~ s!(^/|/$)!!g; if (my $action = $self->get_action($name, $namespace || '')) { $action->dispatch($self, @args); return $self->state; } } else { my $last = $self->stack->[-1]; if ($last and $last->{obj}->isa('Ark::Controller') and my $action = $self->get_action($target, $last->{obj}->namespace)) { $action->dispatch($self, @args); return $self->state; } } } my $error = qq/Couldn't forward to $target, Invalid action or component/; $self->log( error => $error ); push @{ $self->error }, $error; return 0; } sub detach { shift->forward(@_); die $DETACH; } sub dispatch { my $self = shift; my $match = $self->request->match; if ($match) { $self->dispatch_private_action('begin') and $self->dispatch_auto_action and $match->dispatch($self); $self->detached(0); $self->dispatch_private_action('end') unless $self->res->is_deferred or $self->res->is_streaming; } else { $self->log( error => 'no action found' ); } } sub dispatch_action { my ($self, $name) = @_; my $action = ($self->router->get_actions($name, $self->req->action->namespace))[-1] or return 1; $action->dispatch($self); !@{ $self->error }; } sub dispatch_private_action { my ($self, $name) = @_; my $action = ($self->router->get_actions($name, $self->req->action->namespace))[-1]; return 1 unless ($action and $action->attributes->{Private}); $action->dispatch($self); !@{ $self->error }; } sub dispatch_auto_action { my $self = shift; for my $auto ($self->router->get_actions('auto', $self->req->action->namespace)) { next unless $auto->attributes->{Private}; $auto->dispatch($self); return 0 unless $self->state; } 1; } sub depth { scalar @{ shift->stack }; } sub execute { my ($self, $obj, $method, @args) = @_; my $class = ref $obj; $self->state(0); push @{ $self->stack }, { obj => $obj, method => $method, args => \@args, as_string => "${class}->${method}" }; my $error; try { $self->execute_action($obj, $method, @args); } catch { $error = $_; }; pop @{ $self->stack }; if ($error) { if ($error =~ /^${DETACH} at /) { die $DETACH if ($self->depth >= 1); $self->detached(1); } else { push @{ $self->error }, $error; $self->state(0); } } $self->state; } sub execute_action { my ($self, $obj, $method, @args) = @_; my $state = $obj->$method($self, @args); $self->state( defined $state ? $state : undef ); } sub redirect { my ($self, $uri, $status) = @_; $status ||= '302'; $self->res->status($status); $self->res->header( Location => $uri ); } sub redirect_and_detach { my $self = shift; $self->redirect(@_); $self->detach; } sub uri_for { my ($self, @path) = @_; my $params = ref $path[-1] eq 'HASH' ? pop @path : {}; my $base = $self->req->base; $base =~ s!/*$!!; (my $path = join '/', @path) =~ s!/{2,}!/!g; $path =~ s!^/+!!; my $uri = URI::WithBase->new($path, $base . '/'); $uri->query_form($params); $uri->abs; } sub finalize { my $self = shift; my $is_deferred = $self->response->is_deferred; if ($is_deferred) { my $action = $self->request->action; if ($action) { $self->dispatch_private_action('end'); } } $self->finalize_headers; $self->finalize_body; $self->finalize_encoding; $self->response->finalize if $self->response->is_deferred; $self->finalized(1); } sub finalize_headers {} sub finalize_body {} sub DEMOLISH { my $self = shift; $self->finalize unless $self->finalized; } __PACKAGE__->meta->make_immutable;