Mercurial > pub > Impl
view Lib/IMPL/Web/Application/ControllerUnit.pm @ 385:0d63f5273307
sync
author | sergey |
---|---|
date | Tue, 11 Feb 2014 01:05:55 +0400 |
parents | 77df11605d3a |
children |
line wrap: on
line source
use strict; package IMPL::Web::Application::ControllerUnit; use parent qw(IMPL::Object); use IMPL::Class::Property; use IMPL::DOM::Transform::PostToDOM; use IMPL::DOM::Schema; use Class::Inspector; use File::Spec; use Sub::Name; use constant { CONTROLLER_METHODS => 'controller_methods', STATE_CORRECT => 'correct', STATE_NEW => 'new', STATE_INVALID => 'invalid', TTYPE_FORM => 'form', TTYPE_TRANS => 'tran' }; BEGIN { public property action => prop_get | owner_set; public property application => prop_get | owner_set; public property query => prop_get | owner_set; public property response => prop_get | owner_set; public property formData => prop_get | owner_set; public property formSchema => prop_get | owner_set; public property formErrors => prop_get | owner_set; } my %publicProps = map {$_->Name , 1} __PACKAGE__->GetMeta(typeof IMPL::Class::PropertyInfo); __PACKAGE__->class_data(CONTROLLER_METHODS,{}); our @schemaInc; sub CTOR { my ($this,$action,$args) = @_; $this->action($action); $this->application($action->application); $this->query($action->query); $this->response($action->response); $this->$_($args->{$_}) foreach qw(formData formSchema formErrors); } sub unitNamespace() { "" } sub transactions { my ($self,%methods) = @_; while (my ($method,$info) = each %methods) { if ($info and ref $info ne 'HASH') { warn "Bad transaction $method description"; $info = {}; } $info->{wrapper} = 'TransactionWrapper'; $info->{method} ||= $method; $info->{context}{transactionType} = TTYPE_TRANS; $self->class_data(CONTROLLER_METHODS)->{$method} = $info; } } sub forms { my ($self,%forms) = @_; while ( my ($method,$info) = each %forms ) { die new IMPL::Exception("A method doesn't exists in the controller",$self,$method) unless $self->can($method); if ( not ref $info ) { $self->class_data(CONTROLLER_METHODS)->{$method} = { wrapper => 'FormWrapper', schema => $info, method => $method, context => { transactionType => TTYPE_FORM } }; } elsif (ref $info eq 'HASH') { $info->{wrapper} = 'FormWrapper'; $info->{method} ||= $method; $info->{context}{transactionType} = TTYPE_FORM; $self->class_data(CONTROLLER_METHODS)->{$method} = $info; } else { die new IMPL::Exception("Unsupported method information",$self,$method); } } } sub InvokeAction { my ($self,$method,$action) = @_; if (my $methodInfo = $self->class_data(CONTROLLER_METHODS)->{$method}) { if (my $ctx = $methodInfo->{context}) { $action->context->{$_} = $ctx->{$_} foreach keys %$ctx; } if (my $wrapper = $methodInfo->{wrapper}) { return $self->$wrapper($method,$action,$methodInfo); } else { return $self->TransactionWrapper($method,$action,$methodInfo); } } else { die new IMPL::InvalidOperationException("Invalid method call",$self,$method); } } sub MakeParams { my ($this,$methodInfo) = @_; my $params; if ($params = $methodInfo->{parameters} and ref $params eq 'ARRAY') { return map $this->ResolveParam($_,$methodInfo->{inflate}{$_}), @$params; } return(); } sub ResolveParam { my ($this,$param,$inflate) = @_; if ( $param =~ /^::(\w+)$/ and $publicProps{$1}) { return $this->$1(); } else { my $value; if ( my $rx = $inflate->{rx} ) { $value = $this->action->param($param,$rx); } else { $value = $this->query->param($param); } if (my $method = $inflate->{method}) { $value = $this->$method($value); } return $value; } } sub TransactionWrapper { my ($self,$method,$action,$methodInfo) = @_; my $unit = $self->new($action); my $handler = $methodInfo->{method}; return $unit->$handler($unit->MakeParams($methodInfo)); } sub FormWrapper { my ($self,$method,$action,$methodInfo) = @_; my $schema = $methodInfo->{schema} ? $self->loadSchema($methodInfo->{schema}) : $self->unitSchema; my $process = $action->query->param('process') || 0; my $form = $methodInfo->{form} || $action->query->param('form') || $method; my %result; my $transform = IMPL::DOM::Transform::PostToDOM->new( undef, $schema, $form ); my $handler = $methodInfo->{method}; $result{formName} = $form; $result{formSchema} = $schema; if ($process) { $result{formData} = $transform->Transform($action->query); $result{formErrors} = $transform->Errors->as_list; if ($transform->Errors->Count) { $result{state} = STATE_INVALID; } else { $result{state} = STATE_CORRECT; my $unit = $self->new($action,\%result); eval { $result{result} = $unit->$handler($unit->MakeParams($methodInfo)); }; if (my $err = $@) { $result{state} = STATE_INVALID; if (eval { $err->isa(typeof IMPL::WrongDataException) } ) { $result{formErrors} = $err->Args; } else { die $err; } } } } else { if (my $initMethod = $methodInfo->{init}) { my $unit = $self->new($action,\%result); $result{formData} = $transform->Transform( $unit->$initMethod($unit->MakeParams($methodInfo)) ); } else { $result{formData} = $transform->Transform($action->query); } # ignore errors for new forms #$result{formErrors} = $transform->Errors->as_list; $result{state} = STATE_NEW; } return \%result; } sub loadSchema { my ($self,$name) = @_; foreach my $path (map File::Spec->catfile($_,$name) ,@schemaInc) { return IMPL::DOM::Schema->LoadSchema($path) if -f $path; } die new IMPL::Exception("A schema isn't found", $name); } sub unitSchema { my ($self) = @_; my $class = ref $self || $self; my @parts = split(/:+/, $class); my $file = pop @parts; $file = "${file}.schema.xml"; foreach my $inc ( @schemaInc ) { my $path = File::Spec->catfile($inc,@parts,$file); return IMPL::DOM::Schema->LoadSchema($path) if -f $path; } return undef; } sub discover { my ($this) = @_; my $methods = $this->class_data(CONTROLLER_METHODS); my $namespace = $this->unitNamespace; (my $module = typeof($this)) =~ s/^$namespace//; my %smd = ( module => [grep $_, split /::/, $module ], ); while (my ($method,$info) = each %$methods) { my %methodInfo = ( name => $method ); $methodInfo{parameters} = [ grep /^[^\:]/, @{ $info->{parameters} } ] if ref $info->{parameters} eq 'ARRAY'; push @{$smd{methods}},\%methodInfo; } return \%smd; } __PACKAGE__->transactions( discover => undef ); 1; __END__ =pod =head1 NAME C<IMPL::Web::Application::ControllerUnit> - базовый класс для обработчика транзакций в модели контроллера. =head1 DESCRIPTION Классы, наследуемые от данного класса называется пакетом транзакций. Часть методов в таком классе объявляются как транзакции при помощи методов C<transaction>, C<form>. Перед выполнением транзакции создается экземпляр объекта, в рамках которого будет выполнена транзакция. Для этого вызывается метод C<InvokeAction($method,$action)>, который создает/восстанавливает контекст транзакции. Транзакции на данный момент делятся на простые и формы. Различные типы транзакций выполняются при помощи различных оберток (C<TransactionWrapper> и C<FormWrapper>). Каждая обертка отвечает за конструирование экземпляра объекта и вызов метода для выполнения транзакции, а также за возврат результата выполнения. =head2 Простые транзакции Простые транзакции получаю только запрос, без предварительной обработки, и возвращенный результат напрямую передается пользователю. =head2 Формы При использовании форм запрос предварительно обрабатывается, для получения DOM документа с данными формы. Для постороенния DOM документа используется схема. При этом становятся доступны дополнительные свойства C<formData>, C<formSchema>, C<formErrors>. Результат выполнения транзакции не возвращается наверх напрямую, а включается в структуру, которая выглядит следующим образом =begin code { state => '{ new | correct | invalid }', result => $transactionResult, formData => $formDOM, formSchema => $formSchema, formErrors => @errors } =end code =over =item C<state> Состояние верификации формы. =over =item C<new> Первоначальное содержимое формы, оно может быть некорректным, но это нормально. В данном состоянии транзакция обычно не выполняется. =item C<correct> Данные формы корректны, транзакция выполнена, и ее результат доступен через поле C<result> =item C<invalid> Содержимое формы не прошло верификацию, ошибки доступны через поле C<formErrors>. Транзакция не выполнялась. =back =item C<result> Результат выполнения транзакции, если конечно таковая выполнялась. =item C<formData> ДОМ документ с данными формами. Документ существует всегда, не зависимо от его корректности, может быть использован для построения формы, уже заполненную параметрами. =item C<formSchema> Схема данных формы, может использоваться для построения динамических форм. =item C<formErrors> Ссылка на массив с ошибками при проверки формы. =back =head1 MEMBERS =over =item C<[get] application> Объект приложения, которое обрабатывает запрос. =item C<[get] query> Текущий запрос. =item C<[get] response> Текущий ответ. =item C<[get] formData> C<IMPL::DOM::Document> документ с данныим, если данный запрос является формой. =item C<[get] formSchema> C<IMPL::DOM::Schema> документ со схемой формы данного запроса. =item C<[get] formErrors> Ошибки верификации данных, если таковые были. Обычно при наличии ошибок в форме, транзакция не выполняется, а эти ошибки передаются в ответ. =item C<InvokeAction($method,$action)> Конструирует контекст выполнения транзакции, может быть переопределен для конструирования контекста по своим правилам. =item C<TransactionWrapper($method,$action,$methodInfo)> Обертка для конструирования простых транзакций, может быть переопределен для конструирования контекста по своим правилам. =item C<FormWrapper($method,$action,$methodInfo)> Обертка для конструирования форм, может быть переопределен для конструирования контекста по своим правилам. =item C<discover()> Метод, опубликованный для вызова контроллером, возвращает описание методов в формате C<Simple Module Definition>. =begin code # SMD structure { module => ['Foo','Bar'], methods => [ { name => 'search', parameters => ['text','limit'] #optional } ] } =end code =back =head1 EXAMPLE =begin code package MyBooksUnit; use strict; use parent qw(IMPL::Web::Application::ControllerUnit); __PACKAGE__->PassThroughArgs; sub unitDataClass { 'My::Books' } __PACKAGE__->transactions( find => { parameters => [qw(author)] }, info => { parameters => [qw(id)] } ); __PACKAGE__->forms( create => 'books.create.xml' ); sub find { my ($this,$author) = @_; return $this->ds->find({author => $author}); } sub info { my ($this,$id) = @_; return $this->ds->find({id => $id}); } sub create { my ($this) = @_; my %book = map { $_->nodeName, $_->nodeValue } $this->formData->selectNodes([qw(author_id title year ISBN)]); return $this->ds->create(\%book); } =end code =cut