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