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