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