Apache-Voodoo

 view release on metacpan or  search on metacpan

lib/Apache/Voodoo/Loader/Dynamic.pm  view on Meta::CPAN

package Apache::Voodoo::Loader::Dynamic;

$VERSION = "3.0200";

use strict;
use warnings;

use base("Apache::Voodoo::Loader");

sub new {
	my $class = shift;

	my $self = {};
	bless $self,$class;

	$self->{'module'} = shift;

	$self->{'bootstrapping'} = 1;
	$self->refresh;
	$self->{'bootstrapping'} = 0;

	$self->{'parents'} = {};
	foreach (eval '@{'.$self->{'module'}.'::ISA}') {
		$self->{'parents'}->{$_} = $self->get_mtime($_);
	}

	return $self;
}

sub init {
	my $self = shift;

	$self->{'config'} = \@_;
	$self->{'object'}->init(@_);
}

sub get_mtime {
	my $self = shift;
	my $file = shift || $self->{'module'};

	$file =~ s/::/\//go;
	$file .= ".pm";

	return 0 unless defined($INC{$file});

	my $mtime = (stat($INC{$file}))[9];

	return $mtime;
}

sub refresh {
	my $self = shift;

	$self->{'object'} = $self->load_module;
	$self->{'mtime'}  = $self->get_mtime;

	# zap our created closures.
	foreach my $method (keys %{$self->{'provides'}}) {
		# a little help from the Cookbook 10.14
		no strict 'refs';
		no warnings 'redefine';
		*$method = undef;
	}
	$self->{'provides'} = {};
}

#
# Override the built in 'can' to allow:
#   a) trigger dynamically reloading the module as needed
#   b) dynamically create closures to link Apache::Voodoo::Handler with the controllers
#
sub can {
	my $self   = shift;
	my $method = shift;
	my $nosub  = shift;

	# find out if this thing has changed
	if ($self->{'mtime'} != $self->get_mtime) {
		$self->refresh;
		$self->{'object'}->init(@{$self->{'config'}});
	}

	if (defined $self->{'provides'}->{$method}) {
		return 1;
	}
	elsif ($self->{'object'}->isa("Apache::Voodoo::Zombie") || $self->{'object'}->can($method)) {
		# Either we have a dead module and we map whatever was requested or
		# we have a live one and has the requested method.

		# cache the existance of this method
		$self->{'provides'}->{$method} = 1;

		# If we used the autoloader to get here, then we want to keep using
		# it. Bypass the creation of the closure.
		unless ($nosub) {
			# create a closeure for this method (a little help from the Cookbook 10.14)
			no strict 'refs';
			no warnings 'redefine';
			*$method = sub { my $self = shift; return $self->_handle($method,@_); };
		}
		return 1;
	}

	return 0;
}

#
# In scnearios where the caller doesn't know that can has been overloaded, we'll use
# autoload to catch it and call our overloaded can.  We unfortunately end up with two
# different ways to do a very similar task because the constraints are slightly different.
# We want the calls from the A::V::Handler to the controllers to be aware of what methods
# actually exist so it can either call them or not.  The controllers talking to the models
# shouldn't have to do anything special or even be aware that they're talking to this
# proxy object, thus the need for a autoload variation.
#
sub AUTOLOAD {
	next unless ref($_[0]);

	our $AUTOLOAD;
	my $method = $AUTOLOAD;
	$method =~ s/.*:://;

	my $self = shift;

	if ($self->can($method,'1')) {
		return $self->_handle($method,@_);
	}

	# we don't handle this one
	next;
}

# now we need a stub for destroy to keep autoloader happy.
sub DESTROY { }

sub _handle {
	my $self = shift;
	my $method = shift;
	my @params = @_;

	# check parent modules for change
	foreach my $module (eval '@{'.$self->{'module'}.'::ISA}') {
		my $t = $self->get_mtime($module);
		if ($self->{'parents'}->{$module} != $t) {
			$self->{'parents'}->{$module} = $t;

			my $file = $module;
			$file =~ s/::/\//go;
			$file .= ".pm";

			no warnings 'redefine';
			delete $INC{$file};
			eval {
				no warnings 'redefine';
				require $file;
			};



( run in 0.645 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )