view Lib/IMPL/Web/Application/Action.pm @ 194:4d0e1962161c

Replaced tabs with spaces IMPL::Web::View - fixed document model, new features (control classes, document constructor parameters)
author cin
date Tue, 10 Apr 2012 20:08:29 +0400
parents d1676be8afcc
children c8fe3f84feba
line wrap: on
line source

package IMPL::Web::Application::Action;
use strict;

use parent qw(IMPL::Object IMPL::Object::Autofill);

__PACKAGE__->PassThroughArgs;

use IMPL::Class::Property;

BEGIN {
    public property application => prop_get | owner_set;
    public property query => prop_get | owner_set;
    public property response => prop_get | owner_set;
    public property responseFactory => prop_get | owner_set;
    public property context => prop_get | owner_set;
    private property _entryPoint => prop_all;
}

sub CTOR {
    my ($this) = @_;
    
    $this->responseFactory('IMPL::Web::Application::Response') unless $this->responseFactory; 
    $this->response( $this->responseFactory->new(query => $this->query) );
    $this->context({});
}

sub Invoke {
    my ($this) = @_;
    
    if ($this->_entryPoint) {
        $this->_entryPoint->();
    } else {
        die new IMPL::InvalidOperationException("At least one handler is required");
    }
}

sub ReinitResponse {
    my ($this) = @_;
    
    die new IMPL::InvalidOperationException("Response already sent") if $this->response->isHeaderPrinted;
    
    $this->response->Discard;
    $this->response($this->responseFactory->new(query => $this->query));
}

sub ChainHandler {
    my ($this,$handler) = @_;
    
    my $delegateNext = $this->_entryPoint();
    
    if (ref $handler eq 'CODE') {
        $this->_entryPoint( sub {
            $handler->($this,$delegateNext);    
        } );
    } elsif (ref $handler and UNIVERSAL::isa($handler,'IMPL::Web::QueryHandler')) {
        $this->_entryPoint( sub {
            $handler->Invoke($this,$delegateNext);
        } );
    } elsif ($handler and not ref $handler) {
        
        if (my $method = $this->can($handler) ) {
            $this->_entryPoint( sub {
                $method->($this,$delegateNext);            
            } );
        } else {
            {
                no strict 'refs';
                eval "require $handler; 1;" or die new IMPL::InvalidArgumentException("An invalid handler supplied",$handler,"Failed to load module") unless keys %{"${handler}::"};
            }
            
            if (UNIVERSAL::isa($handler,'IMPL::Web::QueryHandler')) {
                $this->_entryPoint( sub {
                    $handler->Invoke($this,$delegateNext);
                } );    
            } else {
                die new IMPL::InvalidArgumentException("An invalid handler supplied",$handler);
            }
        }
    } else {
        die new IMPL::InvalidArgumentException("An invalid handler supplied",$handler);
    }
    
}

sub cookie {
    my ($this,$name,$rx) = @_;
    
    $this->_launder(scalar( $this->query->cookie($name) ), $rx );
}

sub param {
    my ($this,$name,$rx) = @_;
    
    $this->_launder(scalar( $this->query->param($name) ), $rx );
}

