1#============================================================= -*-Perl-*- 2# 3# Template::Service 4# 5# DESCRIPTION 6# Module implementing a template processing service which wraps a 7# template within PRE_PROCESS and POST_PROCESS templates and offers 8# ERROR recovery. 9# 10# AUTHOR 11# Andy Wardley <abw@wardley.org> 12# 13# COPYRIGHT 14# Copyright (C) 1996-2007 Andy Wardley. All Rights Reserved. 15# 16# This module is free software; you can redistribute it and/or 17# modify it under the same terms as Perl itself. 18# 19#============================================================================ 20 21package Template::Service; 22 23use strict; 24use warnings; 25use base 'Template::Base'; 26use Template::Config; 27use Template::Exception; 28use Template::Constants; 29use Scalar::Util 'blessed'; 30 31use constant EXCEPTION => 'Template::Exception'; 32 33our $VERSION = 2.80; 34our $DEBUG = 0 unless defined $DEBUG; 35our $ERROR = ''; 36 37 38#======================================================================== 39# ----- PUBLIC METHODS ----- 40#======================================================================== 41 42#------------------------------------------------------------------------ 43# process($template, \%params) 44# 45# Process a template within a service framework. A service may encompass 46# PRE_PROCESS and POST_PROCESS templates and an ERROR hash which names 47# templates to be substituted for the main template document in case of 48# error. Each service invocation begins by resetting the state of the 49# context object via a call to reset(). The AUTO_RESET option may be set 50# to 0 (default: 1) to bypass this step. 51#------------------------------------------------------------------------ 52 53sub process { 54 my ($self, $template, $params) = @_; 55 my $context = $self->{ CONTEXT }; 56 my ($name, $output, $procout, $error); 57 $output = ''; 58 59 $self->debug("process($template, ", 60 defined $params ? $params : '<no params>', 61 ')') if $self->{ DEBUG }; 62 63 $context->reset() 64 if $self->{ AUTO_RESET }; 65 66 # pre-request compiled template from context so that we can alias it 67 # in the stash for pre-processed templates to reference 68 eval { $template = $context->template($template) }; 69 return $self->error($@) 70 if $@; 71 72 # localise the variable stash with any parameters passed 73 # and set the 'template' variable 74 $params ||= { }; 75 # TODO: change this to C<||=> so we can use a template parameter 76 $params->{ template } = $template 77 unless ref $template eq 'CODE'; 78 $context->localise($params); 79 80 SERVICE: { 81 # PRE_PROCESS 82 eval { 83 foreach $name (@{ $self->{ PRE_PROCESS } }) { 84 $self->debug("PRE_PROCESS: $name") if $self->{ DEBUG }; 85 $output .= $context->process($name); 86 } 87 }; 88 last SERVICE if ($error = $@); 89 90 # PROCESS 91 eval { 92 foreach $name (@{ $self->{ PROCESS } || [ $template ] }) { 93 $self->debug("PROCESS: $name") if $self->{ DEBUG }; 94 $procout .= $context->process($name); 95 } 96 }; 97 if ($error = $@) { 98 last SERVICE 99 unless defined ($procout = $self->_recover(\$error)); 100 } 101 102 if (defined $procout) { 103 # WRAPPER 104 eval { 105 foreach $name (reverse @{ $self->{ WRAPPER } }) { 106 $self->debug("WRAPPER: $name") if $self->{ DEBUG }; 107 $procout = $context->process($name, { content => $procout }); 108 } 109 }; 110 last SERVICE if ($error = $@); 111 $output .= $procout; 112 } 113 114 # POST_PROCESS 115 eval { 116 foreach $name (@{ $self->{ POST_PROCESS } }) { 117 $self->debug("POST_PROCESS: $name") if $self->{ DEBUG }; 118 $output .= $context->process($name); 119 } 120 }; 121 last SERVICE if ($error = $@); 122 } 123 124 $context->delocalise(); 125 delete $params->{ template }; 126 127 if ($error) { 128 # $error = $error->as_string if ref $error; 129 return $self->error($error); 130 } 131 132 return $output; 133} 134 135 136#------------------------------------------------------------------------ 137# context() 138# 139# Returns the internal CONTEXT reference. 140#------------------------------------------------------------------------ 141 142sub context { 143 return $_[0]->{ CONTEXT }; 144} 145 146 147#======================================================================== 148# -- PRIVATE METHODS -- 149#======================================================================== 150 151sub _init { 152 my ($self, $config) = @_; 153 my ($item, $data, $context, $block, $blocks); 154 my $delim = $config->{ DELIMITER }; 155 $delim = ':' unless defined $delim; 156 157 # coerce PRE_PROCESS, PROCESS and POST_PROCESS to arrays if necessary, 158 # by splitting on non-word characters 159 foreach $item (qw( PRE_PROCESS PROCESS POST_PROCESS WRAPPER )) { 160 $data = $config->{ $item }; 161 $self->{ $item } = [ ], next unless (defined $data); 162 $data = [ split($delim, $data || '') ] 163 unless ref $data eq 'ARRAY'; 164 $self->{ $item } = $data; 165 } 166 # unset PROCESS option unless explicitly specified in config 167 $self->{ PROCESS } = undef 168 unless defined $config->{ PROCESS }; 169 170 $self->{ ERROR } = $config->{ ERROR } || $config->{ ERRORS }; 171 $self->{ AUTO_RESET } = defined $config->{ AUTO_RESET } 172 ? $config->{ AUTO_RESET } : 1; 173 $self->{ DEBUG } = ( $config->{ DEBUG } || 0 ) 174 & Template::Constants::DEBUG_SERVICE; 175 176 $context = $self->{ CONTEXT } = $config->{ CONTEXT } 177 || Template::Config->context($config) 178 || return $self->error(Template::Config->error); 179 180 return $self; 181} 182 183 184#------------------------------------------------------------------------ 185# _recover(\$exception) 186# 187# Examines the internal ERROR hash array to find a handler suitable 188# for the exception object passed by reference. Selecting the handler 189# is done by delegation to the exception's select_handler() method, 190# passing the set of handler keys as arguments. A 'default' handler 191# may also be provided. The handler value represents the name of a 192# template which should be processed. 193#------------------------------------------------------------------------ 194 195sub _recover { 196 my ($self, $error) = @_; 197 my $context = $self->{ CONTEXT }; 198 my ($hkey, $handler, $output); 199 200 # there shouldn't ever be a non-exception object received at this 201 # point... unless a module like CGI::Carp messes around with the 202 # DIE handler. 203 return undef 204 unless blessed($$error) && $$error->isa(EXCEPTION); 205 206 # a 'stop' exception is thrown by [% STOP %] - we return the output 207 # buffer stored in the exception object 208 return $$error->text() 209 if $$error->type() eq 'stop'; 210 211 my $handlers = $self->{ ERROR } 212 || return undef; ## RETURN 213 214 if (ref $handlers eq 'HASH') { 215 if ($hkey = $$error->select_handler(keys %$handlers)) { 216 $handler = $handlers->{ $hkey }; 217 $self->debug("using error handler for $hkey") if $self->{ DEBUG }; 218 } 219 elsif ($handler = $handlers->{ default }) { 220 # use default handler 221 $self->debug("using default error handler") if $self->{ DEBUG }; 222 } 223 else { 224 return undef; ## RETURN 225 } 226 } 227 else { 228 $handler = $handlers; 229 $self->debug("using default error handler") if $self->{ DEBUG }; 230 } 231 232 eval { $handler = $context->template($handler) }; 233 if ($@) { 234 $$error = $@; 235 return undef; ## RETURN 236 }; 237 238 $context->stash->set('error', $$error); 239 eval { 240 $output .= $context->process($handler); 241 }; 242 if ($@) { 243 $$error = $@; 244 return undef; ## RETURN 245 } 246 247 return $output; 248} 249 250 251 252#------------------------------------------------------------------------ 253# _dump() 254# 255# Debug method which return a string representing the internal object 256# state. 257#------------------------------------------------------------------------ 258 259sub _dump { 260 my $self = shift; 261 my $context = $self->{ CONTEXT }->_dump(); 262 $context =~ s/\n/\n /gm; 263 264 my $error = $self->{ ERROR }; 265 $error = join('', 266 "{\n", 267 (map { " $_ => $error->{ $_ }\n" } 268 keys %$error), 269 "}\n") 270 if ref $error; 271 272 local $" = ', '; 273 return <<EOF; 274$self 275PRE_PROCESS => [ @{ $self->{ PRE_PROCESS } } ] 276POST_PROCESS => [ @{ $self->{ POST_PROCESS } } ] 277ERROR => $error 278CONTEXT => $context 279EOF 280} 281 282 2831; 284 285__END__ 286 287=head1 NAME 288 289Template::Service - General purpose template processing service 290 291=head1 SYNOPSIS 292 293 use Template::Service; 294 295 my $service = Template::Service->new({ 296 PRE_PROCESS => [ 'config', 'header' ], 297 POST_PROCESS => 'footer', 298 ERROR => { 299 user => 'user/index.html', 300 dbi => 'error/database', 301 default => 'error/default', 302 }, 303 }); 304 305 my $output = $service->process($template_name, \%replace) 306 || die $service->error(), "\n"; 307 308=head1 DESCRIPTION 309 310The C<Template::Service> module implements an object class for providing 311a consistent template processing service. 312 313Standard header (L<PRE_PROCESS|PRE_PROCESS_POST_PROCESS>) and footer 314(L<POST_PROCESS|PRE_PROCESS_POST_PROCESS>) templates may be specified which 315are prepended and appended to all templates processed by the service (but not 316any other templates or blocks C<INCLUDE>d or C<PROCESS>ed from within). An 317L<ERROR> hash may be specified which redirects the service to an alternate 318template file in the case of uncaught exceptions being thrown. This allows 319errors to be automatically handled by the service and a guaranteed valid 320response to be generated regardless of any processing problems encountered. 321 322A default C<Template::Service> object is created by the L<Template> module. 323Any C<Template::Service> options may be passed to the L<Template> 324L<new()|Template#new()> constructor method and will be forwarded to the 325L<Template::Service> constructor. 326 327 use Template; 328 329 my $template = Template->new({ 330 PRE_PROCESS => 'header', 331 POST_PROCESS => 'footer', 332 }); 333 334Similarly, the C<Template::Service> constructor will forward all configuration 335parameters onto other default objects (e.g. L<Template::Context>) that it may 336need to instantiate. 337 338A C<Template::Service> object (or subclass) can be explicitly instantiated and 339passed to the L<Template> L<new()|Template#new()> constructor method as the 340L<SERVICE> item. 341 342 use Template; 343 use Template::Service; 344 345 my $service = Template::Service->new({ 346 PRE_PROCESS => 'header', 347 POST_PROCESS => 'footer', 348 }); 349 350 my $template = Template->new({ 351 SERVICE => $service, 352 }); 353 354The C<Template::Service> module can be sub-classed to create custom service 355handlers. 356 357 use Template; 358 use MyOrg::Template::Service; 359 360 my $service = MyOrg::Template::Service->new({ 361 PRE_PROCESS => 'header', 362 POST_PROCESS => 'footer', 363 COOL_OPTION => 'enabled in spades', 364 }); 365 366 my $template = Template->new({ 367 SERVICE => $service, 368 }); 369 370The L<Template> module uses the L<Template::Config> 371L<service()|Template::Config#service()> factory method to create a default 372service object when required. The C<$Template::Config::SERVICE> package 373variable may be set to specify an alternate service module. This will be 374loaded automatically and its L<new()> constructor method called by the 375L<service()|Template::Config#service()> factory method when a default service 376object is required. Thus the previous example could be written as: 377 378 use Template; 379 380 $Template::Config::SERVICE = 'MyOrg::Template::Service'; 381 382 my $template = Template->new({ 383 PRE_PROCESS => 'header', 384 POST_PROCESS => 'footer', 385 COOL_OPTION => 'enabled in spades', 386 }); 387 388=head1 METHODS 389 390=head2 new(\%config) 391 392The C<new()> constructor method is called to instantiate a C<Template::Service> 393object. Configuration parameters may be specified as a HASH reference or 394as a list of C<name =E<gt> value> pairs. 395 396 my $service1 = Template::Service->new({ 397 PRE_PROCESS => 'header', 398 POST_PROCESS => 'footer', 399 }); 400 401 my $service2 = Template::Service->new( ERROR => 'error.html' ); 402 403The C<new()> method returns a C<Template::Service> object or C<undef> on 404error. In the latter case, a relevant error message can be retrieved by the 405L<error()|Template::Base#error()> class method or directly from the 406C<$Template::Service::ERROR> package variable. 407 408 my $service = Template::Service->new(\%config) 409 || die Template::Service->error(); 410 411 my $service = Template::Service->new(\%config) 412 || die $Template::Service::ERROR; 413 414=head2 process($input, \%replace) 415 416The C<process()> method is called to process a template specified as the first 417parameter, C<$input>. This may be a file name, file handle (e.g. C<GLOB> or 418C<IO::Handle>) or a reference to a text string containing the template text. An 419additional hash reference may be passed containing template variable 420definitions. 421 422The method processes the template, adding any 423L<PRE_PROCESS|PRE_PROCESS_POST_PROCESS> or 424L<POST_PROCESS|PRE_PROCESS_POST_PROCESS> templates defined, and returns the 425output text. An uncaught exception thrown by the template will be handled by a 426relevant L<ERROR> handler if defined. Errors that occur in the 427L<PRE_PROCESS|PRE_PROCESS_POST_PROCESS> or 428L<POST_PROCESS|PRE_PROCESS_POST_PROCESS> templates, or those that occur in the 429main input template and aren't handled, cause the method to return C<undef> to 430indicate failure. The appropriate error message can be retrieved via the 431L<error()|Template::Base#error()> method. 432 433 $service->process('myfile.html', { title => 'My Test File' }) 434 || die $service->error(); 435 436=head2 context() 437 438Returns a reference to the internal context object which is, by default, an 439instance of the L<Template::Context> class. 440 441=head1 CONFIGURATION OPTIONS 442 443The following list summarises the configuration options that can be provided 444to the C<Template::Service> L<new()> constructor. Please consult 445L<Template::Manual::Config> for further details and examples of each 446configuration option in use. 447 448=head2 PRE_PROCESS, POST_PROCESS 449 450The L<PRE_PROCESS|Template::Manual::Config#PRE_PROCESS_POST_PROCESS> and 451L<POST_PROCESS|Template::Manual::Config#PRE_PROCESS_POST_PROCESS> options may 452be set to contain the name(s) of template files which should be processed 453immediately before and/or after each template. These do not get added to 454templates processed into a document via directives such as C<INCLUDE> 455C<PROCESS>, C<WRAPPER>, etc. 456 457 my $service = Template::Service->new({ 458 PRE_PROCESS => 'header', 459 POST_PROCESS => 'footer', 460 }; 461 462Multiple templates may be specified as a reference to a list. Each is 463processed in the order defined. 464 465 my $service = Template::Service->new({ 466 PRE_PROCESS => [ 'config', 'header' ], 467 POST_PROCESS => 'footer', 468 }; 469 470=head2 PROCESS 471 472The L<PROCESS|Template::Manual::Config#PROCESS> option may be set to contain 473the name(s) of template files which should be processed instead of the main 474template passed to the C<Template::Service> L<process()> method. This can be used to 475apply consistent wrappers around all templates, similar to the use of 476L<PRE_PROCESS|PRE_PROCESS_POST_PROCESS> and 477L<POST_PROCESS|PRE_PROCESS_POST_PROCESS> templates. 478 479 my $service = Template::Service->new({ 480 PROCESS => 'content', 481 }; 482 483 # processes 'content' instead of 'foo.html' 484 $service->process('foo.html'); 485 486A reference to the original template is available in the C<template> 487variable. Metadata items can be inspected and the template can be 488processed by specifying it as a variable reference (i.e. prefixed by 489'C<$>') to an C<INCLUDE>, C<PROCESS> or C<WRAPPER> directive. 490 491Example C<PROCESS> template: 492 493 <html> 494 <head> 495 <title>[% template.title %]</title> 496 </head> 497 <body> 498 [% PROCESS $template %] 499 </body> 500 </html> 501 502=head2 ERROR 503 504The L<ERROR|Template::Manual::Config#ERROR> (or C<ERRORS> if you prefer) 505configuration item can be used to name a single template or specify a hash 506array mapping exception types to templates which should be used for error 507handling. If an uncaught exception is raised from within a template then the 508appropriate error template will instead be processed. 509 510If specified as a single value then that template will be processed 511for all uncaught exceptions. 512 513 my $service = Template::Service->new({ 514 ERROR => 'error.html' 515 }); 516 517If the L<ERROR/ERRORS|Template::Manual::Config#ERROR> item is a hash reference 518the keys are assumed to be exception types and the relevant template for a 519given exception will be selected. A C<default> template may be provided for 520the general case. 521 522 my $service = Template::Service->new({ 523 ERRORS => { 524 user => 'user/index.html', 525 dbi => 'error/database', 526 default => 'error/default', 527 }, 528 }); 529 530=head2 AUTO_RESET 531 532The L<AUTO_RESET|Template::Manual::Config#AUTO_RESET> option is set by default 533and causes the local C<BLOCKS> cache for the L<Template::Context> object to be 534reset on each call to the L<Template> L<process()|Template#process()> method. 535This ensures that any C<BLOCK>s defined within a template will only persist until 536that template is finished processing. 537 538=head2 DEBUG 539 540The L<DEBUG|Template::Manual::Config#DEBUG> option can be used to enable 541debugging messages from the C<Template::Service> module by setting it to include 542the C<DEBUG_SERVICE> value. 543 544 use Template::Constants qw( :debug ); 545 546 my $template = Template->new({ 547 DEBUG => DEBUG_SERVICE, 548 }); 549 550=head1 AUTHOR 551 552Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/> 553 554=head1 COPYRIGHT 555 556Copyright (C) 1996-2007 Andy Wardley. All Rights Reserved. 557 558This module is free software; you can redistribute it and/or 559modify it under the same terms as Perl itself. 560 561=head1 SEE ALSO 562 563L<Template>, L<Template::Context> 564 565=cut 566 567# Local Variables: 568# mode: perl 569# perl-indent-level: 4 570# indent-tabs-mode: nil 571# End: 572# 573# vim: expandtab shiftwidth=4: 574