root/branches/feature-narrow-tables/lib/MT/Meta/Proxy.pm @ 1836

Revision 1836, 12.9 kB (checked in by bchoate, 20 months ago)

Updates to support expressing meta columns in 'column_defs' structure.
Changes to registry lookup to support handler declarations.
Added meta type aliases for MT type names ('string', 'integer', etc.). BugId:68749

  • Property svn:executable set to *
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id: Proxy.pm 71506 2008-01-18 23:13:43Z ykerherve $
6
7package MT::Meta::Proxy;
8use strict;
9use warnings;
10
11use MT::Meta;
12use MT::Serialize;
13
14our $HAS_ZLIB = 0;
15eval "require Compress::Zlib;";
16$HAS_ZLIB = 1 unless $@;
17
18my $serializer = MT::Serialize->new('MT');
19
20sub new {
21    my $class = shift;
22    my($obj)  = @_;
23    my $proxy = bless { pkg => ref($obj) }, $class;
24    $proxy->set_primary_keys($obj) if $obj->has_primary_key;
25    $proxy;
26}
27
28sub is_changed {
29    my $proxy = shift;
30    my($col) = @_;
31    return unless $proxy->{__objects}; ## don't remove this line
32                                       ## see below. we should probably change this idiom
33
34    if ($col) {
35        return 0 unless exists $proxy->{__objects}{$col};
36        my $pkg  = $proxy->{pkg};
37        my $meta = $proxy->{__objects}{$col};
38        my $field = MT::Meta->metadata_by_name($pkg, $col)
39            or return 0;
40        my $type = $field->{type}
41            or return 0;
42        return $meta->is_changed($type);
43    } else {
44        foreach my $field (keys %{ $proxy->{__objects} } ) {
45            next if $field eq '';
46            return 1 if $proxy->is_changed($field);
47        }
48        return;
49    }
50}
51
52sub exists_meta {
53    my $proxy = shift;
54    my($col)  = @_;
55
56    $proxy->lazy_load_objects;
57    return exists $proxy->{__objects}->{$col};
58}
59
60sub get {
61    my $proxy = shift;
62    my ($col) = @_;
63
64    $proxy->lazy_load_objects;
65
66    if (exists $proxy->{__objects}->{$col}) {
67        my $pkg  = $proxy->{pkg};
68        my $meta = $proxy->{__objects}->{$col};
69
70        my $field = MT::Meta->metadata_by_name($pkg, $col)
71            or Carp::croak("Metadata $col on $pkg not found.");
72        my $type = $field->{type}
73            or Carp::croak("$col not found on $pkg meta fields");
74
75        unless ($meta->has_column($type)) {
76            Carp::croak("something is wrong: $type not in column_values of metadata");
77        }
78        return $meta->$type;
79    } else {
80        ## no metadata row in the database ... return undef, not ''
81        return undef;
82    }
83}
84
85sub get_hash {
86    my $proxy = shift;
87    my ($col) = @_;
88
89    $proxy->lazy_load_objects;
90
91    my $collection = {};
92
93    foreach my $name (keys %{ $proxy->{__objects} }) {
94        $collection->{$name} = $proxy->get($name);
95    }
96
97    return $collection;
98}
99
100sub get_collection {
101    my $proxy = shift;
102    my ($col) = @_;
103
104    $proxy->lazy_load_objects;
105
106    my $collection = {};
107
108    foreach my $name (keys %{ $proxy->{__objects} }) {
109        if ($name =~ m/^\Q$col\E\.(.+)$/) {
110            my $suffix = $1;
111            $collection->{$suffix} = $proxy->get($name);
112        }
113    }
114
115    return $collection;
116}
117
118sub meta_pkg {
119    my $proxy = shift;
120    return $proxy->{pkg}->meta_pkg;
121}
122
123sub create_meta_object {
124    my $proxy = shift;
125    my($col, $value) = @_;
126
127    my $pkg = $proxy->{pkg};
128    my $meta = $proxy->meta_pkg->new;
129
130    my $field = MT::Meta->metadata_by_name($pkg, $col)
131        or Carp::croak("there's no field $col on $pkg");
132
133    my $type_id = $field->{type_id}
134        or Carp::croak("no type_id for $col");
135    my $id = $field->{id};
136    my $type = $MT::Meta::Types{$type_id};
137
138    $meta->type($col);
139    $meta->$type($value);
140
141    $meta;
142}
143
144sub set {
145    my $proxy = shift;
146    my ($col, $value) = @_;
147
148    # xxx When you update the metadata, you have to preserve the
149    # original data as well. This should be eliminated by adding the
150    # update optimization for metadata columns
151    $proxy->lazy_load_objects;
152
153    $proxy->{__objects}->{$col} = $proxy->create_meta_object($col, $value);
154    $proxy->get($col);
155}
156
157sub save {
158    my $proxy = shift;
159
160    # perl funkiness ... keys %{ $proxy->{__objects} } will automatically clobber
161    # empty hash reference on that key!
162    return unless $proxy->{__objects};
163
164    foreach my $field (keys %{ $proxy->{__objects} } ) {
165        next if $field eq '';
166        next unless $proxy->is_changed($field);
167        my $meta_obj = $proxy->{__objects}->{$field};
168
169        ## primary key from core object
170        foreach my $pkey (keys %{ $proxy->{__pkeys} } ) {
171            my $pval = $proxy->{__pkeys}->{$pkey};
172            $meta_obj->$pkey($pval);
173        }
174
175        my $pkg = $proxy->{pkg};
176        my $meta = MT::Meta->metadata_by_name($pkg, $field)
177            or Carp::croak("Metadata $field on $pkg not found.");
178        my $type = $meta->{type};
179        my $meta_is_blob = ($meta_obj->properties->{column_defs}->{$type}||'') eq 'blob';
180
181        ## xxx can be a hook?
182        if ( ! defined $meta_obj->$type() ) {
183            $meta_obj->remove;
184        }
185        else {
186            serialize_blob($field, $meta_obj) if $meta_is_blob;
187            if ($MT::Meta::REPLACE_ENABLED) {
188                $meta_obj->replace;
189            } 
190            else {
191                $meta_obj->save;
192            }
193            unserialize_blob($meta_obj) if $meta_is_blob;
194        }
195    }
196}
197
198sub remove {
199    my $proxy = shift;
200    my $meta_pkg = $proxy->meta_pkg;
201    Carp::croak("Deletion of meta without PK installed") 
202        unless $proxy->{__pkeys};
203
204    my %args = ($_[1] and ref($_[1]) eq 'HASH') ? %{ $_[1] } : ();
205    $args{nofetch} = 1;
206
207    $meta_pkg->remove($proxy->{__pkeys}, \%args);
208
209    delete $proxy->{__objects};
210}
211
212sub set_primary_keys {
213    my ($proxy, $obj) = @_;
214
215    if (my $pkmap = $proxy->meta_pkg->properties->{pk_map}) {
216        my $pkeys;
217        while (my($object_key, $meta_key) = each %$pkmap) {
218            $pkeys->{$meta_key} = $obj->$object_key();
219        }
220        $proxy->{__pkeys} = $pkeys;
221        return;
222    }
223    ## Map the N fields of the object's primary key to the first N fields of the meta object's primary key.
224    ## TODO: can we assume the meta class's primary key starts with the host package's primary key?
225    ## TODO: isn't there some idiom for iterating over two arrays in tandem?
226    my @class_keys = @{ $obj->primary_key_tuple };
227    my @meta_keys  = @{ $proxy->meta_pkg->primary_key_tuple };
228    my $pkeys = {};
229    for my $i (0..$#class_keys) {
230        my $pkey = $class_keys[$i];
231        $pkeys->{$meta_keys[$i]} = $obj->$pkey();
232    }
233
234    $proxy->{__pkeys} = $pkeys;
235}
236
237sub lazy_load_objects {
238    my $proxy = shift;
239    $proxy->load_objects if ! exists $proxy->{__objects} && $proxy->{__pkeys};
240}
241
242sub load_objects {
243    my $proxy = shift;
244
245    my $pkg = $proxy->{pkg};
246    my $meta_pkg = $proxy->meta_pkg;
247
248    my @objs  = $meta_pkg->search($proxy->{__pkeys});
249
250    foreach my $meta_obj (@objs) {
251        my $type_id = $meta_obj->type;
252
253        my $field = MT::Meta->metadata_by_id($pkg, $type_id);
254        unless ($field) {
255            MT->log("Metadata ID $type_id on $pkg not found");
256            next;
257        }
258       
259        my $name  = $field->{name};
260        my $type  = $field->{type};
261
262        unserialize_blob($meta_obj)
263            if ($meta_obj->properties->{column_defs}->{$type}||'') eq 'blob';
264        $proxy->{__objects}->{$name} = $meta_obj;
265    }
266}
267
268# This expose our unserialization just in case someone needs it
269# PhenoType differ does.
270sub do_unserialization {
271    my $class = shift;
272    my $dataref = shift;
273
274    return $dataref unless defined $$dataref;
275    $$dataref =~ s/^([ABCINPSZ]{3})://;
276    my $prefix = $1;
277    unless (defined $prefix) {
278        return $dataref;
279    }
280
281    if ($prefix eq 'ZIP') {
282        unless ($HAS_ZLIB) {
283            Carp::croak("FATAL: cannot deal with this zipped data, Zlib is missing");
284        }
285        my $deflated = Compress::Zlib::uncompress($dataref);
286        unless ($deflated =~ s/^(BIN|ASC)://) {
287            Carp::croak("Cannot find subprefix in 'ZIP:' blob $deflated");
288        }
289        my $subprefix = $1;
290        if ($subprefix eq 'BIN') {
291            my $val = $serializer->unserialize($deflated);
292            if (defined $val) {
293                return $val; # it's a ref already.
294            } else {
295                return \$val;
296            }
297        } 
298        else {
299            return \$deflated;
300        }
301    } elsif ($prefix eq 'BIN') {
302        my $val = $serializer->unserialize($$dataref);
303        if (defined $val) {
304            return $val; # it's a ref already.
305        } else {
306            return \$val;
307        }
308    } elsif ($prefix eq 'ASC') {
309        return $dataref;
310    } else {
311        warn "Something's wrong with the data: prefix is $prefix";
312        return $dataref;
313    }
314}
315
316sub unserialize_blob {
317    my $meta_obj = shift;
318    for my $column (@{ $meta_obj->columns_of_type('blob') }) {
319        my $data = $meta_obj->$column();
320
321        my $unser = do_unserialization($meta_obj, \$data);
322       
323        # set it back to the unserialized data structure
324        $meta_obj->$column($$unser, { no_changed_flag => 1 });
325    }
326}
327
328sub serialize_blob {
329    my $field = shift;
330    my $meta_obj = shift;
331    for my $column (@{ $meta_obj->columns_of_type('blob') }) {
332        my $data = $meta_obj->$column();
333
334        my $val;
335        if (ref $data) {
336            $val = 'BIN:' . $serializer->serialize(\$data);
337        } elsif (defined $data) {
338            $val = 'ASC:' . $data;
339        } else {
340            $val = undef;
341        }
342       
343        if ($HAS_ZLIB && defined $val && $meta_obj->blob_requires_zip($field, \$val)) {
344            my $zipped = Compress::Zlib::compress($val);
345            $val = 'ZIP:' . $zipped;
346        }
347
348        # set it back the serialized data
349        $meta_obj->$column($val, { no_changed_flag => 1 });
350    }
351}
352
353sub deflate_meta {
354    my $proxy = shift;
355
356    ## Load all metadata for the object, so that we can store it. Odds are,
357    ## we've already got it anyway.
358    $proxy->lazy_load_objects;
359
360    my $meta = {};
361    for my $field (keys %{ $proxy->{__objects} } ) {
362        next if $field eq '';
363        $meta->{$field} = $proxy->get($field);
364    }
365    $meta;
366}
367
368sub inflate_meta {
369    my $proxy = shift;
370    my($deflated) = @_;
371    for my $key (keys %$deflated) {
372        my $value = eval { $proxy->create_meta_object($key, $deflated->{$key}) };
373        next if $@; ## probably 2 versions of the code using the same memcached
374        $proxy->{__objects}{$key} = $value;
375        $proxy->{__objects}{$key}{changed_cols} = {};
376    }
377}
378
379sub refresh {
380    my $proxy = shift;
381    # just delete and let the Proxy lazy load it afterwards
382    delete $proxy->{__objects};
383    return 1;
384}
385
3861;
387
388__END__
389
390=head1 NAME
391
392MT::Meta::Proxy - interface to a MT::Object's meta data object
393
394=head1 SYNOPSIS
395
396    package Foo;
397    use base qw( MT::Object );
398
399    __PACKAGE__->install_properties({ ... });
400
401    __PACKAGE__->install_meta({
402        datasource => 'foo_meta',
403        fields     => [
404            { name => 'selfaware', type => 'vchar', key => 1 },
405        ],
406    });
407
408    sub meta_args {
409        +{ key => 'foo' };
410    }
411
412
413    package main;
414    # then what?
415
416
417=head1 DESCRIPTION
418
419The I<MT::Meta::Proxy> is the interface between a I<MT::Object> and
420its meta data class generated by I<MT::Meta>.
421
422=head1 USAGE
423
424=head2 MT::Meta::Proxy->new($obj)
425
426Returns a new metadata proxy to manage metadata for the given
427I<MT::Object> instance.
428
429=head2 $proxy->get($field)
430
431Returns the value of the metadata field I<$field> represented by this proxy.
432
433=head2 $proxy->meta_pkg()
434
435Returns the name of the class containing the metadata this proxy will get and
436set. The meta data class name is typically the original I<MT::Object>
437class appended with C<::Meta>.
438
439=head2 $proxy->create_meta_object($field, $value)
440
441Returns a new instance of the meta data class this proxy manages, representing
442the metadata field I<$field> and containing the value I<$value>.
443
444As I<create_meta_object> will not put the object under this proxy's management,
445you should not use it directly, but instead prefer to use I<set>.
446
447=head2 $proxy->set($field, $value)
448
449Sets the metadata field I<$field> under this proxy's care to the value I<$value>.
450
451=head2 $proxy->save()
452
453Saves each of the meta data objects this proxy manages that have been changed.
454
455=head2 $proxy->remove()
456
457Removes all of the meta data objects this proxy manages from the database and
458local memory storage.
459
460=head2 $proxy->set_primary_keys($obj)
461
462Sets the primary keys of I<$proxy> to those of the MT::Object instance
463I<$obj>.
464
465=head2 $proxy->lazy_load_objects()
466
467Loads the meta data objects this proxy manages if they have not already been
468loaded and cached in local memory storage. The actual loading is performed by
469I<load_objects>.
470
471=head2 $proxy->load_objects()
472
473Loads all the meta data objects this proxy manages into local memory storage,
474regardless of whether they've already been loaded.
475
476=head2 $proxy->deflate_meta()
477
478Returns a flat hash reference containing all the metadata managed by this
479proxy.
480
481=head2 $proxy->inflate_meta($hash)
482
483Restores the internal state of this proxy with the metadata fields and values
484found in the flat hash reference I<$hash>. Note the proxy will assume that the
485hash contains the saved values of the meta data. That is, the fields named in
486I<$hash> will I<not> be marked as changed by I<inflate_meta()>.
487
488
489=head1 SEE ALSO
490
491I<MT::Object>, I<MT::Meta>
492
493=cut
494
Note: See TracBrowser for help on using the browser.