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   200984 use strict;
  1         2  
  1         35  
5 1     1   4 use warnings;
  1         1  
  1         27  
6 1     1   541 use Dancer::Plugin;
  1         39445  
  1         66  
7 1     1   11 use Mango;
  1         1  
  1         6  
8 1     1   20 use Scalar::Util 'blessed';
  1         2  
  1         41  
9 1     1   603 use Dancer qw{:syntax};
  1         118603  
  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             Horribly under-tested, may induce seizures and sudden death. You have been warned.
30             Additionally, this module will require MongoDB 2.6+. This is primarily because Mango
31             requires it. You will get an error "MongoDB wire protocol version 2 required" if this
32             is not the case.
33              
34             =cut
35              
36             our $VERSION = 0.40;
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             register_plugin(for_versions => ['1', '2']);
152              
153             # Given the settings to use, try to get a database connection
154             sub _get_connection {
155 0     0     my $settings = shift;
156              
157             # Assemble the Connection String:
158 0 0 0       my $dsn = 'mongodb://' .
159             ( $settings->{host} || 'localhost' ) .
160             ( defined $settings->{port} ? ':' . $settings->{port} : () );
161              
162 0           my $dbh = Mango->new($dsn);
163              
164 0 0         $dbh->default_db($settings->{db_name})
165             if defined $settings->{db_name};
166              
167 0 0 0       if (defined $settings->{username} && defined $settings->{password}) {
168 0           push @{$settings->{db_credentials}}, [ $settings->{db_name}, $settings->{username}, $settings->{password}];
  0            
169             }
170              
171              
172 0 0 0       if (defined $settings->{db_credentials} and ref $settings->{db_credentials} eq 'ARRAY') {
173 0           $dbh->credentials($settings->{db_credentials});
174             }
175              
176 0 0         if (defined $settings->{ioloop}) {
177 0           my ( $module, $function ) = split(/\-\>/, $settings->{ioloop});
178 0           $dbh->ioloop($module->$function);
179             }
180              
181 0 0         if (defined $settings->{j}) {
182 0           $dbh->j($settings->{j})
183             };
184              
185 0 0         if (defined $settings->{max_bson_size}) {
186 0           $dbh->max_bson_size($settings->{max_bson_size})
187             };
188              
189 0 0         if (defined $settings->{max_connections}) {
190 0           $dbh->max_connections($settings->{max_connections})
191             }
192              
193 0 0         if (defined $settings->{max_write_batch_size}) {
194 0           $dbh->max_write_batch_size($settings->{max_write_batch_size})
195             }
196              
197 0 0         if ( defined $settings->{protocol}) {
198 0           my ( $module, $function ) = split(/\-\>/, $settings->{protocol});
199 0           $dbh->protocol($module->$function);
200             }
201              
202 0 0         if ( defined $settings->{w}) {
203 0           $dbh->w($settings->{w})
204             }
205              
206 0 0         if ( defined $settings->{wtimeout}) {
207 0           $dbh->wtimeout($settings->{wtimeout})
208             }
209              
210             #$dbh->on( error => \&_mango_error() );
211             #$dbh->on( connection => \&_mango_connection() );
212              
213 0 0         if (!$dbh) {
214 0           $logger->(error => "Database connection failed - " . $lasterror);
215 0           execute_hook('database_connection_failed', $settings);
216 0           return;
217             }
218              
219 0           execute_hook('database_connected', $dbh);
220              
221 0           return $dbh;
222             }
223              
224             # Check the connection is alive
225             sub _check_connection {
226 0     0     my $dbh = shift;
227 0 0         return unless $dbh;
228              
229 0           my $curs;
230              
231 0           $lasterror = undef;
232              
233 0           eval {
234 0           $curs = $dbh->db($settings->{db_name})->collection('prototype')->find_one();
235             };
236              
237 0 0         if (!defined $lasterror) {
238 0           return 1;
239             }
240              
241 0           return;
242             }
243              
244             sub _mango_error {
245 0     0     my ( $mango, $err ) = @_;
246 0           $lasterror = $err;
247 0           return;
248             }
249              
250             sub _mango_connection {
251 0     0     return;
252             }
253              
254             sub _get_settings {
255 0     0     my $name = shift;
256 0           my $return_settings;
257              
258             # If no name given, just return the default settings
259 0 0         if (!defined $name) {
260 0           $return_settings = { %$settings };
261             # Yeah, you can have ZERO settings in Mongo.
262             } else {
263             # If there are no named connections in the config, bail now:
264 0 0         return unless exists $settings->{connections};
265              
266             # OK, find a matching config for this name:
267 0 0         if (my $named_settings = $settings->{connections}{$name}) {
268             # Take a (shallow) copy of the settings, so we don't change them
269 0           $return_settings = { %$named_settings };
270             } else {
271             # OK, didn't match anything
272 0           $logger->('error',
273             "Asked for a database handle named '$name' but no matching "
274             ."connection details found in config"
275             );
276             }
277             }
278              
279             # If the setting wasn't provided, default to 30 seconds; if a false value is
280             # provided, though, leave it alone. (Older versions just checked for
281             # truthiness, so a value of zero would still default to 30 seconds, which
282             # isn't ideal.)
283 0 0         if (!exists $return_settings->{connection_check_threshold}) {
284 0           $return_settings->{connection_check_threshold} = 30;
285             }
286              
287 0           return $return_settings;
288              
289             }
290             1;
291              
292             __END__