/[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.2 - (show annotations)
Sun Jan 19 02:05:42 2003 UTC (21 years, 5 months ago) by joko
Branch: MAIN
Changes since 1.1: +5 -143 lines
- removed pod-documentation: now in Data/Transfer/Sync.pod

1 ## $Id: Core.pm,v 1.1 2003/01/19 01:23:04 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.1 2003/01/19 01:23:04 joko
10 ## + new from Data/Transfer/Sync.pm
11 ##
12 ## Revision 1.11 2002/12/23 07:10:59 joko
13 ## + using MD5 for checksum generation again - the 32-bit integer hash from DBI seems to be too lazy
14 ##
15 ## Revision 1.10 2002/12/19 01:07:16 joko
16 ## + fixed output done via $logger
17 ##
18 ## Revision 1.9 2002/12/16 07:02:34 jonen
19 ## + added comment
20 ##
21 ## Revision 1.8 2002/12/15 02:03:09 joko
22 ## + fixed logging-messages
23 ## + additional metadata-checks
24 ##
25 ## Revision 1.7 2002/12/13 21:49:34 joko
26 ## + sub configure
27 ## + sub checkOptions
28 ##
29 ## Revision 1.6 2002/12/06 04:49:10 jonen
30 ## + disabled output-puffer here
31 ##
32 ## Revision 1.5 2002/12/05 08:06:05 joko
33 ## + bugfix with determining empty fields (Null) with DBD::CSV
34 ## + debugging
35 ## + updated comments
36 ##
37 ## Revision 1.4 2002/12/03 15:54:07 joko
38 ## + {import}-flag is now {prepare}-flag
39 ##
40 ## Revision 1.3 2002/12/01 22:26:59 joko
41 ## + minor cosmetics for logging
42 ##
43 ## Revision 1.2 2002/12/01 04:43:25 joko
44 ## + mapping deatil entries may now be either an ARRAY or a HASH
45 ## + erase flag is used now (for export-operations)
46 ## + expressions to refer to values inside deep nested structures
47 ## - removed old mappingV2-code
48 ## + cosmetics
49 ## + sub _erase_all
50 ##
51 ## Revision 1.1 2002/11/29 04:45:50 joko
52 ## + initial check in
53 ##
54 ## Revision 1.1 2002/10/10 03:44:21 cvsjoko
55 ## + new
56 ## ----------------------------------------------------------------------------------------
57
58
59 package Data::Transfer::Sync::Core;
60
61 use strict;
62 use warnings;
63
64 use mixin::with qw( Data::Transfer::Sync );
65
66
67 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - main
68
69 use Data::Dumper;
70
71 use misc::HashExt;
72 use libp qw( md5_base64 );
73 use libdb qw( quotesql hash2Sql );
74 use Data::Transform::Deep qw( hash2object refexpr2perlref );
75 use Data::Compare::Struct qw( getDifference isEmpty );
76 use Data::Storage::Container;
77 use DesignPattern::Object;
78
79 # get logger instance
80 my $logger = Log::Dispatch::Config->instance;
81
82 $| = 1;
83
84 =pod
85 sub new {
86 my $invocant = shift;
87 my $class = ref($invocant) || $invocant;
88 my $self = {};
89 $logger->debug( __PACKAGE__ . "->new(@_)" );
90 bless $self, $class;
91 $self->configure(@_);
92 return $self;
93 }
94 =cut
95
96
97 sub _init {
98 my $self = shift;
99
100 # build new container if necessary
101 $self->{container} = Data::Storage::Container->new() if !$self->{container};
102
103 # add storages to container (optional)
104 foreach (keys %{$self->{storages}}) {
105 $self->{container}->addStorage($_, $self->{storages}->{$_});
106 }
107
108 return 1;
109
110 }
111
112 sub _initV1 {
113 my $self = shift;
114 # tag storages with id-authority and checksum-provider information
115 # TODO: better store tag inside metadata to hold bits together!
116 map { $self->{container}->{storage}->{$_}->{isIdentAuthority} = 1 } @{$self->{id_authorities}};
117 map { $self->{container}->{storage}->{$_}->{isChecksumAuthority} = 1; } @{$self->{checksum_authorities}};
118 map { $self->{container}->{storage}->{$_}->{isWriteProtected} = 1; } @{$self->{write_protected}};
119 }
120
121
122 sub _preCheckOptions {
123
124 my $self = shift;
125 my $opts = shift;
126
127 #print Dumper($opts);
128 #exit;
129
130 # the type of the to-be-synced item
131 if (!$opts->{source}->{nodeType}) {
132 $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"source-type\".");
133 return;
134 }
135 # the name of the (container-) node the items are listed in
136 if (!$opts->{source}->{nodeName}) {
137 $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"source-node\".");
138 return;
139 }
140
141 # a "map"-declaration which module to use for mapping- and/or lookup-purposes
142 if (!$opts->{map}) {
143 $logger->warning( __PACKAGE__ . "->_preCheckOptions: No mapping supplied - please check key 'map|mappings' in global configuration or specify additional argument '--mapping-module'.");
144 return;
145 }
146 if (!$opts->{map}->{moduleName}) {
147 $logger->warning( __PACKAGE__ . "->_preCheckOptions: Currently only perl-modules can provide mappings: Please specify one with '--mapping-module'.");
148 return;
149 }
150
151 return 1;
152
153 }
154
155
156
157
158
159
160
161
162
163 sub _buildFieldmappingV1 {
164 my $self = shift;
165
166 # build mapping
167 # incoming: and Array of node map entries (Array or Hash) - e.g.
168 # [ 'source:item_name' => 'target:class_val' ]
169 # { source => 'event->startDateTime', target => 'begindate' }
170 foreach (@{$self->{args}->{mapping}}) {
171 if (ref $_ eq 'ARRAY') {
172 my @entry1 = split(':', $_->[0]);
173 my @entry2 = split(':', $_->[1]);
174 my $descent = [];
175 my $node = [];
176 $descent->[0] = $entry1[0];
177 $descent->[1] = $entry2[0];
178 $node->[0] = $entry1[1];
179 $node->[1] = $entry2[1];
180 push @{$self->{meta}->{$descent->[0]}->{childnodes}}, $node->[0];
181 push @{$self->{meta}->{$descent->[1]}->{childnodes}}, $node->[1];
182 } elsif (ref $_ eq 'HASH') {
183 foreach my $entry_key (keys %$_) {
184 my $entry_val = $_->{$entry_key};
185 push @{$self->{meta}->{$entry_key}->{childnodes}}, $entry_val;
186 }
187 }
188
189 }
190
191 }
192
193 sub _buildMetadataV1 {
194 my $self = shift;
195
196 # decompose identifiers for each partner
197 # TODO: refactor!!! take this list from already established/given metadata
198 foreach ('source', 'target') {
199
200 # get/set metadata for further processing
201
202 # Partner and Node (e.g.: "L:Country" or "R:countries.csv")
203 if (my $item = $self->{args}->{$_}) {
204 my @item = split(':', $item);
205 $self->{meta}->{$_}->{dbkey} = $item[0];
206 $self->{meta}->{$_}->{node} = $item[1];
207 }
208
209 # Filter
210 if (my $item_filter = $self->{args}->{$_ . '_filter'}) {
211 $self->{meta}->{$_}->{filter} = $item_filter;
212 }
213
214 # IdentProvider
215 if (my $item_ident = $self->{args}->{$_ . '_ident'}) {
216 my @item_ident = split(':', $item_ident);
217 $self->{meta}->{$_}->{IdentProvider} = { method => $item_ident[0], arg => $item_ident[1] };
218 }
219
220 #print Dumper($self->{meta});
221
222 # TODO: ChecksumProvider
223
224 # exclude properties/subnodes
225 if (my $item_exclude = $self->{args}->{$_ . '_exclude'}) {
226 $self->{meta}->{$_}->{subnodes_exclude} = $item_exclude;
227 }
228
229 # TypeProvider
230 if (my $item_type = $self->{args}->{$_ . '_type'}) {
231 my @item_type = split(':', $item_type);
232 $self->{meta}->{$_}->{TypeProvider} = { method => $item_type[0], arg => $item_type[1] };
233 }
234
235 # Callbacks - writers (will be triggered _before_ writing to target)
236 if (my $item_writers = $self->{args}->{$_ . '_callbacks_write'}) {
237 my $descent = $_; # this is important since the following code inside the map wants to use its own context variables
238 map { $self->{meta}->{$descent}->{Callback}->{write}->{$_}++; } @$item_writers;
239 }
240
241 # Callbacks - readers (will be triggered _after_ reading from source)
242 if (my $item_readers = $self->{args}->{$_ . '_callbacks_read'}) {
243 my $descent = $_;
244 map { $self->{meta}->{$descent}->{Callback}->{read}->{$_}++; } @$item_readers;
245 }
246
247 # resolve storage objects
248 #$self->{$_} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
249 # relink references to metainfo
250 $self->{meta}->{$_}->{storage} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
251 #print "iiiiisprov: ", Dumper($self->{meta}->{$_}->{storage}), "\n";
252 }
253
254 }
255
256 # TODO: abstract the hardwired use of "source" and "target" in here somehow - hmmmm....... /(="§/%???
257 sub _syncNodes {
258
259 my $self = shift;
260
261 my $tc = OneLineDumpHash->new( {} );
262 my $results;
263
264 # set of objects is already in $self->{args}
265 # TODO: make independent of the terminology "object..."
266 $results = $self->{args}->{objectSet} if $self->{args}->{objectSet};
267
268 # apply filter
269 if (my $filter = $self->{meta}->{source}->{filter}) {
270 #print Dumper($filter);
271 #exit;
272 $results ||= $self->_getNodeList('source', $filter);
273 }
274
275 # get reference to node list from convenient method provided by CORE-HANDLE
276 #$results ||= $self->{source}->getListUnfiltered($self->{meta}->{source}->{node});
277 #$results ||= $self->{meta}->{source}->{storage}->getListUnfiltered($self->{meta}->{source}->{node});
278 $results ||= $self->_getNodeList('source');
279
280 # checkpoint: do we actually have a list to iterate through?
281 if (!$results || !@{$results}) {
282 $logger->notice( __PACKAGE__ . "->syncNodes: No nodes to synchronize." );
283 return;
284 }
285
286 # dereference
287 my @results = @{$results};
288
289 # iterate through set
290 foreach my $source_node_real (@results) {
291
292 $tc->{total}++;
293
294 #print "======================== iter", "\n";
295
296 # clone object (in case we have to modify it here)
297 # TODO:
298 # - is a "deep_copy" needed here if occouring modifications take place?
299 # - puuhhhh, i guess a deep_copy would destroy tangram mechanisms?
300 # - after all, just take care for now that this object doesn't get updated!
301 my $source_node = $source_node_real;
302
303 # modify entry - handle new style callbacks (the readers)
304 #print Dumper($source_node);
305 #exit;
306
307 my $descent = 'source';
308
309 # handle callbacks right now while scanning them (asymmetric to the writers)
310 my $map_callbacks = {};
311 if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
312
313 my $error = 0;
314
315 foreach my $node (keys %{$callbacks->{read}}) {
316
317 my $object = $source_node;
318 my $value; # = $source_node->{$node};
319
320 # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
321 my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_read';
322 my $evalstring = 'return ' . $perl_callback . '( { object => $object, property => $node, value => $value, storage => $self->{meta}->{$descent}->{storage} } );';
323 #print $evalstring, "\n"; exit;
324 my $cb_result = eval($evalstring);
325 if ($@) {
326 die $@;
327 $error = 1;
328 print $@, "\n";
329 }
330 # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
331
332 $source_node->{$node} = $cb_result;
333
334 }
335
336 }
337
338 #print Dumper($source_node);
339
340 # exclude defined fields (simply delete from object)
341 map { delete $source_node->{$_} } @{$self->{meta}->{source}->{subnodes_exclude}};
342
343 # here we accumulate information about the status of the current node (payload/row/object/item/entry)
344 $self->{node} = {};
345 $self->{node}->{source}->{payload} = $source_node;
346
347 #print "res - ident", "\n";
348
349 # determine ident of entry
350 my $identOK = $self->_resolveNodeIdent('source');
351 #if (!$identOK && lc $self->{args}->{direction} ne 'import') {
352 if (!$identOK) {
353 #print Dumper($self->{meta}->{source});
354 $logger->critical( __PACKAGE__ . "->syncNodes: No ident found in source node \"$self->{meta}->{source}->{node}\", try to \"prepare\" this node first?" );
355 return;
356 }
357
358 #print "statload", "\n";
359 #print "ident: ", $self->{node}->{source}->{ident}, "\n";
360 #print Dumper($self->{node});
361
362 my $statOK = $self->_statloadNode('target', $self->{node}->{source}->{ident});
363
364 #print Dumper($self->{node});
365
366 # mark node as new either if there's no ident or if stat/load failed
367 if (!$statOK) {
368 $self->{node}->{status}->{new} = 1;
369 print "n" if $self->{verbose};
370 }
371
372 #print "checksum", "\n";
373
374 # determine status of entry by synchronization method
375 if ( (lc $self->{args}->{method} eq 'checksum') ) {
376 #if ( $statOK && (lc $self->{args}->{method} eq 'checksum') ) {
377 #if ( !$self->{node}->{status}->{new} && (lc $self->{args}->{method} eq 'checksum') ) {
378
379 # TODO:
380 # is this really worth a "critical"???
381 # no - it should just be a debug appendix i believe
382
383 #print "readcs", "\n";
384
385 # calculate checksum of source node
386 #$self->_calcChecksum('source');
387 if (!$self->_readChecksum('source')) {
388 $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"source\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
389 $tc->{skip}++;
390 print "s" if $self->{verbose};
391 next;
392 }
393
394 # get checksum from synchronization target
395 $self->_readChecksum('target');
396 #if (!$self->_readChecksum('target')) {
397 # $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"target\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
398 # next;
399 #}
400
401 # pre flight check: do we actually have a checksum provided?
402 #if (!$self->{node}->{source}->{checksum}) {
403 # print "Source checksum for entry with ident \"$self->{node}->{source}->{ident}\" could not be calculated, maybe it's missing?.", "\n";
404 # return;
405 #}
406
407 # determine if entry is "new" or "dirty"
408 # after all, this seems to be the point where the hammer falls.....
409 print "c" if $self->{verbose};
410 $self->{node}->{status}->{new} = !$self->{node}->{target}->{checksum};
411 if (!$self->{node}->{status}->{new}) {
412 $self->{node}->{status}->{dirty} =
413 $self->{node}->{status}->{new} ||
414 (!$self->{node}->{source}->{checksum} || !$self->{node}->{target}->{checksum}) ||
415 ($self->{node}->{source}->{checksum} ne $self->{node}->{target}->{checksum}) ||
416 $self->{args}->{force};
417 }
418
419 }
420
421 # first reaction on entry-status: continue with next entry if the current is already "in sync"
422 if (!$self->{node}->{status}->{new} && !$self->{node}->{status}->{dirty}) {
423 $tc->{in_sync}++;
424 next;
425 }
426
427 # build map to actually transfer the data from source to target
428 $self->_buildMap();
429
430
431 #print Dumper($self->{node}); exit;
432
433 #print "attempt", "\n";
434
435 # additional (new) checks for feature "write-protection"
436 if ($self->{meta}->{target}->{storage}->{isWriteProtected}) {
437 $tc->{attempt_transfer}++;
438 print "\n" if $self->{verbose};
439 $logger->notice( __PACKAGE__ . "->syncNodes: Target is write-protected. Will not insert or modify node. " .
440 "(Ident: $self->{node}->{source}->{ident} " . "Dump:\n" . Dumper($self->{node}->{source}->{payload}) . ")" );
441 print "\n" if $self->{verbose};
442 $tc->{skip}++;
443 next;
444 }
445
446 # transfer contents of map to target
447 if ($self->{node}->{status}->{new}) {
448 $tc->{attempt_new}++;
449 $self->_doTransferToTarget('insert');
450 # asymmetry: refetch node from target to re-calculate new ident and checksum (TODO: is IdentAuthority of relevance here?)
451 #print Dumper($self->{node});
452 $self->_statloadNode('target', $self->{node}->{target}->{ident}, 1);
453 $self->_readChecksum('target');
454
455 } elsif ($self->{node}->{status}->{dirty}) {
456 $tc->{attempt_modify}++;
457 # asymmetry: get ident before updating (TODO: is IdentAuthority of relevance here?)
458 $self->{node}->{target}->{ident} = $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}};
459 $self->_doTransferToTarget('update');
460 $self->_readChecksum('target');
461 }
462
463 if ($self->{node}->{status}->{ok}) {
464 $tc->{ok}++;
465 print "t" if $self->{verbose};
466 }
467
468 if ($self->{node}->{status}->{error}) {
469 $tc->{error}++;
470 push( @{$tc->{error_per_row}}, $self->{node}->{status}->{error} );
471 print "e" if $self->{verbose};
472 }
473
474 # change ident in source (take from target), if transfer was ok and target is an IdentAuthority
475 # this is (for now) called a "retransmit" indicated by a "r"-character when verbosing
476 if ($self->{node}->{status}->{ok} && $self->{meta}->{target}->{storage}->{isIdentAuthority}) {
477 print "r" if $self->{verbose};
478 #print Dumper($self->{meta});
479 #print Dumper($self->{node});
480 #exit;
481 $self->_doModifySource_IdentChecksum($self->{node}->{target}->{ident});
482 }
483
484 print ":" if $self->{verbose};
485
486 }
487
488 print "\n" if $self->{verbose};
489
490 # build user-message from some stats
491 my $msg = "statistics: $tc";
492
493 if ($tc->{error_per_row}) {
494 $msg .= "\n";
495 $msg .= "errors from \"error_per_row\":" . "\n";
496 $msg .= Dumper($tc->{error_per_row});
497 }
498
499 # todo!!!
500 #sysevent( { usermsg => $msg, level => $level }, $taskEvent );
501 $logger->info( __PACKAGE__ . "->syncNodes: $msg" );
502
503 return $tc;
504
505 }
506
507
508 # refactor this as some core-function to do a generic dump resolving data-encapsulations of e.g. Set::Object
509 sub _dumpCompact {
510 my $self = shift;
511
512 #my $vars = \@_;
513 my @data = ();
514
515 my $count = 0;
516 foreach (@_) {
517 my $item = {};
518 foreach my $key (keys %$_) {
519 my $val = $_->{$key};
520
521 #print Dumper($val);
522
523 if (ref $val eq 'Set::Object') {
524 #print "========================= SET", "\n";
525 #print Dumper($val);
526 #print Dumper($val->members());
527 #$val = $val->members();
528 #$vars->[$count]->{$key} = $val->members() if $val->can("members");
529 #$item->{$key} = $val->members() if $val->can("members");
530 $item->{$key} = $val->members();
531 #print Dumper($vars->[$count]->{$key});
532
533 } else {
534 $item->{$key} = $val;
535 }
536
537 }
538 push @data, $item;
539 $count++;
540 }
541
542 #print "Dump:", Dumper(@data), "\n";
543
544 $Data::Dumper::Indent = 0;
545 my $result = Dumper(@data);
546 $Data::Dumper::Indent = 2;
547 return $result;
548
549 }
550
551
552 sub _calcChecksum {
553
554 my $self = shift;
555 my $descent = shift;
556 my $specifier = shift;
557
558 # calculate checksum for current object
559 my $ident = $self->{node}->{$descent}->{ident};
560
561 # build dump of this node
562 my $payload = $self->{node}->{$descent}->{payload};
563 #my $dump = $ident . "\n" . $item->quickdump();
564 #my $dump = $ident . "\n" . Dumper($item);
565 my $dump = $ident . "\n" . $self->_dumpCompact($payload);
566
567 # TODO: $logger->dump( ... );
568 #$logger->debug( __PACKAGE__ . ": " . $dump );
569 #$logger->dump( __PACKAGE__ . ": " . $dump );
570
571 # calculate checksum from dump
572 # note: the 32-bit integer hash from DBI seems
573 # to generate duplicates with small payloads already in ranges of hundreds of items/rows!!!
574 # try to avoid to use it or try to use it only for payloads greater than, hmmm, let's say 30 chars?
575 # (we had about 15 chars average per item (row))
576
577 # md5-based fingerprint, base64 encoded (from Digest::MD5)
578 $self->{node}->{$descent}->{checksum} = md5_base64($dump) . '==';
579 # 32-bit integer "hash" value (maybe faster?) (from DBI)
580 #$self->{node}->{$descent}->{checksum} = DBI::hash($dump, 1);
581
582 # signal good
583 return 1;
584
585 }
586
587
588 sub _readChecksum {
589 my $self = shift;
590
591 my $descent = shift;
592
593 #print "getcheck:", "\n"; print Dumper($self->{node}->{$descent});
594
595 if (!$self->{node}->{$descent}) {
596 # signal checksum bad
597 return;
598 }
599
600 # get checksum for current entry
601 # TODO: don't have the checksum column/property hardcoded as "cs" here, make this configurable somehow
602
603 if ($self->{meta}->{$descent}->{storage}->{isChecksumAuthority}) {
604 #$self->{node}->{$descent}->{checksum} = $entry->{cs};
605 #$self->{node}->{$descent}->{checksum} = $self->_calcChecksum($descent); # $entry->{cs};
606 #print "descent: $descent", "\n";
607 $self->_calcChecksum($descent);
608 #print "checksum: ", $self->{node}->{$descent}->{checksum}, "\n";
609 } else {
610
611 #$self->{node}->{$descent}->{checksum} = $entry->{cs};
612 $self->{node}->{$descent}->{checksum} = $self->{node}->{$descent}->{payload}->{cs};
613 }
614
615 # signal checksum good
616 return 1;
617
618 }
619
620
621 sub _buildMap {
622
623 my $self = shift;
624
625 # field-structure for building sql
626 # mapping of sql-fieldnames to object-attributes
627 $self->{node}->{map} = {};
628
629 # manually set ...
630 # ... object-id
631 $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}} = $self->{node}->{source}->{ident};
632 # ... checksum
633 $self->{node}->{map}->{cs} = $self->{node}->{source}->{checksum};
634
635 #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
636
637 # for transferring flat structures via simple (1:1) mapping
638 # TODO: diff per property / property value
639
640 if ($self->{args}->{mapping}) {
641 # apply mapping from $self->{args}->{mapping} to $self->{node}->{map}
642 #foreach my $key (@{$self->{meta}->{source}->{childnodes}}) {
643 my @childnodes = @{$self->{meta}->{source}->{childnodes}};
644 for (my $mapidx = 0; $mapidx <= $#childnodes; $mapidx++) {
645 #my $map_right = $self->{args}->{mapping}->{$key};
646
647 $self->{node}->{source}->{propcache} = {};
648 $self->{node}->{target}->{propcache} = {};
649
650 # get property name
651 $self->{node}->{source}->{propcache}->{property} = $self->{meta}->{source}->{childnodes}->[$mapidx];
652 $self->{node}->{target}->{propcache}->{property} = $self->{meta}->{target}->{childnodes}->[$mapidx];
653 #print "map: $map_right", "\n";
654
655 # get property value
656 my $value;
657
658 # detect for callback - old style - (maybe the better???)
659 if (ref($self->{node}->{target}->{map}) eq 'CODE') {
660 #$value = &$map_right($objClone);
661 } else {
662 # plain (scalar?) value
663 #$value = $objClone->{$map_right};
664 $self->{node}->{source}->{propcache}->{value} = $self->{node}->{source}->{payload}->{$self->{node}->{source}->{propcache}->{property}};
665 }
666 #$self->{node}->{map}->{$key} = $value;
667
668 # detect expression
669 # for transferring deeply nested structures described by expressions
670 #print "val: $self->{node}->{source}->{propcache}->{value}", "\n";
671 if ($self->{node}->{source}->{propcache}->{property} =~ s/^expr://) {
672
673 # create an anonymous sub to act as callback target dispatcher
674 my $cb_dispatcher = sub {
675 #print "=============== CALLBACK DISPATCHER", "\n";
676 #print "ident: ", $self->{node}->{source}->{ident}, "\n";
677 #return $self->{node}->{source}->{ident};
678
679 };
680
681
682 #print Dumper($self->{node});
683
684 # build callback map for helper function
685 #my $cbmap = { $self->{meta}->{source}->{IdentProvider}->{arg} => $cb_dispatcher };
686 my $cbmap = {};
687 my $value = refexpr2perlref($self->{node}->{source}->{payload}, $self->{node}->{source}->{propcache}->{property}, $cbmap);
688 $self->{node}->{source}->{propcache}->{value} = $value;
689 }
690
691 # encode values dependent on type of underlying storage here - expand cases...
692 my $storage_type = $self->{meta}->{target}->{storage}->{locator}->{type};
693 if ($storage_type eq 'DBI') {
694 # ...for sql
695 $self->{node}->{source}->{propcache}->{value} = quotesql($self->{node}->{source}->{propcache}->{value});
696 }
697 elsif ($storage_type eq 'Tangram') {
698 # iso? utf8 already possible?
699
700 } elsif ($storage_type eq 'LDAP') {
701 # TODO: encode utf8 here?
702 }
703
704 # store value to transfer map
705 $self->{node}->{map}->{$self->{node}->{target}->{propcache}->{property}} = $self->{node}->{source}->{propcache}->{value};
706
707 }
708 }
709
710
711 # TODO: $logger->dump( ... );
712 #$logger->debug( "sqlmap:" . "\n" . Dumper($self->{node}->{map}) );
713 #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
714 #print "entrystatus: ", Dumper($self->{node}), "\n";
715
716 }
717
718 sub _resolveNodeIdent {
719 my $self = shift;
720 my $descent = shift;
721
722 #print Dumper($self->{node}->{$descent});
723
724 # get to the payload
725 #my $item = $specifier->{item};
726 my $payload = $self->{node}->{$descent}->{payload};
727
728 # resolve method to get to the id of the given item
729 # we use global metadata and the given descent for this task
730 #my $ident = $self->{$descent}->id($item);
731 #my $ident = $self->{meta}->{$descent}->{storage}->id($item);
732
733 my $ident;
734 my $provider_method = $self->{meta}->{$descent}->{IdentProvider}->{method};
735 my $provider_arg = $self->{meta}->{$descent}->{IdentProvider}->{arg};
736
737 # resolve to ident
738 if ($provider_method eq 'property') {
739 $ident = $payload->{$provider_arg};
740
741 } elsif ($provider_method eq 'storage_method') {
742 #$ident = $self->{meta}->{$descent}->{storage}->id($item);
743 $ident = $self->{meta}->{$descent}->{storage}->$provider_arg($payload);
744 }
745
746 $self->{node}->{$descent}->{ident} = $ident;
747
748 return 1 if $ident;
749
750 }
751
752
753 sub _modifyNode {
754 my $self = shift;
755 my $descent = shift;
756 my $action = shift;
757 my $map = shift;
758 my $crit = shift;
759
760 # map for new style callbacks
761 my $map_callbacks = {};
762
763 # checks go first!
764
765 # TODO: this should be reviewed first - before extending ;-)
766 # TODO: this should be extended:
767 # count this cases inside the caller to this sub and provide a better overall message
768 # if this counts still zero in the end:
769 # "No nodes have been touched for modify: Do you have column-headers in your csv file?"
770 if (not defined $self->{node}) {
771 #$logger->critical( __PACKAGE__ . "->_modifyNode failed: \"$descent\" node is empty." );
772 #return;
773 }
774
775 # transfer callback nodes from value map to callback map - handle them afterwards! - (new style callbacks)
776 if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
777 foreach my $callback (keys %{$callbacks->{write}}) {
778 $map_callbacks->{write}->{$callback} = $map->{$callback};
779 delete $map->{$callback};
780 }
781 }
782
783
784 #print Dumper($self->{meta});
785
786 # DBI speaks SQL
787 if ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'DBI') {
788
789 #print Dumper($self->{node});
790 my $sql_main;
791 # translate map to sql
792 #print $action, "\n"; exit;
793 #print $self->{meta}->{$descent}->{node}, "\n"; exit;
794 #print "action:";
795 #print $action, "\n";
796 #$action = "anc";
797 #print "yai", "\n";
798
799 #print Dumper($map);
800 #delete $map->{cs};
801
802 if (lc($action) eq 'insert') {
803 $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_INSERT');
804 } elsif (lc $action eq 'update') {
805 $crit ||= "$self->{meta}->{$descent}->{IdentProvider}->{arg}='$self->{node}->{$descent}->{ident}'";
806 $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_UPDATE', $crit);
807 }
808
809 #$sql_main = "UPDATE currencies_csv SET oid='abcdef' WHERE text='Australian Dollar' AND key='AUD';";
810 #$sql_main = "UPDATE currencies_csv SET oid='huhu2' WHERE ekey='AUD'";
811
812 #print "sql: ", $sql_main, "\n";
813 #exit;
814
815 # transfer data
816 my $sqlHandle = $self->{meta}->{$descent}->{storage}->sendCommand($sql_main);
817
818 #exit;
819
820 # handle errors
821 if ($sqlHandle->err) {
822 #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
823 $self->{node}->{status}->{error} = {
824 statement => $sql_main,
825 state => $sqlHandle->state,
826 err => $sqlHandle->err,
827 errstr => $sqlHandle->errstr,
828 };
829 } else {
830 $self->{node}->{status}->{ok} = 1;
831 }
832
833 # Tangram does it the oo-way (naturally)
834 } elsif ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'Tangram') {
835 my $sql_main;
836 my $object;
837
838 # determine classname
839 my $classname = $self->{meta}->{$descent}->{node};
840
841 # properties to exclude
842 my @exclude = @{$self->{meta}->{$descent}->{subnodes_exclude}};
843
844
845 if (my $identProvider = $self->{meta}->{$descent}->{IdentProvider}) {
846 push @exclude, $identProvider->{arg};
847 }
848
849 # new feature:
850 # - check TypeProvider metadata property from other side
851 # - use argument (arg) inside as a classname for object creation on this side
852 #my $otherSide = $self->_otherSide($descent);
853 if (my $typeProvider = $self->{meta}->{$descent}->{TypeProvider}) {
854 #print Dumper($map);
855 $classname = $map->{$typeProvider->{arg}};
856 # remove nodes from map also (push nodes to "subnodes_exclude" list)
857 push @exclude, $typeProvider->{arg};
858 }
859
860 # exclude banned properties (remove from map)
861 #map { delete $self->{node}->{map}->{$_} } @{$self->{args}->{exclude}};
862 map { delete $map->{$_} } @exclude;
863
864 # list of properties
865 my @props = keys %{$map};
866
867 # transfer data
868 if (lc $action eq 'insert') {
869
870 # build array to initialize object
871 #my @initarray = ();
872 #map { push @initarray, $_, undef; } @props;
873
874 # make the object persistent in four steps:
875 # - raw create (perl / class tangram scope)
876 # - engine insert (tangram scope) ... this establishes inheritance - don't try to fill in inherited properties before!
877 # - raw fill-in from hash (perl scope)
878 # - engine update (tangram scope) ... this updates all properties just filled in
879
880 # create new object ...
881 #my $object = $classname->new( @initarray );
882 $object = $classname->new();
883
884 # ... pass to orm ...
885 $self->{meta}->{$descent}->{storage}->insert($object);
886
887 # ... and initialize with empty (undef'd) properties.
888 #print Dumper(@props);
889 map { $object->{$_} = undef; } @props;
890
891 # mix in values ...
892 hash2object($object, $map);
893
894 # ... and re-update@orm.
895 #print Dumper($object);
896 $self->{meta}->{$descent}->{storage}->update($object);
897
898 # asymmetry: get ident after insert
899 # TODO:
900 # - just do this if it is an IdentAuthority
901 # - use IdentProvider metadata here
902 #print Dumper($self->{meta}->{$descent});
903 my $oid = $self->{meta}->{$descent}->{storage}->id($object);
904 #print "oid: $oid", "\n";
905 $self->{node}->{$descent}->{ident} = $oid;
906
907
908 } elsif (lc $action eq 'update') {
909
910 # get fresh object from orm first
911 $object = $self->{meta}->{$descent}->{storage}->load($self->{node}->{$descent}->{ident});
912
913 #print Dumper($self->{node});
914
915 # mix in values
916 #print Dumper($object);
917 hash2object($object, $map);
918 #print Dumper($object);
919 #exit;
920 $self->{meta}->{$descent}->{storage}->update($object);
921 }
922
923 my $error = 0;
924
925 # handle new style callbacks - this is a HACK - do this without an eval!
926 #print Dumper($map);
927 #print "cb: ", Dumper($self->{meta}->{$descent}->{Callback});
928 #print Dumper($map_callbacks);
929 foreach my $node (keys %{$map_callbacks->{write}}) {
930 #print Dumper($node);
931 my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_write';
932 my $evalstring = $perl_callback . '( { object => $object, value => $map_callbacks->{write}->{$node}, storage => $self->{meta}->{$descent}->{storage} } );';
933 #print $evalstring, "\n"; exit;
934 eval($evalstring);
935 if ($@) {
936 $error = 1;
937 print $@, "\n";
938 }
939
940 #print "after eval", "\n";
941
942 if (!$error) {
943 # re-update@orm
944 $self->{meta}->{$descent}->{storage}->update($object);
945 }
946 }
947
948 # handle errors
949 if ($error) {
950 #print "error", "\n";
951 =pod
952 my $sqlHandle;
953 #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
954 $self->{node}->{status}->{error} = {
955 statement => $sql_main,
956 state => $sqlHandle->state,
957 err => $sqlHandle->err,
958 errstr => $sqlHandle->errstr,
959 };
960 =cut
961 # rollback....
962 #print "rollback", "\n";
963 $self->{meta}->{$descent}->{storage}->erase($object);
964 #print "after rollback", "\n";
965 } else {
966 $self->{node}->{status}->{ok} = 1;
967 }
968
969
970 }
971
972 }
973
974 # TODO:
975 # this should be split up into...
976 # - a "_statNode" (should just touch the node to check for existance)
977 # - a "_loadNode" (should load node completely)
978 # - maybe additionally a "loadNodeProperty" (may specify properties to load)
979 # - introduce $self->{nodecache} for this purpose
980 # TODO:
981 # should we:
982 # - not pass ident in here but resolve it via "$descent"?
983 # - refactor this and stuff it with additional debug/error message
984 # - this = the way the implicit load mechanism works
985 sub _statloadNode {
986
987 my $self = shift;
988 my $descent = shift;
989 my $ident = shift;
990 my $force = shift;
991
992 # fetch entry to retrieve checksum from
993 # was:
994 if (!$self->{node}->{$descent} || $force) {
995 # is:
996 #if (!$self->{node}->{$descent}->{item} || $force) {
997
998 if (!$ident) {
999 #print "\n", "Attempt to fetch entry implicitely by ident failed: no ident given! This may result in an insert if no write-protection is in the way.", "\n";
1000 return;
1001 }
1002
1003 # patch for DBD::CSV
1004 if ($ident && $ident eq 'Null') {
1005 return;
1006 }
1007
1008 #print "yai!", "\n";
1009
1010 my $query = {
1011 node => $self->{meta}->{$descent}->{node},
1012 subnodes => [qw( cs )],
1013 criterias => [
1014 { key => $self->{meta}->{$descent}->{IdentProvider}->{arg},
1015 op => 'eq',
1016 val => $ident },
1017 ]
1018 };
1019
1020 #print Dumper($query);
1021
1022 my $result = $self->{meta}->{$descent}->{storage}->sendQuery($query);
1023
1024 my $entry = $result->getNextEntry();
1025
1026 #print Dumper($entry);
1027 #print "pers: " . $self->{meta}->{$descent}->{storage}->is_persistent($entry), "\n";
1028 #my $state = $self->{meta}->{$descent}->{storage}->_fetch_object_state($entry, { name => 'TransactionHop' } );
1029 #print Dumper($state);
1030
1031 my $status = $result->getStatus();
1032
1033 #print Dumper($status);
1034
1035 # TODO: enhance error handling (store inside tc)
1036 #if (!$row) {
1037 # print "\n", "row error", "\n";
1038 # next;
1039 #}
1040
1041 # these checks run before actually loading payload- and meta-data to node-container
1042
1043 # 1st level - hard error
1044 if ($status && $status->{err}) {
1045 $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - hard error (that's ok): $status->{err}" );
1046 return;
1047 }
1048
1049 # 2nd level - logical (empty/notfound) error
1050 if (($status && $status->{empty}) || !$entry) {
1051 $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - logical error (that's ok)" );
1052 #print "no entry (logical)", "\n";
1053 return;
1054 }
1055
1056 #print Dumper($entry);
1057
1058 # was:
1059 # $self->{node}->{$descent}->{ident} = $ident;
1060 # is:
1061 # TODO: re-resolve ident from entry via metadata "IdentProvider" here - like elsewhere
1062 $self->{node}->{$descent}->{ident} = $ident;
1063 $self->{node}->{$descent}->{payload} = $entry;
1064
1065 }
1066
1067 return 1;
1068
1069 }
1070
1071 sub _doTransferToTarget {
1072 my $self = shift;
1073 my $action = shift;
1074 $self->_modifyNode('target', $action, $self->{node}->{map});
1075 }
1076
1077 sub _doModifySource_IdentChecksum {
1078 my $self = shift;
1079 my $ident_new = shift;
1080 # this changes an old node to a new one including ident and checksum
1081 # TODO:
1082 # - eventually introduce an external resource to store this data to
1083 # - we won't have to "re"-modify the source node here
1084 my $map = {
1085 $self->{meta}->{source}->{IdentProvider}->{arg} => $ident_new,
1086 cs => $self->{node}->{target}->{checksum},
1087 };
1088
1089 #print Dumper($map);
1090 #print Dumper($self->{node});
1091 #exit;
1092
1093 $self->_modifyNode('source', 'update', $map);
1094 }
1095
1096
1097 # this is a shortcut method
1098 # ... let's try to avoid _any_ redundant code in here (ok... - at the cost of method lookups...)
1099 sub _getNodeList {
1100 my $self = shift;
1101 my $descent = shift;
1102 my $filter = shift;
1103 return $self->{meta}->{$descent}->{storage}->getListFiltered($self->{meta}->{$descent}->{node}, $filter);
1104 }
1105
1106
1107 sub _prepareNode_MetaProperties {
1108 my $self = shift;
1109 my $descent = shift;
1110
1111 $logger->info( __PACKAGE__ . "->_prepareNode_MetaProperties( descent $descent )" );
1112
1113 # TODO: this should (better) be: "my $firstnode = $self->_getFirstNode($descent);"
1114 my $list = $self->_getNodeList($descent);
1115
1116 # get first node
1117 my $firstnode = $list->[0];
1118
1119 # check if node contains meta properties/nodes
1120 # TODO: "cs" is hardcoded here!
1121 my @required = ( $self->{meta}->{$descent}->{IdentProvider}->{arg}, 'cs' );
1122 my @found = keys %$firstnode;
1123 #my @diff = getDifference(\@found, \@required);
1124 my $diff = getDifference(\@required, \@found);
1125 #print Dumper(@found);
1126 #print Dumper(@required);
1127 #print Dumper(@diff);
1128 #if (!$#diff || $#diff == -1) {
1129 if (isEmpty($diff)) {
1130 $logger->warning( __PACKAGE__ . "->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter..." );
1131 foreach (@required) {
1132 my $sql = "ALTER TABLE $self->{meta}->{$descent}->{node} ADD COLUMN $_";
1133 #print "sql: $sql", "\n";
1134 my $res = $self->{meta}->{$descent}->{storage}->sendCommand($sql);
1135 #print Dumper($res->getStatus());
1136 }
1137 }
1138
1139 }
1140
1141 sub _prepareNode_DummyIdent {
1142 my $self = shift;
1143 my $descent = shift;
1144
1145 $logger->info( __PACKAGE__ . "->_prepareNode_DummyIdent( descent $descent )" );
1146
1147 my $list = $self->_getNodeList($descent);
1148 #print Dumper($list);
1149 my $i = 0;
1150 my $ident_base = 5678983;
1151 my $ident_appendix = '0001';
1152 foreach my $node (@$list) {
1153 my $ident_dummy = $i + $ident_base;
1154 $ident_dummy .= $ident_appendix;
1155 my $map = {
1156 $self->{meta}->{$descent}->{IdentProvider}->{arg} => $ident_dummy,
1157 cs => undef,
1158 };
1159
1160 # diff lists and ...
1161 my $diff = getDifference([keys %$node], [keys %$map]);
1162 next if $#{$diff} == -1;
1163
1164 # ... build criteria including all columns
1165 my @crits;
1166 foreach my $property (@$diff) {
1167 next if !$property;
1168 my $value = $node->{$property};
1169 next if !$value;
1170 push @crits, "$property='" . quotesql($value) . "'";
1171 }
1172 my $crit = join ' AND ', @crits;
1173 print "p" if $self->{verbose};
1174
1175 #print Dumper($map);
1176 #print Dumper($crit);
1177
1178 $self->_modifyNode($descent, 'update', $map, $crit);
1179 $i++;
1180 }
1181
1182 print "\n" if $self->{verbose};
1183
1184 if (!$i) {
1185 $logger->warning( __PACKAGE__ . "->_prepareNode_DummyIdent: no nodes touched" );
1186 }
1187
1188 }
1189
1190 # TODO: handle this in an abstract way (wipe out use of 'source' and/or 'target' inside core)
1191 sub _otherSide {
1192 my $self = shift;
1193 my $descent = shift;
1194 return 'source' if $descent eq 'target';
1195 return 'target' if $descent eq 'source';
1196 return '';
1197 }
1198
1199 sub _erase_all {
1200 my $self = shift;
1201 my $descent = shift;
1202 #my $node = shift;
1203 my $node = $self->{meta}->{$descent}->{node};
1204 $self->{meta}->{$descent}->{storage}->eraseAll($node);
1205 }
1206
1207 1;

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