view Lib/IMPL/Web/Application/ResourceContract.pm @ 245:7c517134c42f

Added Unsupported media type Web exception corrected resourceLocation setting in the resource Implemented localizable resources for text messages fixed TT view scopings, INIT block in controls now sets globals correctly.
author sergey
date Mon, 29 Oct 2012 03:15:22 +0400
parents abc7c26bf615
children 814d755e5d12
line wrap: on
line source

package IMPL::Web::Application::ResourceContract;
use strict;
use IMPL::Const qw(:prop);
use IMPL::declare {
	require => {
		Exception            => 'IMPL::Exception',
		ArgumentException    => '-IMPL::ArgumentException',
		KeyNotFoundException => '-IMPL::KeyNotFoundException',
		ResourceClass        => 'IMPL::Web::Application::Resource'
	  },
	  base  => [ 'IMPL::Object' => undef ],
	  props => [
		resourceFactory  => PROP_ALL,
		verbs       => PROP_ALL,
		_namedResources  => PROP_ALL,
		_regexpResources => PROP_ALL | PROP_LIST,
	  ]
};

sub CTOR {
	my $this = shift;
	my %args = @_;

	$this->resourceFactory( $args{resourceFactory} || ResourceClass );

	my $resources  = $args{resources}  || [];
	my $verbs = $args{verbs} || {};

	die ArgumentException->new(
		resources => 'resources parameter must be a reference to an array' )
	  unless ref $resources eq 'ARRAY';

	die ArgumentException->new(
		opearations => 'operations parameter must be a reference to a hash' )
	  unless ref $verbs eq 'HASH';

	$this->verbs(
		{ map { lc($_), $verbs->{$_} } keys %$verbs } );

	my %nameMap;

	foreach my $res (@$resources) {
		next unless $res->{contract};
		if ( my $name = $res->{name} ) {
			$nameMap{$name} = $res;
		}
		if ( $res->{match} ) {
			$this->_regexpResources->Append($res);
		}
	}

	$this->_namedResources( \%nameMap );
}

sub AddChildResourceContract {
    my ($this,$res) = @_;
    
    die ArgumentException->new(res => "A valid child resource definition")
        unless ref $res eq 'HASH';
        
    $this->_namedResources->{$res->{name}} = $res if $res->{name};
    $this->_regexpResources->Append($res) if $res->{match};
    
    return; 
}

sub CreateResource {
	my $this = shift;
	my %args = @_;

	return $this->resourceFactory->new( %args, contract => $this );
}

