#!/usr/bin/perl # # $Header: /Users/claude/fuzz/lib/Genezzo/Havok/RCS/SysHook.pm,v 7.14 2007/11/20 07:41:42 claude Exp claude $ # # copyright (c) 2005-2007 Jeffrey I Cohen, all rights reserved, worldwide # # package Genezzo::Havok::SysHook; use Genezzo::Util; use Genezzo::Dict; use strict; use warnings; use warnings::register; use Carp; our $VERSION; our $Got_Hooks; # set to 1 after all hooks get loaded our %SysHookOriginal; # save original value of all hooks for posterity our %ReqObjList; # Object-Oriented Require our %ReqObjMethod; # Object-Oriented Meth our $MAKEDEPS; BEGIN { $VERSION = do { my @r = (q$Revision: 7.14 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker $Got_Hooks = 0; my $pak1 = __PACKAGE__; $MAKEDEPS = { 'NAME' => $pak1, 'ABSTRACT' => ' ', 'AUTHOR' => 'Jeffrey I Cohen (jcohen@cpan.org)', 'LICENSE' => 'gpl', 'VERSION' => $VERSION, # 'UPDATED' => Genezzo::Dict::time_iso8601() }; # end makedeps $MAKEDEPS->{'PREREQ_HAVOK'} = { 'Genezzo::Havok' => '0.0', 'Genezzo::Havok::Utils' => '0.0', # for userfunctions }; # DML is an array, not a hash my $now = do { my @r = (q$Date: 2007/11/20 07:41:42 $ =~ m|Date:(\s+)(\d+)/(\d+)/(\d+)(\s+)(\d+):(\d+):(\d+)|); sprintf ("%04d-%02d-%02dT%02d:%02d:%02d", $r[1],$r[2],$r[3],$r[5],$r[6],$r[7]); }; my $dml = [ # "i havok 4 $pak1 SYSTEM $now 0 $VERSION" ]; my %tabdefs = ('sys_hook' => { create_table => 'xid=n pkg=c hook=c replace=c xtype=c xname=c args=c owner=c creationdate=c version=c', dml => $dml } ); $MAKEDEPS->{'TABLEDEFS'} = \%tabdefs; my @sql_funcs = qw( add_sys_hook ); my @ins1; my $ccnt = 1; for my $pfunc (@sql_funcs) { my %attr = (module => $pak1, function => "sql_func_" . $pfunc, creationdate => $now, argstyle => 'HASH', sqlname => $pfunc); my @attr_list; while ( my ($kk, $vv) = each (%attr)) { push @attr_list, '\'' . $kk . '=' . $vv . '\''; } my $bigstr = "select add_user_function(" . join(", ", @attr_list) . ") from dual"; push @ins1, $bigstr; $ccnt++; } # add help for all functions push @ins1, "select add_help(\'$pak1\') from dual"; # register havok module push @ins1, "select register_havok_package(" . "\'modname=" . $pak1 . "\', ". "\'creationdate=" . $now . "\', ". "\'version=" . $VERSION . "\'". ") from dual"; # XXX XXX: NOTE: check is for install, which is after create_table/dml $MAKEDEPS->{'DML'} = [ { check => [ "select * from user_functions where xname = \'$pak1\'" ], install => \@ins1 } ]; # print Data::Dumper->Dump([$MAKEDEPS]); } our $GZERR = sub { my %args = (@_); return unless (exists($args{msg})); if (exists($args{self})) { my $self = $args{self}; if (defined($self) && exists($self->{GZERR})) { my $err_cb = $self->{GZERR}; return &$err_cb(%args); } } carp $args{msg} if warnings::enabled(); }; sub MakeYML { use Genezzo::Havok; my $makedp = $MAKEDEPS; # $makedp->{'UPDATED'} = Genezzo::Dict::time_iso8601(); return Genezzo::Havok::MakeYML($makedp); } # XXX XXX: Note: This method and the associated SQL script are # deprecated, since all the work is done in HavokUse sub MakeSQL { my $bigSQL; ($bigSQL = <. #Use the help command: # # select add_sys_hook('help') from dual; # #to list the valid parameters # # # EOF_HELP my $msg = $bigHelp; return $msg; } # end getpod sub _build_sql_for_sys_hook { my %required = ( xid => "no xid!", pkg => "no package!", hook => "no hook!", replace => "no replace!", xname => "no xname!", args => "no args!" ); my $now = Genezzo::Dict::time_iso8601(); my %optional = ( xtype => "require", creationdate => $now, owner => "SYSTEM", version => 0 ); my %args = ( %optional, @_); # synonyms $args{xname} = $args{module} if (exists($args{module})); $args{args} = $args{function} if (exists($args{function})); $args{pkg} = $args{package} if (exists($args{package})); return undef unless (Validate(\%args, \%required)); my $pattern = "\'%s\', " x 9; $pattern .= "\'%s\' "; my $bigstr = "insert into sys_hook values (" . sprintf($pattern, $args{xid}, $args{pkg}, $args{hook}, $args{replace}, $args{xtype}, $args{xname}, $args{args}, $args{owner}, $args{creationdate}, $args{version}); $bigstr .= ")"; return $bigstr; } sub sql_func_add_sys_hook { my %args= @_; my $dict = $args{dict}; my $dbh = $args{dbh}; my $fn_args = $args{function_args}; # print Data::Dumper->Dump($fn_args); my $now = Genezzo::Dict::time_iso8601(); # list the optional values my %nargs = ( xtype => "require", creationdate => $now, owner => "SYSTEM", version => 0, dict => $dict ); my $do_help = 0; $do_help = 1 unless (scalar(@{$fn_args})); my $valid = 'xid|pkg|hook|replace|xtype|xname|args|owner|creationdate|version'; $valid .= '|module|function|package'; # additional synonyms for my $argi (@{$fn_args}) { # separate key=val pairs into hash args my @foo; @foo = ($argi =~ m/^(\s*\w+\s*\=\s*)(.*)(\s*)$/) if ($argi =~ m/\w+\s*\=/); if ($argi =~ m/^\s*($valid)\s*\=/i) { my $nargtype = $foo[0]; # remove the spaces and equals ("="); $nargtype =~ s/\s//g; $nargtype =~ s/\=//g; $nargtype = 'xname' if ($nargtype =~ m/^module/i); $nargtype = 'args' if ($nargtype =~ m/^function/i); $nargtype = 'pkg' if ($nargtype =~ m/^package/i); $nargs{lc($nargtype)} = $foo[1]; } else { if (scalar(@{$fn_args}) == 1) { if ($argi =~ m/^help$/i) { $do_help = 1; last; } } # end if 1 arg } } # end for if ($do_help) { my $outi = "Valid arguments are:\n "; $outi .= join(" ",split(/\|/, $valid)) . "\n"; my $bigexample; ($bigexample = <DictTableGetTable (tname => "sys_hook") ; my $tv = tied(%{$hashi}); $nargs{xid} = $dict->DictGetNextVal(tname => "sys_hook", col => "xid", tieval => $tv); } my $bigstr = _build_sql_for_sys_hook(%nargs); return 0 unless(defined($bigstr)); my $sth = $dbh->prepare($bigstr); return 0 unless ($sth); # insert the function definition in the user_function table return 0 unless ($sth->execute()); # load the hook return Genezzo::Havok::SysHook::LoadSysHook(%nargs); } sub LoadSysHook { my %optional; my %required = ( xid => "no xid!", xtype => "no xtype!", xname => "no xname!", owner => "no owner!", creationdate => "no creationdate!", args => "no args!", pkg => "no pkg!", hook => "no hook!", replace => "no replace!", dict => "no dictionary!" ); my %args = (%optional, @_); return 0 unless (Validate(\%args, \%required)); my $xid = $args{xid}; my $xtype = $args{xtype}; my $xname = $args{xname}; my $owner = $args{owner}; my $dat = $args{creationdate}; my $xargs = $args{args}; my $xpkg = $args{pkg}; my $hook = $args{hook}; my $repl = $args{replace}; my $dict = $args{dict}; my $stat = 1; my $save_previous_hook; # block 1 { my $mainf = $xpkg . "::" . $hook; my @varlist; # list of variables to hold previous value of coderef if (defined($repl) && length($repl)) { # build name of variable to hold previous value of # hook coderef $repl = $xname . "::" . $repl; # scope for require package push @varlist, $repl; } # have we seen this hook before? unless (exists($SysHookOriginal{"$mainf"})) { # create a placeholder, even for non-existant # functions. Then we know to "undef" them if we # re-initialize... $SysHookOriginal{"$mainf"} = undef; # save the original value for a hook variable if necessary my $orig_var = 'SysHookOriginal{"'. $mainf . '"}'; push @varlist, $orig_var; } if (scalar(@varlist)) { # we have a hook that needs saving... $save_previous_hook = ""; # save to modules "replace" var and SysHookOriginal # if necessary for my $varname (@varlist) { $save_previous_hook .= '$' . $varname . ' = \&' . $mainf . ' if defined(&' . $mainf . ');'; } greet $save_previous_hook; } else { # do nothing $save_previous_hook = undef; } } # end block 1 if ($xtype =~ m/^(oo_require|require)$/i) { my $req_str = "require $xname"; eval "$save_previous_hook" if (defined($save_previous_hook)); greet $req_str; unless (eval $req_str) { my %earg = (#self => $self, msg => "no such package - $xname - for table sys_hook, row $xid"); &$GZERR(%earg) if (defined($GZERR)); # next; return 0; } # check if package has GZERR function, and redefine it to use # our version (since our version might get redefined to point # to parent routine). my $gz_err_var = $xname . "::GZERR"; my $use_gzerr; my $s1 = "\$use_gzerr = defined(\$$gz_err_var);"; eval "$s1"; greet $s1, $use_gzerr; # XXX XXX: check for existance of "args" function... no strict 'refs'; no warnings 'redefine'; my @inargs; if ($xargs =~ m/\s/) { @inargs = split(/\s/, $xargs); } else { push @inargs, $xargs; } if ($xtype =~ m/^(oo_require)$/i) { # Object-Oriented Require unless (exists($ReqObjList{"$xname"})) { whisper "init object for package $xname"; my $obj; my $initstr = '$obj = ' . $xname ; $initstr .= "->" . 'SysHookInit($dict)'; whisper "$initstr"; eval " $initstr " ; if ($@) { my %earg = (#self => $self, msg => "$@\nbad pkg init : $initstr"); &$GZERR(%earg) if (defined($GZERR)); $stat = 0; } # create an entry even if the init fails $ReqObjList{"$xname"} = $obj; } } my $obj1; for my $fname (@inargs) { # Note: add functions to specified namespace... my $mainf = $xpkg . "::" . $hook; my $packf = $xname . "::" . $fname; my $func = "sub " . $mainf ; if (($xtype =~ m/^(oo_require)$/i) && exists($ReqObjList{"$xname"}) && defined($ReqObjList{"$xname"})) { $obj1 = $ReqObjList{"$xname"}; #$ReqObjMethod{$packf} = sub { $obj1->$packf(@_) }; #$func .= '{ return $ReqObjMethod{' . $packf . '}->(@_); }'; # $func .= '{ my $mref = sub { $obj1->$packf(@_) };'; # $func .= ' return $mref->(@_); }'; # lots of work to avoid 'Variable "$mref" may be # unavailable...' $func .= '{ my $mref = ' . 'sub { $Genezzo::Havok::SysHook::ReqObjList{"' . $xname .'"}->' . $packf . '(@_) };'; $func .= ' return $mref->(@_); }'; # $mref = sub { $obj1->$packf(@_) }; # $func .= '{ return $mref->(@_); }'; } else { $func .= "{ return " . $packf . '(@_); }'; } whisper $func; # eval {$func } ; eval " $func " ; if ($@) { my %earg = (#self => $self, msg => "$@\nbad function : $func"); &$GZERR(%earg) if (defined($GZERR)); $stat = 0; } } } # end if $xtype =~ (oo_require|require) elsif ($xtype =~ m/^function$/i) { my $doublecolon = "::"; # XXX XXX: what about hook name? what should it mean? unless ($xname =~ m/$doublecolon/) { # Note: add functions to namespace... $xname = $xpkg . "::" . $xname; } my $func = "sub " . $xname . " " . $xargs; # whisper $func; # eval {$func } ; eval " $func " ; if ($@) { my %earg = (#self => $self, msg => "$@\nbad function : $func"); &$GZERR(%earg) if (defined($GZERR)); $stat = 0; } } # end if xtpe =~ function else { my %earg = (#self => $self, msg => "unknown user extension - $xtype"); &$GZERR(%earg) if (defined($GZERR)); $stat = 0; } return $stat; } # end loadsyshook sub HavokInit { # whoami; my %optional = (phase => "init"); my %required = (dict => "no dictionary!", flag => "no flag" ); my %args = (%optional, @_); # my @stat; push @stat, 0, $args{flag}; # whoami (%args); return @stat unless (Validate(\%args, \%required)); if ($Got_Hooks) { # XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX # don't load hooks twice to avoid circular links!! Is it # sufficient to call the first entry for each hook and reset # the hook to its "replace" var, i.e for dicthook1, set # dicthook1 = &Howdy_Hook ? Or use SysHookOriginal hash? # XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX $stat[0] = 1; # ok! return @stat; } my $dict = $args{dict}; my $phase = $args{phase}; return @stat unless ($dict->DictTableExists(tname => "sys_hook", silent_notexists => 1)); my $hashi = $dict->DictTableGetTable (tname => "sys_hook") ; return @stat # no User Extensions unless (defined ($hashi)); my $tv = tied(%{$hashi}); while ( my ($kk, $vv) = each ( %{$hashi})) { my $getcol = $dict->_get_col_hash("sys_hook"); my $xid = $vv->[$getcol->{xid}]; my $xtype = $vv->[$getcol->{xtype}]; my $xname = $vv->[$getcol->{xname}]; my $owner = $vv->[$getcol->{owner}]; my $dat = $vv->[$getcol->{creationdate}]; my $xargs = $vv->[$getcol->{args}]; my $xpkg = $vv->[$getcol->{pkg}]; my $hook = $vv->[$getcol->{hook}]; my $repl = $vv->[$getcol->{replace}]; # greet $vv; my $lstat = LoadSysHook( xid => $xid, xtype => $xtype, xname => $xname, owner => $owner, creationdate => $dat, args => $xargs, pkg => $xpkg, hook => $hook, replace => $repl, dict => $dict ); } # end while $Got_Hooks = 1; greet %SysHookOriginal; $stat[0] = 1; # ok! return @stat; } sub HavokCleanup { # whoami; return HavokInit(@_, phase => "cleanup"); } END { } # module clean-up code here (global destructor) ## YOUR CODE GOES HERE 1; # don't forget to return a true value from the file __END__ # Below is stub documentation for your module. You better edit it! =head1 NAME Genezzo::Havok::SysHook - load the SysHook table =head1 SYNOPSIS # don't say "use Genezzo::Havok::SysHook". Update the # dictionary havok table: select HavokUse('Genezzo::Havok::SysHook') from dual; =head1 DESCRIPTION Basic Havok module - load the SysHook table create table sys_hook ( xid number, pkg char, hook char, replace char, xtype char, xname char, args char, owner char, creationdate char, version char ); =over 4 =item xid - a unique id number =item pkg - name of package for this hook =item hook - name of hook function =item replace - unique name for previous hook coderef. If blank or null, just replace existing hook, otherwise is variable name for previous version of the hook, and may get called from new hook =item xtype - the string "require" or "function" =item xname - if xtype = "require", then xname is a package name, like "Text::Soundex". if xtype = "function", xname is a function name. A function name may be qualified with a package. =item args - if xtype = "require", an (optional) blank-separated list of functions to import to the default Genezzo namespace. if xtype = "function", supply an actual function body in curly braces. =item owner - owner of the package or function =item creationdate - date row was created =back =head2 Example: insert into sys_hook values (1, 'Genezzo::Dict', 'dicthook1', 'Howdy_Hook', 'require', 'Genezzo::Havok::Examples', 'Howdy', 'SYSTEM', '2004-09-21T12:12'); The row causes SysHook to "require Genezzo::Havok::Examples", and calls the "Howdy" function from the hook function "dicthook1" in the package Genezzo::Dict. The previous coderef for the function "dicthook1" (if it exists) is assigned to $Genezzo::Havok::Examples::Howdy_Hook. The Howdy function can call &$Howdy_Hook() to activate the original "dicthook1" function. =head1 ARGUMENTS =head1 FUNCTIONS =over 4 =back =head1 RISKS Replacing system functions in an operational database has approximately the same level of risk exposure as running with the bulls at Pamplona with your pants around your ankles. Which is to say, "somewhat foolhardy". =head2 EXPORT =over 4 =back =head1 LIMITATIONS =head1 TODO =over 4 =item should be able to dynamically create hook vars, versus using existing "our" vars. =item should we do something smart on dictionary shutdown, like unload hooks? Or have a clever way to re-init and reload a hook? =back =head1 AUTHOR Jeffrey I. Cohen, jcohen@genezzo.com =head1 SEE ALSO L. Copyright (c) 2005-2007 Jeffrey I Cohen. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Address bug reports and comments to: jcohen@genezzo.com For more information, please visit the Genezzo homepage at L =cut