view Lib/IMPL/Web/Application.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 2ffe6f661605
line wrap: on
line source

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

use parent qw(IMPL::Config IMPL::Object::Singleton);

require IMPL::Web::Application::Action;
require IMPL::Web::Application::Response;

use IMPL::Class::Property;
use CGI;

__PACKAGE__->PassThroughArgs;

public property handlerError => prop_all;
public property actionFactory => prop_all;
public property handlersQuery => prop_all | prop_list;
public property responseCharset => prop_all;
public property security => prop_all;
public property options => prop_all;
public property fetchRequestMethod => prop_all;


sub CTOR {
    my ($this) = @_;
    
    $this->actionFactory(typeof IMPL::Web::Application::Action) unless $this->actionFactory;
    $this->responseCharset('utf-8') unless $this->responseCharset;
    $this->fetchRequestMethod(\&defaultFetchRequest) unless $this->fetchRequestMethod;
    $this->handlerError(\&defaultHandlerError) unless $this->handlerError;
}

sub Run {
    my ($this) = @_;
    
    while (my $query = $this->FetchRequest()) {
        
        my $action = $this->actionFactory->new(
            query => $query,
            application => $this, 
        );
        
        eval {
            $action->response->charset($this->responseCharset);
            
            $action->ChainHandler($_) foreach $this->handlersQuery;
            
            $action->Invoke();
            
            $action->response->Complete;
        };
        if ($@) {
            my $e = $@;
            # we are expecting this method to be safe otherwise we can trust nothing in this wolrd 
            $this->handlerError()->($this,$action,$e);
        }
    }
}

sub FetchRequest {
    my ($this) = @_;
    
    if( ref $this->fetchRequestMethod eq 'CODE' ) {
        return $this->fetchRequestMethod->($this);
    } else {
        die new IMPL::Exception("Unknown fetchRequestMethod type",ref $this->fetchRequestMethod);
    }
}

{
    my $hasFetched = 0;

    sub defaultFetchRequest {
        my ($this) = @_;
        return undef if $hasFetched;
        $hasFetched = 1;
        my $query = CGIWrapper->new();
        $query->charset($this->responseCharset);
        return $query;
    }
}

sub defaultHandlerError {
    my ($this,$action,$e) = @_;
    warn $e;
    if ( eval {    $action->ReinitResponse(); 1; } ) {
        $action->response->contentType('text/plain');
        $action->response->charset($this->responseCharset);
        $action->response->status(500);
        my $hout = $action->response->streamBody;
        print $hout $e;
        $action->response->Complete();
    }    
}

package CGIWrapper;
use parent qw(CGI);

use Encode;

our $NO_DECODE = 0;

sub param {
    my $this = shift;
    
    return $this->SUPER::param(@_) if $NO_DECODE;
    
    if (wantarray) {
        my @result = $this->SUPER::param(@_);
        
        return map Encode::is_utf8($_) ? $_ : Encode::decode($this->charset,$_,Encode::LEAVE_SRC), @result;
    } else {
        my $result = $this->SUPER::param(@_);
        
        return Encode::is_utf8($result) ? $result : Encode::decode($this->charset,$result,Encode::LEAVE_SRC);
    }

}

sub upload {
    my $this = shift;
    
    local $NO_DECODE = 1;
    my $oldCharset = $this->charset();
    $this->charset('ISO-8859-1');
    
    my $fh = $this->SUPER::upload(@_);
    
    $this->charset($oldCharset);
    return $fh;
}

1;

__END__

=pod

=head1 SYNOPSIS

=begin code

require MyApp;

my $instance = spawn MyApp('app.config');

$instance->Run();

=end code

=head1 DESCRIPTION

C< use parent qw( IMPL::Config IMPL::Object::Singleton )>

Зкземпляр приложения содержит в себе глобальные настройки, реализует контроллер запросов,
в качестве источника запросов используется CGI или иной совместимый модуль.

