]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Utils/Fieldmapper.pm
Fix the links in the Fieldmapper, which were borked whenever a
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Utils / Fieldmapper.pm
1 package Fieldmapper;
2 use OpenSRF::Utils::JSON;
3 use Data::Dumper;
4 use base 'OpenSRF::Application';
5 use OpenSRF::Utils::Logger;
6 use OpenSRF::Utils::SettingsClient;
7 use OpenSRF::System;
8 use XML::LibXML;
9
10 my $log = 'OpenSRF::Utils::Logger';
11
12 use vars qw/$fieldmap $VERSION/;
13
14 sub publish_fieldmapper {
15         my ($self,$client,$class) = @_;
16
17         return $fieldmap unless (defined $class);
18         return undef unless (exists($$fieldmap{$class}));
19         return {$class => $$fieldmap{$class}};
20 }
21 __PACKAGE__->register_method(
22         api_name        => 'opensrf.open-ils.system.fieldmapper',
23         api_level       => 1,
24         method          => 'publish_fieldmapper',
25 );
26
27 #
28 # To dump the Javascript version of the fieldmapper struct use the command:
29 #
30 #       PERL5LIB=~/cvs/ILS/OpenSRF/src/perlmods/:~/cvs/ILS/Open-ILS/src/perlmods/ GEN_JS=1 perl -MOpenILS::Utils::Fieldmapper -e 'print "\n";'
31 #
32 # ... adjusted for your CVS sandbox, of course.
33 #
34
35 sub classes {
36         return () unless (defined $fieldmap);
37         return keys %$fieldmap;
38 }
39
40 sub get_attribute {
41         my $attr_list = shift;
42         my $attr_name = shift;
43
44         my $attr = $attr_list->getNamedItem( $attr_name );
45         if( defined( $attr ) ) {
46                 return $attr->getValue();
47         }
48         return undef;
49 }
50
51 sub load_fields {
52         my $field_list = shift;
53         my $fm = shift;
54
55         # Get attributes of the field list.  Since there is only one
56         # <field> per class, these attributes logically belong to the
57         # enclosing class, and that's where we load them.
58
59         my $field_attr_list = $field_list->attributes();
60
61         my $sequence  = get_attribute( $field_attr_list, 'oils_persist:sequence' );
62         if( ! defined( $sequence ) ) {
63                 $sequence = '';
64         }
65         my $primary   = get_attribute( $field_attr_list, 'oils_persist:primary' );
66
67         # Load attributes into the Fieldmapper ----------------------
68
69         $$fieldmap{$fm}{ sequence } = $sequence;
70         $$fieldmap{$fm}{ identity } = $primary;
71
72         # Load each field -------------------------------------------
73
74         for my $field ( $field_list->childNodes() ) {    # For each <field>
75                 if( $field->nodeName eq 'field' ) {
76         
77                         my $attribute_list = $field->attributes();
78                         
79                         my $name     = get_attribute( $attribute_list, 'name' );
80                         my $array_position = get_attribute( $attribute_list, 'oils_obj:array_position' );
81                         my $virtual  = get_attribute( $attribute_list, 'oils_persist:virtual' );
82                         if( ! defined( $virtual ) ) {
83                                 $virtual = "false";
84                         }
85                         my $selector = get_attribute( $attribute_list, 'reporter:selector' );
86
87                         $$fieldmap{$fm}{fields}{ $name } =
88                                 { virtual => ( $virtual eq 'true' ) ? 1 : 0,
89                                   position => $array_position,
90                                 };
91
92                         # The selector attribute, if present at all, attaches to only one
93                         # of the fields in a given class.  So if we see it, we store it at
94                         # the level of the enclosing class.
95
96                         if( defined( $selector ) ) {
97                                 $$fieldmap{$fm}{selector} = $selector;
98                         }
99                 }
100         }
101 }
102
103 sub load_links {
104         my $link_list = shift;
105         my $fm = shift;
106
107         for my $link ( $link_list->childNodes() ) {    # For each <link>
108                 if( $link->nodeName eq 'link' ) {
109                         my $attribute_list = $link->attributes();
110                         
111                         my $field   = get_attribute( $attribute_list, 'field' );
112                         my $reltype = get_attribute( $attribute_list, 'reltype' );
113                         my $key     = get_attribute( $attribute_list, 'key' );
114                         my $class   = get_attribute( $attribute_list, 'class' );
115
116                         $$fieldmap{$fm}{links}{ $field } =
117                                 { class   => $class,
118                                   reltype => $reltype,
119                                   key     => $key,
120                                 };
121                 }
122         }
123 }
124
125 sub load_class {
126         my $class_node = shift;
127
128         # Get attributes ---------------------------------------------
129
130         my $attribute_list = $class_node->attributes();
131
132         my $fm               = get_attribute( $attribute_list, 'oils_obj:fieldmapper' );
133         $fm                  = 'Fieldmapper::' . $fm;
134         my $id               = get_attribute( $attribute_list, 'id' );
135         my $controller       = get_attribute( $attribute_list, 'controller' );
136         my $virtual          = get_attribute( $attribute_list, 'virtual' );
137         if( ! defined( $virtual ) ) {
138                 $virtual = 'false';
139         }
140         my $tablename        = get_attribute( $attribute_list, 'oils_persist:tablename' );
141         if( ! defined( $tablename ) ) {
142                 $tablename = '';
143         }
144         my $restrict_primary = get_attribute( $attribute_list, 'oils_persist:restrict_primary' );
145
146         # Load the attributes into the Fieldmapper --------------------
147
148         $log->debug("Building Fieldmapper class for [$fm] from IDL");
149
150         $$fieldmap{$fm}{ hint }             = $id;
151         $$fieldmap{$fm}{ virtual }          = ( $virtual eq 'true' ) ? 1 : 0;
152         $$fieldmap{$fm}{ table }            = $tablename;
153         $$fieldmap{$fm}{ controller }       = [ split ' ', $controller ];
154         $$fieldmap{$fm}{ restrict_primary } = $restrict_primary;
155
156         # Load fields and links
157
158         for my $child ( $class_node->childNodes() ) {
159                 my $nodeName = $child->nodeName;
160                 if( $nodeName eq 'fields' ) {
161                         load_fields( $child, $fm );
162                 } elsif( $nodeName eq 'links' ) {
163                         load_links( $child, $fm );
164                 }
165         }
166 }
167
168 import();
169 sub import {
170         my $class = shift;
171         my %args = @_;
172
173         return if (keys %$fieldmap);
174         return if (!OpenSRF::System->connected && !$args{IDL});
175
176         # parse the IDL ...
177         my $parser = XML::LibXML->new();
178         my $file = $args{IDL} || OpenSRF::Utils::SettingsClient->new->config_value( 'IDL' );
179         my $fmdoc = $parser->parse_file( $file );
180         my $rootnode = $fmdoc->documentElement();
181
182         for my $child ( $rootnode->childNodes() ) {    # For each <class>
183                 my $nodeName = $child->nodeName;
184                 if( $nodeName eq 'class' ) {
185                         load_class( $child );
186                 }
187         }
188
189         #-------------------------------------------------------------------------------
190         # Now comes the evil!  Generate classes
191
192         for my $pkg ( __PACKAGE__->classes ) {
193                 (my $cdbi = $pkg) =~ s/^Fieldmapper:://o;
194
195                 eval <<"                PERL";
196                         package $pkg;
197                         use base 'Fieldmapper';
198                 PERL
199
200                 my $pos = 0;
201                 for my $vfield ( qw/isnew ischanged isdeleted/ ) {
202                         $$fieldmap{$pkg}{fields}{$vfield} = { position => $pos, virtual => 1 };
203                         $pos++;
204                 }
205
206                 if (exists $$fieldmap{$pkg}{proto_fields}) {
207                         for my $pfield ( sort keys %{ $$fieldmap{$pkg}{proto_fields} } ) {
208                                 $$fieldmap{$pkg}{fields}{$pfield} = { position => $pos, virtual => $$fieldmap{$pkg}{proto_fields}{$pfield} };
209                                 $pos++;
210                         }
211                 }
212
213                 OpenSRF::Utils::JSON->register_class_hint(
214                         hint => $pkg->json_hint,
215                         name => $pkg,
216                         type => 'array',
217                 );
218
219         }
220 }
221
222 sub new {
223         my $self = shift;
224         my $value = shift;
225         $value = [] unless (defined $value);
226         return bless $value => $self->class_name;
227 }
228
229 sub decast {
230         my $self = shift;
231         return [ @$self ];
232 }
233
234 sub DESTROY {}
235
236 sub AUTOLOAD {
237         my $obj = shift;
238         my $value = shift;
239         (my $field = $AUTOLOAD) =~ s/^.*://o;
240         my $class_name = $obj->class_name;
241
242         my $fpos = $field;
243         $fpos  =~ s/^clear_//og ;
244
245         my $pos = $$fieldmap{$class_name}{fields}{$fpos}{position};
246
247         if ($field =~ /^clear_/o) {
248                 {       no strict 'subs';
249                         *{$obj->class_name."::$field"} = sub {
250                                 my $self = shift;
251                                 $self->[$pos] = undef;
252                                 return 1;
253                         };
254                 }
255                 return $obj->$field();
256         }
257
258         die "No field by the name $field in $class_name!"
259                 unless (exists $$fieldmap{$class_name}{fields}{$field} && defined($pos));
260
261
262         {       no strict 'subs';
263                 *{$obj->class_name."::$field"} = sub {
264                         my $self = shift;
265                         my $new_val = shift;
266                         $self->[$pos] = $new_val if (defined $new_val);
267                         return $self->[$pos];
268                 };
269         }
270         return $obj->$field($value);
271 }
272
273 sub Selector {
274         my $self = shift;
275         return $$fieldmap{$self->class_name}{selector};
276 }
277
278 sub Identity {
279         my $self = shift;
280         return $$fieldmap{$self->class_name}{identity};
281 }
282
283 sub RestrictPrimary {
284         my $self = shift;
285         return $$fieldmap{$self->class_name}{restrict_primary};
286 }
287
288 sub Sequence {
289         my $self = shift;
290         return $$fieldmap{$self->class_name}{sequence};
291 }
292
293 sub Table {
294         my $self = shift;
295         return $$fieldmap{$self->class_name}{table};
296 }
297
298 sub Controller {
299         my $self = shift;
300         return $$fieldmap{$self->class_name}{controller};
301 }
302
303 sub class_name {
304         my $class_name = shift;
305         return ref($class_name) || $class_name;
306 }
307
308 sub real_fields {
309         my $self = shift;
310         my $class_name = $self->class_name;
311         my $fields = $$fieldmap{$class_name}{fields};
312
313         my @f = grep {
314                         !$$fields{$_}{virtual}
315                 } sort {$$fields{$a}{position} <=> $$fields{$b}{position}} keys %$fields;
316
317         return @f;
318 }
319
320 sub has_field {
321         my $self = shift;
322         my $field = shift;
323         my $class_name = $self->class_name;
324         return 1 if grep { $_ eq $field } keys %{$$fieldmap{$class_name}{fields}};
325         return 0;
326 }
327
328 sub properties {
329         my $self = shift;
330         my $class_name = $self->class_name;
331         return keys %{$$fieldmap{$class_name}{fields}};
332 }
333
334 sub to_bare_hash {
335         my $self = shift;
336
337         my %hash = ();
338         for my $f ($self->properties) {
339                 my $val = $self->$f;
340                 $hash{$f} = $val;
341         }
342
343         return \%hash;
344 }
345
346 sub clone {
347         my $self = shift;
348         return $self->new( [@$self] );
349 }
350
351 sub api_level {
352         my $self = shift;
353         return $fieldmap->{$self->class_name}->{api_level};
354 }
355
356 sub cdbi {
357         my $self = shift;
358         return $fieldmap->{$self->class_name}->{cdbi};
359 }
360
361 sub is_virtual {
362         my $self = shift;
363         my $field = shift;
364         return $fieldmap->{$self->class_name}->{proto_fields}->{$field} if ($field);
365         return $fieldmap->{$self->class_name}->{virtual};
366 }
367
368 sub is_readonly {
369         my $self = shift;
370         my $field = shift;
371         return $fieldmap->{$self->class_name}->{readonly};
372 }
373
374 sub json_hint {
375         my $self = shift;
376         return $fieldmap->{$self->class_name}->{hint};
377 }
378
379
380 1;