2 use OpenSRF::Utils::JSON;
4 use OpenSRF::Utils::Logger;
5 use OpenSRF::Utils::SettingsClient;
8 use Scalar::Util 'blessed';
10 my $log = 'OpenSRF::Utils::Logger';
12 use vars qw/$fieldmap $VERSION/;
15 # To dump the Javascript version of the fieldmapper struct use the command:
17 # PERL5LIB=:~/vcs/ILS/Open-ILS/src/perlmods/lib/ GEN_JS=1 perl -MOpenILS::Utils::Fieldmapper -e 'print "\n";'
19 # ... adjusted for your VCS sandbox of choice, of course.
23 return () unless (defined $fieldmap);
24 return keys %$fieldmap;
27 # Find a Fieldmapper class given the json hint.
30 foreach (keys %$fieldmap) {
31 return $_ if ($fieldmap->{$_}->{hint} eq $hint);
37 my $attr_list = shift;
38 my $attr_name = shift;
40 my $attr = $attr_list->getNamedItem( $attr_name );
41 if( defined( $attr ) ) {
42 return $attr->getValue();
48 my $field_list = shift;
51 # Get attributes of the field list. Since there is only one
52 # <field> per class, these attributes logically belong to the
53 # enclosing class, and that's where we load them.
55 my $field_attr_list = $field_list->attributes();
57 my $sequence = get_attribute( $field_attr_list, 'oils_persist:sequence' );
58 if( ! defined( $sequence ) ) {
61 my $primary = get_attribute( $field_attr_list, 'oils_persist:primary' );
63 # Load attributes into the Fieldmapper ----------------------
65 $$fieldmap{$fm}{ sequence } = $sequence;
66 $$fieldmap{$fm}{ identity } = $primary;
68 # Load each field -------------------------------------------
70 my $array_position = 0;
71 for my $field ( $field_list->childNodes() ) { # For each <field>
72 if( $field->nodeName eq 'field' ) {
74 my $attribute_list = $field->attributes();
76 my $name = get_attribute( $attribute_list, 'name' );
77 next if( $name eq 'isnew' || $name eq 'ischanged' || $name eq 'isdeleted' );
78 my $required = get_attribute( $attribute_list, 'oils_obj:required' );
79 my $validate = get_attribute( $attribute_list, 'oils_obj:validate' );
80 my $virtual = get_attribute( $attribute_list, 'oils_persist:virtual' );
81 if( ! defined( $virtual ) ) {
84 my $selector = get_attribute( $attribute_list, 'reporter:selector' );
86 $$fieldmap{$fm}{fields}{ $name } =
87 { virtual => ( $virtual eq 'true' ) ? 1 : 0,
88 required => ( $required eq 'true' ) ? 1 : 0,
89 position => $array_position,
92 $$fieldmap{$fm}{fields}{ $name }{validate} = qr/$validate/ if (defined($validate));
94 # The selector attribute, if present at all, attaches to only one
95 # of the fields in a given class. So if we see it, we store it at
96 # the level of the enclosing class.
98 if( defined( $selector ) ) {
99 $$fieldmap{$fm}{selector} = $selector;
106 # Load the standard 3 virtual fields ------------------------
108 for my $vfield ( qw/isnew ischanged isdeleted/ ) {
109 $$fieldmap{$fm}{fields}{ $vfield } =
110 { position => $array_position,
118 my $link_list = shift;
121 for my $link ( $link_list->childNodes() ) { # For each <link>
122 if( $link->nodeName eq 'link' ) {
123 my $attribute_list = $link->attributes();
125 my $field = get_attribute( $attribute_list, 'field' );
126 my $reltype = get_attribute( $attribute_list, 'reltype' );
127 my $key = get_attribute( $attribute_list, 'key' );
128 my $class = get_attribute( $attribute_list, 'class' );
129 my $map = get_attribute( $attribute_list, 'map' );
131 $$fieldmap{$fm}{links}{ $field } =
142 my $class_node = shift;
144 # Get attributes ---------------------------------------------
146 my $attribute_list = $class_node->attributes();
148 my $fm = get_attribute( $attribute_list, 'oils_obj:fieldmapper' );
149 $fm = 'Fieldmapper::' . $fm;
150 my $id = get_attribute( $attribute_list, 'id' );
151 my $controller = get_attribute( $attribute_list, 'controller' );
152 my $virtual = get_attribute( $attribute_list, 'virtual' );
153 if( ! defined( $virtual ) ) {
156 my $tablename = get_attribute( $attribute_list, 'oils_persist:tablename' );
157 if( ! defined( $tablename ) ) {
160 my $restrict_primary = get_attribute( $attribute_list, 'oils_persist:restrict_primary' );
161 my $field_safe = get_attribute( $attribute_list, 'oils_persist:field_safe' );
163 # Load the attributes into the Fieldmapper --------------------
165 $log->debug("Building Fieldmapper class for [$fm] from IDL");
167 $$fieldmap{$fm}{ hint } = $id;
168 $$fieldmap{$fm}{ virtual } = ( $virtual eq 'true' ) ? 1 : 0;
169 $$fieldmap{$fm}{ table } = $tablename;
170 $$fieldmap{$fm}{ controller } = [ split ' ', $controller ];
171 $$fieldmap{$fm}{ restrict_primary } = $restrict_primary;
172 $$fieldmap{$fm}{ field_safe } = $field_safe;
174 # Load fields and links
176 for my $child ( $class_node->childNodes() ) {
177 my $nodeName = $child->nodeName;
178 if( $nodeName eq 'fields' ) {
179 load_fields( $child, $fm );
180 } elsif( $nodeName eq 'links' ) {
181 load_links( $child, $fm );
191 return if (keys %$fieldmap);
192 return if (!OpenSRF::System->connected && !$args{IDL});
195 my $parser = XML::LibXML->new();
196 my $file = $args{IDL} || OpenSRF::Utils::SettingsClient->new->config_value( 'IDL' );
197 my $fmdoc = $parser->parse_file( $file );
198 my $rootnode = $fmdoc->documentElement();
200 for my $child ( $rootnode->childNodes() ) { # For each <class>
201 my $nodeName = $child->nodeName;
202 if( $nodeName eq 'class' ) {
203 load_class( $child );
207 #-------------------------------------------------------------------------------
208 # Now comes the evil! Generate classes
210 for my $pkg ( __PACKAGE__->classes ) {
211 (my $cdbi = $pkg) =~ s/^Fieldmapper:://o;
215 use base 'Fieldmapper';
218 if (exists $$fieldmap{$pkg}{proto_fields}) {
219 for my $pfield ( sort keys %{ $$fieldmap{$pkg}{proto_fields} } ) {
220 $$fieldmap{$pkg}{fields}{$pfield} = { position => $pos, virtual => $$fieldmap{$pkg}{proto_fields}{$pfield} };
225 OpenSRF::Utils::JSON->register_class_hint(
226 hint => $pkg->json_hint,
237 $value = [] unless (defined $value);
238 return bless $value => $self->class_name;
251 (my $field = $AUTOLOAD) =~ s/^.*://o;
252 my $class_name = $obj->class_name;
255 $fpos =~ s/^clear_//og ;
257 my $pos = $$fieldmap{$class_name}{fields}{$fpos}{position};
259 if ($field =~ /^clear_/o) {
261 *{$obj->class_name."::$field"} = sub {
263 $self->[$pos] = undef;
267 return $obj->$field();
270 die "No field by the name $field in $class_name!"
271 unless (exists $$fieldmap{$class_name}{fields}{$field} && defined($pos));
275 *{$obj->class_name."::$field"} = sub {
278 $self->[$pos] = $new_val if (defined $new_val);
279 return $self->[$pos];
282 return $obj->$field($value);
287 return $$fieldmap{$self->class_name}{selector};
292 return $$fieldmap{$self->class_name}{identity};
295 sub RestrictPrimary {
297 return $$fieldmap{$self->class_name}{restrict_primary};
302 return $$fieldmap{$self->class_name}{sequence};
307 return $$fieldmap{$self->class_name}{table};
312 return $$fieldmap{$self->class_name}{controller};
318 return undef unless ($f);
319 return $$fieldmap{$self->class_name}{fields}{$f}{required};
324 return undef unless (ref $self);
326 my $opts = shift || {};
327 my $no_virt = $$opts{no_virt}; # skip virtual fields
328 my $skip_fields = $$opts{skip_fields} || {}; # eg. {au => ['passwd']}
329 my @to_skip = @{$$skip_fields{$self->json_hint}}
330 if $$skip_fields{$self->json_hint};
332 my $dom = XML::LibXML::Document->new;
333 my $root = $dom->createElement( $self->json_hint );
334 $dom->setDocumentElement( $root );
336 my @field_names = $no_virt ? $self->real_fields : $self->properties;
338 for my $f (@field_names) {
339 next if ($f eq 'isnew');
340 next if ($f eq 'ischanged');
341 next if ($f eq 'isdeleted');
342 next if (grep {$_ eq $f} @to_skip);
344 my $value = $self->$f();
345 my $element = $dom->createElement( $f );
347 $value = [$value] if (blessed($value)); # fm object
349 if (ref($value)) { # array
350 for my $k (@$value) {
352 my $subdoc = $k->toXML($opts);
354 my $subnode = $subdoc->documentElement;
355 $dom->adoptNode($subnode);
356 $element->appendChild($subnode);
357 } elsif (ref $k) { # not sure what to do here
358 $element->appendText($k);
359 } else { # meh .. just append, I guess
360 $element->appendText($k);
364 $element->appendText($value);
367 $root->appendChild($element);
376 return undef unless ($f);
377 return 1 if (!exists($$fieldmap{$self->class_name}{fields}{$f}{validate}));
378 return $self->$f =~ $$fieldmap{$self->class_name}{fields}{$f}{validate};
382 my $class_name = shift;
383 return ref($class_name) || $class_name;
388 my $class_name = $self->class_name;
389 my $fields = $$fieldmap{$class_name}{fields};
392 !$$fields{$_}{virtual}
393 } sort {$$fields{$a}{position} <=> $$fields{$b}{position}} keys %$fields;
401 my $class_name = $self->class_name;
402 return 1 if grep { $_ eq $field } keys %{$$fieldmap{$class_name}{fields}};
408 my $class_name = $self->class_name;
409 return keys %{$$fieldmap{$class_name}{fields}};
416 for my $f ($self->properties) {
426 return $self->new( [@$self] );
431 return $fieldmap->{$self->class_name}->{api_level};
436 return $fieldmap->{$self->class_name}->{cdbi};
442 return $fieldmap->{$self->class_name}->{proto_fields}->{$field} if ($field);
443 return $fieldmap->{$self->class_name}->{virtual};
449 return $fieldmap->{$self->class_name}->{readonly};
454 return $fieldmap->{$self->class_name}->{hint};