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

Revision 1864, 13.0 kB (checked in by mpaschal, 20 months ago)

Check column for blobness correctly when loading, too
BugzID: 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
180        my $meta_col_def = $meta_obj->column_def($type);
181        my $meta_is_blob = $meta_col_def ? $meta_col_def->{type} eq 'blob' : 0;
182
183        ## xxx can be a hook?
184        if ( ! defined $meta_obj->$type() ) {
185            $meta_obj->remove;
186        }
187        else {
188            serialize_blob($field, $meta_obj) if $meta_is_blob;
189            if ($MT::Meta::REPLACE_ENABLED) {
190                $meta_obj->replace;
191            } 
192            else {
193                $meta_obj->save;
194            }
195            unserialize_blob($meta_obj) if $meta_is_blob;
196        }
197    }
198}
199
200sub remove {
201    my $proxy = shift;
202    my $meta_pkg = $proxy->meta_pkg;
203    Carp::croak("Deletion of meta without PK installed") 
204        unless $proxy->{__pkeys};
205
206    my %args = ($_[1] and ref($_[1]) eq 'HASH') ? %{ $_[1] } : ();
207    $args{nofetch} = 1;
208
209    $meta_pkg->remove($proxy->{__pkeys}, \%args);
210
211    delete $proxy->{__objects};
212}
213
214sub set_primary_keys {
215    my ($proxy, $obj) = @_;
216
217    if (my $pkmap = $proxy->meta_pkg->properties->{pk_map}) {
218        my $pkeys;
219        while (my($object_key, $meta_key) = each %$pkmap) {
220            $pkeys->{$meta_key} = $obj->$object_key();
221        }
222        $proxy->{__pkeys} = $pkeys;
223        return;
224    }
225    ## Map the N fields of the object's primary key to the first N fields of the meta object's primary key.
226    ## TODO: can we assume the meta class's primary key starts with the host package's primary key?
227    ## TODO: isn't there some idiom for iterating over two arrays in tandem?
228    my @class_keys = @{ $obj->primary_key_tuple };
229    my @meta_keys  = @{ $proxy->meta_pkg->primary_key_tuple };
230    my $pkeys = {};
231    for my $i (0..$#class_keys) {
232        my $pkey = $class_keys[$i];
233        $pkeys->{$meta_keys[$i]} = $obj->$pkey();
234    }
235
236    $proxy->{__pkeys} = $pkeys;
237}
238
239sub lazy_load_objects {
240    my $proxy = shift;
241    $proxy->load_objects if ! exists $proxy->{__objects} && $proxy->{__pkeys};
242}
243
244sub load_objects {
245    my $proxy = shift;
246
247    my $pkg = $proxy->{pkg};
248    my $meta_pkg = $proxy->meta_pkg;
249
250    my @objs  = $meta_pkg->search($proxy->{__pkeys});
251
252    foreach my $meta_obj (@objs) {
253        my $type_id = $meta_obj->type;
254
255        my $field = MT::Meta->metadata_by_id($pkg, $type_id);
256        unless ($field) {
257            MT->log("Metadata ID $type_id on $pkg not found");
258            next;
259        }
260       
261        my $name  = $field->{name};
262        my $type  = $field->{type};
263
264        my $meta_col_def = $meta_obj->column_def($type);
265        my $meta_is_blob = $meta_col_def ? $meta_col_def->{type} eq 'blob' : 0;
266
267        unserialize_blob($meta_obj) if $meta_is_blob;
268        $proxy->{__objects}->{$name} = $meta_obj;
269    }
270}
271
272# This expose our unserialization just in case someone needs it
273# PhenoType differ does.
274sub do_unserialization {
275    my $class = shift;
276    my $dataref = shift;
277
278    return $dataref unless defined $$dataref;
279    $$dataref =~ s/^([ABCINPSZ]{3})://;
280    my $prefix = $1;
281    unless (defined $prefix) {
282        return $dataref;
283    }
284
285    if ($prefix eq 'ZIP') {
286        unless ($HAS_ZLIB) {
287            Carp::croak("FATAL: cannot deal with this zipped data, Zlib is missing");
288        }
289        my $deflated = Compress::Zlib::uncompress($dataref);
290        unless ($deflated =~ s/^(BIN|ASC)://) {
291            Carp::croak("Cannot find subprefix in 'ZIP:' blob $deflated");
292        }
293        my $subprefix = $1;
294        if ($subprefix eq 'BIN') {
295            my $val = $serializer->unserialize($deflated);
296            if (defined $val) {
297                return $val; # it's a ref already.
298            } else {
299                return \$val;
300            }
301        } 
302        else {
303            return \$deflated;
304        }
305    } elsif ($prefix eq 'BIN') {
306        my $val = $serializer->unserialize($$dataref);
307        if (defined $val) {
308            return $val; # it's a ref already.
309        } else {
310            return \$val;
311        }
312    } elsif ($prefix eq 'ASC') {
313        return $dataref;
314    } else {
315        warn "Something's wrong with the data: prefix is $prefix";
316        return $dataref;
317    }
318}
319
320sub unserialize_blob {
321    my $meta_obj = shift;
322    for my $column (@{ $meta_obj->columns_of_type('blob') }) {
323        my $data = $meta_obj->$column();
324
325        my $unser = do_unserialization($meta_obj, \$data);
326       
327        # set it back to the unserialized data structure
328        $meta_obj->$column($$unser, { no_changed_flag => 1 });
329    }
330}
331
332sub serialize_blob {
333    my $field = shift;
334    my $meta_obj = shift;
335    for my $column (@{ $meta_obj->columns_of_type('blob') }) {
336        my $data = $meta_obj->$column();
337
338        my $val;
339        if (ref $data) {
340            $val = 'BIN:' . $serializer->serialize(\$data);
341        } elsif (defined $data) {
342            $val = 'ASC:' . $data;
343        } else {
344            $val = undef;
345        }
346       
347        if ($HAS_ZLIB && defined $val && $meta_obj->blob_requires_zip($field, \$val)) {
348            my $zipped = Compress::Zlib::compress($val);
349            $val = 'ZIP:' . $zipped;
350        }
351
352        # set it back the serialized data
353        $meta_obj->$column($val, { no_changed_flag => 1 });
354    }
355}
356
357sub deflate_meta {
358    my $proxy = shift;
359
360    ## Load all metadata for the object, so that we can store it. Odds are,
361    ## we've already got it anyway.
362    $proxy->lazy_load_objects;
363
364    my $meta = {};
365    for my $field (keys %{ $proxy->{__objects} } ) {
366        next if $field eq '';
367        $meta->{$field} = $proxy->get($field);
368    }
369    $meta;
370}
371
372sub inflate_meta {
373    my $proxy = shift;
374    my($deflated) = @_;
375    for my $key (keys %$deflated) {
376        my $value = eval { $proxy->create_meta_object($key, $deflated->{$key}) };
377        next if $@; ## probably 2 versions of the code using the same memcached
378        $proxy->{__objects}{$key} = $value;
379        $proxy->{__objects}{$key}{changed_cols} = {};
380    }
381}
382
383sub refresh {
384    my $proxy = shift;
385    # just delete and let the Proxy lazy load it afterwards
386    delete $proxy->{__objects};
387    return 1;
388}
389
3901;
391
392__END__
393
394=head1 NAME
395
396MT::Meta::Proxy - interface to a MT::Object's meta data object
397
398=head1 SYNOPSIS
399
400    package Foo;
401    use base qw( MT::Object );
402
403    __PACKAGE__->install_properties({ ... });
404
405    __PACKAGE__->install_meta({
406        datasource => 'foo_meta',
407        fields     => [
408            { name => 'selfaware', type => 'vchar', key => 1 },
409        ],
410    });
411
412    sub meta_args {
413        +{ key => 'foo' };
414    }
415
416
417    package main;
418    # then what?
419
420
421=head1 DESCRIPTION
422
423The I<MT::Meta::Proxy> is the interface between a I<MT::Object> and
424its meta data class generated by I<MT::Meta>.
425
426=head1 USAGE
427
428=head2 MT::Meta::Proxy->new($obj)
429
430Returns a new metadata proxy to manage metadata for the given
431I<MT::Object> instance.
432
433=head2 $proxy->get($field)
434
435Returns the value of the metadata field I<$field> represented by this proxy.
436
437=head2 $proxy->meta_pkg()
438
439Returns the name of the class containing the metadata this proxy will get and
440set. The meta data class name is typically the original I<MT::Object>
441class appended with C<::Meta>.
442
443=head2 $proxy->create_meta_object($field, $value)
444
445Returns a new instance of the meta data class this proxy manages, representing
446the metadata field I<$field> and containing the value I<$value>.
447
448As I<create_meta_object> will not put the object under this proxy's management,
449you should not use it directly, but instead prefer to use I<set>.
450
451=head2 $proxy->set($field, $value)
452
453Sets the metadata field I<$field> under this proxy's care to the value I<$value>.
454
455=head2 $proxy->save()
456
457Saves each of the meta data objects this proxy manages that have been changed.
458
459=head2 $proxy->remove()
460
461Removes all of the meta data objects this proxy manages from the database and
462local memory storage.
463
464=head2 $proxy->set_primary_keys($obj)
465
466Sets the primary keys of I<$proxy> to those of the MT::Object instance
467I<$obj>.
468
469=head2 $proxy->lazy_load_objects()
470
471Loads the meta data objects this proxy manages if they have not already been
472loaded and cached in local memory storage. The actual loading is performed by
473I<load_objects>.
474
475=head2 $proxy->load_objects()
476
477Loads all the meta data objects this proxy manages into local memory storage,
478regardless of whether they've already been loaded.
479
480=head2 $proxy->deflate_meta()
481
482Returns a flat hash reference containing all the metadata managed by this
483proxy.
484
485=head2 $proxy->inflate_meta($hash)
486
487Restores the internal state of this proxy with the metadata fields and values
488found in the flat hash reference I<$hash>. Note the proxy will assume that the
489hash contains the saved values of the meta data. That is, the fields named in
490I<$hash> will I<not> be marked as changed by I<inflate_meta()>.
491
492
493=head1 SEE ALSO
494
495I<MT::Object>, I<MT::Meta>
496
497=cut
498
Note: See TracBrowser for help on using the browser.