| 
373
 | 
     1 package IMPL::Web::Application::Resource;
 | 
| 
 | 
     2 use strict;
 | 
| 
 | 
     3 
 | 
| 
 | 
     4 use constant {
 | 
| 
 | 
     5 	ResourceClass => __PACKAGE__
 | 
| 
 | 
     6 };
 | 
| 
 | 
     7 use Scalar::Util qw(blessed);
 | 
| 
 | 
     8 
 | 
| 
 | 
     9 use IMPL::lang qw(:hash :base);
 | 
| 
 | 
    10 use IMPL::Const qw(:prop);
 | 
| 
 | 
    11 use IMPL::declare {
 | 
| 
 | 
    12 	require => {
 | 
| 
 | 
    13 		Exception => 'IMPL::Exception',
 | 
| 
 | 
    14 		OpException => '-IMPL::InvalidOperationException',
 | 
| 
 | 
    15 		NotFoundException => 'IMPL::Web::NotFoundException',
 | 
| 
 | 
    16 		ResourceInterface => '-IMPL::Web::Application',
 | 
| 
 | 
    17 		Loader => 'IMPL::Code::Loader'
 | 
| 
 | 
    18 	},
 | 
| 
 | 
    19 	base => [
 | 
| 
 | 
    20 		'IMPL::Object' => undef,
 | 
| 
 | 
    21 		'IMPL::Web::Application::ResourceBase' => '@_'
 | 
| 
 | 
    22 	],
 | 
| 
 | 
    23 	props => [
 | 
| 
 | 
    24 		access => PROP_RW,
 | 
| 
 | 
    25 		verbs => PROP_RW,
 | 
| 
 | 
    26 		children => PROP_RW
 | 
| 
 | 
    27 	]
 | 
| 
 | 
    28 };
 | 
| 
 | 
    29 
 | 
| 
 | 
    30 __PACKAGE__->static_accessor(verbNames => [qw(get post put delete options head)]);
 | 
| 
 | 
    31 __PACKAGE__->static_accessor(httpMethodPrefix => 'Http');
 | 
| 
 | 
    32 
 | 
| 
 | 
    33 sub CTOR {
 | 
| 
 | 
    34 	my ($this, %args) = @_;
 | 
| 
 | 
    35 	
 | 
| 
 | 
    36 	my %verbs;
 | 
| 
 | 
    37 	my $httpPrefix = $this->httpMethodPrefix;
 | 
| 
 | 
    38 	
 | 
| 
 | 
    39 	foreach my $verb (@{$this->verbNames}) {
 | 
| 
 | 
    40 		my $method = exists $args{$verb} ? $args{$verb} : $this->can($httpPrefix . ucfirst($verb));
 | 
| 
 | 
    41 		$verbs{$verb} = $method
 | 
| 
 | 
    42 			if $method;
 | 
| 
 | 
    43 	}
 | 
| 
 | 
    44 	
 | 
| 
 | 
    45 	hashApply(\%verbs,$args{verbs})
 | 
| 
 | 
    46 		if ref($args{verbs}) eq 'HASH' ;
 | 
| 
 | 
    47 	
 | 
| 
 | 
    48 	$this->children($args{children} || $this->GetChildResources());
 | 
| 
 | 
    49 	
 | 
| 
 | 
    50 	$this->access($args{access})
 | 
| 
 | 
    51 		if $args{access};
 | 
| 
 | 
    52 
 | 
| 
 | 
    53 	$this->verbs(\%verbs);
 | 
| 
 | 
    54 }
 | 
| 
 | 
    55 
 | 
| 
 | 
    56 sub _isInvokable {
 | 
| 
 | 
    57 	my ($this,$method) = @_;
 | 
| 
 | 
    58 	
 | 
| 
 | 
    59 	return 
 | 
| 
 | 
    60 		(blessed($method) and $method->can('Invoke')) ||
 | 
| 
 | 
    61 		ref($method) eq 'CODE'
 | 
| 
 | 
    62 }
 | 
| 
 | 
    63 
 | 
| 
 | 
    64 sub _invoke {
 | 
| 
 | 
    65 	my ($this,$method,@args) = @_;
 | 
| 
 | 
    66 	
 | 
| 
 | 
    67 	if(blessed($method) and $method->can('Invoke')) {
 | 
| 
 | 
    68 		return $method->Invoke($this,@args);
 | 
| 
 | 
    69 	} elsif(ref($method) eq 'CODE' || (not(ref($method)) and $this->can($method))) {
 | 
| 
 | 
    70 		return $this->$method(@args);
 | 
| 
 | 
    71 	} else {
 | 
| 
 | 
    72 		die OpException->new("Can't invoke the specified method: $method");
 | 
| 
 | 
    73 	}
 | 
| 
 | 
    74 }
 | 
