File Coverage

blib/lib/XAO/Objects.pm
Criterion Covered Total %
statement 77 77 100.0
branch 32 40 80.0
condition 22 35 62.8
subroutine 9 9 100.0
pod 2 2 100.0
total 142 163 87.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XAO::Objects - dynamic objects loader
4              
5             =head1 SYNOPSIS
6              
7             use XAO::Objects;
8              
9             sub foo {
10             ...
11             my $page=XAO::Objects->new(objname => 'Web::Page');
12             }
13              
14             =head1 DESCRIPTION
15              
16             Loader for XAO dynamic objects. This module is most extensively used
17             throughout all XAO utilities and packages.
18              
19             The idea of XAO dynamic objects is to seamlessly allow multiple projects
20             co-exist in the same run-time environment -- for instance multiple web
21             sites in mod_perl environment. Using traditional Perl modules or objects
22             it is impossible to have different implementations of an object in the
23             same namespace -- once one site loads a Some::Object the code is then
24             re-used by all sites executing in the same instance of Apache/mod_perl.
25              
26             The architecture of XAO::Web and XAO::FS requires the ability to load an
27             object by name and at the same time provide a pissibly different
28             functionality for different sites.
29              
30             This is achieved by always loading XAO objects using functions of
31             XAO::Objects package.
32              
33             Have a look at this example:
34              
35             my $dobj=XAO::Objects->new(objname => 'Web::Date');
36              
37             What happens when this code is executed is that in case current site has
38             an extended version of Web::Date object -- this extended version will be
39             returned, otherwise the standard Web::Date is used. This allows for
40             customizations of a standard object specific to a web site without
41             affecting other web sites.
42              
43             For creating an site specific object based on standard object the
44             following syntax should be used:
45              
46             package XAO::DO::Web::MyObject;
47             use strict;
48             use XAO::Objects;
49              
50             use base XAO::Objects->load(objname => 'Web::Page');
51              
52             sub display ($%) {
53             my $self=shift;
54             my $args=get_args(\@_);
55              
56             .....
57             }
58              
59             To extend or alter the functionality of a standard object the following
60             syntax should be used to avoid infinite loop in the object loader:
61              
62             package XAO::DO::Web::Date;
63             use strict;
64             use XAO::Objects;
65              
66             use base XAO::Objects->load(objname => 'Web::Date', baseobj => 1);
67              
68             XAO::Objects is not limited to web site use only, in fact it is used in
69             XAO Foundation server to load database objects, in XAO::Catalogs to load
70             custom catalog filters and so on.
71              
72             =head1 FUNCTIONS
73              
74             The following functions are available. They can be
75             called either as 'XAO::Objects->function()' or as
76             'XAO::Objects::function()'. XAO::Objects never creates objects of its
77             own namespace, so these are functions, not methods.
78              
79             =over
80              
81             =cut
82              
83             ###############################################################################
84             package XAO::Objects;
85 8     8   1228 use strict;
  8         17  
  8         236  
86 8     8   40 use warnings;
  8         16  
  8         273  
87 8     8   42 use feature qw(state);
  8         15  
  8         948  
88 8     8   1016 use XAO::Base qw($homedir $projectsdir);
  8         45  
  8         1043  
89 8     8   80 use XAO::Utils qw(:args :debug);
  8         17  
  8         1032  
90 8     8   55 use XAO::Errors qw(XAO::Objects);
  8         25  
  8         42  
91 8     8   3074 use XAO::Projects;
  8         18  
  8         8034  
