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