root/branches/release-38/lib/MT/Meta/Proxy.pm @ 2308

Revision 2308, 12.2 kB (checked in by bchoate, 19 months ago)

Support cloning of metadata. BugId:79649

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