sub FindChildResourceInfo {
	my ( $this, $name ) = @_;

	if ( my $info = $this->_namedResources->{$name} ) {
		return $info, [$name];
	}
	else {
		foreach my $info ( $this->_regexpResources ) {
			my $rx = $info->{match};
			if(my @childId = $name =~ m/$rx/) {
			    return $info, \@childId; 
			}
		}
	}

	return;
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::Application::ResourceContract> - описание ресурса

=head1 SYNIOSIS

=begin code

use IMPL::require {
	ResourceContract => 'IMPL::Web::Application::ResourceContract',
	OperationContract => 'IMPL::Web::Application::OperationContract'
};

my $contract = ResourceContract->new(
    verbs => {
    	get => OperationContract->new(
            binding => sub {
            	my ($resource,$action) = @_;
                return "Hello!";
            }
        ),
        post => OperationContract->new(
            parameters => [
                IMPL::Transform::DataToModel->new() # создаем преобразование для формы
            ],
            binding => sub {
            	my ($resource,$action,$data) = @_;
            	return $resource->model->AddItem($data);
            },
            success => sub {
            	my ($resource,$action,$result) = @_;
            	return IMPL::Web::HttpResponse->Redirect(
            	   location => $resource->location->Child($result->id)
            	)
            },
            error => sub {
            	my ($resource,$action,$error) = @_;
            	$action->errors->Append($error);
            	return $resource->model;
            }
            
        ),
    },
    resources => [
        {
        	name => 'info',
        	binding => sub {
        		return $_[0]->model->info;
        	},
        	contract => ResourceContract->new(
        	   verbs => {
	        	   get => OperationContract->new(
	        	       binding => sub {
	        	       	   my ($resource,$action) = @_;
	        	       	   return $resource->model;
	        	       }
	        	   )
        	   }
        	)
        }
    ]
)

my $obj = My::App::Data->fetch('something');

my $resource = $contract->CreateResource(
    model => $obj,
    parent => $prentResource,
    id => 'item-something'
);

my $child = $contract->FetchChildResource('info');

=end code 

=head1 DESCRIPTION

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


Ресурс представляе собой набор контрактов операций, соответствующих методам
C<HTTP> которые доступны у данного ресурса. Кроме операций ресурс состоит из
дочерних ресурсов, которые могут соответствовать регулярным выражениям, либо
иметь фиксированные имена.

Каждая операция над ресурсом C<IMPL::Web::Application::OperationContract>
описывается преобразованием параметров, привязкой к предметной области,
дополнительным обработчиком результата выполнения привязки, либо обработчиком
исключения, если привязку не удалось выполнить.

Основное назначение контракта - создавать объекты ресурсов, над которыми
контроллер запросов C<HTTP> сможет выполнить операцию. Контракт может создавать
дочерние ресурсы, на основе указанного родительского ресурса и идетификатора
нового ресурса. При этом будет найден подходящий контракт для дочернего ресурса
и с его помощью создан дочерний ресурс.

=head2 Динамический контракт

Основная функция контракта - превращать данные модели предметной области в
данные ресурсной модели, тоесть в ресурсы, для чего каждый контракт обязан
реализовывать метод C<CreateResource(%args)>.

Результатом выполнения этого метода должен быть Web-ресурс, см.
C<IMPL::Web::Application::Resource>. Другими словами не существует жесткого
требования к реализации самого контракта, как и того, что созданный ресурс
должен ссылаться именно на этот контракт (да и вообще ссылаться на контракт).

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

=begin code

package My::Web::Application::ContractMapper;
use strict;
use IMPL::Const qw(:prop);
use IMPL::declare {
    require => {
        ForbiddenException => 'IMPL::Web::Forbidden'  
    },
    base => [
        'IMPL::Object' => undef,
        'IMPL::Object::Autofill' => '@_'
    ],
    props => [
        map => PROP_GET | PROP_OWNERSET
    ]
}

sub CreateResource {
    my ($this,%args) = @_;
    
    my $type = ref $args{model} || '_default';
    
    my $contract = $this->map->{$type};
    
    die ForbiddenException->new()
        unless $contract;
    
    return $contract->CreateResource(%args);
} 

=end code

=head1 MEMBERS

=head2 C<CTOR(%args)>

=over

=item * C<resourceFactory>

Фабрика объектов C<IMPL::Object::Factory> которая будет использоваться при
создании новых ресурсов. По-умолчанию C<IMPL::Web::Application::Resource>.

=item * C<operations>

Хеш с доступными действиями над C<HTTP> ресурсом, ключом является имя ресурса,
значением C<IMPL::Web::Application::OperationContract>.

=item * C<resources>

Ссылка на массив хешей, каждый хеш описывает правила, как получить дочерний
ресурс и связать его с контрактом. Ниже преведено описание элементов хеша.

=over

=item * C<name>

Имя дочернегно ресурса.

=item * C<match>

Регулярное выражение, которому должно удовлетворять имя дочернего ресурса. 

=item * C<bind>

Делегат, получающий модель для дочернего ресурса. Первым параметром ему
передается родительский объект, далее передаются граппы из регулярного
выражения, если это ресурс с именем удовлетворяющим регулярному выражению из
элемента C<match>, либо имя ресурса, если это ресурс с именем, указанным в
элементе C<name>.

=item * C<contract>

Ссылка на C<IMPL::Web::Application::ResourceContract> для дочернего ресурса.
У данного контракта используется только метод C<CreateContract>.

=back

По крайней мере C<name> или C<match> должны присутсвовать.

=back

=head2 C<CreateResource(%args)>

Создает ресурс, параметры C<%args> будут переданы напрямую констркутору
ресурса, для создания ресурса используется фабрика C<resourceFactory>.
При создании, конгструктору ресурса, будет передана ссылка на текущй контракт.

По-сути никакого дополнительного функционала данный метод не несет.

=head2 C<FindChildResourceInfo($childId)>

Используется для поиска информации о дочернем ресурсе, возвращает список из двух
элементов. C<($info,$childIdParts)>

=over

=item * C<$info>

Информация о контракте дочернего ресурса, как правило это ссылка на хеш, похожий
по формату на 

=back

=head2 C<[get]verbs>

Хеш с доступными действиями над C<HTTP> ресурсом, все имена операций приведены
к нижнему регистру.

=begin code

my $result = $contract->verbs->{get}->Invoke($resource,$action);

=end code

=cut