Процесс обработки запроса состоит из следующих частей

=over

=item 1

Получение cgi запроса

=item 2

Создание объекта C<IMPL::Web::Application::Action>

=item 3

Формирование цепочки вызовов при помощи C<< IMPL::Web::Application::Action->ChainHandler >>

=item 4

Выполнение запроса C<< IMPL::Web::Application::Action->Invoke >>

=cut

Также приложение поддерживает отложенное создание объектов, которые по первому обращению
к свойствам. Это реализовано в базовом классе C< IMPL::Configuration >. Для настройки
активаторов можно использовать свойство C<options>, в которое должен быть помещен хеш
со ссылками на активаторы, см. пример ниже C<CONFIGURATION>. 

=head2 CONFIGURATION

Ниже приведен пример конфигурации приложения

=begin code xml

<?xml version="1.0" encoding="UTF-8"?>
<Application id='app' type="Test::Web::Application::Instance">
    
    <!-- Begin custom properties -->
    <name>Sample application</name>
    <dataSource type='IMPL::Config::Activator' id='ds'>
        <factory>IMPL::Object</factory>
        <parameters type='HASH'>
            <db>data</db>
            <user>nobody</user>
        </parameters>
    </dataSource>
    <securityMod type='IMPL::Config::Activator'>
        <factory>IMPL::Object</factory>
        <parameters type='HASH'>
            <ds refid='ds'/>
        </parameters>
    </securityMod>    
    <!-- End custom properties -->
    
    <!-- direct access to the activators -->
    <options type="HASH">
        <dataSource refid='ds'/>
    </options>
    
    <!-- Set default output encoding, can be changed due query handling -->
    <responseCharset>utf-8</responseCharset>
    
    <!-- Actions creation configuration -->
    <actionFactory type="IMPL::Object::Factory">
        
        <!-- Construct actions -->        
        <factory>IMPL::Web::Application::Action</factory>
        <parameters type='HASH'>
            
            <!-- with special responseFactory -->
            <responseFactory type='IMPL::Object::Factory'>
            
                <!-- Where resopnses have a special streamOut -->
                <factory>IMPL::Web::Application::Response</factory>
                <parameters type='HASH'>
                
                    <!-- in memory dummy output instead of STDOUT -->
                    <streamOut>memory</streamOut>
                    
                </parameters>
            </responseFactory>
        </parameters>
    </actionFactory>
    
    <!-- Query processing chain -->
    <handlersQuery type="IMPL::Object::List">
        <item type="IMPL::Web::QueryHandler::PageFormat">
            <templatesCharset>cp1251</templatesCharset>
        </item>
    </handlersQuery>
</Application>

=end code xml

=head1 MEMBERS

=over

=item C<[get,set] handlerError>

Обработчик который будет вызван в случае возникновения необработанной ошибки
в процессе работы приложения. После чего приложение корректно завершается.

=item C<[get,set] actionFactory>

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

=begin code

my $action = $this->actionFactory->new(
    query => $query,
    application => $this, 
);

=end code

=item C< [get,set] fetchRequestMethod >

Метод получения CGI запроса. Возвращает C<CGI> объект следующего запроса, если
запросов больше нет, то возвращает C<undef>. По-умолчанию использует C<defaultFetchRequest>.

Может быть как ссылкой на функцию, так и объектом типа C<IMPL::Web::Application::RequestFetcher>.

=item C< [get,set,list] handlersQuery >

Список обработчиков запросов, которые будут переданы созданному объекту-действию.

=item C< [get,set] responseCharset>

Кодировка ответа клиенту.

=item C< [get,set] security >

Объект C<IMPL::Web::Security>, для работы с инфраструктурой безопасности.

=item C< [get,set] options >

Обычно ссылка на хеш с настраиваемыми объектами, используется для возможности
програмной настройки активаторов, т.к. напрямую через свойства приложения получить
к ним доступ не получится.
 
=back

=cut