root/branches/release-39/lib/MT/Meta/Proxy.pm @ 2468

Revision 2468, 12.1 kB (checked in by bchoate, 18 months ago)

Suppress MT log message for unrecognized metadata types. BugId:79968

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