source: main/trunk/greenstone2/perllib/DBDrivers/BaseDBDriver.pm@ 30355

Last change on this file since 30355 was 30355, checked in by jmt12, 8 years ago

Initial checkin of OO drivers for new dbutils system

File size: 14.6 KB
Line 
1###############################################################################
2#
3# BaseDBDriver.pm -- base class for all the database drivers
4# A component of the Greenstone digital library software from the New Zealand
5# Digital Library Project at the University of Waikato, New Zealand.
6#
7# Copyright (c) 2015 New Zealand Digital Library Project
8#
9# This program is free software; you can redistribute it and/or modify it under
10# the terms of the GNU General Public License as published by the Free Software
11# Foundation; either version 2 of the License, or (at your option) any later
12# version.
13#
14# This program is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# this program; if not, write to the Free Software Foundation, Inc., 675 Mass
21# Ave, Cambridge, MA 02139, USA.
22#
23###############################################################################
24
25package DBDrivers::BaseDBDriver;
26
27# Pragma
28use strict;
29no strict 'subs';
30no strict 'refs'; # allow filehandles to be variables and viceversa
31
32# Libaries
33use Time::HiRes qw( gettimeofday );
34use gsprintf 'gsprintf';
35
36
37## @function constructor
38#
39sub new
40{
41 my $class = shift(@_);
42 my $debug = shift(@_);
43 my $self = {};
44 # Debug messages for this driver
45 $self->{'debug'} = $debug; # 1 to enable
46 # We'll use this in places other than 70HyphenFormat
47 $self->{'70hyphen'} = '-' x 70;
48 # Keep track of all opened file handles, but only for drivers that support
49 # persistent connections
50 $self->{'handle_pool'} = {};
51 # Default file extension - in this case it is an error to create a DB from
52 # BaseDBDriver
53 $self->{'default_file_extension'} = 'err';
54 # Support
55 $self->{'supports_datestamp'} = 0;
56 $self->{'supports_merge'} = 0;
57 $self->{'supports_persistentconnection'} = 0;
58 $self->{'supports_rss'} = 0;
59 $self->{'supports_set'} = 0;
60 $self->{'write_only'} = 0; # Some drivers are one way - i.e. STDOUTXML
61 bless($self, $class);
62 return $self;
63}
64## new(void) => BaseDBDriver ##
65
66
67## @function DESTROY
68#
69# Built-in destructor block that, unlike END, gets passed a reference to self.
70# Responsible for properly closing any open database handles.
71#
72sub DESTROY
73{
74 my $self = shift(@_);
75 # Close all remaining filehandles
76 foreach my $infodb_file_path (keys(%{$self->{'handle_pool'}})) {
77 my $infodb_handle = $self->{'handle_pool'}->{$infodb_file_path};
78 # By passing the filepath as the second argument we instruct the driver
79 # that we actually want to close the connection by passing a non-zero
80 # value, but we sneakily optimize things a little as the close method
81 # can now check to see if it's been provided a file_path rather than
82 # having to search the handle pool for it. The file_path is needed to
83 # remove the closed handle from the pool anyway.
84 $self->close_infodb_write_handle($infodb_handle, $infodb_file_path);
85 }
86}
87## DESTROY(void) => void ##
88
89
90###############################################################################
91## Protected Functions
92###############################################################################
93
94
95## @function debugPrint(string) => void
96#
97sub debugPrint
98{
99 my $self = shift(@_);
100 my $message = shift(@_);
101 if ($self->{'debug'}) {
102 my ($seconds, $microseconds) = gettimeofday();
103 print STDERR '[DEBUG:' . $seconds . '.' . $microseconds . '] ' . (caller 1)[3] . '() ' . $message . "\n";
104 }
105}
106## debugPrint(string) => void ##
107
108
109## @function debugPrintFunctionHeader(*) => void
110#
111sub debugPrintFunctionHeader
112{
113 my $self = shift(@_);
114 if ($self->{'debug'}) {
115 my @arguments;
116 foreach my $argument (@_) {
117 if ($argument !~ /^-?\d+(\.?\d+)?$/) {
118 push(@arguments, '"' . $argument . '"');
119 }
120 else {
121 push(@arguments, $argument);
122 }
123 }
124 my $message = '(' . join(', ', @arguments) . ')';
125 # Would love to just call debugPrint() here, but then caller would be wrong
126 my ($seconds, $microseconds) = gettimeofday();
127 print STDERR '[DEBUG:' . $seconds . '.' . $microseconds . '] ' . (caller 1)[3] . $message . "\n";
128 }
129}
130## debugPrintFunctionHeader(*) => void
131
132
133## @function errorPrint(string, integer) => void
134#
135sub errorPrint
136{
137 my $self = shift(@_);
138 my $message = shift(@_);
139 my $is_fatal = shift(@_);
140 print STDERR 'Error in ' . (caller 1)[3] . '! ' . $message . "\n";
141 if ($is_fatal) {
142 exit();
143 }
144}
145## errorPrint(string, integer) => void ##
146
147
148## @function registerConnectionIfPersistent(filehandle, string, string) => void
149#
150sub registerConnectionIfPersistent
151{
152 my $self = shift(@_);
153 my $conn = shift(@_);
154 my $path = shift(@_);
155 my $append = shift(@_);
156 if ($self->{'supports_persistentconnection'}) {
157 $self->debugPrintFunctionHeader($conn, $path, $append);
158 my $fhid = $path;
159 if (defined $append && $append eq '-append') {
160 $fhid .= ' [APPEND]';
161 }
162 $self->debugPrint('Registering connection: "' . $fhid . '"');
163 $self->{'handle_pool'}->{$fhid} = $conn;
164 }
165 return;
166}
167## registerConnectionIfPersistent(filehandle, string, string) => void ##
168
169
170## @function removeConnectionIfPersistent(filehandle, string) => integer
171#
172sub removeConnectionIfPersistent
173{
174 my $self = shift(@_);
175 my $handle = shift(@_);
176 my $force_close = shift(@_);
177 my $continue_close = 1;
178 if ($self->{'supports_persistentconnection'}) {
179 $self->debugPrintFunctionHeader($handle, $force_close);
180 if (defined($force_close)) {
181 # We'll need the file path so we can locate and remove the entry
182 # in the handle pool (plus possibly the [APPEND] suffix for those
183 # connections in opened in append mode)
184 my $fhid = undef;
185 # Sometimes we can cheat, as the force_close variable will have the
186 # file_path in it thanks to the DESTROY block above. Doing a regex
187 # on force_close will treat it like a string no matter what it was,
188 # and we can search for the appropriate file extension that should
189 # be there for valid paths.
190 my $pattern = '\.' . $self->{'default_file_extension'} . '(\s\[APPEND\])?$';
191 if ($force_close =~ /$pattern/) {
192 $fhid = $force_close;
193 }
194 # If we can't cheat then we are stuck finding which connection in
195 # the handle_pool we are about to close. Need to compare objects
196 # using refaddr()
197 else {
198 foreach my $possible_fhid (keys %{$self->{'handle_pool'}}) {
199 my $possible_handle = $self->{'handle_pool'}->{$possible_fhid};
200 if (ref($handle) && ref($possible_handle) && refaddr($handle) == refaddr($possible_handle)) {
201 $fhid = $possible_fhid;
202 last;
203 }
204 }
205 }
206 # If we found the fhid we can proceed to close the connection
207 if (defined($fhid)) {
208 $self->debugPrint('Closing persistent connection: ' . $fhid);
209 delete($self->{'handle_pool'}->{$fhid});
210 $continue_close = 1;
211 }
212 else {
213 print STDERR "Warning! About to close persistent database handle, but couldn't locate in open handle pool.\n";
214 }
215 }
216 # Persistent connection don't close *unless* force close is set
217 else {
218 $continue_close = 0;
219 }
220 }
221 return $continue_close;
222}
223## removeConnectionIfPersistent(filehandle, string) => integer ##
224
225
226##
227#
228sub retrieveConnectionIfPersistent
229{
230 my $self = shift(@_);
231 my $path = shift(@_);
232 my $append = shift(@_); # -append support
233 my $conn; # This should be populated
234 if ($self->{'supports_persistentconnection'}) {
235 $self->debugPrintFunctionHeader($path, $append);
236 my $fhid = $path;
237 # special case: if the append mode has changed for a persistent
238 # connection, we need to close the old connection first or things
239 # will get wiggy.
240 if (defined $append && $append eq '-append') {
241 # see if there is a non-append mode connection already open
242 if (defined $self->{'handle_pool'}->{$path}) {
243 $self->debugPrint("Append mode added - closing existing non-append mode connection");
244 my $old_conn = $self->{'handle_pool'}->{$path};
245 $self->close_infodb_write_handle($old_conn, $path);
246 }
247 # Append -append so we know what happened.
248 $fhid .= ' [APPEND]';
249 }
250 else {
251 my $fhid_append = $path . ' [APPEND]';
252 if (defined $self->{'handle_pool'}->{$fhid_append}) {
253 $self->debugPrint("Append mode removed - closing existing append mode connection");
254 my $old_conn = $self->{'handle_pool'}->{$fhid_append};
255 $self->close_infodb_write_handle($old_conn, $fhid_append);
256 }
257 }
258 if (defined $self->{'handle_pool'}->{$fhid}) {
259 $self->debugPrint('Retrieving existing connection: ' . $fhid);
260 $conn = $self->{'handle_pool'}->{$fhid};
261 }
262 }
263 return $conn;
264}
265## ##
266
267
268
269
270
271
272
273###############################################################################
274## Public Functions
275###############################################################################
276
277
278## @function convert_infodb_hash_to_string(hashmap) => string
279#
280sub convert_infodb_hash_to_string
281{
282 my $self = shift(@_);
283 my $infodb_map = shift(@_);
284 my $infodb_entry_value = "";
285 foreach my $infodb_value_key (keys(%$infodb_map)) {
286 foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}}) {
287 $infodb_entry_value .= "<$infodb_value_key>" . $infodb_value . "\n";
288 }
289 }
290 return $infodb_entry_value;
291}
292## convert_infodb_hash_to_string(hashmap) => string ##
293
294
295## @function convert_infodb_string_to_hash(string) => hashmap
296#
297sub convert_infodb_string_to_hash
298{
299 my $self = shift(@_);
300 my $infodb_entry_value = shift(@_);
301 my $infodb_map = ();
302
303 if (!defined $infodb_entry_value) {
304 print STDERR "Warning: No value to convert into a infodb hashtable\n";
305 }
306 else {
307 while ($infodb_entry_value =~ /^<(.*?)>(.*)$/mg) {
308 my $infodb_value_key = $1;
309 my $infodb_value = $2;
310
311 if (!defined($infodb_map->{$infodb_value_key})) {
312 $infodb_map->{$infodb_value_key} = [ $infodb_value ];
313 }
314 else {
315 push(@{$infodb_map->{$infodb_value_key}}, $infodb_value);
316 }
317 }
318 }
319
320 return $infodb_map;
321}
322## convert_infodb_string_to_hash(string) => hashmap ##
323
324
325## @function get_infodb_file_path(string, string) => string
326#
327sub get_infodb_file_path
328{
329 my $self = shift(@_);
330 my $collection_name = shift(@_);
331 my $infodb_directory_path = shift(@_);
332 my $infodb_file_name = &util::get_dirsep_tail($collection_name) . '.' . $self->{'default_file_extension'};
333 my $infodb_file_path = &FileUtils::filenameConcatenate($infodb_directory_path, $infodb_file_name);
334 # Correct the path separators to work in Cygwin
335 if ($^O eq "cygwin") {
336 $infodb_file_path = `cygpath -w "$infodb_file_path"`;
337 chomp($infodb_file_path);
338 $infodb_file_path =~ s%\\%\\\\%g;
339 }
340 return $infodb_file_path;
341}
342## get_infodb_file_path(string, string) => string ##
343
344
345## @function supportsDatestamp(void) => integer
346#
347sub supportsDatestamp
348{
349 my $self = shift(@_);
350 return $self->{'supports_datestamp'};
351}
352## supportsDatestamp(void) => integer ##
353
354
355## @function supportsMerge(void) => boolean
356#
357sub supportsMerge
358{
359 my $self = shift(@_);
360 return $self->{'supports_merge'};
361}
362## supportsMerge(void) => integer ##
363
364
365## @function supportsPersistentConnection(void) => integer
366#
367sub supportsPersistentConnection
368{
369 my $self = shift(@_);
370 return $self->{'supports_persistentconnection'};
371}
372## supportsPersistentConnection(void) => integer ##
373
374
375## @function supportsRSS(void) => integer
376#
377sub supportsRSS
378{
379 my $self = shift(@_);
380 return $self->{'supports_rss'};
381}
382## supportsRSS(void) => integer ##
383
384
385## @function supportsSet(void) => integer
386#
387# Not all drivers support the notion of set
388#
389sub supportsSet
390{
391 my $self = shift(@_);
392 return $self->{'supports_set'};
393}
394## supportsSet(void) => integer ##
395
396
397sub writeOnly
398{
399 my $self = shift(@_);
400 return $self->{'write_only'};
401}
402## writeOnly() ##
403
404###############################################################################
405## Virtual Functions
406###############################################################################
407
408
409## @function close_infodb_write_handle(*) => void
410#
411sub close_infodb_write_handle
412{
413 my $self = shift(@_);
414 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
415 die("\n");
416}
417## close_infodb_write_handle(*) => void ##
418
419
420## @function delete_infodb_entry(*) => void
421#
422sub delete_infodb_entry
423{
424 my $self = shift(@_);
425 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
426 die("\n");
427}
428## delete_infodb_entry(*) => void ##
429
430
431## @function mergeDatabases(*) => void
432#
433sub mergeDatabases
434{
435 my $self = shift(@_);
436 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
437 die("\n");
438}
439## mergeDatabases(*) => void ##
440
441
442## @function open_infodb_write_handle(*) => void
443#
444sub open_infodb_write_handle
445{
446 my $self = shift(@_);
447 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
448 die("\n");
449}
450## open_infodb_write_handle(*) => void ##
451
452
453## @function set_infodb_entry(*) => void
454#
455sub set_infodb_entry
456{
457 my $self = shift(@_);
458 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
459 die("\n");
460}
461## set_infodb_entry(*) => void ##
462
463
464## @function read_infodb_entry(*) => void
465#
466sub read_infodb_entry
467{
468 my $self = shift(@_);
469 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
470 die("\n");
471}
472## read_infodb_entry(*) => void ##
473
474
475## @function read_infodb_rawentry(*) => string
476#
477sub read_infodb_rawentry
478{
479 my $self = shift(@_);
480 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
481 die("\n");
482}
483## read_infodb_rawentry(*) => string ##
484
485
486## @function read_infodb_file(*) => void
487#
488sub read_infodb_file
489{
490 my $self = shift(@_);
491 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
492 die("\n");
493}
494## read_infodb_file(*) => void ##
495
496
497## @function read_infodb_keys(*) => void
498#
499sub read_infodb_keys
500{
501 my $self = shift(@_);
502 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
503 die("\n");
504}
505## read_infodb_keys(*) => void ##
506
507
508## @function write_infodb_entry(*) => void
509#
510sub write_infodb_entry
511{
512 my $self = shift(@_);
513 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
514 die("\n");
515}
516## write_infodb_entry(*) => void ##
517
518
519## @function write_infodb_rawentry(*) => void
520#
521sub write_infodb_rawentry
522{
523 my $self = shift(@_);
524 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
525 die("\n");
526}
527## write_infodb_rawentry(*) => void ##
528
529
530## @function
531#
532sub mergeDatabases
533{
534 my $self = shift(@_);
535 gsprintf(STDERR, (caller(0))[3] . " {common.must_be_implemented}\n");
536 die("\n");
537}
538## mergeDatabase(string, string) => integer ##
539
5401;
Note: See TracBrowser for help on using the repository browser.