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