| 
 | 
    75 
 | 
| 
 | 
    76 sub HttpGet {
 | 
| 
 | 
    77 	shift->model;
 | 
| 
 | 
    78 }
 | 
| 
 | 
    79 
 | 
| 
 | 
    80 sub Fetch {
 | 
| 
 | 
    81 	my ($this,$childId) = @_;
 | 
| 
 | 
    82 	
 | 
| 
 | 
    83 	my $children = $this->children
 | 
| 
 | 
    84 		or die NotFoundException->new( $this->location->url, $childId );
 | 
| 
 | 
    85 
 | 
| 
 | 
    86 	if (ref($children) eq 'HASH') {
 | 
| 
 | 
    87 		return $children->{$childId};
 | 
| 
374
 | 
    88 	} elsif($this->_isInvokable($children)) {
 | 
| 
 | 
    89 		return $this->_invoke($children,$childId);
 | 
| 
373
 | 
    90 	} else {
 | 
| 
374
 | 
    91 		die OpException->new("Invalid resource description", $childId, $children);
 | 
| 
373
 | 
    92 	}
 | 
| 
 | 
    93 }
 | 
| 
 | 
    94 
 | 
| 
 | 
    95 sub FetchChildResource {
 | 
| 
 | 
    96 	my ($this,$childId) = @_;
 | 
| 
 | 
    97 	
 | 
| 
 | 
    98 	my $info = $this->Fetch($childId);
 | 
| 
 | 
    99 	
 | 
| 
 | 
   100 	return $info
 | 
| 
 | 
   101 		if (is($info,ResourceInterface));
 | 
| 
 | 
   102 	
 | 
| 
 | 
   103 	return $this->CreateChildResource($info, $childId)
 | 
| 
 | 
   104 		if ref($info) eq 'HASH';
 | 
| 
 | 
   105 		
 | 
| 
 | 
   106 	die OpException->new("Invalid resource description", $childId, $info);
 | 
| 
 | 
   107 }
 | 
| 
 | 
   108 
 | 
| 
 | 
   109 sub CreateChildResource {
 | 
| 
 | 
   110 	my ($this,$info, $childId) = @_;
 | 
| 
 | 
   111 	
 | 
| 
 | 
   112 	my $params = hashApply(
 | 
| 
 | 
   113 		{
 | 
| 
 | 
   114 			parent => $this,
 | 
| 
 | 
   115 			id => $childId,
 | 
| 
 | 
   116 			request => $this->request,
 | 
| 
 | 
   117 			class => ResourceClass
 | 
| 
 | 
   118 		},
 | 
| 
 | 
   119 		$info
 | 
| 
 | 
   120 	);
 | 
| 
 | 
   121 	
 | 
| 
 | 
   122 	$params->{model} = $this->_invoke($params->{model})
 | 
| 
 | 
   123 		if $this->_isInvokable($params->{model});
 | 
| 
 | 
   124 	
 | 
| 
 | 
   125 	my $factory = Loader->default->Require($params->{class});
 | 
| 
 | 
   126 	
 | 
| 
 | 
   127 	return $factory->new(%$params);
 | 
| 
 | 
   128 }
 | 
| 
 | 
   129 
 | 
| 
 | 
   130 sub GetChildResources {
 | 
| 
 | 
   131 	return {};
 | 
| 
 | 
   132 }
 | 
| 
 | 
   133 
 | 
| 
 | 
   134 1;
 | 
| 
 | 
   135 
 | 
| 
 | 
   136 __END__
 | 
| 
 | 
   137 
 | 
| 
 | 
   138 =pod
 | 
| 
 | 
   139 
 | 
| 
 | 
   140 =head1 NAME
 | 
| 
 | 
   141 
 | 
| 
 | 
   142 C<IMPL::Web::Application::Resource> - Ресурс C<REST> веб приложения
 | 
| 
 | 
   143 
 | 
| 
 | 
   144 =head1 SYNOPSIS
 | 
| 
 | 
   145 
 | 
| 
 | 
   146 =begin code
 | 
| 
 | 
   147 
 | 
| 
 | 
   148 use IMPL::require {
 | 
| 
 | 
   149 	Resource => 'IMPL::Web::Application::Resource',
 | 
| 
 | 
   150 	Security => 'IMPL::Security',
 | 
| 
 | 
   151 	NotFoundException => 'IMPL::Web::NotFoundException',
 | 
| 
 | 
   152 	ForbiddenException => 'IMPL::Web::ForbiddenException'
 | 
| 
 | 
   153 };
 | 
| 
 | 
   154 
 | 
