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   1145 use strict;
  8         18  
  8         228  
86 8     8   37 use warnings;
  8         15  
  8         297  
87 8     8   48 use feature qw(state);
  8         16  
  8         878  
88 8     8   870 use XAO::Base qw($homedir $projectsdir);
  8         46  
  8         1085  
89 8     8   74 use XAO::Utils qw(:args :debug);
  8         26  
  8         1000  
90 8     8   53 use XAO::Errors qw(XAO::Objects);
  8         17  
  8         41  
91 8     8   3236 use XAO::Projects;
  8         21  
  8         7604  
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 66 50 66 66 1 3155 my $class=(scalar(@_)%2 || ref($_[1]) ? shift(@_) : 'XAO::Objects');
143 66         192 my $args=get_args(\@_);
144              
145 66   33     166 my $objname=$args->{'objname'} ||
146             throw XAO::E::Objects "- no objname given";
147              
148 66         109 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 66         232 my $current_sitename=XAO::Projects::get_current_project_name();
155 66   100     351 my $sitename=$args->{'sitename'} || $current_sitename || '';
156              
157 66 100       171 if($objname eq 'Config') {
158 12 50 66     73 $baseobj || $sitename ||
159             throw XAO::E::Objects "- no sitename given for Config object";
160             }
161              
162             # Checking the cache first
163             #
164 66         92 state %objref_cache;
165              
166 66 100       263 my $cache_key=($baseobj ? '^' : '') . $sitename . '/' . $objname;
167 66         175 my $objref=$objref_cache{$cache_key};
168              
169 66 100       2852 return $objref if $objref;
170              
171             # There might be an inheritance chain configured for library
172             # projects.
173             #
174 37         57 my @siteinc;
175 37 100 100     191 if($sitename && !$baseobj) {
176 24         60 push(@siteinc,$sitename);
177             }
178              
179 37 100       80 if($args->{'include'}) {
180 4 50       22 push(@siteinc,ref $args->{'include'} ? @{$args->{'include'}} : $args->{'include'});
  4         18  
181             }
182              
183 37 100 100     141 if($current_sitename && $current_sitename eq $sitename) {
184 22         60 my $config=XAO::Projects::get_current_project();
185 22 100 66     135 if($config && $config->is_embedded('hash') && (my $include=$config->get('/xao/objects/include'))) {
      100        
186 11         25 push(@siteinc,@$include);
187             }
188             }
189              
190             ### dprint "----$sitename:$objname: SITEINC: (".join('|',@siteinc).")";
191              
192 37 100       90 if(@siteinc) {
193 30         104 (my $objfile=$objname) =~ s/::/\//sg;
194 30         61 $objfile.='.pm';
195              
196 30         78 foreach my $sn (@siteinc) {
197 39         130 my $objpath="$projectsdir/$sn/objects/$objfile";
198              
199 39 100       871 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         105 my $pkg="XAO::DO::${sn}::${objname}";
209 22         120 (my $pkgfile=$pkg)=~s/::/\//sg;
210 22         49 $pkgfile.='.pm';
211              
212             # It is possible we already loaded this before
213             #
214 22 100       73 if(!$INC{$pkgfile}) {
215 21 50       722 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         72 my $text=do { local $/; };
  21         122  
  21         708  
223              
224 21         211 close(F);
225              
226 21         800 $text=~s{^\s*(package\s+)XAO::DO::$objname(\s*;)}
227 21 50       85 {$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         7577  
233             eval "\n#line 1 \"$objpath\"\n" . $text;
234 21 50       1329  
235             !$@ ||
236             throw XAO::E::Objects "- error loading $objname ($objpath) -- $@";
237 21         134  
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         39  
250             $objref=$pkg;
251 22         47  
252             last;
253             }
254             }
255              
256             # System installed package is the default
257 37 100       98 #
258 15         41 if(! $objref) {
259 15         1289 $objref="XAO::DO::${objname}";
260 15 100       451 eval "require $objref";
261             !$@ ||
262             throw XAO::E::Objects "- error loading $objname ($objref) -- $@";
263             }
264              
265             # In case no object was found.
266             #
267 36 50       98 $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 36         4071 #
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 42 50 33 42 1 12912 sub new ($%) {
288 42         253 my $class=(scalar(@_)%2 || ref($_[1]) ? shift(@_) : 'XAO::Objects');
289             my $args=get_args(\@_);
290 42   33     131  
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 42   33     174 #
296             my $objref=$class->load($args) ||
297             throw XAO::E::Objects "- can't load object ($objname)";
298              
299             # Creating instance of that object
300 41   33     314 #
301             my $obj=$objref->new($args) ||
302             throw XAO::E::Objects "- error creating instance of $objref ($@)";
303 41         188  
304             return $obj;
305             }
306              
307             ###############################################################################
308              
309             1;
310             __END__