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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.3 - (show 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 ## $Id: Core.pm,v 1.2 2003/01/19 02:05:42 joko Exp $
2 ##
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 ## $Log: Core.pm,v $
9 ## Revision 1.2 2003/01/19 02:05:42 joko
10 ## - removed pod-documentation: now in Data/Transfer/Sync.pod
11 ##
12 ## Revision 1.1 2003/01/19 01:23:04 joko
13 ## + new from Data/Transfer/Sync.pm
14 ##
15 ## 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 #my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_read';
189 my $perl_callback = $self->{meta}->{$descent}->{nodeType} . '::' . $node . '_read';
190 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 $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 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