| 
 | 
   155 my $model = Resource->new(
 | 
| 
 | 
   156 	get => sub { },
 | 
| 
 | 
   157 	verbs => {
 | 
| 
 | 
   158 		# non-standart verbs placed here 
 | 
| 
 | 
   159 		myverb => sub { }
 | 
| 
 | 
   160 	},
 | 
| 
 | 
   161 	#child resources can be a hash
 | 
| 
 | 
   162 	chidren => {
 | 
| 
 | 
   163 		user => {
 | 
| 
 | 
   164 			# a resource class may be specified optionally
 | 
| 
 | 
   165 			# class => Resource,
 | 
| 
 | 
   166 			model => sub {
 | 
| 
 | 
   167 				return Security->principal
 | 
| 
 | 
   168 			},
 | 
| 
 | 
   169 			# the default get implementation is implied
 | 
| 
 | 
   170 			# get => sub { shift->model },
 | 
| 
 | 
   171 			access => sub {
 | 
| 
 | 
   172 				my ($this,$verb) = @_;
 | 
| 
 | 
   173 				die ForbiddenException->new()
 | 
| 
 | 
   174 					if Security->principal->isNobody
 | 
| 
 | 
   175 			} 
 | 
| 
 | 
   176 		},
 | 
| 
 | 
   177 		catalog => {
 | 
| 
 | 
   178 			get => sub {
 | 
| 
 | 
   179 				my $ctx = shift->application->ConnectDb()->AutoPtr();
 | 
| 
 | 
   180 				
 | 
| 
 | 
   181 				return $ctx->products->find_rs({ in_stock => 1 });
 | 
| 
 | 
   182 			},
 | 
| 
 | 
   183 			# chid resource may be created dynamically
 | 
| 
 | 
   184 			children => sub {
 | 
| 
 | 
   185 				# binds model against the parent reource and id
 | 
| 
 | 
   186 				my ($this,$id) = @_;
 | 
| 
 | 
   187 					
 | 
| 
 | 
   188 				($id) = ($id =~ /^(\w+)$/)
 | 
| 
 | 
   189 					or die NotFoundException->new($id);
 | 
| 
 | 
   190 				
 | 
| 
 | 
   191 				my $ctx = shift->application->ConnectDb()->AutoPtr();
 | 
| 
 | 
   192 				
 | 
| 
 | 
   193 				my $item = $ctx->products->fetch($id);
 | 
| 
 | 
   194 				
 | 
| 
 | 
   195 				die NotFoundException->new()
 | 
| 
 | 
   196 					unless $item;
 | 
| 
 | 
   197 				
 | 
| 
 | 
   198 				# return parameters for the new resource
 | 
| 
 | 
   199 				return {
 | 
| 
 | 
   200 					model => $item,
 | 
| 
 | 
   201 					get => sub { shift->model }
 | 
| 
 | 
   202 				};
 | 
| 
 | 
   203 			}
 | 
| 
 | 
   204 		},
 | 
| 
 | 
   205 		# dynamically binds whole child resource. The result of binding is
 | 
| 
 | 
   206 		# the new resource or a hash with arguments to create one 
 | 
| 
 | 
   207 		posts => sub {
 | 
| 
 | 
   208 			my ($this,$id) = @_;
 | 
| 
 | 
   209 			
 | 
| 
 | 
   210 			# this approach can be used to create a dynamic resource relaying
 | 
| 
 | 
   211 			# on the type of the model
 | 
| 
 | 
   212 			
 | 
| 
 | 
   213 			return Resource->new(
 | 
| 
 | 
   214 				id => $id,
 | 
| 
 | 
   215 				parent => $this,
 | 
| 
 | 
   216 				get => sub { shift->model }
 | 
| 
 | 
   217 			);
 | 
| 
 | 
   218 			
 | 
| 
 | 
   219 			# ditto
 | 
| 
 | 
   220 			# parent and id will be mixed in automagically
 | 
| 
 | 
   221 			# return { get => sub { shift->model} } 
 | 
| 
 | 
   222 		},
 | 
| 
 | 
   223 		post_only => {
 | 
| 
 | 
   224 			get => undef, # remove GET verb implicitly
 | 
| 
 | 
   225 			post => sub {
 | 
| 
 | 
   226 				my ($this) = @_;
 | 
| 
 | 
   227 			}
 | 
| 
 | 
   228 		}
 | 
| 
 | 
   229 	}
 | 
| 
 | 
   230 );
 | 
| 
 | 
   231 
 | 
| 
 | 
   232 =end code
 | 
| 
 | 
   233 
 | 
| 
 | 
   234 Альтернативный вариант для создания класса ресурса.
 | 