92              
93             our $VERSION=(2.001);
94              
95             # Prototypes
96             #
97             sub load (@);
98             sub new ($%);
99              
100             ###############################################################################
101              
102             =item load
103              
104             Pre-loads an object into memory for quicker access and inheritance.
105              
106             On success returns class name of the loaded object, on error --
107             undefined value.
108              
109             It is allowed to call load outside of any site context - it just would
110             not check site specific objects.
111              
112             Arguments:
113              
114             objname => object name (required)
115             baseobj => ignore site specific objects even if they exist (optional)
116             sitename => should only be used to load Config object
117              
118             When called from an established site context that context is checked for
119             an optional configuration value /xao/objects/include that may contain a
120             list of "library" projects that are checked for object implementations.
121              
122             Alternatively, the list of library sites can be given in 'include'
123             argument to load(). This is useful in loading prototypes of Config
124             itself when the configuration is not available yet.
125              
126             Given an objname equal "Foo::Bar" the logic is this:
127              
128             1. If there a current site, or a sitename is given, check that site
129             for objects/Foo/Bar.pm and load if it exists.
130             2. If there is a /xao/objects/include configuration or an 'include'
131             argument, then check that list of sites for their objects/Foo/Bar.pm
132             implementations, returning first found if any.
133             3. Default to the system XAO::DO::Foo::Bar implementation if none
134             are found in site context.
135              
136             If there is a 'baseobj' argument then the first step is skipped and the
137             search is started with included sites defaulting to the system object.
138              
139             =cut
140              
141             sub load (@) {
142 73 50 66 73 1 3446 my $class=(scalar(@_)%2 || ref($_[1]) ? shift(@_) : 'XAO::Objects');
143 73         235 my $args=get_args(\@_);
144              
145 73   33     198 my $objname=$args->{'objname'} ||
146             throw XAO::E::Objects "- no objname given";
147              
148 73         126 my $baseobj=$args->{'baseobj'};
149              
150             # Config object is a special case. When we load it we do not have
151             # site configuration yet and so we have to rely on supplied site
152             # name.
153             #
154 73         247 my $current_sitename=XAO::Projects::get_current_project_name();
155 73   100     411 my $sitename=$args->{'sitename'} || $current_sitename || '';
156              
157 73 100       185 if($objname eq 'Config') {
158 12 50 66     94 $baseobj || $sitename ||
159             throw XAO::E::Objects "- no sitename given for Config object";
160             }
161              
162             # Checking the cache first
163             #
164 73         122 state %objref_cache;
165              
166 73 100       276 my $cache_key=($baseobj ? '^' : '') . $sitename . '/' . $objname;
167 73         153 my $objref=$objref_cache{$cache_key};
168              
169 73 100       3262 return $objref if $objref;
170              
171             # There might be an inheritance chain configured for library
172             # projects.
173             #
174 38         59 my @siteinc;
175 38 100 100     170 if($sitename && !$baseobj) {
176 25         71 push(@siteinc,$sitename);
177             }
178              
179 38 100       89 if($args->{'include'}) {
180 4 50       28 push(@siteinc,ref $args->{'include'} ? @{$args->{'include'}} : $args->{'include'});
  4         23  
181             }
182              
183 38 100 100     134 if($current_sitename && $current_sitename eq $sitename) {
184 23         58 my $config=XAO::Projects::get_current_project();
185 23 100 66     116 if($config && $config->is_embedded('hash') && (my $include=$config->get('/xao/objects/include'))) {
      100        
186 11         35 push(@siteinc,@$include);
187             }
188             }
189              
190             ### dprint "----$sitename:$objname: SITEINC: (".join('|',@siteinc).")";
191              
192 38 100       89 if(@siteinc) {
193 31         95 (my $objfile=$objname) =~ s/::/\//sg;
194 31         66 $objfile.='.pm';
195              
196 31         85 foreach my $sn (@siteinc) {
197 40         137 my $objpath="$projectsdir/$sn/objects/$objfile";
198              
199 40 100       1014 next unless -f $objpath;
200              
201             ### dprint "----$sitename:$objname: Have $objname in $objpath";
202              
203             # %INC has package names converted to file notation.
204             # In our case there is no real file for
205             # XAO/DO/sitename/Foo.pm, but the convention is
206             # still kept.
207             #
208 22         104 my $pkg="XAO::DO::${sn}::${objname}";
209 22         126 (my $pkgfile=$pkg)=~s/::/\//sg;
210 22         51 $pkgfile.='.pm';
211              
212             # It is possible we already loaded this before
213             #
214 22 100       88 if(!$INC{$pkgfile}) {
215 21 50       710 open(F,$objpath) ||
216             throw XAO::E::Objects "- unable to open $objpath: $!";
217              
218             # Changing $/ can affect module initialization below, so
219             # making it in as small scope as possible (bug fix by
220             # Eugene Karpachov).
221             #
222 21         81 my $text=do { local $/; };
  21         122  
  21         715  
223              
224 21         222 close(F);
225              
226 21         830 $text=~s{^\s*(package\s+)XAO::DO::$objname(\s*;)}
227 21 50       98 {$1$pkg$2}m;
228             $1 ||
229             throw XAO::E::Objects "- package name is not XAO::DO::$objname in $objpath";
230              
231             ### dprint "----$sitename:$objname: (((".($text=~/^(.*?)\n/s ? $1 : '').")))";
232 21         8058  
233             eval "\n#line 1 \"$objpath\"\n" . $text;
234 21 50       1485  
235             !$@ ||
236             throw XAO::E::Objects "- error loading $objname ($objpath) -- $@";
237 21         115  
238             $INC{$pkgfile}=$objpath;
239              
240             ### dprint "----$sitename:$objname: INC{$pkgfile}=",$INC{$pkgfile};
241             ### if(1) {
242             ### no strict 'refs';
243             ### my $scope=$pkg.'::';
244             ### foreach my $k (sort keys %$scope) {
245             ### dprint "------${scope}{$k}=$scope->{$k}";
246             ### }
247             ### }
248             }
249 22         41  
250             $objref=$pkg;
251 22         41  
252             last;
253             }
254             }
255              
256             # System installed package is the default
257 38 100       106 #
258 16         45 if(! $objref) {
259 16         1606 $objref="XAO::DO::${objname}";
260 16 100       470 eval "require $objref";
261             !$@ ||
262             throw XAO::E::Objects "- error loading $objname ($objref) -- $@";
263             }
264              
265             # In case no object was found.
266             #
267 37 50       86 $objref ||
268             throw XAO::E::Objects "- no object file found for sitename='$sitename', objname='$objname'";
269              
270             ### dprint "----$sitename:$objname: ============ load($objname) cache{$cache_key}=$objref";
271              
272             # Returning class name and storing into cache
273 37         4020 #
274             return $objref_cache{$cache_key}=$objref;
275             }
276              
277             ###############################################################################
278              
279             =item new (%)
280              
281             Creates an instance of named object. There is just one required
282             argument - 'objname', everything else is passed into object's
283             constructor unmodified.
284              
285             =cut
286              
287 48 50 33 48 1 13581 sub new ($%) {
288 48         351 my $class=(scalar(@_)%2 || ref($_[1]) ? shift(@_) : 'XAO::Objects');
289             my $args=get_args(\@_);
290 48   33     155  
291             my $objname=$args->{'objname'} ||
292             throw XAO::E::Objects "- no 'objname' given";
293              
294             # Looking up what is real object reference for that objname.
295 48   33     198 #
296             my $objref=$class->load($args) ||
297             throw XAO::E::Objects "- can't load object ($objname)";
298              
299             # Creating instance of that object
300 47   33     421 #
301             my $obj=$objref->new($args) ||
302             throw XAO::E::Objects "- error creating instance of $objref ($@)";
303 47         208  
304             return $obj;
305             }
306              
307             ###############################################################################
308              
309             1;
310             __END__