Checking in changes prior to tagging of version 0.08. Changelog diff is:
[catagits/Catalyst-Plugin-Cache.git] / lib / Catalyst / Plugin / Cache.pm
CommitLineData
c28ee69c 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Cache;
303b9f5c 4use base qw(Class::Accessor::Fast Class::Data::Inheritable);
c28ee69c 5
6use strict;
7use warnings;
8
cfeadc99 9our $VERSION = "0.08";
d6861a7f 10
c28ee69c 11use Scalar::Util ();
23b2d59b 12use Catalyst::Utils ();
c28ee69c 13use Carp ();
ab8e499f 14use MRO::Compat;
c28ee69c 15
2e4bde89 16use Catalyst::Plugin::Cache::Curried;
17
c28ee69c 18__PACKAGE__->mk_classdata( "_cache_backends" );
2e4bde89 19__PACKAGE__->mk_accessors( "_default_curried_cache" );
c28ee69c 20
21sub setup {
22 my $app = shift;
23
24 # set it once per app, not once per plugin,
25 # and don't overwrite if some plugin was wicked
26 $app->_cache_backends({}) unless $app->_cache_backends;
27
ab8e499f 28 my $ret = $app->maybe::next::method( @_ );
c28ee69c 29
30 $app->setup_cache_backends;
31
32 $ret;
33}
34
0ba5145c 35sub _get_cache_plugin_config {
36 my ($app) = @_;
37 return $app->config->{'Plugin::Cache'} || $app->config->{cache};
38}
39
23b2d59b 40sub get_default_cache_backend_config {
41 my ( $app, $name ) = @_;
0ba5145c 42 $app->_get_cache_plugin_config->{backend} || $app->get_cache_backend_config("default");
23b2d59b 43}
44
45sub get_cache_backend_config {
46 my ( $app, $name ) = @_;
0ba5145c 47 $app->_get_cache_plugin_config->{backends}{$name};
23b2d59b 48}
49
50sub setup_cache_backends {
51 my $app = shift;
52
53 # give plugins a chance to find things for themselves
ab8e499f 54 $app->maybe::next::method;
23b2d59b 55
0ba5145c 56 # FIXME - Don't know why the _get_cache_plugin_config method doesn't work here!
57 my $conf = $app->config->{'Plugin::Cache'} ? $app->config->{'Plugin::Cache'}->{backends} : $app->config->{cache}->{backends};
58 foreach my $name ( keys %$conf ) {
23b2d59b 59 next if $app->get_cache_backend( $name );
60 $app->setup_generic_cache_backend( $name, $app->get_cache_backend_config( $name ) || {} );
61 }
62
33002c69 63 if ( !$app->get_cache_backend("default") ) {
fba82fef 64 ### XXX currently we dont have a fallback scenario
65 ### so die here with the error message. Once we have
66 ### an in memory fallback, we may consider silently
67 ### logging the error and falling back to that.
68 ### If we dont die here, the app will silently start
69 ### up and then explode at the first cache->get or
70 ### cache->set request with a FIXME error
71 #local $@;
72 #eval {
73 $app->setup_generic_cache_backend( default => $app->get_default_cache_backend_config || {} );
74 #};
75
85a748b9 76 }
23b2d59b 77}
78
79sub default_cache_store {
80 my $app = shift;
0ba5145c 81 $app->_get_cache_plugin_config->{default_store} || $app->guess_default_cache_store;
23b2d59b 82}
83
84sub guess_default_cache_store {
85 my $app = shift;
86
87 my @stores = map { /Cache::Store::(.*)$/ ? $1 : () } $app->registered_plugins;
88
89 if ( @stores == 1 ) {
90 return $stores[0];
91 } else {
92 Carp::croak "You must configure a default store type unless you use exactly one store plugin.";
93 }
94}
95
96sub setup_generic_cache_backend {
97 my ( $app, $name, $config ) = @_;
98 my %config = %$config;
99
100 if ( my $class = delete $config{class} ) {
fba82fef 101
102 ### try as list and as hashref, collect the
103 ### error if things go wrong
104 ### if all goes well, exit the loop
105 my @errors;
106 for my $aref ( [%config], [\%config] ) {
107 eval { $app->setup_cache_backend_by_class(
108 $name, $class, @$aref
109 );
110 } ? do { @errors = (); last }
111 : push @errors, "\t$@";
112 }
113
114 ### and die with the errors if we have any
115 die "Couldn't construct $class with either list style or hash ref style param passing:\n @errors" if @errors;
116
23b2d59b 117 } elsif ( my $store = delete $config->{store} || $app->default_cache_store ) {
118 my $method = lc("setup_${store}_cache_backend");
119
120 Carp::croak "You must load the $store cache store plugin (if it exists). ".
121 "Please consult the Catalyst::Plugin::Cache documentation on how to configure hetrogeneous stores."
122 unless $app->can($method);
123
887cc08f 124 $app->$method( $name, \%config );
23b2d59b 125 } else {
126 $app->log->warn("Couldn't setup the cache backend named '$name'");
127 }
128}
129
130sub setup_cache_backend_by_class {
131 my ( $app, $name, $class, @args ) = @_;
132 Catalyst::Utils::ensure_class_loaded( $class );
133 $app->register_cache_backend( $name => $class->new( @args ) );
134}
135
136# end of spaghetti setup DWIM
c28ee69c 137
138sub cache {
2e4bde89 139 my ( $c, @meta ) = @_;
140
141 if ( @meta == 1 ) {
142 my $name = $meta[0];
143 return ( $c->get_preset_curried($name) || $c->get_cache_backend($name) );
144 } elsif ( !@meta ) {
145 # be nice and always return the same one for the simplest case
146 return ( $c->_default_curried_cache || $c->_default_curried_cache( $c->curry_cache( @meta ) ) );
c28ee69c 147 } else {
2e4bde89 148 return $c->curry_cache( @meta );
c28ee69c 149 }
150}
151
5a00a29b 152sub construct_curried_cache {
153 my ( $c, @meta ) = @_;
154 return $c->curried_cache_class( @meta )->new( @meta );
155}
156
157sub curried_cache_class {
158 my ( $c, @meta ) = @_;
0ba5145c 159 $c->_get_cache_plugin_config->{curried_class} || "Catalyst::Plugin::Cache::Curried";
5a00a29b 160}
161
2e4bde89 162sub curry_cache {
163 my ( $c, @meta ) = @_;
5a00a29b 164 return $c->construct_curried_cache( $c, $c->_cache_caller_meta, @meta );
2e4bde89 165}
166
167sub get_preset_curried {
168 my ( $c, $name ) = @_;
169
0ba5145c 170 if ( ref( my $preset = $c->_get_cache_plugin_config->{profiles}{$name} ) ) {
2e4bde89 171 return $preset if Scalar::Util::blessed($preset);
172
173 my @meta = ( ( ref $preset eq "HASH" ) ? %$preset : @$preset );
174 return $c->curry_cache( @meta );
175 }
176
177 return;
178}
179
c28ee69c 180sub get_cache_backend {
181 my ( $c, $name ) = @_;
182 $c->_cache_backends->{$name};
183}
184
185sub register_cache_backend {
186 my ( $c, $name, $backend ) = @_;
187
188 no warnings 'uninitialized';
189 Carp::croak("$backend does not look like a cache backend - "
aed484da 190 . "it must be an object supporting get, set and remove")
191 unless eval { $backend->can("get") && $backend->can("set") && $backend->can("remove") };
c28ee69c 192
193 $c->_cache_backends->{$name} = $backend;
194}
195
196sub unregister_cache_backend {
197 my ( $c, $name ) = @_;
198 delete $c->_cache_backends->{$name};
199}
200
201sub default_cache_backend {
202 my $c = shift;
203 $c->get_cache_backend( "default" ) || $c->temporary_cache_backend;
204}
205
206sub temporary_cache_backend {
207 my $c = shift;
208 die "FIXME - make up an in memory cache backend, that hopefully works well for the current engine";
209}
210
5a00a29b 211sub _cache_caller_meta {
212 my $c = shift;
213
214 my ( $caller, $component, $controller );
215
216 for my $i ( 0 .. 15 ) { # don't look to far
217 my @info = caller(2 + $i) or last;
218
85a748b9 219 $caller ||= \@info unless $info[0] =~ /Plugin::Cache/;
5a00a29b 220 $component ||= \@info if $info[0]->isa("Catalyst::Component");
221 $controller ||= \@info if $info[0]->isa("Catalyst::Controller");
222
223 last if $caller && $component && $controller;
224 }
225
85a748b9 226 my ( $caller_pkg, $component_pkg, $controller_pkg ) =
227 map { $_ ? $_->[0] : undef } $caller, $component, $controller;
228
5a00a29b 229 return (
85a748b9 230 'caller' => $caller_pkg,
231 component => $component_pkg,
232 controller => $controller_pkg,
233 caller_frame => $caller,
234 component_frame => $component,
235 controller_frame => $controller,
5a00a29b 236 );
237}
238
c28ee69c 239# this gets a shit name so that the plugins can override a good name
240sub choose_cache_backend_wrapper {
241 my ( $c, @meta ) = @_;
242
c627df81 243 Carp::croak("metadata must be an even sized list") unless @meta % 2 == 0;
c28ee69c 244
245 my %meta = @meta;
5a00a29b 246
247 unless ( exists $meta{'caller'} ) {
248 my %caller = $c->_cache_caller_meta;
249 @meta{keys %caller} = values %caller;
250 }
c28ee69c 251
252 # allow the cache client to specify who it wants to cache with (but loeave room for a hook)
253 if ( exists $meta{backend} ) {
254 if ( Scalar::Util::blessed($meta{backend}) ) {
255 return $meta{backend};
256 } else {
257 return $c->get_cache_backend( $meta{backend} ) || $c->default_cache_backend;
258 }
259 };
260
c28ee69c 261 if ( my $chosen = $c->choose_cache_backend( %meta ) ) {
262 $chosen = $c->get_cache_backend( $chosen ) unless Scalar::Util::blessed($chosen); # if it's a name find it
263 return $chosen if Scalar::Util::blessed($chosen); # only return if it was an object or name lookup worked
264
265 # FIXME
266 # die "no such backend"?
267 # currently, we fall back to default
268 }
269
270 return $c->default_cache_backend;
271}
272
ab8e499f 273sub choose_cache_backend { shift->maybe::next::method( @_ ) } # a convenient fallback
c28ee69c 274
275sub cache_set {
390db05f 276 my ( $c, $key, $value, %meta ) = @_;
277 $c->choose_cache_backend_wrapper( key => $key, value => $value, %meta )
278 ->set( $key, $value, exists $meta{expires} ? $meta{expires} : () );
c28ee69c 279}
280
281sub cache_get {
282 my ( $c, $key, @meta ) = @_;
283 $c->choose_cache_backend_wrapper( key => $key, @meta )->get( $key );
284}
285
aed484da 286sub cache_remove {
c28ee69c 287 my ( $c, $key, @meta ) = @_;
aed484da 288 $c->choose_cache_backend_wrapper( key => $key, @meta )->remove( $key );
c28ee69c 289}
290
291__PACKAGE__;
292
293__END__
294
295=pod
296
297=head1 NAME
298
85a748b9 299Catalyst::Plugin::Cache - Flexible caching support for Catalyst.
c28ee69c 300
301=head1 SYNOPSIS
302
5a00a29b 303 use Catalyst qw/
304 Cache
305 /;
306
307 # configure a backend or use a store plugin
0ba5145c 308 __PACKAGE__->config->{'Plugin::Cache'}{backend} = {
85a748b9 309 class => "Cache::Bounded",
fba82fef 310 # ... params for Cache::Bounded...
85a748b9 311 };
5a00a29b 312
fba82fef 313 # typical example for Cache::Memcached::libmemcached
0ba5145c 314 __PACKAGE__->config->{'Plugin::Cache'}{backend} = {
fba82fef 315 class => "Cache::Memcached::libmemcached",
316 servers => ['127.0.0.1:11211'],
317 debug => 2,
318 };
319
320
c627df81 321 # In a controller:
5a00a29b 322
323 sub foo : Local {
324 my ( $self, $c, $id ) = @_;
325
326 my $cache = $c->cache;
327
328 my $result;
329
330 unless ( $result = $cache->get( $id ) ) {
c627df81 331 # ... calculate result ...
5a00a29b 332 $c->cache->set( $id, $result );
333 }
334 };
c28ee69c 335
336=head1 DESCRIPTION
337
c627df81 338This plugin gives you access to a variety of systems for caching
339data. It allows you to use a very simple configuration API, while
340maintaining the possibility of flexibility when you need it later.
5a00a29b 341
c627df81 342Among its features are support for multiple backends, segmentation based
343on component or controller, keyspace partitioning, and so more, in
344various subsidiary plugins.
5a00a29b 345
85a748b9 346=head1 METHODS
347
348=over 4
349
350=item cache $profile_name
351
352=item cache %meta
353
c627df81 354Return a curried object with metadata from C<$profile_name> or as
355explicitly specified.
85a748b9 356
c627df81 357If a profile by the name C<$profile_name> doesn't exist, but a backend
358object by that name does exist, the backend will be returned instead,
359since the interface for curried caches and backends is almost identical.
85a748b9 360
c627df81 361This method can also be called without arguments, in which case is
362treated as though the C<%meta> hash was empty.
85a748b9 363
c627df81 364See L</METADATA> for details.
85a748b9 365
366=item curry_cache %meta
367
c627df81 368Return a L<Catalyst::Plugin::Cache::Curried> object, curried with C<%meta>.
85a748b9 369
c627df81 370See L</METADATA> for details.
85a748b9 371
372=item cache_set $key, $value, %meta
373
374=item cache_get $key, %meta
375
376=item cache_remove $key, %meta
377
c627df81 378These cache operations will call L<choose_cache_backend> with %meta, and
379then call C<set>, C<get>, or C<remove> on the resulting backend object.
85a748b9 380
381=item choose_cache_backend %meta
382
c627df81 383Select a backend object. This should return undef if no specific backend
384was selected - its caller will handle getting C<default_cache_backend>
385on its own.
85a748b9 386
387This method is typically used by plugins.
388
389=item get_cache_backend $name
390
391Get a backend object by name.
392
393=item default_cache_backend
394
395Return the default backend object.
396
397=item temporary_cache_backend
398
c627df81 399When no default cache backend is configured this method might return a
400backend known to work well with the current L<Catalyst::Engine>. This is
401a stub.
85a748b9 402
403=item
404
405=back
406
c627df81 407=head1 METADATA
85a748b9 408
409=head2 Introduction
410
c627df81 411Whenever you set or retrieve a key you may specify additional metadata
412that will be used to select a specific backend.
85a748b9 413
414This metadata is very freeform, and the only key that has any meaning by
415default is the C<backend> key which can be used to explicitly choose a backend
416by name.
417
c627df81 418The C<choose_cache_backend> method can be overridden in order to
419facilitate more intelligent backend selection. For example,
420L<Catalyst::Plugin::Cache::Choose::KeyRegexes> overrides that method to
421select a backend based on key regexes.
85a748b9 422
c627df81 423Another example is a L<Catalyst::Plugin::Cache::ControllerNamespacing>,
424which wraps backends in objects that perform key mangling, in order to
425keep caches namespaced per controller.
85a748b9 426
427However, this is generally left as a hook for larger, more complex
c627df81 428applications. Most configurations should make due XXXX
85a748b9 429
c627df81 430The simplest way to dynamically select a backend is based on the
431L</Cache Profiles> configuration.
85a748b9 432
433=head2 Meta Data Keys
434
435C<choose_cache_backend> is called with some default keys.
436
437=over 4
438
439=item key
440
c627df81 441Supplied by C<cache_get>, C<cache_set>, and C<cache_remove>.
85a748b9 442
443=item value
444
c627df81 445Supplied by C<cache_set>.
85a748b9 446
447=item caller
448
449The package name of the innermost caller that doesn't match
450C<qr/Plugin::Cache/>.
451
452=item caller_frame
453
454The entire C<caller($i)> frame of C<caller>.
455
456=item component
457
c627df81 458The package name of the innermost caller who C<isa>
459L<Catalyst::Component>.
85a748b9 460
461=item component_frame
462
463This entire C<caller($i)> frame of C<component>.
464
465=item controller
466
c627df81 467The package name of the innermost caller who C<isa>
468L<Catalyst::Controller>.
85a748b9 469
470=item controller_frame
471
472This entire C<caller($i)> frame of C<controller>.
473
474=back
475
c627df81 476=head2 Metadata Currying
85a748b9 477
c627df81 478In order to avoid specifying C<%meta> over and over again you may call
479C<cache> or C<curry_cache> with C<%meta> once, and get back a B<curried
480cache object>. This object responds to the methods C<get>, C<set>, and
481C<remove>, by appending its captured metadata and delegating them to
482C<cache_get>, C<cache_set>, and C<cache_remove>.
85a748b9 483
484This is simpler than it sounds.
485
486Here is an example using currying:
487
488 my $cache = $c->cache( %meta ); # cache is curried
489
490 $cache->set( $key, $value );
491
492 $cache->get( $key );
493
494And here is an example without using currying:
495
496 $c->cache_set( $key, $value, %meta );
497
498 $c->cache_get( $key, %meta );
499
500See L<Catalyst::Plugin::Cache::Curried> for details.
501
0f0237aa 502=head1 CONFIGURATION
503
0ba5145c 504 $c->config->{'Plugin::Cache'} = {
85a748b9 505 ...
506 };
0f0237aa 507
c627df81 508All configuration parameters should be provided in a hash reference
0ba5145c 509under the C<Plugin::Cache> key in the C<config> hash.
0f0237aa 510
85a748b9 511=head2 Backend Configuration
512
513Configuring backend objects is done by adding hash entries under the
c627df81 514C<backends> key in the main config.
85a748b9 515
c627df81 516A special case is that the hash key under the C<backend> (singular) key
517of the main config is assumed to be the backend named C<default>.
85a748b9 518
0f0237aa 519=over 4
520
521=item class
522
85a748b9 523Instantiate a backend from a L<Cache> compatible class. E.g.
0f0237aa 524
0ba5145c 525 $c->config->{'Plugin::Cache'}{backends}{small_things} = {
85a748b9 526 class => "Cache::Bounded",
527 interval => 1000,
528 size => 10000,
529 };
530
0ba5145c 531 $c->config->{'Plugin::Cache'}{backends}{large_things} = {
bd2e2f72 532 class => "Cache::Memcached",
85a748b9 533 data => '1.2.3.4:1234',
534 };
0f0237aa 535
85a748b9 536The options in the hash are passed to the class's C<new> method.
0f0237aa 537
85a748b9 538The class will be C<required> as necessary during setup time.
0f0237aa 539
85a748b9 540=item store
0f0237aa 541
c627df81 542Instantiate a backend using a store plugin, e.g.
0f0237aa 543
0ba5145c 544 $c->config->{'Plugin::Cache'}{backend} = {
85a748b9 545 store => "FastMmap",
546 };
0f0237aa 547
c627df81 548Store plugins typically require less configuration because they are
549specialized for L<Catalyst> applications. For example
85a748b9 550L<Catalyst::Plugin::Cache::Store::FastMmap> will specify a default
c627df81 551C<share_file>, and additionally use a subclass of L<Cache::FastMmap>
552that can also store non reference data.
85a748b9 553
554The store plugin must be loaded.
555
556=back
0f0237aa 557
85a748b9 558=head2 Cache Profiles
559
560=over 4
0f0237aa 561
562=item profiles
563
c627df81 564Supply your own predefined profiles for cache metadata, when using the
565C<cache> method.
85a748b9 566
567For example when you specify
568
0ba5145c 569 $c->config->{'Plugin::Cache'}{profiles}{thumbnails} = {
85a748b9 570 backend => "large_things",
571 };
572
573And then get a cache object like this:
574
575 $c->cache("thumbnails");
576
577It is the same as if you had done:
578
579 $c->cache( backend => "large_things" );
580
581=back
582
c627df81 583=head2 Miscellaneous Configuration
85a748b9 584
585=over 4
586
587=item default_store
588
c627df81 589When you do not specify a C<store> parameter in the backend
590configuration this one will be used instead. This configuration
591parameter is not necessary if only one store plugin is loaded.
0f0237aa 592
593=back
594
595=head1 TERMINOLOGY
23b2d59b 596
597=over 4
598
599=item backend
600
601An object that responds to the methods detailed in
602L<Catalyst::Plugin::Cache::Backend> (or more).
603
604=item store
605
c627df81 606A plugin that provides backends of a certain type. This is a bit like a
607factory.
23b2d59b 608
0f0237aa 609=item cache
610
611Stored key/value pairs of data for easy re-access.
612
c627df81 613=item metadata
85a748b9 614
c627df81 615"Extra" information about the item being stored, which can be used to
616locate an appropriate backend.
85a748b9 617
23b2d59b 618=item curried cache
619
0f0237aa 620 my $cache = $c->cache(type => 'thumbnails');
621 $cache->set('pic01', $thumbnaildata);
622
c627df81 623A cache which has been pre-configured with a particular set of
624namespacing data. In the example the cache returned could be one
625specifically tuned for storing thumbnails.
0f0237aa 626
c627df81 627An object that responds to C<get>, C<set>, and C<remove>, and will
628automatically add metadata to calls to C<< $c->cache_get >>, etc.
23b2d59b 629
630=back
631
772299b1 632=head1 SEE ALSO
633
c627df81 634L<Cache> - the generic cache API on CPAN.
772299b1 635
636L<Catalyst::Plugin::Cache::Store> - how to write a store plugin.
637
638L<Catalyst::Plugin::Cache::Curried> - the interface for curried caches.
639
640L<Catalyst::Plugin::Cache::Choose::KeyRegexes> - choose a backend based on
641regex matching on the keys. Can be used to partition the keyspace.
642
643L<Catalyst::Plugin::Cache::ControllerNamespacing> - wrap backend objects in a
c627df81 644name mangler so that every controller gets its own keyspace.
772299b1 645
271f5106 646=head1 AUTHOR
647
648Yuval Kogman, C<nothingmuch@woobling.org>
649
dc5fda2b 650Jos Boumans, C<kane@cpan.org>
651
271f5106 652=head1 COPYRIGHT & LICENSE
c28ee69c 653
271f5106 654Copyright (c) Yuval Kogman, 2006. All rights reserved.
655
656This library is free software, you can redistribute it and/or modify it under
657the same terms as Perl itself, as well as under the terms of the MIT license.
658
659=cut
c28ee69c 660