| 
 | 
   235 
 | 
| 
 | 
   236 =begin code
 | 
| 
 | 
   237 
 | 
| 
 | 
   238 package MyResource;
 | 
| 
 | 
   239 
 | 
| 
 | 
   240 use IMPL::declare {
 | 
| 
 | 
   241 	require => {
 | 
| 
 | 
   242 		ForbiddenException => 'IMPL::Web::ForbiddenException'
 | 
| 
 | 
   243 	},
 | 
| 
 | 
   244 	base => [
 | 
| 
 | 
   245 		'IMPL::Web::Application::Resource' => '@_'
 | 
| 
 | 
   246 	]
 | 
| 
 | 
   247 };
 | 
| 
 | 
   248 
 | 
| 
 | 
   249 sub ds {
 | 
| 
 | 
   250 	my ($this) = @_;
 | 
| 
 | 
   251 	
 | 
| 
 | 
   252 	$this->context->{ds} ||= $this->application->ConnectDb();
 | 
| 
 | 
   253 }
 | 
| 
 | 
   254 
 | 
| 
 | 
   255 sub InvokeHttpVerb {
 | 
| 
 | 
   256 	my $this = shift;
 | 
| 
 | 
   257 	
 | 
| 
 | 
   258 	$this->ds->Begin();
 | 
| 
 | 
   259 	
 | 
| 
 | 
   260 	my $result = $this->next::method(@_);
 | 
| 
 | 
   261 	
 | 
| 
 | 
   262 	# in case of error the data context will be disposed and the transaction
 | 
| 
 | 
   263 	# will be reverted
 | 
| 
 | 
   264 	$this->ds->Commit();
 | 
| 
 | 
   265 	
 | 
| 
 | 
   266 	return $result;
 | 
| 
 | 
   267 }
 | 
| 
 | 
   268 
 | 
| 
 | 
   269 # this method is inherited by default 
 | 
| 
 | 
   270 # sub HttpGet {
 | 
| 
 | 
   271 #     shift->model
 | 
| 
 | 
   272 #	
 | 
| 
 | 
   273 # }
 | 
| 
 | 
   274 
 | 
| 
 | 
   275 sub HttpPost {
 | 
| 
 | 
   276 	my ($this) = @_;
 | 
| 
 | 
   277 	
 | 
| 
 | 
   278 	my %data = map {
 | 
| 
 | 
   279 		$_,
 | 
| 
 | 
   280 		$this->request->param($_)
 | 
| 
 | 
   281 	} qw(name description value);
 | 
| 
 | 
   282 	
 | 
| 
 | 
   283 	die ForbiddenException->new("The item with the scpecified name can't be created'")
 | 
| 
 | 
   284 		if(not $data{name} or $this->ds->items->find({ name => $data{name}))
 | 
| 
 | 
   285 	
 | 
| 
 | 
   286 	$this->ds->items->insert(\%data);
 | 
| 
 | 
   287 	
 | 
| 
 | 
   288 	return $this->NoContent();
 | 
| 
 | 
   289 }
 | 
| 
 | 
   290 
 | 
| 
 | 
   291 sub Fetch {
 | 
| 
 | 
   292 	my ($this,$childId) = @_;
 | 
| 
 | 
   293 	
 | 
| 
 | 
   294 	my $item = $this->ds->items->find({name => $childId})
 | 
| 
 | 
   295 		or die NotFoundException->new();
 | 
| 
 | 
   296 
 | 
| 
 | 
   297 	# return parameters for the child resource
 | 
| 
 | 
   298 	return { model => $item, role => "item food" };
 | 
| 
 | 
   299 }
 | 
| 
 | 
   300 
 | 
| 
 | 
   301 =end code
 | 
| 
 | 
   302 
 | 
| 
374
 | 
   303 =head1 MEMBERS
 | 
| 
 | 
   304 
 | 
| 
 | 
   305 =head2 C<[virtual]Fetch($childId)>
 | 
| 
 | 
   306 
 | 
| 
 | 
   307 Метод для получения дочернего ресурса.
 | 
| 
 | 
   308 
 | 
| 
 | 
   309 Возвращает параметры для создания дочернего ресурса, либо уже созданный ресурс.
 | 
| 
 | 
   310 Создание дочернего ресурса происходит при помощи метода C<CreateChildResource()>
 | 
| 
 | 
   311 который добавляет недостающие параметры к возвращенным в данным методом и
 | 
| 
 | 
   312 создает новый ресурс
 | 
| 
 | 
   313 
 | 
| 
 | 
   314 =head2 C<> 
 | 
| 
 | 
   315 
 | 
| 
373
 | 
   316 =cut
 | 
| 
 | 
   317 
 |