sub _launder {
    my ($this,$value,$rx) = @_;
    
    if ( $value ) {
        if ($rx) {
            if ( my @result = ($value =~ m/$rx/) ) {
                return @result > 1 ? \@result : $result[0];
            } else {
                return undef;
            }
        } else {
            return $value;
        }
    } else {
        return undef;
    }
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::Application::Action> - Обертка вокруг C<CGI> запроса.

=head1 DESCRIPTION

C<[Infrastructure]>

Определяет порядок выполнения запроса. Запрос выполняется последовательным вызовом
цепочки обработчиков, при этом обработчики сами вызывают следующие.
Обработчики выполняются в порядке, обратном их добавлению.

Типичная цепочка может быть такой, в порядке добавления

=begin code

IMPL::Web::QueryHandler::SecCallToMethod
IMPL::Web::QueryHandler::AuthenticateCookie
IMPL::Web::QueryHandler::PageFormat

=end code

что приведет к следующей последовательности

=begin code

# the application creates a new Action object 

my $action = $application->actionFactory->new(
    action => $application, # the application passes self
    query => $query # current CGI query
);

# forms query handlers stack

$action->ChainHandler($_) foreach qw (
    IMPL::Web::QueryHandler::SecCallToMethod
    IMPL::Web::QueryHandler::AuthenticateCookie
    IMPL::Web::QueryHandler::PageFormat
);

# and finally invokes the action

$action->Invoke() {
    
    # some internals
    
    IMPL::Web::QueryHandler::PageFormat->Invoke($action,$nextHandlerIsAuthHandler) {
        
        #some internals
        
        my $result = $nextHandlerIsAuthHandler() {
            
            # some internals

            IMPL::Web::QueryHandler::AuthenticateCookie->Invoke($action,$nextHandlerIsSecCall) {
                
                # some internals
                # do auth and generate security $context 
                
                # impersonate $context and call the next handler                
                return $context->Impersonate($nextHandlerIsSecCall) {
                    
                    # some internals 
                    
                    IMPL::Web::QueryHandler::SecCallToMethod->Invoke($action,undef) {
                        
                        # next handler isn't present as it is the last hanler
                        
                        # some internals
                        # calculate the $method and the $target from CGI request
                                                
                        IMPL::Security->AccessCheck($target,$method);
                        return $target->$method();
                        
                    }
                    
                }
                
            }
        }
        
        # some intenals
        # formatted output to $action->response->streamBody        
    }        
}

=end code

или как альтернатива может быть еще

=begin code

IMPL::Web::QueryHandler::SecCallToMethod
IMPL::Web::QueryHandler::AuthenticateCookie
IMPL::Web::QueryHandler::Filter->new( target => IMPL::Transform::ObjectToJSON->new() , method => 'Transform')
IMLP::Web::QueryHandler::JSONFormat


=end code

В данной цепочке также происходит вызов метода, но его результат потом преобразуется
в простые структуры и передается JSON преобразователю. Таким образом модулю логики
не требуется знать о выходном формате, всю работу проделают дополнительные фильтры.

=head1 MEMBERS

=head2 PROPERTIES

=over

=item C< [get] application>

Экземпляр приложения создавшего текущий объект

=item C< [get] query >

Экземпляр C<CGI> запроса

=item C< [get] response >

Ответ на C<CGI> заспрос C<IMPL::Web::Application::Response>

=item C< [get] responseFactory >

Фабрика ответов на запрос, используется для создания нового ответа
либо при конструировании текущего объекта C<IMPL::Web::Application::Action>,
либо при вызове метода C<ReinitResponse> у текущего объекта.

По умолчанию имеет значение C<IMPL::Web::Application::Response>

=back

=head2 METHODS

=over

=item C< ReinitResponse() >

Отмена старого ответа C<response> и создание вместо него нового.

Данная операция обычно проводится при обработке ошибок, когда
уже сформированный ответ требуется отменить. Следует заметить,
что эта операция не возможна, если ответ частично или полностью
отправлен клиенту. Тогда возникает исключение C<IMPL::InvalidOperationException>.

=item C< ChainHandler($handler) >

Добавляет новый обработчик в цепочку. Выполнение цепочки начинается с конца,
тоесть последний добавленный будет выполнен первым. 

=back

=head1 HANDLERS

=head2 subroutines

=over

=item CODE ref

Ссылка на процедуру может являться обработчиком, при этом функция будет вызвана с
двумя параметрами: ссылкой на action объект, и точкой входа следующего обработчика.

=item Method Name

Имя метода, передается в виде строки. У текущего объекта action ищется метод с
указанным именем, после чего используется ссылка на этот метод для вызова с двумя
параметрами: ссылкой на action объект, и точкой входа следующего обработчика.

Получается вызов идентичный следующему C<< $action->MethodName($nextHandler) >>; 

=back 

=head2 C< IMPL::Web::QueryHandler >

Любой объект наследованный от C< IMPL::Web::QueryHandler > может быть
использован в качестве обработчика запроса

=cut