File Coverage

blib/lib/Dancer/Plugin/Mango.pm
Criterion Covered Total %
statement 18 110 16.3
branch 0 62 0.0
condition 0 24 0.0
subroutine 6 13 46.1
pod 0 1 0.0
total 24 210 11.4


line stmt bran cond sub pod time code
1             # ABSTRACT: MongoDB plugin for the Dancer micro framework
2             package Dancer::Plugin::Mango;
3              
4 1     1   204682 use strict;
  1         2  
  1         31  
5 1     1   4 use warnings;
  1         1  
  1         22  
6 1     1   505 use Dancer::Plugin;
  1         40341  
  1         69  
7 1     1   11 use Mango;
  1         1  
  1         8  
8 1     1   20 use Scalar::Util 'blessed';
  1         2  
  1         40  
9 1     1   576 use Dancer qw{:syntax};
  1         116921  
  1         5  
10              
11             my $dancer_version = (exists &dancer_version) ? int(dancer_version()) : 1;
12             my ($logger);
13             if ($dancer_version == 1) {
14             require Dancer::Config;
15             Dancer::Config->import();
16              
17             $logger = sub { Dancer::Logger->can($_[0])->($_[1]) };
18             } else {
19             $logger = sub { log @_ };
20             }
21              
22             =encoding utf8
23             =head1 NAME
24              
25             Dancer::Plugin::Mango - MongoDB connections as provided by Mango.
26              
27             =head1 STATUS
28              
29             Tested in a production environment. It's a good idea to read the documentation for
30             Mango as it's async. Which means you must be ready to handle this. In most of my
31             code I'm using the loop function to wait for the server response. However, for
32             inserts, I'm not waiting at all. Handy.
33              
34             =cut
35              
36             our $VERSION = 0.41;
37              
38             my $settings = undef;
39             my $conn = undef;
40             my $lasterror = undef;
41              
42             sub _load_db_settings {
43 0     0     $settings = plugin_setting;
44             }
45              
46             my %handles;
47             # Hashref used as key for default handle, so we don't have a magic value that
48             # the user could use for one of their connection names and cause problems
49             # (Kudos to Igor Bujna for the idea)
50             my $def_handle = {};
51              
52             sub moango {
53              
54 0     0 0   my ( $self, $arg ) = plugin_args(@_);
55              
56 0 0 0       $arg = shift if blessed($arg) and $arg->isa('Dancer::Core::DSL');
57              
58             # The key to use to store this handle in %handles. This will be either the
59             # name supplied to database(), the hashref supplied to database() (thus, as
60             # long as the same hashref of settings is passed, the same handle will be
61             # reused) or $def_handle if database() is called without args:
62              
63 0 0         _load_db_settings() if ( !$settings);
64              
65 0           my $handle_key;
66             my $conn_details; # connection settings to use.
67 0           my $handle;
68              
69              
70             # Accept a hashref of settings to use, if desired. If so, we use this
71             # hashref to look for the handle, too, so as long as the same hashref is
72             # passed to the database() keyword, we'll reuse the same handle:
73 0 0         if (ref $arg eq 'HASH') {
74 0           $handle_key = $arg;
75 0           $conn_details = $arg;
76             } else {
77 0 0         $handle_key = defined $arg ? $arg : $def_handle;
78 0           $conn_details = _get_settings($arg);
79 0 0         if (!$conn_details) {
80 0   0       $logger->(error => "No DB settings for " . ($arg || "default connection"));
81 0           return;
82             }
83             }
84              
85             # To be fork safe and thread safe, use a combination of the PID and TID (if
86             # running with use threads) to make sure no two processes/threads share
87             # handles. Implementation based on DBIx::Connector by David E. Wheeler.
88 0           my $pid_tid = $$;
89 0 0         $pid_tid .= '_' . threads->tid if $INC{'threads.pm'};
90              
91             # OK, see if we have a matching handle
92 0   0       $handle = $handles{$pid_tid}{$handle_key} || {};
93              
94 0 0         if ($handle->{dbh}) {
95             # If we should never check, go no further:
96 0 0         if (!$conn_details->{connection_check_threshold}) {
97 0           return $handle->{dbh};
98             }
99              
100 0 0 0       if ($handle->{dbh}{Active} && $conn_details->{connection_check_threshold} &&
      0        
101             time - $handle->{last_connection_check}
102             < $conn_details->{connection_check_threshold})
103             {
104 0           return $handle->{dbh};
105             } else {
106 0 0         if (_check_connection($handle->{dbh})) {
107 0           $handle->{last_connection_check} = time;
108 0           return $handle->{dbh};
109             } else {
110              
111 0           $logger->(debug => "Database connection went away, reconnecting");
112 0           execute_hook('database_connection_lost', $handle->{dbh});
113              
114 0           return $handle->{dbh}= _get_connection($conn_details);
115              
116             }
117             }
118             } else {
119             # Get a new connection
120 0 0         if ($handle->{dbh} = _get_connection($conn_details)) {
121 0           $handle->{last_connection_check} = time;
122 0           $handles{$pid_tid}{$handle_key} = $handle;
123              
124 0 0 0       if (ref $handle_key && ref $handle_key ne ref $def_handle) {
125             # We were given a hashref of connection settings. Shove a
126             # reference to that hashref into the handle, so that the hashref
127             # doesn't go out of scope for the life of the handle.
128             # Otherwise, that area of memory could be re-used, and, given
129             # different DB settings in a hashref that just happens to have
130             # the same address, we'll happily hand back the original handle.
131             # See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=665221
132             # Thanks to Sam Kington for suggesting this fix :)
133 0           $handle->{_orig_settings_hashref} = $handle_key;
134             }
135 0           return $handle->{dbh};
136             } else {
137 0           return;
138             }
139             }
140             };
141              
142             ## return a connected MongoDB object
143             ## registering both mango and mongo due to a typo that was released
144             register mango => \&moango;
145             register mongo => \&moango;
146              
147             register_hook(qw(mongodb_connected
148             mongodb_connection_lost
149             mongodb_connection_failed
150             mongodb_error));
151              
152             register_plugin(for_versions => ['1', '2']);
153              
154             # Given the settings to use, try to get a database connection
155             sub _get_connection {
156 0     0     my $settings = shift;
157              
158             # Assemble the Connection String:
159 0 0 0       my $dsn = 'mongodb://' .
160             ( $settings->{host} || 'localhost' ) .
161             ( defined $settings->{port} ? ':' . $settings->{port} : () );
162              
163 0           my $dbh = Mango->new($dsn);
164              
165 0 0         $dbh->default_db($settings->{db_name})
166             if defined $settings->{db_name};
167              
168 0 0 0       if (defined $settings->{username} && defined $settings->{password}) {
169 0           push @{$settings->{db_credentials}}, [ $settings->{db_name}, $settings->{username}, $settings->{password}];
  0            
170             }
171              
172              
173 0 0 0       if (defined $settings->{db_credentials} and ref $settings->{db_credentials} eq 'ARRAY') {
174 0           $dbh->credentials($settings->{db_credentials});
175             }
176              
177 0 0         if (defined $settings->{ioloop}) {
178 0           my ( $module, $function ) = split(/\-\>/, $settings->{ioloop});
179 0           $dbh->ioloop($module->$function);
180             }
181              
182 0 0         if (defined $settings->{j}) {
183 0           $dbh->j($settings->{j})
184             };
185              
186 0 0         if (defined $settings->{max_bson_size}) {
187 0           $dbh->max_bson_size($settings->{max_bson_size})
188             };
189              
190 0 0         if (defined $settings->{max_connections}) {
191 0           $dbh->max_connections($settings->{max_connections})
192             }
193              
194 0 0         if (defined $settings->{max_write_batch_size}) {
195 0           $dbh->max_write_batch_size($settings->{max_write_batch_size})
196             }
197              
198 0 0         if ( defined $settings->{protocol}) {
199 0           my ( $module, $function ) = split(/\-\>/, $settings->{protocol});
200 0           $dbh->protocol($module->$function);
201             }
202              
203 0 0         if ( defined $settings->{w}) {
204 0           $dbh->w($settings->{w})
205             }
206              
207 0 0         if ( defined $settings->{wtimeout}) {
208 0           $dbh->wtimeout($settings->{wtimeout})
209             }
210              
211             #$dbh->on( error => \&_mango_error() );
212             #$dbh->on( connection => \&_mango_connection() );
213              
214 0 0         if (!$dbh) {
215 0           $logger->(error => "Database connection failed - " . $lasterror);
216 0           execute_hook('database_connection_failed', $settings);
217 0           return;
218             }
219              
220 0           execute_hook('database_connected', $dbh);
221              
222 0           return $dbh;
223             }
224              
225             # Check the connection is alive
226             sub _check_connection {
227 0     0     my $dbh = shift;
228 0 0         return unless $dbh;
229              
230 0           my $curs;
231              
232 0           $lasterror = undef;
233              
234 0           eval {
235 0           $curs = $dbh->db($settings->{db_name})->collection('prototype')->find_one();
236             };
237              
238 0 0         if (!defined $lasterror) {
239 0           return 1;
240             }
241              
242 0           return;
243             }
244              
245             sub _mango_error {
246 0     0     my ( $mango, $err ) = @_;
247 0           $lasterror = $err;
248 0           return;
249             }
250              
251             sub _mango_connection {
252 0     0     return;
253             }
254              
255             sub _get_settings {
256 0     0     my $name = shift;
257 0           my $return_settings;
258              
259             # If no name given, just return the default settings
260 0 0         if (!defined $name) {
261 0           $return_settings = { %$settings };
262             # Yeah, you can have ZERO settings in Mongo.
263             } else {
264             # If there are no named connections in the config, bail now:
265 0 0         return unless exists $settings->{connections};
266              
267             # OK, find a matching config for this name:
268 0 0         if (my $named_settings = $settings->{connections}{$name}) {
269             # Take a (shallow) copy of the settings, so we don't change them
270 0           $return_settings = { %$named_settings };
271             } else {
272             # OK, didn't match anything
273 0           $logger->('error',
274             "Asked for a database handle named '$name' but no matching "
275             ."connection details found in config"
276             );
277             }
278             }
279              
280             # If the setting wasn't provided, default to 30 seconds; if a false value is
281             # provided, though, leave it alone. (Older versions just checked for
282             # truthiness, so a value of zero would still default to 30 seconds, which
283             # isn't ideal.)
284 0 0         if (!exists $return_settings->{connection_check_threshold}) {
285 0           $return_settings->{connection_check_threshold} = 30;
286             }
287              
288 0           return $return_settings;
289              
290             }
291             1;
292              
293             __END__