/[cvs]/nfo/perl/libs/Data/Transfer/Sync/Core.pm
ViewVC logotype

Annotation of /nfo/perl/libs/Data/Transfer/Sync/Core.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.3 - (hide annotations)
Mon Jan 20 17:01:14 2003 UTC (21 years, 5 months ago) by joko
Branch: MAIN
Changes since 1.2: +7 -284 lines
+ cosmetics and debugging
+ probably refactored code to new plugin-modules 'Metadata.pm' and/or 'StorageInterface.pm' (guess it was the last one...)

1 joko 1.3 ## $Id: Core.pm,v 1.2 2003/01/19 02:05:42 joko Exp $
2 joko 1.1 ##
3     ## Copyright (c) 2002 Andreas Motl <andreas.motl@ilo.de>
4     ##
5     ## See COPYRIGHT section in pod text below for usage and distribution rights.
6     ##
7     ## ----------------------------------------------------------------------------------------
8 joko 1.2 ## $Log: Core.pm,v $
9 joko 1.3 ## Revision 1.2 2003/01/19 02:05:42 joko
10     ## - removed pod-documentation: now in Data/Transfer/Sync.pod
11     ##
12 joko 1.2 ## Revision 1.1 2003/01/19 01:23:04 joko
13     ## + new from Data/Transfer/Sync.pm
14     ##
15 joko 1.1 ## Revision 1.11 2002/12/23 07:10:59 joko
16     ## + using MD5 for checksum generation again - the 32-bit integer hash from DBI seems to be too lazy
17     ##
18     ## Revision 1.10 2002/12/19 01:07:16 joko
19     ## + fixed output done via $logger
20     ##
21     ## Revision 1.9 2002/12/16 07:02:34 jonen
22     ## + added comment
23     ##
24     ## Revision 1.8 2002/12/15 02:03:09 joko
25     ## + fixed logging-messages
26     ## + additional metadata-checks
27     ##
28     ## Revision 1.7 2002/12/13 21:49:34 joko
29     ## + sub configure
30     ## + sub checkOptions
31     ##
32     ## Revision 1.6 2002/12/06 04:49:10 jonen
33     ## + disabled output-puffer here
34     ##
35     ## Revision 1.5 2002/12/05 08:06:05 joko
36     ## + bugfix with determining empty fields (Null) with DBD::CSV
37     ## + debugging
38     ## + updated comments
39     ##
40     ## Revision 1.4 2002/12/03 15:54:07 joko
41     ## + {import}-flag is now {prepare}-flag
42     ##
43     ## Revision 1.3 2002/12/01 22:26:59 joko
44     ## + minor cosmetics for logging
45     ##
46     ## Revision 1.2 2002/12/01 04:43:25 joko
47     ## + mapping deatil entries may now be either an ARRAY or a HASH
48     ## + erase flag is used now (for export-operations)
49     ## + expressions to refer to values inside deep nested structures
50     ## - removed old mappingV2-code
51     ## + cosmetics
52     ## + sub _erase_all
53     ##
54     ## Revision 1.1 2002/11/29 04:45:50 joko
55     ## + initial check in
56     ##
57     ## Revision 1.1 2002/10/10 03:44:21 cvsjoko
58     ## + new
59     ## ----------------------------------------------------------------------------------------
60    
61    
62     package Data::Transfer::Sync::Core;
63    
64     use strict;
65     use warnings;
66    
67     use mixin::with qw( Data::Transfer::Sync );
68    
69    
70     # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - main
71    
72     use Data::Dumper;
73    
74     use misc::HashExt;
75     use libp qw( md5_base64 );
76     use libdb qw( quotesql hash2Sql );
77     use Data::Transform::Deep qw( hash2object refexpr2perlref );
78     use Data::Compare::Struct qw( getDifference isEmpty );
79     use Data::Storage::Container;
80     use DesignPattern::Object;
81    
82     # get logger instance
83     my $logger = Log::Dispatch::Config->instance;
84    
85     $| = 1;
86    
87     =pod
88     sub new {
89     my $invocant = shift;
90     my $class = ref($invocant) || $invocant;
91     my $self = {};
92     $logger->debug( __PACKAGE__ . "->new(@_)" );
93     bless $self, $class;
94     $self->configure(@_);
95     return $self;
96     }
97     =cut
98    
99    
100     sub _init {
101     my $self = shift;
102    
103     # build new container if necessary
104     $self->{container} = Data::Storage::Container->new() if !$self->{container};
105    
106     # add storages to container (optional)
107     foreach (keys %{$self->{storages}}) {
108     $self->{container}->addStorage($_, $self->{storages}->{$_});
109     }
110    
111     return 1;
112    
113     }
114    
115     sub _initV1 {
116     my $self = shift;
117     # tag storages with id-authority and checksum-provider information
118     # TODO: better store tag inside metadata to hold bits together!
119     map { $self->{container}->{storage}->{$_}->{isIdentAuthority} = 1 } @{$self->{id_authorities}};
120     map { $self->{container}->{storage}->{$_}->{isChecksumAuthority} = 1; } @{$self->{checksum_authorities}};
121     map { $self->{container}->{storage}->{$_}->{isWriteProtected} = 1; } @{$self->{write_protected}};
122     }
123    
124    
125     # TODO: abstract the hardwired use of "source" and "target" in here somehow - hmmmm....... /(="ยง/%???
126     sub _syncNodes {
127    
128     my $self = shift;
129    
130     my $tc = OneLineDumpHash->new( {} );
131     my $results;
132    
133     # set of objects is already in $self->{args}
134     # TODO: make independent of the terminology "object..."
135     $results = $self->{args}->{objectSet} if $self->{args}->{objectSet};
136    
137     # apply filter
138     if (my $filter = $self->{meta}->{source}->{filter}) {
139     #print Dumper($filter);
140     #exit;
141     $results ||= $self->_getNodeList('source', $filter);
142     }
143    
144     # get reference to node list from convenient method provided by CORE-HANDLE
145     $results ||= $self->_getNodeList('source');
146    
147     # checkpoint: do we actually have a list to iterate through?
148     if (!$results || !@{$results}) {
149     $logger->notice( __PACKAGE__ . "->syncNodes: No nodes to synchronize." );
150     return;
151     }
152    
153     # dereference
154     my @results = @{$results};
155    
156     # iterate through set
157     foreach my $source_node_real (@results) {
158    
159     $tc->{total}++;
160    
161     #print "======================== iter", "\n";
162    
163     # clone object (in case we have to modify it here)
164     # TODO:
165     # - is a "deep_copy" needed here if occouring modifications take place?
166     # - puuhhhh, i guess a deep_copy would destroy tangram mechanisms?
167     # - after all, just take care for now that this object doesn't get updated!
168     my $source_node = $source_node_real;
169    
170     # modify entry - handle new style callbacks (the readers)
171     #print Dumper($source_node);
172     #exit;
173    
174     my $descent = 'source';
175    
176     # handle callbacks right now while scanning them (asymmetric to the writers)
177     my $map_callbacks = {};
178     if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
179    
180     my $error = 0;
181    
182     foreach my $node (keys %{$callbacks->{read}}) {
183    
184     my $object = $source_node;
185     my $value; # = $source_node->{$node};
186    
187     # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
188 joko 1.3 #my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_read';
189     my $perl_callback = $self->{meta}->{$descent}->{nodeType} . '::' . $node . '_read';
190 joko 1.1 my $evalstring = 'return ' . $perl_callback . '( { object => $object, property => $node, value => $value, storage => $self->{meta}->{$descent}->{storage} } );';
191     #print $evalstring, "\n"; exit;
192     my $cb_result = eval($evalstring);
193     if ($@) {
194     die $@;
195     $error = 1;
196     print $@, "\n";
197     }
198     # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
199    
200     $source_node->{$node} = $cb_result;
201    
202     }
203    
204     }
205    
206     #print Dumper($source_node);
207    
208     # exclude defined fields (simply delete from object)
209     map { delete $source_node->{$_} } @{$self->{meta}->{source}->{subnodes_exclude}};
210    
211     # here we accumulate information about the status of the current node (payload/row/object/item/entry)
212     $self->{node} = {};
213     $self->{node}->{source}->{payload} = $source_node;
214    
215     #print "res - ident", "\n";
216    
217     # determine ident of entry
218     my $identOK = $self->_resolveNodeIdent('source');
219     #if (!$identOK && lc $self->{args}->{direction} ne 'import') {
220     if (!$identOK) {
221     #print Dumper($self->{meta}->{source});
222 joko 1.3 $logger->critical( __PACKAGE__ . "->syncNodes: No ident found in source node ( nodeName='$self->{meta}->{source}->{nodeName}', nodeType='$self->{meta}->{source}->{nodeType}') try to \"prepare\" this node first?" );
223 joko 1.1 return;
224     }
225    
226     my $statOK = $self->_statloadNode('target', $self->{node}->{source}->{ident});
227    
228     # mark node as new either if there's no ident or if stat/load failed
229     if (!$statOK) {
230     $self->{node}->{status}->{new} = 1;
231     print "n" if $self->{verbose};
232     }
233    
234     #print "checksum", "\n";
235    
236     # determine status of entry by synchronization method
237     if ( (lc $self->{args}->{method} eq 'checksum') ) {
238     #if ( $statOK && (lc $self->{args}->{method} eq 'checksum') ) {
239     #if ( !$self->{node}->{status}->{new} && (lc $self->{args}->{method} eq 'checksum') ) {
240    
241     # TODO:
242     # is this really worth a "critical"???
243     # no - it should just be a debug appendix i believe
244    
245     #print "readcs", "\n";
246    
247     # calculate checksum of source node
248     #$self->_calcChecksum('source');
249     if (!$self->_readChecksum('source')) {
250     $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"source\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
251     $tc->{skip}++;
252     print "s" if $self->{verbose};
253     next;
254     }
255    
256     # get checksum from synchronization target
257     $self->_readChecksum('target');
258     #if (!$self->_readChecksum('target')) {
259     # $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"target\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
260     # next;
261     #}
262    
263     # pre flight check: do we actually have a checksum provided?
264     #if (!$self->{node}->{source}->{checksum}) {
265     # print "Source checksum for entry with ident \"$self->{node}->{source}->{ident}\" could not be calculated, maybe it's missing?.", "\n";
266     # return;
267     #}
268    
269     # determine if entry is "new" or "dirty"
270     # after all, this seems to be the point where the hammer falls.....
271     print "c" if $self->{verbose};
272     $self->{node}->{status}->{new} = !$self->{node}->{target}->{checksum};
273     if (!$self->{node}->{status}->{new}) {
274     $self->{node}->{status}->{dirty} =
275     $self->{node}->{status}->{new} ||
276     (!$self->{node}->{source}->{checksum} || !$self->{node}->{target}->{checksum}) ||
277     ($self->{node}->{source}->{checksum} ne $self->{node}->{target}->{checksum}) ||
278     $self->{args}->{force};
279     }
280    
281     }
282    
283     # first reaction on entry-status: continue with next entry if the current is already "in sync"
284     if (!$self->{node}->{status}->{new} && !$self->{node}->{status}->{dirty}) {
285     $tc->{in_sync}++;
286     next;
287     }
288    
289     # build map to actually transfer the data from source to target
290     $self->_buildMap();
291    
292    
293     #print Dumper($self->{node}); exit;
294    
295     #print "attempt", "\n";
296    
297     # additional (new) checks for feature "write-protection"
298     if ($self->{meta}->{target}->{storage}->{isWriteProtected}) {
299     $tc->{attempt_transfer}++;
300     print "\n" if $self->{verbose};
301     $logger->notice( __PACKAGE__ . "->syncNodes: Target is write-protected. Will not insert or modify node. " .
302     "(Ident: $self->{node}->{source}->{ident} " . "Dump:\n" . Dumper($self->{node}->{source}->{payload}) . ")" );
303     print "\n" if $self->{verbose};
304     $tc->{skip}++;
305     next;
306     }
307    
308     # transfer contents of map to target
309     if ($self->{node}->{status}->{new}) {
310     $tc->{attempt_new}++;
311     $self->_doTransferToTarget('insert');
312     # asymmetry: refetch node from target to re-calculate new ident and checksum (TODO: is IdentAuthority of relevance here?)
313     #print Dumper($self->{node});
314     $self->_statloadNode('target', $self->{node}->{target}->{ident}, 1);
315     $self->_readChecksum('target');
316    
317     } elsif ($self->{node}->{status}->{dirty}) {
318     $tc->{attempt_modify}++;
319     # asymmetry: get ident before updating (TODO: is IdentAuthority of relevance here?)
320     $self->{node}->{target}->{ident} = $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}};
321     $self->_doTransferToTarget('update');
322     $self->_readChecksum('target');
323     }
324    
325     if ($self->{node}->{status}->{ok}) {
326     $tc->{ok}++;
327     print "t" if $self->{verbose};
328     }
329    
330     if ($self->{node}->{status}->{error}) {
331     $tc->{error}++;
332     push( @{$tc->{error_per_row}}, $self->{node}->{status}->{error} );
333     print "e" if $self->{verbose};
334     }
335    
336     # change ident in source (take from target), if transfer was ok and target is an IdentAuthority
337     # this is (for now) called a "retransmit" indicated by a "r"-character when verbosing
338     if ($self->{node}->{status}->{ok} && $self->{meta}->{target}->{storage}->{isIdentAuthority}) {
339     print "r" if $self->{verbose};
340     #print Dumper($self->{meta});
341     #print Dumper($self->{node});
342     #exit;
343     $self->_doModifySource_IdentChecksum($self->{node}->{target}->{ident});
344     }
345    
346     print ":" if $self->{verbose};
347    
348     }
349    
350     print "\n" if $self->{verbose};
351    
352     # build user-message from some stats
353     my $msg = "statistics: $tc";
354    
355     if ($tc->{error_per_row}) {
356     $msg .= "\n";
357     $msg .= "errors from \"error_per_row\":" . "\n";
358     $msg .= Dumper($tc->{error_per_row});
359     }
360    
361     # todo!!!
362     #sysevent( { usermsg => $msg, level => $level }, $taskEvent );
363     $logger->info( __PACKAGE__ . "->syncNodes: $msg" );
364    
365     return $tc;
366    
367     }
368    
369    
370     # refactor this as some core-function to do a generic dump resolving data-encapsulations of e.g. Set::Object
371     sub _dumpCompact {
372     my $self = shift;
373    
374     #my $vars = \@_;
375     my @data = ();
376    
377     my $count = 0;
378     foreach (@_) {
379     my $item = {};
380     foreach my $key (keys %$_) {
381     my $val = $_->{$key};
382    
383     #print Dumper($val);
384    
385     if (ref $val eq 'Set::Object') {
386     #print "========================= SET", "\n";
387     #print Dumper($val);
388     #print Dumper($val->members());
389     #$val = $val->members();
390     #$vars->[$count]->{$key} = $val->members() if $val->can("members");
391     #$item->{$key} = $val->members() if $val->can("members");
392     $item->{$key} = $val->members();
393     #print Dumper($vars->[$count]->{$key});
394    
395     } else {
396     $item->{$key} = $val;
397     }
398    
399     }
400     push @data, $item;
401     $count++;
402     }
403    
404     #print "Dump:", Dumper(@data), "\n";
405    
406     $Data::Dumper::Indent = 0;
407     my $result = Dumper(@data);
408     $Data::Dumper::Indent = 2;
409     return $result;
410    
411     }
412    
413    
414     sub _calcChecksum {
415    
416     my $self = shift;
417     my $descent = shift;
418     my $specifier = shift;
419    
420     # calculate checksum for current object
421     my $ident = $self->{node}->{$descent}->{ident};
422    
423     # build dump of this node
424     my $payload = $self->{node}->{$descent}->{payload};
425     #my $dump = $ident . "\n" . $item->quickdump();
426     #my $dump = $ident . "\n" . Dumper($item);
427     my $dump = $ident . "\n" . $self->_dumpCompact($payload);
428    
429     # TODO: $logger->dump( ... );
430     #$logger->debug( __PACKAGE__ . ": " . $dump );
431     #$logger->dump( __PACKAGE__ . ": " . $dump );
432    
433     # calculate checksum from dump
434     # note: the 32-bit integer hash from DBI seems
435     # to generate duplicates with small payloads already in ranges of hundreds of items/rows!!!
436     # try to avoid to use it or try to use it only for payloads greater than, hmmm, let's say 30 chars?
437     # (we had about 15 chars average per item (row))
438    
439     # md5-based fingerprint, base64 encoded (from Digest::MD5)
440     $self->{node}->{$descent}->{checksum} = md5_base64($dump) . '==';
441     # 32-bit integer "hash" value (maybe faster?) (from DBI)
442     #$self->{node}->{$descent}->{checksum} = DBI::hash($dump, 1);
443    
444     # signal good
445     return 1;
446    
447     }
448    
449    
450     sub _readChecksum {
451     my $self = shift;
452    
453     my $descent = shift;
454    
455     #print "getcheck:", "\n"; print Dumper($self->{node}->{$descent});
456    
457     if (!$self->{node}->{$descent}) {
458     # signal checksum bad
459     return;
460     }
461    
462     # get checksum for current entry
463     # TODO: don't have the checksum column/property hardcoded as "cs" here, make this configurable somehow
464    
465     if ($self->{meta}->{$descent}->{storage}->{isChecksumAuthority}) {
466     #$self->{node}->{$descent}->{checksum} = $entry->{cs};
467     #$self->{node}->{$descent}->{checksum} = $self->_calcChecksum($descent); # $entry->{cs};
468     #print "descent: $descent", "\n";
469     $self->_calcChecksum($descent);
470     #print "checksum: ", $self->{node}->{$descent}->{checksum}, "\n";
471     } else {
472    
473     #$self->{node}->{$descent}->{checksum} = $entry->{cs};
474     $self->{node}->{$descent}->{checksum} = $self->{node}->{$descent}->{payload}->{cs};
475     }
476    
477     # signal checksum good
478     return 1;
479    
480     }
481    
482    
483     sub _buildMap {
484    
485     my $self = shift;
486    
487     # field-structure for building sql
488     # mapping of sql-fieldnames to object-attributes
489     $self->{node}->{map} = {};
490    
491     # manually set ...
492     # ... object-id
493     $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}} = $self->{node}->{source}->{ident};
494     # ... checksum
495     $self->{node}->{map}->{cs} = $self->{node}->{source}->{checksum};
496    
497     #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
498    
499     # for transferring flat structures via simple (1:1) mapping
500     # TODO: diff per property / property value
501    
502     if ($self->{args}->{mapping}) {
503     # apply mapping from $self->{args}->{mapping} to $self->{node}->{map}
504     #foreach my $key (@{$self->{meta}->{source}->{childnodes}}) {
505     my @childnodes = @{$self->{meta}->{source}->{childnodes}};
506     for (my $mapidx = 0; $mapidx <= $#childnodes; $mapidx++) {
507     #my $map_right = $self->{args}->{mapping}->{$key};
508    
509     $self->{node}->{source}->{propcache} = {};
510     $self->{node}->{target}->{propcache} = {};
511    
512     # get property name
513     $self->{node}->{source}->{propcache}->{property} = $self->{meta}->{source}->{childnodes}->[$mapidx];
514     $self->{node}->{target}->{propcache}->{property} = $self->{meta}->{target}->{childnodes}->[$mapidx];
515     #print "map: $map_right", "\n";
516    
517     # get property value
518     my $value;
519    
520     # detect for callback - old style - (maybe the better???)
521     if (ref($self->{node}->{target}->{map}) eq 'CODE') {
522     #$value = &$map_right($objClone);
523     } else {
524     # plain (scalar?) value
525     #$value = $objClone->{$map_right};
526     $self->{node}->{source}->{propcache}->{value} = $self->{node}->{source}->{payload}->{$self->{node}->{source}->{propcache}->{property}};
527     }
528     #$self->{node}->{map}->{$key} = $value;
529    
530     # detect expression
531     # for transferring deeply nested structures described by expressions
532     #print "val: $self->{node}->{source}->{propcache}->{value}", "\n";
533     if ($self->{node}->{source}->{propcache}->{property} =~ s/^expr://) {
534    
535     # create an anonymous sub to act as callback target dispatcher
536     my $cb_dispatcher = sub {
537     #print "=============== CALLBACK DISPATCHER", "\n";
538     #print "ident: ", $self->{node}->{source}->{ident}, "\n";
539     #return $self->{node}->{source}->{ident};
540    
541     };
542    
543    
544     #print Dumper($self->{node});
545    
546     # build callback map for helper function
547     #my $cbmap = { $self->{meta}->{source}->{IdentProvider}->{arg} => $cb_dispatcher };
548     my $cbmap = {};
549     my $value = refexpr2perlref($self->{node}->{source}->{payload}, $self->{node}->{source}->{propcache}->{property}, $cbmap);
550     $self->{node}->{source}->{propcache}->{value} = $value;
551     }
552    
553     # encode values dependent on type of underlying storage here - expand cases...
554     my $storage_type = $self->{meta}->{target}->{storage}->{locator}->{type};
555     if ($storage_type eq 'DBI') {
556     # ...for sql
557     $self->{node}->{source}->{propcache}->{value} = quotesql($self->{node}->{source}->{propcache}->{value});
558     }
559     elsif ($storage_type eq 'Tangram') {
560     # iso? utf8 already possible?
561    
562     } elsif ($storage_type eq 'LDAP') {
563     # TODO: encode utf8 here?
564     }
565    
566     # store value to transfer map
567     $self->{node}->{map}->{$self->{node}->{target}->{propcache}->{property}} = $self->{node}->{source}->{propcache}->{value};
568    
569     }
570     }
571    
572    
573     # TODO: $logger->dump( ... );
574     #$logger->debug( "sqlmap:" . "\n" . Dumper($self->{node}->{map}) );
575     #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
576     #print "entrystatus: ", Dumper($self->{node}), "\n";
577    
578     }
579    
580    
581    
582     sub _modifyNode {
583     my $self = shift;
584     my $descent = shift;
585     my $action = shift;
586     my $map = shift;
587     my $crit = shift;
588    
589     # map for new style callbacks
590     my $map_callbacks = {};
591    
592     # checks go first!
593    
594     # TODO: this should be reviewed first - before extending ;-)
595     # TODO: this should be extended:
596     # count this cases inside the caller to this sub and provide a better overall message
597     # if this counts still zero in the end:
598     # "No nodes have been touched for modify: Do you have column-headers in your csv file?"
599     if (not defined $self->{node}) {
600     #$logger->critical( __PACKAGE__ . "->_modifyNode failed: \"$descent\" node is empty." );
601     #return;
602     }
603    
604     # transfer callback nodes from value map to callback map - handle them afterwards! - (new style callbacks)
605     if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
606     foreach my $callback (keys %{$callbacks->{write}}) {
607     $map_callbacks->{write}->{$callback} = $map->{$callback};
608     delete $map->{$callback};
609     }
610     }
611    
612    
613     #print Dumper($self->{meta});
614    
615     # DBI speaks SQL
616     if ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'DBI') {
617    
618     #print Dumper($self->{node});
619     my $sql_main;
620     # translate map to sql
621     #print $action, "\n"; exit;
622     #print $self->{meta}->{$descent}->{node}, "\n"; exit;
623     #print "action:";
624     #print $action, "\n";
625     #$action = "anc";
626     #print "yai", "\n";
627    
628     #print Dumper($map);
629     #delete $map->{cs};
630    
631     if (lc($action) eq 'insert') {
632     $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_INSERT');
633     } elsif (lc $action eq 'update') {
634     $crit ||= "$self->{meta}->{$descent}->{IdentProvider}->{arg}='$self->{node}->{$descent}->{ident}'";
635     $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_UPDATE', $crit);
636     }
637    
638     #$sql_main = "UPDATE currencies_csv SET oid='abcdef' WHERE text='Australian Dollar' AND key='AUD';";
639     #$sql_main = "UPDATE currencies_csv SET oid='huhu2' WHERE ekey='AUD'";
640    
641     #print "sql: ", $sql_main, "\n";
642     #exit;
643    
644     # transfer data
645     my $sqlHandle = $self->{meta}->{$descent}->{storage}->sendCommand($sql_main);
646    
647     #exit;
648    
649     # handle errors
650     if ($sqlHandle->err) {
651     #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
652     $self->{node}->{status}->{error} = {
653     statement => $sql_main,
654     state => $sqlHandle->state,
655     err => $sqlHandle->err,
656     errstr => $sqlHandle->errstr,
657     };
658     } else {
659     $self->{node}->{status}->{ok} = 1;
660     }
661    
662     # Tangram does it the oo-way (naturally)
663     } elsif ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'Tangram') {
664     my $sql_main;
665     my $object;
666    
667     # determine classname
668     my $classname = $self->{meta}->{$descent}->{node};
669    
670     # properties to exclude
671     my @exclude = @{$self->{meta}->{$descent}->{subnodes_exclude}};
672    
673    
674     if (my $identProvider = $self->{meta}->{$descent}->{IdentProvider}) {
675     push @exclude, $identProvider->{arg};
676     }
677    
678     # new feature:
679     # - check TypeProvider metadata property from other side
680     # - use argument (arg) inside as a classname for object creation on this side
681     #my $otherSide = $self->_otherSide($descent);
682     if (my $typeProvider = $self->{meta}->{$descent}->{TypeProvider}) {
683     #print Dumper($map);
684     $classname = $map->{$typeProvider->{arg}};
685     # remove nodes from map also (push nodes to "subnodes_exclude" list)
686     push @exclude, $typeProvider->{arg};
687     }
688    
689     # exclude banned properties (remove from map)
690     #map { delete $self->{node}->{map}->{$_} } @{$self->{args}->{exclude}};
691     map { delete $map->{$_} } @exclude;
692    
693     # list of properties
694     my @props = keys %{$map};
695    
696     # transfer data
697     if (lc $action eq 'insert') {
698    
699     # build array to initialize object
700     #my @initarray = ();
701     #map { push @initarray, $_, undef; } @props;
702    
703     # make the object persistent in four steps:
704     # - raw create (perl / class tangram scope)
705     # - engine insert (tangram scope) ... this establishes inheritance - don't try to fill in inherited properties before!
706     # - raw fill-in from hash (perl scope)
707     # - engine update (tangram scope) ... this updates all properties just filled in
708    
709     # create new object ...
710     #my $object = $classname->new( @initarray );
711     $object = $classname->new();
712    
713     # ... pass to orm ...
714     $self->{meta}->{$descent}->{storage}->insert($object);
715    
716     # ... and initialize with empty (undef'd) properties.
717     #print Dumper(@props);
718     map { $object->{$_} = undef; } @props;
719    
720     # mix in values ...
721     hash2object($object, $map);
722    
723     # ... and re-update@orm.
724     #print Dumper($object);
725     $self->{meta}->{$descent}->{storage}->update($object);
726    
727     # asymmetry: get ident after insert
728     # TODO:
729     # - just do this if it is an IdentAuthority
730     # - use IdentProvider metadata here
731     #print Dumper($self->{meta}->{$descent});
732     my $oid = $self->{meta}->{$descent}->{storage}->id($object);
733     #print "oid: $oid", "\n";
734     $self->{node}->{$descent}->{ident} = $oid;
735    
736    
737     } elsif (lc $action eq 'update') {
738    
739     # get fresh object from orm first
740     $object = $self->{meta}->{$descent}->{storage}->load($self->{node}->{$descent}->{ident});
741    
742     #print Dumper($self->{node});
743    
744     # mix in values
745     #print Dumper($object);
746     hash2object($object, $map);
747     #print Dumper($object);
748     #exit;
749     $self->{meta}->{$descent}->{storage}->update($object);
750     }
751    
752     my $error = 0;
753    
754     # handle new style callbacks - this is a HACK - do this without an eval!
755     #print Dumper($map);
756     #print "cb: ", Dumper($self->{meta}->{$descent}->{Callback});
757     #print Dumper($map_callbacks);
758     foreach my $node (keys %{$map_callbacks->{write}}) {
759     #print Dumper($node);
760     my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_write';
761     my $evalstring = $perl_callback . '( { object => $object, value => $map_callbacks->{write}->{$node}, storage => $self->{meta}->{$descent}->{storage} } );';
762     #print $evalstring, "\n"; exit;
763     eval($evalstring);
764     if ($@) {
765     $error = 1;
766     print $@, "\n";
767     }
768    
769     #print "after eval", "\n";
770    
771     if (!$error) {
772     # re-update@orm
773     $self->{meta}->{$descent}->{storage}->update($object);
774     }
775     }
776    
777     # handle errors
778     if ($error) {
779     #print "error", "\n";
780     =pod
781     my $sqlHandle;
782     #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
783     $self->{node}->{status}->{error} = {
784     statement => $sql_main,
785     state => $sqlHandle->state,
786     err => $sqlHandle->err,
787     errstr => $sqlHandle->errstr,
788     };
789     =cut
790     # rollback....
791     #print "rollback", "\n";
792     $self->{meta}->{$descent}->{storage}->erase($object);
793     #print "after rollback", "\n";
794     } else {
795     $self->{node}->{status}->{ok} = 1;
796     }
797    
798    
799     }
800    
801     }
802    
803     sub _doTransferToTarget {
804     my $self = shift;
805     my $action = shift;
806     $self->_modifyNode('target', $action, $self->{node}->{map});
807     }
808    
809     sub _doModifySource_IdentChecksum {
810     my $self = shift;
811     my $ident_new = shift;
812     # this changes an old node to a new one including ident and checksum
813     # TODO:
814     # - eventually introduce an external resource to store this data to
815     # - we won't have to "re"-modify the source node here
816     my $map = {
817     $self->{meta}->{source}->{IdentProvider}->{arg} => $ident_new,
818     cs => $self->{node}->{target}->{checksum},
819     };
820    
821     #print Dumper($map);
822     #print Dumper($self->{node});
823     #exit;
824    
825     $self->_modifyNode('source', 'update', $map);
826     }
827    
828    
829    
830     sub _prepareNode_MetaProperties {
831     my $self = shift;
832     my $descent = shift;
833    
834     $logger->info( __PACKAGE__ . "->_prepareNode_MetaProperties( descent $descent )" );
835    
836     # TODO: this should (better) be: "my $firstnode = $self->_getFirstNode($descent);"
837     my $list = $self->_getNodeList($descent);
838    
839     # get first node
840     my $firstnode = $list->[0];
841    
842     # check if node contains meta properties/nodes
843     # TODO: "cs" is hardcoded here!
844     my @required = ( $self->{meta}->{$descent}->{IdentProvider}->{arg}, 'cs' );
845     my @found = keys %$firstnode;
846     #my @diff = getDifference(\@found, \@required);
847     my $diff = getDifference(\@required, \@found);
848     #print Dumper(@found);
849     #print Dumper(@required);
850     #print Dumper(@diff);
851     #if (!$#diff || $#diff == -1) {
852     if (isEmpty($diff)) {
853     $logger->warning( __PACKAGE__ . "->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter..." );
854     foreach (@required) {
855     my $sql = "ALTER TABLE $self->{meta}->{$descent}->{node} ADD COLUMN $_";
856     #print "sql: $sql", "\n";
857     my $res = $self->{meta}->{$descent}->{storage}->sendCommand($sql);
858     #print Dumper($res->getStatus());
859     }
860     }
861    
862     }
863    
864     sub _prepareNode_DummyIdent {
865     my $self = shift;
866     my $descent = shift;
867    
868     $logger->info( __PACKAGE__ . "->_prepareNode_DummyIdent( descent $descent )" );
869    
870     my $list = $self->_getNodeList($descent);
871     #print Dumper($list);
872     my $i = 0;
873     my $ident_base = 5678983;
874     my $ident_appendix = '0001';
875     foreach my $node (@$list) {
876     my $ident_dummy = $i + $ident_base;
877     $ident_dummy .= $ident_appendix;
878     my $map = {
879     $self->{meta}->{$descent}->{IdentProvider}->{arg} => $ident_dummy,
880     cs => undef,
881     };
882    
883     # diff lists and ...
884     my $diff = getDifference([keys %$node], [keys %$map]);
885     next if $#{$diff} == -1;
886    
887     # ... build criteria including all columns
888     my @crits;
889     foreach my $property (@$diff) {
890     next if !$property;
891     my $value = $node->{$property};
892     next if !$value;
893     push @crits, "$property='" . quotesql($value) . "'";
894     }
895     my $crit = join ' AND ', @crits;
896     print "p" if $self->{verbose};
897    
898     #print Dumper($map);
899     #print Dumper($crit);
900    
901     $self->_modifyNode($descent, 'update', $map, $crit);
902     $i++;
903     }
904    
905     print "\n" if $self->{verbose};
906    
907     if (!$i) {
908     $logger->warning( __PACKAGE__ . "->_prepareNode_DummyIdent: no nodes touched" );
909     }
910    
911     }
912    
913     # TODO: handle this in an abstract way (wipe out use of 'source' and/or 'target' inside core)
914     sub _otherSide {
915     my $self = shift;
916     my $descent = shift;
917     return 'source' if $descent eq 'target';
918     return 'target' if $descent eq 'source';
919     return '';
920     }
921    
922     sub _erase_all {
923     my $self = shift;
924     my $descent = shift;
925     #my $node = shift;
926     my $node = $self->{meta}->{$descent}->{node};
927     $self->{meta}->{$descent}->{storage}->eraseAll($node);
928     }
929    
930     1;

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed