root/branches/release-35/lib/MT/Meta/Proxy.pm @ 1962

Revision 1962, 12.0 kB (checked in by bchoate, 20 months ago)

Removing zlib serialization support.

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