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

Revision 1849, 12.9 kB (checked in by mpaschal, 20 months ago)

Column defs in MT::Objects are hashes, not just strings, so check for blob type fields appropriately
Use MT::Object's hash-column-def-aware columns_of_type() method
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        unserialize_blob($meta_obj)
265            if ($meta_obj->properties->{column_defs}->{$type}||'') eq 'blob';
266        $proxy->{__objects}->{$name} = $meta_obj;
267    }
268}
269
270# This expose our unserialization just in case someone needs it
271# PhenoType differ does.
272sub do_unserialization {
273    my $class = shift;
274    my $dataref = shift;
275
276    return $dataref unless defined $$dataref;
277    $$dataref =~ s/^([ABCINPSZ]{3})://;
278    my $prefix = $1;
279    unless (defined $prefix) {
280        return $dataref;
281    }
282
283    if ($prefix eq 'ZIP') {
284        unless ($HAS_ZLIB) {
285            Carp::croak("FATAL: cannot deal with this zipped data, Zlib is missing");
286        }
287        my $deflated = Compress::Zlib::uncompress($dataref);
288        unless ($deflated =~ s/^(BIN|ASC)://) {
289            Carp::croak("Cannot find subprefix in 'ZIP:' blob $deflated");
290        }
291        my $subprefix = $1;
292        if ($subprefix eq 'BIN') {
293            my $val = $serializer->unserialize($deflated);
294            if (defined $val) {
295                return $val; # it's a ref already.
296            } else {
297                return \$val;
298            }
299        } 
300        else {
301            return \$deflated;
302        }
303    } elsif ($prefix eq 'BIN') {
304        my $val = $serializer->unserialize($$dataref);
305        if (defined $val) {
306            return $val; # it's a ref already.
307        } else {
308            return \$val;
309        }
310    } elsif ($prefix eq 'ASC') {
311        return $dataref;
312    } else {
313        warn "Something's wrong with the data: prefix is $prefix";
314        return $dataref;
315    }
316}
317
318sub unserialize_blob {
319    my $meta_obj = shift;
320    for my $column (@{ $meta_obj->columns_of_type('blob') }) {
321        my $data = $meta_obj->$column();
322
323        my $unser = do_unserialization($meta_obj, \$data);
324       
325        # set it back to the unserialized data structure
326        $meta_obj->$column($$unser, { no_changed_flag => 1 });
327    }
328}
329
330sub serialize_blob {
331    my $field = shift;
332    my $meta_obj = shift;
333    for my $column (@{ $meta_obj->columns_of_type('blob') }) {
334        my $data = $meta_obj->$column();
335
336        my $val;
337        if (ref $data) {
338            $val = 'BIN:' . $serializer->serialize(\$data);
339        } elsif (defined $data) {
340            $val = 'ASC:' . $data;
341        } else {
342            $val = undef;
343        }
344       
345        if ($HAS_ZLIB && defined $val && $meta_obj->blob_requires_zip($field, \$val)) {
346            my $zipped = Compress::Zlib::compress($val);
347            $val = 'ZIP:' . $zipped;
348        }
349
350        # set it back the serialized data
351        $meta_obj->$column($val, { no_changed_flag => 1 });
352    }
353}
354
355sub deflate_meta {
356    my $proxy = shift;
357
358    ## Load all metadata for the object, so that we can store it. Odds are,
359    ## we've already got it anyway.
360    $proxy->lazy_load_objects;
361
362    my $meta = {};
363    for my $field (keys %{ $proxy->{__objects} } ) {
364        next if $field eq '';
365        $meta->{$field} = $proxy->get($field);
366    }
367    $meta;
368}
369
370sub inflate_meta {
371    my $proxy = shift;
372    my($deflated) = @_;
373    for my $key (keys %$deflated) {
374        my $value = eval { $proxy->create_meta_object($key, $deflated->{$key}) };
375        next if $@; ## probably 2 versions of the code using the same memcached
376        $proxy->{__objects}{$key} = $value;
377        $proxy->{__objects}{$key}{changed_cols} = {};
378    }
379}
380
381sub refresh {
382    my $proxy = shift;
383    # just delete and let the Proxy lazy load it afterwards
384    delete $proxy->{__objects};
385    return 1;
386}
387
3881;
389
390__END__
391
392=head1 NAME
393
394MT::Meta::Proxy - interface to a MT::Object's meta data object
395
396=head1 SYNOPSIS
397
398    package Foo;
399    use base qw( MT::Object );
400
401    __PACKAGE__->install_properties({ ... });
402
403    __PACKAGE__->install_meta({
404        datasource => 'foo_meta',
405        fields     => [
406            { name => 'selfaware', type => 'vchar', key => 1 },
407        ],
408    });
409
410    sub meta_args {
411        +{ key => 'foo' };
412    }
413
414
415    package main;
416    # then what?
417
418
419=head1 DESCRIPTION
420
421The I<MT::Meta::Proxy> is the interface between a I<MT::Object> and
422its meta data class generated by I<MT::Meta>.
423
424=head1 USAGE
425
426=head2 MT::Meta::Proxy->new($obj)
427
428Returns a new metadata proxy to manage metadata for the given
429I<MT::Object> instance.
430
431=head2 $proxy->get($field)
432
433Returns the value of the metadata field I<$field> represented by this proxy.
434
435=head2 $proxy->meta_pkg()
436
437Returns the name of the class containing the metadata this proxy will get and
438set. The meta data class name is typically the original I<MT::Object>
439class appended with C<::Meta>.
440
441=head2 $proxy->create_meta_object($field, $value)
442
443Returns a new instance of the meta data class this proxy manages, representing
444the metadata field I<$field> and containing the value I<$value>.
445
446As I<create_meta_object> will not put the object under this proxy's management,
447you should not use it directly, but instead prefer to use I<set>.
448
449=head2 $proxy->set($field, $value)
450
451Sets the metadata field I<$field> under this proxy's care to the value I<$value>.
452
453=head2 $proxy->save()
454
455Saves each of the meta data objects this proxy manages that have been changed.
456
457=head2 $proxy->remove()
458
459Removes all of the meta data objects this proxy manages from the database and
460local memory storage.
461
462=head2 $proxy->set_primary_keys($obj)
463
464Sets the primary keys of I<$proxy> to those of the MT::Object instance
465I<$obj>.
466
467=head2 $proxy->lazy_load_objects()
468
469Loads the meta data objects this proxy manages if they have not already been
470loaded and cached in local memory storage. The actual loading is performed by
471I<load_objects>.
472
473=head2 $proxy->load_objects()
474
475Loads all the meta data objects this proxy manages into local memory storage,
476regardless of whether they've already been loaded.
477
478=head2 $proxy->deflate_meta()
479
480Returns a flat hash reference containing all the metadata managed by this
481proxy.
482
483=head2 $proxy->inflate_meta($hash)
484
485Restores the internal state of this proxy with the metadata fields and values
486found in the flat hash reference I<$hash>. Note the proxy will assume that the
487hash contains the saved values of the meta data. That is, the fields named in
488I<$hash> will I<not> be marked as changed by I<inflate_meta()>.
489
490
491=head1 SEE ALSO
492
493I<MT::Object>, I<MT::Meta>
494
495=cut
496
Note: See TracBrowser for help on using the browser.