view Lib/IMPL/Web/Application/Action.pm @ 111:6c25ea91c985

ControllerUnit concept
author wizard
date Tue, 18 May 2010 01:33:37 +0400
parents 9f5795a10939
children b56ebc31bf18
line wrap: on
line source

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

use base 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;
	
	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) );
}

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);
	}
	
}

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