1use warnings;
2use strict;
3
4package Jifty::Plugin::ClassLoader;
5
6=head1 NAME
7
8Jifty::Plugin::ClassLoader - Automatically generates application classes
9
10=head1 DESCRIPTION
11
12C<Jifty::Plugin::ClassLoader> loads additional model and action classes on
13behalf of the application out of the configured plugin classes.  Unlike,
14C<Jifty::ClassLoader>, this class will only autogenerate classes if the
15plugin provides them.  The plugin classes are checked before the base Jifty
16classes, so that a plugin can override the Jifty class, just as any
17existing application classes will be loaded first.
18
19=head2 new
20
21Returns a new ClassLoader object.  Doing this installs a hook into
22C<@INC> that allows L<Jifty::Plugin::ClassLoader> to dynamically create
23needed classes if they do not exist already.  This works because if
24use/require encounters a blessed reference in C<@INC>, it will
25invoke the INC method with the name of the module it is searching
26for on the reference.
27
28Takes two mandatory arguments, C<base>, which should be the
29application's base path; and C<plugin> which is the plugin classname.
30
31=cut
32
33sub new {
34    my $class = shift;
35    my %args = (@_);
36    my @exist = grep {ref $_ eq $class and $_->{base} eq $args{base}} @INC;
37     return $exist[0] if @exist;
38
39
40    my $self = bless {%args}, $class;
41
42    push @INC, $self;
43    return $self;
44}
45
46=head2 INC
47
48The hook that is called when a module has been C<require>'d that
49cannot be found on disk.  The following stub classes are
50auto-generated:
51
52=over
53
54=item I<Application>
55
56An empty application base class is created that doesn't provide any
57methods or inherit from anything.
58
59=item I<Application>::Record
60
61An empty class that descends from L<Jifty::Record> is created.
62
63=item I<Application>::Collection
64
65An empty class that descends from L<Jifty::Collection> is created.
66
67=item I<Application>::Notification
68
69An empty class that descends from L<Jifty::Notification>.
70
71=item I<Application>::Dispatcher
72
73An empty class that descends from L<Jifty::Dispatcher>.
74
75=item I<Application>::Bootstrap
76
77An empty class that descends from L<Jifty::Bootstrap>.
78
79=item I<Application>::Upgrade
80
81An empty class that descends from L<Jifty::Upgrade>.
82
83=item I<Application>::CurrentUser
84
85An empty class that descends from L<Jifty::CurrentUser>.
86
87=item I<Application>::Model::I<Anything>Collection
88
89If C<I<Application>::Model::I<Something>> is a valid model class, then
90it creates a subclass of L<Jifty::Collection> whose C<record_class> is
91C<I<Application>::Model::I<Something>>.
92
93=item I<Application>::Action::(Create or Update or Delete)I<Anything>
94
95If C<I<Application>::Model::I<Something>> is a valid model class, then
96it creates a subclass of L<Jifty::Action::Record::Create>,
97L<Jifty::Action::Record::Update>, or L<Jifty::Action::Record::Delete>
98whose I<record_class> is C<I<Application>::Model::I<Something>>.
99
100=back
101
102=cut
103
104# This subroutine's name is fully qualified, as perl will ignore a 'sub INC'
105sub Jifty::Plugin::ClassLoader::INC {
106    my ( $self, $module ) = @_;
107
108    my $base = $self->{base};
109    my $plugin = $self->{plugin};
110    return undef unless ( $module and $base and $plugin);
111
112
113
114    # Canonicalize $module to :: style rather than / and .pm style;
115    $module =~ s/.pm$//;
116    $module =~ s{/}{::}g;
117
118    # The quick check
119    return undef unless $module =~ m!^$base!;
120
121    # Note that at this point, all of the plugins classes will already be
122    # loaded, so we can just check their presence when deciding whether
123    # this is a class the plugin intends to autocreate
124    if ( $module =~ m{^(?:$base)::CurrentUser$} ) {
125        my $method = "${plugin}::CurrentUser";
126        if ( Jifty::Util->already_required($method) ) {
127            Jifty->log->debug("Implementing $module using $method");
128            $Jifty::ClassLoader::AUTOGENERATED{$module} = 1;
129            return Jifty::ClassLoader->return_class(
130                  "use warnings; use strict; package $module;\n"
131                . "use base qw/$method/;\n"
132                . "1;" )
133        }
134        else {
135            Jifty->log->debug("Couldn't implement $module using $method");
136        }
137    } elsif ( $module =~ m!^(?:$base)::Action::(Create|Update|Delete|Search)([^\.]+)$! ) {
138        my $model = "::Model::" . $2;
139        my $method = $plugin . "::Action::" . $1 . $2;
140
141        # Check to see if this is an action for a model that this plugin
142        # doesn't provide
143        return undef unless Jifty::Util->already_required("$plugin$model");
144
145        if ( Jifty::Util->already_required($method) ) {
146            Jifty->log->debug("Implementing $module using $method");
147            $Jifty::ClassLoader::AUTOGENERATED{$module} = 1;
148            return Jifty::ClassLoader->return_class(
149                  "use warnings; use strict; package $module;\n"
150                . "use base qw/$method/;\n"
151                . "sub record_class { '$base$model' };\n"
152                . "1;" )
153        }
154        else {
155            Jifty->log->debug("Couldn't implement $module using $method");
156        }
157    } elsif ( $module =~ m{^(?:$base)::(Action|Notification)([^\.]+)$} ) {
158        my $method = $plugin . "::" . $1 . $2;
159        if ( Jifty::Util->already_required($method) ) {
160            Jifty->log->debug("Implementing $module using $method");
161            $Jifty::ClassLoader::AUTOGENERATED{$module} = 1;
162            return Jifty::ClassLoader->return_class(
163                  "use warnings; use strict; package $module;\n"
164                . "use base qw/$method/;\n"
165                . "1;" )
166        }
167        else {
168            Jifty->log->debug("Couldn't implement $module using $method");
169        }
170
171    }
172
173    return undef;
174}
175
176=head2 require
177
178Loads all of the application's Actions and Models.  It additionally
179C<require>'s all Collections and Create/Update actions for each Model
180base class -- which will auto-create them using the above code if they
181do not exist on disk.
182
183=cut
184
185sub require {
186    my $self = shift;
187
188    my $base = $self->{plugin};
189
190
191    # if we don't even have an application class, this trick will not work
192    return unless ($base);
193    Jifty::Util->require($base);
194    Jifty::Util->require($base."::CurrentUser");
195
196    Jifty::Module::Pluggable->import(
197        search_path =>
198          [ map { $base . "::" . $_ } 'Model', 'Action', 'Notification' ],
199        require => 1,
200        except  => qr/\.#/,
201        inner   => 0
202    );
203    $self->{models}{$_} = 1 for grep {/^($base)::Model::(.*)$/ and not /Collection$/} $self->plugins;
204    for my $full (keys %{$self->{models}}) {
205        my($short) = $full =~ /::Model::(.*)/;
206        Jifty::Util->require($full . "Collection");
207        Jifty::Util->require($base . "::Action::" . $_ . $short)
208            for qw/Create Update Delete/;
209    }
210}
211
212=head2 DESTROY
213
214When the ClassLoader gets garbage-collected, its entry in @INC needs
215to be removed.
216
217=cut
218
219# The entries in @INC end up having SvTYPE == SVt_RV, but SvRV(sv) ==
220# 0x0 and !SvROK(sv) (!?)  This may be something that perl should cope
221# with more cleanly.
222#
223# We call this explictly in an END block in Jifty.pm, because
224# otherwise the DESTROY block gets called *after* there's already a
225# bogus entry in @INC
226
227# This bug manifests itself as warnings that look like this:
228
229# Use of uninitialized value in require at /tmp/7730 line 9 during global destruction.
230
231
232sub DESTROY {
233    my $self = shift;
234    @INC = grep {!$self} @INC;
235}
236
2371;
238