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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (show annotations)
Mon Dec 23 07:10:59 2002 UTC (21 years, 6 months ago) by joko
Branch: MAIN
Changes since 1.10: +11 -3 lines
+ using MD5 for checksum generation again - the 32-bit integer hash from DBI seems to be too lazy

1 ## $Id: Sync.pm,v 1.10 2002/12/19 01:07:16 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: Sync.pm,v $
9 ## Revision 1.10 2002/12/19 01:07:16 joko
10 ## + fixed output done via $logger
11 ##
12 ## Revision 1.9 2002/12/16 07:02:34 jonen
13 ## + added comment
14 ##
15 ## Revision 1.8 2002/12/15 02:03:09 joko
16 ## + fixed logging-messages
17 ## + additional metadata-checks
18 ##
19 ## Revision 1.7 2002/12/13 21:49:34 joko
20 ## + sub configure
21 ## + sub checkOptions
22 ##
23 ## Revision 1.6 2002/12/06 04:49:10 jonen
24 ## + disabled output-puffer here
25 ##
26 ## Revision 1.5 2002/12/05 08:06:05 joko
27 ## + bugfix with determining empty fields (Null) with DBD::CSV
28 ## + debugging
29 ## + updated comments
30 ##
31 ## Revision 1.4 2002/12/03 15:54:07 joko
32 ## + {import}-flag is now {prepare}-flag
33 ##
34 ## Revision 1.3 2002/12/01 22:26:59 joko
35 ## + minor cosmetics for logging
36 ##
37 ## Revision 1.2 2002/12/01 04:43:25 joko
38 ## + mapping deatil entries may now be either an ARRAY or a HASH
39 ## + erase flag is used now (for export-operations)
40 ## + expressions to refer to values inside deep nested structures
41 ## - removed old mappingV2-code
42 ## + cosmetics
43 ## + sub _erase_all
44 ##
45 ## Revision 1.1 2002/11/29 04:45:50 joko
46 ## + initial check in
47 ##
48 ## Revision 1.1 2002/10/10 03:44:21 cvsjoko
49 ## + new
50 ## ----------------------------------------------------------------------------------------
51
52
53 package Data::Transfer::Sync;
54
55 use strict;
56 use warnings;
57
58 use Data::Dumper;
59 #use Hash::Merge qw( merge );
60
61 use misc::HashExt;
62 use libp qw( md5_base64 );
63 use libdb qw( quotesql hash2Sql );
64 use Data::Transform::Deep qw( hash2object refexpr2perlref );
65 use Data::Compare::Struct qw( getDifference isEmpty );
66
67 # get logger instance
68 my $logger = Log::Dispatch::Config->instance;
69
70 $| = 1;
71
72 sub new {
73 my $invocant = shift;
74 my $class = ref($invocant) || $invocant;
75 my $self = {};
76 $logger->debug( __PACKAGE__ . "->new(@_)" );
77 bless $self, $class;
78 $self->configure(@_);
79 return $self;
80 }
81
82
83 sub configure {
84 my $self = shift;
85 my @args = @_;
86 if (!isEmpty(\@args)) {
87 my %properties = @_;
88 # merge args to properties
89 map { $self->{$_} = $properties{$_}; } keys %properties;
90 $self->_init();
91 } else {
92 #print "no args!", "\n";
93 }
94 #print Dumper($self);
95 }
96
97 sub _init {
98 my $self = shift;
99
100 $self->{configured} = 1;
101
102 # build new container if necessary
103 $self->{container} = Data::Storage::Container->new() if !$self->{container};
104
105 # add storages to container (optional)
106 foreach (keys %{$self->{storages}}) {
107 $self->{container}->addStorage($_, $self->{storages}->{$_});
108 }
109
110 # tag storages with id-authority and checksum-provider information
111 # TODO: better store tag inside metadata to hold bits together!
112 map { $self->{container}->{storage}->{$_}->{isIdentAuthority} = 1 } @{$self->{id_authorities}};
113 map { $self->{container}->{storage}->{$_}->{isChecksumAuthority} = 1; } @{$self->{checksum_authorities}};
114 map { $self->{container}->{storage}->{$_}->{isWriteProtected} = 1; } @{$self->{write_protected}};
115
116 }
117
118
119 sub prepareOptions {
120
121 my $self = shift;
122 my $opts = shift;
123
124 #print Dumper($opts);
125
126 $opts->{mode} ||= '';
127 $opts->{erase} ||= 0;
128 #$opts->{import} ||= 0;
129
130 $logger->notice( __PACKAGE__ . "->prepareOptions( source_node $opts->{source_node} mode $opts->{mode} erase $opts->{erase} prepare $opts->{prepare} )");
131
132 if (!$opts->{mapping} || !$opts->{mapping_module}) {
133 $logger->warning( __PACKAGE__ . "->prepareOptions: No mapping supplied - please check key 'mappings' in BizWorks/Config.pm");
134 }
135
136 my $evstring = "use $opts->{mapping_module};";
137 eval($evstring);
138 if ($@) {
139 $logger->warning( __PACKAGE__ . "->prepareOptions: error while trying to access mapping - $@");
140 return;
141 }
142
143 # resolve mapping metadata (returned from sub)
144 my $mapObject = $opts->{mapping_module}->new();
145 #print Dumper($map);
146 my $source_node_name = $opts->{source_node};
147 # check if mapping for certain node is contained in mapping object
148 if (!$mapObject->can($source_node_name)) {
149 $logger->warning( __PACKAGE__ . "->prepareOptions: Can't access mapping for node \"$source_node_name\" - please check $opts->{mapping_module}.");
150 return;
151 }
152 my $map = $mapObject->$source_node_name;
153
154 # remove asymmetries from $map (patch keys)
155 $map->{source_node} = $map->{source}; delete $map->{source};
156 $map->{target_node} = $map->{target}; delete $map->{target};
157 $map->{mapping} = $map->{details}; delete $map->{details};
158 $map->{direction} = $map->{mode}; delete $map->{mode};
159
160 # defaults (mostly for backward-compatibility)
161 $map->{source_node} ||= $source_node_name;
162 $map->{source_ident} ||= 'storage_method:id';
163 $map->{target_ident} ||= 'property:oid';
164 $map->{direction} ||= $opts->{mode}; # | PUSH | PULL | FULL
165 $map->{method} ||= 'checksum'; # | timestamp
166 $map->{source_exclude} ||= [qw( cs )];
167
168 # merge map to opts
169 map { $opts->{$_} = $map->{$_}; } keys %$map;
170
171 #print Dumper($opts);
172
173 # TODO: move this to checkOptions...
174
175 # check - do we have a target?
176 if (!$opts->{target_node}) {
177 $logger->warning( __PACKAGE__ . "->prepareOptions: No target given - please check metadata declaration.");
178 return;
179 }
180
181
182 #return $opts;
183 return 1;
184
185 }
186
187
188 sub checkOptions {
189 my $self = shift;
190 my $opts = shift;
191
192 my $result = 1;
193
194 # check - do we have a target node?
195 if (!$opts->{target_node}) {
196 $logger->warning( __PACKAGE__ . "->checkOptions: Error while resolving resource metadata - no 'target node' could be determined.");
197 $result = 0;
198 }
199
200 # check - do we have a mapping?
201 if (!$opts->{mapping} && !$opts->{mapping_module}) {
202 $logger->warning( __PACKAGE__ . "->checkOptions: Error while resolving resource metadata - no 'mapping' could be determined.");
203 $result = 0;
204 }
205
206 return $result;
207
208 }
209
210
211 # TODO: some feature to show off the progress of synchronization (cur/max * 100)
212 sub syncNodes {
213
214 my $self = shift;
215 my $args = shift;
216
217 if (!$self->{configured}) {
218 $logger->critical( __PACKAGE__ . "->syncNodes: Synchronization object is not configured/initialized correctly." );
219 return;
220 }
221
222 # remember arguments through the whole processing
223 $self->{args} = $args;
224
225 $logger->debug( __PACKAGE__ . "->syncNodes: starting" );
226
227 # hash to hold and/or fill in metadata required for the processing
228 $self->{meta} = {};
229
230 # hash to sum up results
231 my $direction_arrow = '';
232
233 # detect synchronization method to determine which optical symbol (directed arrow) to use
234 if (lc $self->{args}->{direction} eq 'push') {
235 $direction_arrow = '->';
236 } elsif (lc $self->{args}->{direction} eq 'pull') {
237 $direction_arrow = '<-';
238 } elsif (lc $self->{args}->{direction} eq 'full') {
239 $direction_arrow = '<->';
240 } else {
241 }
242
243 # decompose identifiers for each partner
244 # TODO: refactor!!! take this list from already established/given metadata
245 foreach ('source', 'target') {
246
247 # get/set metadata for further processing
248
249 # Partner and Node (e.g.: "L:Country" or "R:countries.csv")
250 if (my $item = $self->{args}->{$_}) {
251 my @item = split(':', $item);
252 $self->{meta}->{$_}->{dbkey} = $item[0];
253 $self->{meta}->{$_}->{node} = $item[1];
254 }
255
256 # Filter
257 if (my $item_filter = $self->{args}->{$_ . '_filter'}) {
258 $self->{meta}->{$_}->{filter} = $item_filter;
259 }
260
261 # IdentProvider
262 if (my $item_ident = $self->{args}->{$_ . '_ident'}) {
263 my @item_ident = split(':', $item_ident);
264 $self->{meta}->{$_}->{IdentProvider} = { method => $item_ident[0], arg => $item_ident[1] };
265 }
266
267 # TODO: ChecksumProvider
268
269 # exclude properties/subnodes
270 if (my $item_exclude = $self->{args}->{$_ . '_exclude'}) {
271 $self->{meta}->{$_}->{subnodes_exclude} = $item_exclude;
272 }
273
274 # TypeProvider
275 if (my $item_type = $self->{args}->{$_ . '_type'}) {
276 my @item_type = split(':', $item_type);
277 $self->{meta}->{$_}->{TypeProvider} = { method => $item_type[0], arg => $item_type[1] };
278 }
279
280 # Callbacks - writers (will be triggered _before_ writing to target)
281 if (my $item_writers = $self->{args}->{$_ . '_callbacks_write'}) {
282 my $descent = $_; # this is important since the following code inside the map wants to use its own context variables
283 map { $self->{meta}->{$descent}->{Callback}->{write}->{$_}++; } @$item_writers;
284 }
285
286 # Callbacks - readers (will be triggered _after_ reading from source)
287 if (my $item_readers = $self->{args}->{$_ . '_callbacks_read'}) {
288 my $descent = $_;
289 map { $self->{meta}->{$descent}->{Callback}->{read}->{$_}++; } @$item_readers;
290 }
291
292 # resolve storage objects
293 #$self->{$_} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
294 # relink references to metainfo
295 $self->{meta}->{$_}->{storage} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
296 #print "iiiiisprov: ", Dumper($self->{meta}->{$_}->{storage}), "\n";
297 }
298
299 #print Dumper($self->{meta});
300
301 $logger->info( __PACKAGE__ . "->syncNodes: source=$self->{meta}->{source}->{dbkey}/$self->{meta}->{source}->{node} $direction_arrow target=$self->{meta}->{target}->{dbkey}/$self->{meta}->{target}->{node}" );
302
303 # build mapping
304 # incoming: and Array of node map entries (Array or Hash) - e.g.
305 # [ 'source:item_name' => 'target:class_val' ]
306 # { source => 'event->startDateTime', target => 'begindate' }
307 foreach (@{$self->{args}->{mapping}}) {
308 if (ref $_ eq 'ARRAY') {
309 my @entry1 = split(':', $_->[0]);
310 my @entry2 = split(':', $_->[1]);
311 my $descent = [];
312 my $node = [];
313 $descent->[0] = $entry1[0];
314 $descent->[1] = $entry2[0];
315 $node->[0] = $entry1[1];
316 $node->[1] = $entry2[1];
317 push @{$self->{meta}->{$descent->[0]}->{childnodes}}, $node->[0];
318 push @{$self->{meta}->{$descent->[1]}->{childnodes}}, $node->[1];
319 } elsif (ref $_ eq 'HASH') {
320 foreach my $entry_key (keys %$_) {
321 my $entry_val = $_->{$entry_key};
322 push @{$self->{meta}->{$entry_key}->{childnodes}}, $entry_val;
323 }
324 }
325
326 }
327
328 #print Dumper($self->{meta});
329
330 # check partners/nodes: does partner exist / is node available?
331 foreach my $partner (keys %{$self->{meta}}) {
332
333 # 1. check partners & storages
334 if (!$self->{meta}->{$partner}) {
335 $logger->critical( __PACKAGE__ . "->syncNodes: Could not find partner '$partner' in configuration metadata." );
336 return;
337 }
338
339 my $dbkey = $self->{meta}->{$partner}->{dbkey};
340
341 if (!$self->{meta}->{$partner}->{storage}) {
342 $logger->critical( __PACKAGE__ . "->syncNodes: Could not access storage of partner '$partner' (named '$dbkey'), looks like a configuration-error." );
343 return;
344 }
345
346 # TODO:
347 # 2. check if partners (and nodes?) are actually available....
348 # eventually pre-check mode of access-attempt (read/write) here to provide an "early-croak" if possible
349
350 # 3. check nodes
351 next if $self->{meta}->{$partner}->{storage}->{locator}->{type} eq 'DBI'; # HACK for DBD::CSV - re-enable for others
352 # get node-name
353 my $node = $self->{meta}->{$partner}->{node};
354 if (!$self->{meta}->{$partner}->{storage}->existsChildNode($node)) {
355 $logger->critical( __PACKAGE__ . "->syncNodes: Could not reach node \"$node\" at partner \"$partner\"." );
356 return;
357 }
358
359 }
360
361 # TODO:
362 # + if action == PUSH: start processing
363 # -+ if action == PULL: swap metadata and start processing
364 # - if action == FULL: start processing, then swap metadata and (re-)start processing
365
366 #print Dumper($self->{args});
367
368 # manipulate metainfo according to direction of synchronization
369 if (lc $self->{args}->{direction} eq 'push') {
370 # just do it ...
371 } elsif (lc $self->{args}->{direction} eq 'pull') {
372 #print "=======SWAP", "\n";
373 # swap
374 ($self->{meta}->{source}, $self->{meta}->{target}) =
375 ($self->{meta}->{target}, $self->{meta}->{source});
376 } elsif (lc $self->{args}->{direction} eq 'full') {
377 } else {
378 }
379
380 # import flag means: prepare the source node to be syncable
381 # this is useful if there are e.g. no "ident" or "checksum" columns yet inside a DBI like (row-based) storage
382 if ($self->{args}->{prepare}) {
383 $self->_prepareNode_MetaProperties('source');
384 $self->_prepareNode_DummyIdent('source');
385 #return;
386 #$self->_erase_all($opts->{source_node});
387 }
388
389 # erase flag means: erase the target
390 #if ($opts->{erase}) {
391 if ($self->{args}->{erase}) {
392 # TODO: move this method to the scope of the synchronization core and wrap it around different handlers
393 #print "ERASE", "\n";
394 $self->_erase_all('target');
395 }
396
397 $self->_syncNodes();
398
399 }
400
401
402 # TODO: abstract the hardwired use of "source" and "target" in here somehow - hmmmm....... /(="§/%???
403 sub _syncNodes {
404
405 my $self = shift;
406
407 my $tc = OneLineDumpHash->new( {} );
408 my $results;
409
410 # set of objects is already in $self->{args}
411 # TODO: make independent of the terminology "object..."
412 $results = $self->{args}->{objectSet} if $self->{args}->{objectSet};
413
414 # apply filter
415 if (my $filter = $self->{meta}->{source}->{filter}) {
416 #print Dumper($filter);
417 #exit;
418 $results ||= $self->_getNodeList('source', $filter);
419 }
420
421 # get reference to node list from convenient method provided by CORE-HANDLE
422 #$results ||= $self->{source}->getListUnfiltered($self->{meta}->{source}->{node});
423 #$results ||= $self->{meta}->{source}->{storage}->getListUnfiltered($self->{meta}->{source}->{node});
424 $results ||= $self->_getNodeList('source');
425
426 # checkpoint: do we actually have a list to iterate through?
427 if (!$results || !@{$results}) {
428 $logger->notice( __PACKAGE__ . "->syncNodes: No nodes to synchronize." );
429 return;
430 }
431
432 # dereference
433 my @results = @{$results};
434
435 # iterate through set
436 foreach my $source_node_real (@results) {
437
438 $tc->{total}++;
439
440 #print "======================== iter", "\n";
441
442 # clone object (in case we have to modify it here)
443 # TODO:
444 # - is a "deep_copy" needed here if occouring modifications take place?
445 # - puuhhhh, i guess a deep_copy would destroy tangram mechanisms?
446 # - after all, just take care for now that this object doesn't get updated!
447 my $source_node = $source_node_real;
448
449 # modify entry - handle new style callbacks (the readers)
450 #print Dumper($source_node);
451 #exit;
452
453 my $descent = 'source';
454
455 # handle callbacks right now while scanning them (asymmetric to the writers)
456 my $map_callbacks = {};
457 if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
458
459 my $error = 0;
460
461 foreach my $node (keys %{$callbacks->{read}}) {
462
463 my $object = $source_node;
464 my $value; # = $source_node->{$node};
465
466 # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
467 my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_read';
468 my $evalstring = 'return ' . $perl_callback . '( { object => $object, property => $node, value => $value, storage => $self->{meta}->{$descent}->{storage} } );';
469 #print $evalstring, "\n"; exit;
470 my $cb_result = eval($evalstring);
471 if ($@) {
472 die $@;
473 $error = 1;
474 print $@, "\n";
475 }
476 # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
477
478 $source_node->{$node} = $cb_result;
479
480 }
481
482 }
483
484 #print Dumper($source_node);
485
486 # exclude defined fields (simply delete from object)
487 map { delete $source_node->{$_} } @{$self->{meta}->{source}->{subnodes_exclude}};
488
489 # here we accumulate information about the status of the current node (payload/row/object/item/entry)
490 $self->{node} = {};
491 $self->{node}->{source}->{payload} = $source_node;
492
493 #print "res - ident", "\n";
494
495 # determine ident of entry
496 my $identOK = $self->_resolveNodeIdent('source');
497 #if (!$identOK && lc $self->{args}->{direction} ne 'import') {
498 if (!$identOK) {
499 #print Dumper($self->{meta}->{source});
500 $logger->critical( __PACKAGE__ . "->syncNodes: No ident found in source node \"$self->{meta}->{source}->{node}\", try to \"prepare\" this node first?" );
501 return;
502 }
503
504 #print "statload", "\n";
505 #print "ident: ", $self->{node}->{source}->{ident}, "\n";
506 #print Dumper($self->{node});
507
508 my $statOK = $self->_statloadNode('target', $self->{node}->{source}->{ident});
509
510 #print Dumper($self->{node});
511
512 # mark node as new either if there's no ident or if stat/load failed
513 if (!$statOK) {
514 $self->{node}->{status}->{new} = 1;
515 print "n" if $self->{verbose};
516 }
517
518 #print "checksum", "\n";
519
520 # determine status of entry by synchronization method
521 if ( (lc $self->{args}->{method} eq 'checksum') ) {
522 #if ( $statOK && (lc $self->{args}->{method} eq 'checksum') ) {
523 #if ( !$self->{node}->{status}->{new} && (lc $self->{args}->{method} eq 'checksum') ) {
524
525 # TODO:
526 # is this really worth a "critical"???
527 # no - it should just be a debug appendix i believe
528
529 #print "readcs", "\n";
530
531 # calculate checksum of source node
532 #$self->_calcChecksum('source');
533 if (!$self->_readChecksum('source')) {
534 $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"source\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
535 $tc->{skip}++;
536 print "s" if $self->{verbose};
537 next;
538 }
539
540 # get checksum from synchronization target
541 $self->_readChecksum('target');
542 #if (!$self->_readChecksum('target')) {
543 # $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"target\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
544 # next;
545 #}
546
547 # pre flight check: do we actually have a checksum provided?
548 #if (!$self->{node}->{source}->{checksum}) {
549 # print "Source checksum for entry with ident \"$self->{node}->{source}->{ident}\" could not be calculated, maybe it's missing?.", "\n";
550 # return;
551 #}
552
553 # determine if entry is "new" or "dirty"
554 # after all, this seems to be the point where the hammer falls.....
555 print "c" if $self->{verbose};
556 $self->{node}->{status}->{new} = !$self->{node}->{target}->{checksum};
557 if (!$self->{node}->{status}->{new}) {
558 $self->{node}->{status}->{dirty} =
559 $self->{node}->{status}->{new} ||
560 (!$self->{node}->{source}->{checksum} || !$self->{node}->{target}->{checksum}) ||
561 ($self->{node}->{source}->{checksum} ne $self->{node}->{target}->{checksum}) ||
562 $self->{args}->{force};
563 }
564
565 }
566
567 # first reaction on entry-status: continue with next entry if the current is already "in sync"
568 if (!$self->{node}->{status}->{new} && !$self->{node}->{status}->{dirty}) {
569 $tc->{in_sync}++;
570 next;
571 }
572
573 # build map to actually transfer the data from source to target
574 $self->_buildMap();
575
576
577 #print Dumper($self->{node}); exit;
578
579 #print "attempt", "\n";
580
581 # additional (new) checks for feature "write-protection"
582 if ($self->{meta}->{target}->{storage}->{isWriteProtected}) {
583 $tc->{attempt_transfer}++;
584 print "\n" if $self->{verbose};
585 $logger->notice( __PACKAGE__ . "->syncNodes: Target is write-protected. Will not insert or modify node. " .
586 "(Ident: $self->{node}->{source}->{ident} " . "Dump:\n" . Dumper($self->{node}->{source}->{payload}) . ")" );
587 print "\n" if $self->{verbose};
588 $tc->{skip}++;
589 next;
590 }
591
592 # transfer contents of map to target
593 if ($self->{node}->{status}->{new}) {
594 $tc->{attempt_new}++;
595 $self->_doTransferToTarget('insert');
596 # asymmetry: refetch node from target to re-calculate new ident and checksum (TODO: is IdentAuthority of relevance here?)
597 #print Dumper($self->{node});
598 $self->_statloadNode('target', $self->{node}->{target}->{ident}, 1);
599 $self->_readChecksum('target');
600
601 } elsif ($self->{node}->{status}->{dirty}) {
602 $tc->{attempt_modify}++;
603 # asymmetry: get ident before updating (TODO: is IdentAuthority of relevance here?)
604 $self->{node}->{target}->{ident} = $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}};
605 $self->_doTransferToTarget('update');
606 $self->_readChecksum('target');
607 }
608
609 if ($self->{node}->{status}->{ok}) {
610 $tc->{ok}++;
611 print "t" if $self->{verbose};
612 }
613
614 if ($self->{node}->{status}->{error}) {
615 $tc->{error}++;
616 push( @{$tc->{error_per_row}}, $self->{node}->{status}->{error} );
617 print "e" if $self->{verbose};
618 }
619
620 # change ident in source (take from target), if transfer was ok and target is an IdentAuthority
621 # this is (for now) called a "retransmit" indicated by a "r"-character when verbosing
622 if ($self->{node}->{status}->{ok} && $self->{meta}->{target}->{storage}->{isIdentAuthority}) {
623 print "r" if $self->{verbose};
624 #print Dumper($self->{meta});
625 #print Dumper($self->{node});
626 #exit;
627 $self->_doModifySource_IdentChecksum($self->{node}->{target}->{ident});
628 }
629
630 print ":" if $self->{verbose};
631
632 }
633
634 print "\n" if $self->{verbose};
635
636 # build user-message from some stats
637 my $msg = "statistics: $tc";
638
639 if ($tc->{error_per_row}) {
640 $msg .= "\n";
641 $msg .= "errors from \"error_per_row\":" . "\n";
642 $msg .= Dumper($tc->{error_per_row});
643 }
644
645 # todo!!!
646 #sysevent( { usermsg => $msg, level => $level }, $taskEvent );
647 $logger->info( __PACKAGE__ . "->syncNodes: $msg" );
648
649 return $tc;
650
651 }
652
653
654 # refactor this as some core-function to do a generic dump resolving data-encapsulations of e.g. Set::Object
655 sub _dumpCompact {
656 my $self = shift;
657
658 #my $vars = \@_;
659 my @data = ();
660
661 my $count = 0;
662 foreach (@_) {
663 my $item = {};
664 foreach my $key (keys %$_) {
665 my $val = $_->{$key};
666
667 #print Dumper($val);
668
669 if (ref $val eq 'Set::Object') {
670 #print "========================= SET", "\n";
671 #print Dumper($val);
672 #print Dumper($val->members());
673 #$val = $val->members();
674 #$vars->[$count]->{$key} = $val->members() if $val->can("members");
675 #$item->{$key} = $val->members() if $val->can("members");
676 $item->{$key} = $val->members();
677 #print Dumper($vars->[$count]->{$key});
678
679 } else {
680 $item->{$key} = $val;
681 }
682
683 }
684 push @data, $item;
685 $count++;
686 }
687
688 #print "Dump:", Dumper(@data), "\n";
689
690 $Data::Dumper::Indent = 0;
691 my $result = Dumper(@data);
692 $Data::Dumper::Indent = 2;
693 return $result;
694
695 }
696
697
698 sub _calcChecksum {
699
700 my $self = shift;
701 my $descent = shift;
702 my $specifier = shift;
703
704 # calculate checksum for current object
705 my $ident = $self->{node}->{$descent}->{ident};
706
707 # build dump of this node
708 my $payload = $self->{node}->{$descent}->{payload};
709 #my $dump = $ident . "\n" . $item->quickdump();
710 #my $dump = $ident . "\n" . Dumper($item);
711 my $dump = $ident . "\n" . $self->_dumpCompact($payload);
712
713 # TODO: $logger->dump( ... );
714 #$logger->debug( __PACKAGE__ . ": " . $dump );
715 #$logger->dump( __PACKAGE__ . ": " . $dump );
716
717 # calculate checksum from dump
718 # note: the 32-bit integer hash from DBI seems
719 # to generate duplicates with small payloads already in ranges of hundreds of items/rows!!!
720 # try to avoid to use it or try to use it only for payloads greater than, hmmm, let's say 30 chars?
721 # (we had about 15 chars average per item (row))
722
723 # md5-based fingerprint, base64 encoded (from Digest::MD5)
724 $self->{node}->{$descent}->{checksum} = md5_base64($dump) . '==';
725 # 32-bit integer "hash" value (maybe faster?) (from DBI)
726 #$self->{node}->{$descent}->{checksum} = DBI::hash($dump, 1);
727
728 # signal good
729 return 1;
730
731 }
732
733
734 sub _readChecksum {
735 my $self = shift;
736
737 my $descent = shift;
738
739 #print "getcheck:", "\n"; print Dumper($self->{node}->{$descent});
740
741 if (!$self->{node}->{$descent}) {
742 # signal checksum bad
743 return;
744 }
745
746 # get checksum for current entry
747 # TODO: don't have the checksum column/property hardcoded as "cs" here, make this configurable somehow
748
749 if ($self->{meta}->{$descent}->{storage}->{isChecksumAuthority}) {
750 #$self->{node}->{$descent}->{checksum} = $entry->{cs};
751 #$self->{node}->{$descent}->{checksum} = $self->_calcChecksum($descent); # $entry->{cs};
752 #print "descent: $descent", "\n";
753 $self->_calcChecksum($descent);
754 #print "checksum: ", $self->{node}->{$descent}->{checksum}, "\n";
755 } else {
756
757 #$self->{node}->{$descent}->{checksum} = $entry->{cs};
758 $self->{node}->{$descent}->{checksum} = $self->{node}->{$descent}->{payload}->{cs};
759 }
760
761 # signal checksum good
762 return 1;
763
764 }
765
766
767 sub _buildMap {
768
769 my $self = shift;
770
771 # field-structure for building sql
772 # mapping of sql-fieldnames to object-attributes
773 $self->{node}->{map} = {};
774
775 # manually set ...
776 # ... object-id
777 $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}} = $self->{node}->{source}->{ident};
778 # ... checksum
779 $self->{node}->{map}->{cs} = $self->{node}->{source}->{checksum};
780
781 #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
782
783 # for transferring flat structures via simple (1:1) mapping
784 # TODO: diff per property / property value
785
786 if ($self->{args}->{mapping}) {
787 # apply mapping from $self->{args}->{mapping} to $self->{node}->{map}
788 #foreach my $key (@{$self->{meta}->{source}->{childnodes}}) {
789 my @childnodes = @{$self->{meta}->{source}->{childnodes}};
790 for (my $mapidx = 0; $mapidx <= $#childnodes; $mapidx++) {
791 #my $map_right = $self->{args}->{mapping}->{$key};
792
793 $self->{node}->{source}->{propcache} = {};
794 $self->{node}->{target}->{propcache} = {};
795
796 # get property name
797 $self->{node}->{source}->{propcache}->{property} = $self->{meta}->{source}->{childnodes}->[$mapidx];
798 $self->{node}->{target}->{propcache}->{property} = $self->{meta}->{target}->{childnodes}->[$mapidx];
799 #print "map: $map_right", "\n";
800
801 # get property value
802 my $value;
803
804 # detect for callback - old style - (maybe the better???)
805 if (ref($self->{node}->{target}->{map}) eq 'CODE') {
806 #$value = &$map_right($objClone);
807 } else {
808 # plain (scalar?) value
809 #$value = $objClone->{$map_right};
810 $self->{node}->{source}->{propcache}->{value} = $self->{node}->{source}->{payload}->{$self->{node}->{source}->{propcache}->{property}};
811 }
812 #$self->{node}->{map}->{$key} = $value;
813
814 # detect expression
815 # for transferring deeply nested structures described by expressions
816 #print "val: $self->{node}->{source}->{propcache}->{value}", "\n";
817 if ($self->{node}->{source}->{propcache}->{property} =~ s/^expr://) {
818
819 # create an anonymous sub to act as callback target dispatcher
820 my $cb_dispatcher = sub {
821 #print "=============== CALLBACK DISPATCHER", "\n";
822 #print "ident: ", $self->{node}->{source}->{ident}, "\n";
823 #return $self->{node}->{source}->{ident};
824
825 };
826
827
828 #print Dumper($self->{node});
829
830 # build callback map for helper function
831 #my $cbmap = { $self->{meta}->{source}->{IdentProvider}->{arg} => $cb_dispatcher };
832 my $cbmap = {};
833 my $value = refexpr2perlref($self->{node}->{source}->{payload}, $self->{node}->{source}->{propcache}->{property}, $cbmap);
834 $self->{node}->{source}->{propcache}->{value} = $value;
835 }
836
837 # encode values dependent on type of underlying storage here - expand cases...
838 my $storage_type = $self->{meta}->{target}->{storage}->{locator}->{type};
839 if ($storage_type eq 'DBI') {
840 # ...for sql
841 $self->{node}->{source}->{propcache}->{value} = quotesql($self->{node}->{source}->{propcache}->{value});
842 }
843 elsif ($storage_type eq 'Tangram') {
844 # iso? utf8 already possible?
845
846 } elsif ($storage_type eq 'LDAP') {
847 # TODO: encode utf8 here?
848 }
849
850 # store value to transfer map
851 $self->{node}->{map}->{$self->{node}->{target}->{propcache}->{property}} = $self->{node}->{source}->{propcache}->{value};
852
853 }
854 }
855
856
857 # TODO: $logger->dump( ... );
858 #$logger->debug( "sqlmap:" . "\n" . Dumper($self->{node}->{map}) );
859 #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
860 #print "entrystatus: ", Dumper($self->{node}), "\n";
861
862 }
863
864 sub _resolveNodeIdent {
865 my $self = shift;
866 my $descent = shift;
867
868 #print Dumper($self->{node}->{$descent});
869
870 # get to the payload
871 #my $item = $specifier->{item};
872 my $payload = $self->{node}->{$descent}->{payload};
873
874 # resolve method to get to the id of the given item
875 # we use global metadata and the given descent for this task
876 #my $ident = $self->{$descent}->id($item);
877 #my $ident = $self->{meta}->{$descent}->{storage}->id($item);
878
879 my $ident;
880 my $provider_method = $self->{meta}->{$descent}->{IdentProvider}->{method};
881 my $provider_arg = $self->{meta}->{$descent}->{IdentProvider}->{arg};
882
883 # resolve to ident
884 if ($provider_method eq 'property') {
885 $ident = $payload->{$provider_arg};
886
887 } elsif ($provider_method eq 'storage_method') {
888 #$ident = $self->{meta}->{$descent}->{storage}->id($item);
889 $ident = $self->{meta}->{$descent}->{storage}->$provider_arg($payload);
890 }
891
892 $self->{node}->{$descent}->{ident} = $ident;
893
894 return 1 if $ident;
895
896 }
897
898
899 sub _modifyNode {
900 my $self = shift;
901 my $descent = shift;
902 my $action = shift;
903 my $map = shift;
904 my $crit = shift;
905
906 # map for new style callbacks
907 my $map_callbacks = {};
908
909 # checks go first!
910
911 # TODO: this should be reviewed first - before extending ;-)
912 # TODO: this should be extended:
913 # count this cases inside the caller to this sub and provide a better overall message
914 # if this counts still zero in the end:
915 # "No nodes have been touched for modify: Do you have column-headers in your csv file?"
916 if (not defined $self->{node}) {
917 #$logger->critical( __PACKAGE__ . "->_modifyNode failed: \"$descent\" node is empty." );
918 #return;
919 }
920
921 # transfer callback nodes from value map to callback map - handle them afterwards! - (new style callbacks)
922 if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
923 foreach my $callback (keys %{$callbacks->{write}}) {
924 $map_callbacks->{write}->{$callback} = $map->{$callback};
925 delete $map->{$callback};
926 }
927 }
928
929
930 #print Dumper($self->{meta});
931
932 # DBI speaks SQL
933 if ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'DBI') {
934
935 #print Dumper($self->{node});
936 my $sql_main;
937 # translate map to sql
938 #print $action, "\n"; exit;
939 #print $self->{meta}->{$descent}->{node}, "\n"; exit;
940 #print "action:";
941 #print $action, "\n";
942 #$action = "anc";
943 #print "yai", "\n";
944
945 #print Dumper($map);
946 #delete $map->{cs};
947
948 if (lc($action) eq 'insert') {
949 $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_INSERT');
950 } elsif (lc $action eq 'update') {
951 $crit ||= "$self->{meta}->{$descent}->{IdentProvider}->{arg}='$self->{node}->{$descent}->{ident}'";
952 $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_UPDATE', $crit);
953 }
954
955 #$sql_main = "UPDATE currencies_csv SET oid='abcdef' WHERE text='Australian Dollar' AND key='AUD';";
956 #$sql_main = "UPDATE currencies_csv SET oid='huhu2' WHERE ekey='AUD'";
957
958 #print "sql: ", $sql_main, "\n";
959 #exit;
960
961 # transfer data
962 my $sqlHandle = $self->{meta}->{$descent}->{storage}->sendCommand($sql_main);
963
964 #exit;
965
966 # handle errors
967 if ($sqlHandle->err) {
968 #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
969 $self->{node}->{status}->{error} = {
970 statement => $sql_main,
971 state => $sqlHandle->state,
972 err => $sqlHandle->err,
973 errstr => $sqlHandle->errstr,
974 };
975 } else {
976 $self->{node}->{status}->{ok} = 1;
977 }
978
979 # Tangram does it the oo-way (naturally)
980 } elsif ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'Tangram') {
981 my $sql_main;
982 my $object;
983
984 # determine classname
985 my $classname = $self->{meta}->{$descent}->{node};
986
987 # properties to exclude
988 my @exclude = @{$self->{meta}->{$descent}->{subnodes_exclude}};
989
990
991 if (my $identProvider = $self->{meta}->{$descent}->{IdentProvider}) {
992 push @exclude, $identProvider->{arg};
993 }
994
995 # new feature:
996 # - check TypeProvider metadata property from other side
997 # - use argument (arg) inside as a classname for object creation on this side
998 #my $otherSide = $self->_otherSide($descent);
999 if (my $typeProvider = $self->{meta}->{$descent}->{TypeProvider}) {
1000 #print Dumper($map);
1001 $classname = $map->{$typeProvider->{arg}};
1002 # remove nodes from map also (push nodes to "subnodes_exclude" list)
1003 push @exclude, $typeProvider->{arg};
1004 }
1005
1006 # exclude banned properties (remove from map)
1007 #map { delete $self->{node}->{map}->{$_} } @{$self->{args}->{exclude}};
1008 map { delete $map->{$_} } @exclude;
1009
1010 # list of properties
1011 my @props = keys %{$map};
1012
1013 # transfer data
1014 if (lc $action eq 'insert') {
1015
1016 # build array to initialize object
1017 #my @initarray = ();
1018 #map { push @initarray, $_, undef; } @props;
1019
1020 # make the object persistent in four steps:
1021 # - raw create (perl / class tangram scope)
1022 # - engine insert (tangram scope) ... this establishes inheritance - don't try to fill in inherited properties before!
1023 # - raw fill-in from hash (perl scope)
1024 # - engine update (tangram scope) ... this updates all properties just filled in
1025
1026 # create new object ...
1027 #my $object = $classname->new( @initarray );
1028 $object = $classname->new();
1029
1030 # ... pass to orm ...
1031 $self->{meta}->{$descent}->{storage}->insert($object);
1032
1033 # ... and initialize with empty (undef'd) properties.
1034 #print Dumper(@props);
1035 map { $object->{$_} = undef; } @props;
1036
1037 # mix in values ...
1038 hash2object($object, $map);
1039
1040 # ... and re-update@orm.
1041 #print Dumper($object);
1042 $self->{meta}->{$descent}->{storage}->update($object);
1043
1044 # asymmetry: get ident after insert
1045 # TODO:
1046 # - just do this if it is an IdentAuthority
1047 # - use IdentProvider metadata here
1048 #print Dumper($self->{meta}->{$descent});
1049 my $oid = $self->{meta}->{$descent}->{storage}->id($object);
1050 #print "oid: $oid", "\n";
1051 $self->{node}->{$descent}->{ident} = $oid;
1052
1053
1054 } elsif (lc $action eq 'update') {
1055
1056 # get fresh object from orm first
1057 $object = $self->{meta}->{$descent}->{storage}->load($self->{node}->{$descent}->{ident});
1058
1059 #print Dumper($self->{node});
1060
1061 # mix in values
1062 #print Dumper($object);
1063 hash2object($object, $map);
1064 #print Dumper($object);
1065 #exit;
1066 $self->{meta}->{$descent}->{storage}->update($object);
1067 }
1068
1069 my $error = 0;
1070
1071 # handle new style callbacks - this is a HACK - do this without an eval!
1072 #print Dumper($map);
1073 #print "cb: ", Dumper($self->{meta}->{$descent}->{Callback});
1074 #print Dumper($map_callbacks);
1075 foreach my $node (keys %{$map_callbacks->{write}}) {
1076 #print Dumper($node);
1077 my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_write';
1078 my $evalstring = $perl_callback . '( { object => $object, value => $map_callbacks->{write}->{$node}, storage => $self->{meta}->{$descent}->{storage} } );';
1079 #print $evalstring, "\n"; exit;
1080 eval($evalstring);
1081 if ($@) {
1082 $error = 1;
1083 print $@, "\n";
1084 }
1085
1086 #print "after eval", "\n";
1087
1088 if (!$error) {
1089 # re-update@orm
1090 $self->{meta}->{$descent}->{storage}->update($object);
1091 }
1092 }
1093
1094 # handle errors
1095 if ($error) {
1096 #print "error", "\n";
1097 =pod
1098 my $sqlHandle;
1099 #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
1100 $self->{node}->{status}->{error} = {
1101 statement => $sql_main,
1102 state => $sqlHandle->state,
1103 err => $sqlHandle->err,
1104 errstr => $sqlHandle->errstr,
1105 };
1106 =cut
1107 # rollback....
1108 #print "rollback", "\n";
1109 $self->{meta}->{$descent}->{storage}->erase($object);
1110 #print "after rollback", "\n";
1111 } else {
1112 $self->{node}->{status}->{ok} = 1;
1113 }
1114
1115
1116 }
1117
1118 }
1119
1120 # TODO:
1121 # this should be split up into...
1122 # - a "_statNode" (should just touch the node to check for existance)
1123 # - a "_loadNode" (should load node completely)
1124 # - maybe additionally a "loadNodeProperty" (may specify properties to load)
1125 # - introduce $self->{nodecache} for this purpose
1126 # TODO:
1127 # should we:
1128 # - not pass ident in here but resolve it via "$descent"?
1129 # - refactor this and stuff it with additional debug/error message
1130 # - this = the way the implicit load mechanism works
1131 sub _statloadNode {
1132
1133 my $self = shift;
1134 my $descent = shift;
1135 my $ident = shift;
1136 my $force = shift;
1137
1138 # fetch entry to retrieve checksum from
1139 # was:
1140 if (!$self->{node}->{$descent} || $force) {
1141 # is:
1142 #if (!$self->{node}->{$descent}->{item} || $force) {
1143
1144 if (!$ident) {
1145 #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";
1146 return;
1147 }
1148
1149 # patch for DBD::CSV
1150 if ($ident && $ident eq 'Null') {
1151 return;
1152 }
1153
1154 #print "yai!", "\n";
1155
1156 my $query = {
1157 node => $self->{meta}->{$descent}->{node},
1158 subnodes => [qw( cs )],
1159 criterias => [
1160 { key => $self->{meta}->{$descent}->{IdentProvider}->{arg},
1161 op => 'eq',
1162 val => $ident },
1163 ]
1164 };
1165
1166 #print Dumper($query);
1167
1168 my $result = $self->{meta}->{$descent}->{storage}->sendQuery($query);
1169
1170 my $entry = $result->getNextEntry();
1171
1172 #print Dumper($entry);
1173 #print "pers: " . $self->{meta}->{$descent}->{storage}->is_persistent($entry), "\n";
1174 #my $state = $self->{meta}->{$descent}->{storage}->_fetch_object_state($entry, { name => 'TransactionHop' } );
1175 #print Dumper($state);
1176
1177 my $status = $result->getStatus();
1178
1179 #print Dumper($status);
1180
1181 # TODO: enhance error handling (store inside tc)
1182 #if (!$row) {
1183 # print "\n", "row error", "\n";
1184 # next;
1185 #}
1186
1187 # these checks run before actually loading payload- and meta-data to node-container
1188
1189 # 1st level - hard error
1190 if ($status && $status->{err}) {
1191 $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - hard error (that's ok): $status->{err}" );
1192 return;
1193 }
1194
1195 # 2nd level - logical (empty/notfound) error
1196 if (($status && $status->{empty}) || !$entry) {
1197 $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - logical error (that's ok)" );
1198 #print "no entry (logical)", "\n";
1199 return;
1200 }
1201
1202 #print Dumper($entry);
1203
1204 # was:
1205 # $self->{node}->{$descent}->{ident} = $ident;
1206 # is:
1207 # TODO: re-resolve ident from entry via metadata "IdentProvider" here - like elsewhere
1208 $self->{node}->{$descent}->{ident} = $ident;
1209 $self->{node}->{$descent}->{payload} = $entry;
1210
1211 }
1212
1213 return 1;
1214
1215 }
1216
1217 sub _doTransferToTarget {
1218 my $self = shift;
1219 my $action = shift;
1220 $self->_modifyNode('target', $action, $self->{node}->{map});
1221 }
1222
1223 sub _doModifySource_IdentChecksum {
1224 my $self = shift;
1225 my $ident_new = shift;
1226 # this changes an old node to a new one including ident and checksum
1227 # TODO:
1228 # - eventually introduce an external resource to store this data to
1229 # - we won't have to "re"-modify the source node here
1230 my $map = {
1231 $self->{meta}->{source}->{IdentProvider}->{arg} => $ident_new,
1232 cs => $self->{node}->{target}->{checksum},
1233 };
1234
1235 #print Dumper($map);
1236 #print Dumper($self->{node});
1237 #exit;
1238
1239 $self->_modifyNode('source', 'update', $map);
1240 }
1241
1242
1243 # this is a shortcut method
1244 # ... let's try to avoid _any_ redundant code in here (ok... - at the cost of method lookups...)
1245 sub _getNodeList {
1246 my $self = shift;
1247 my $descent = shift;
1248 my $filter = shift;
1249 return $self->{meta}->{$descent}->{storage}->getListFiltered($self->{meta}->{$descent}->{node}, $filter);
1250 }
1251
1252
1253 sub _prepareNode_MetaProperties {
1254 my $self = shift;
1255 my $descent = shift;
1256
1257 $logger->info( __PACKAGE__ . "->_prepareNode_MetaProperties( descent $descent )" );
1258
1259 # TODO: this should (better) be: "my $firstnode = $self->_getFirstNode($descent);"
1260 my $list = $self->_getNodeList($descent);
1261
1262 # get first node
1263 my $firstnode = $list->[0];
1264
1265 # check if node contains meta properties/nodes
1266 # TODO: "cs" is hardcoded here!
1267 my @required = ( $self->{meta}->{$descent}->{IdentProvider}->{arg}, 'cs' );
1268 my @found = keys %$firstnode;
1269 #my @diff = getDifference(\@found, \@required);
1270 my $diff = getDifference(\@required, \@found);
1271 #print Dumper(@found);
1272 #print Dumper(@required);
1273 #print Dumper(@diff);
1274 #if (!$#diff || $#diff == -1) {
1275 if (isEmpty($diff)) {
1276 $logger->warning( __PACKAGE__ . "->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter..." );
1277 foreach (@required) {
1278 my $sql = "ALTER TABLE $self->{meta}->{$descent}->{node} ADD COLUMN $_";
1279 #print "sql: $sql", "\n";
1280 my $res = $self->{meta}->{$descent}->{storage}->sendCommand($sql);
1281 #print Dumper($res->getStatus());
1282 }
1283 }
1284
1285 }
1286
1287 sub _prepareNode_DummyIdent {
1288 my $self = shift;
1289 my $descent = shift;
1290
1291 $logger->info( __PACKAGE__ . "->_prepareNode_DummyIdent( descent $descent )" );
1292
1293 my $list = $self->_getNodeList($descent);
1294 #print Dumper($list);
1295 my $i = 0;
1296 my $ident_base = 5678983;
1297 my $ident_appendix = '0001';
1298 foreach my $node (@$list) {
1299 my $ident_dummy = $i + $ident_base;
1300 $ident_dummy .= $ident_appendix;
1301 my $map = {
1302 $self->{meta}->{$descent}->{IdentProvider}->{arg} => $ident_dummy,
1303 cs => undef,
1304 };
1305
1306 # diff lists and ...
1307 my $diff = getDifference([keys %$node], [keys %$map]);
1308 next if $#{$diff} == -1;
1309
1310 # ... build criteria including all columns
1311 my @crits;
1312 foreach my $property (@$diff) {
1313 next if !$property;
1314 my $value = $node->{$property};
1315 next if !$value;
1316 push @crits, "$property='" . quotesql($value) . "'";
1317 }
1318 my $crit = join ' AND ', @crits;
1319 print "p" if $self->{verbose};
1320
1321 #print Dumper($map);
1322 #print Dumper($crit);
1323
1324 $self->_modifyNode($descent, 'update', $map, $crit);
1325 $i++;
1326 }
1327
1328 print "\n" if $self->{verbose};
1329
1330 if (!$i) {
1331 $logger->warning( __PACKAGE__ . "->_prepareNode_DummyIdent: no nodes touched" );
1332 }
1333
1334 }
1335
1336 # TODO: handle this in an abstract way (wipe out use of 'source' and/or 'target' inside core)
1337 sub _otherSide {
1338 my $self = shift;
1339 my $descent = shift;
1340 return 'source' if $descent eq 'target';
1341 return 'target' if $descent eq 'source';
1342 return '';
1343 }
1344
1345 sub _erase_all {
1346 my $self = shift;
1347 my $descent = shift;
1348 #my $node = shift;
1349 my $node = $self->{meta}->{$descent}->{node};
1350 $self->{meta}->{$descent}->{storage}->eraseAll($node);
1351 }
1352
1353
1354 =pod
1355
1356
1357 =head1 DESCRIPTION
1358
1359 Data::Transfer::Sync is a module providing a generic synchronization process
1360 across arbitrary/multiple storages based on a ident/checksum mechanism.
1361 It sits on top of Data::Storage.
1362
1363
1364 =head1 REQUIREMENTS
1365
1366 For full functionality:
1367 Data::Storage
1368 Data::Transform
1369 Data::Compare
1370 ... and all their dependencies
1371
1372
1373 =head1 AUTHORS / COPYRIGHT
1374
1375 The Data::Storage module is Copyright (c) 2002 Andreas Motl.
1376 All rights reserved.
1377
1378 You may distribute it under the terms of either the GNU General Public
1379 License or the Artistic License, as specified in the Perl README file.
1380
1381
1382 =head1 SUPPORT / WARRANTY
1383
1384 Data::Storage is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
1385
1386
1387
1388 =head1 BUGS
1389
1390 When in "import" mode for windows file - DBD::AutoCSV may hang.
1391 Hint: Maybe the source node contains an ident-, but no checksum-column?
1392
1393
1394 =head1 USER LEVEL ERRORS
1395
1396 =head4 Mapping
1397
1398 - - - - - - - - - - - - - - - - - - - - - - - - - -
1399 info: BizWorks::Process::Setup->syncResource( source_node Currency mode PULL erase 0 import 0 )critical: BizWorks::Process::Setup->startSync: Can't access mapping for node "Currency" - please check BizWorks::ResourceMapping.
1400 - - - - - - - - - - - - - - - - - - - - - - - - - -
1401 You have to create a sub for each node used in synchronization inside named Perl module. The name of this sub _must_ match
1402 the name of the node you want to sync. This sub holds mapping metadata to give the engine hints about how
1403 to access the otherwise generic nodes.
1404 - - - - - - - - - - - - - - - - - - - - - - - - - -
1405
1406
1407 =head4 DBD::AutoCSV's rulebase
1408
1409 - - - - - - - - - - - - - - - - - - - - - - - - - -
1410 info: BizWorks::Process::Setup->syncResource( source_node Currency mode PULL erase 0 import 0 )
1411 info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1412
1413 Execution ERROR: Error while scanning: Missing first row or scanrule not applied. at C:/home/amo/develop/netfrag.org/nfo/perl/libs/DBD/CSV.p
1414 m line 165, <GEN9> line 1.
1415 called from C:/home/amo/develop/netfrag.org/nfo/perl/libs/Data/Storage/Handler/DBI.pm at 123.
1416
1417 DBI-Error: DBD::AutoCSV::st fetchrow_hashref failed: Attempt to fetch row from a Non-SELECT statement
1418 notice: Data::Transfer::Sync->syncNodes: No nodes to synchronize.
1419 - - - - - - - - - - - - - - - - - - - - - - - - - -
1420 DBD::AutoCSV contains a rulebase which is spooled down while attempting to guess the style of the csv file regarding
1421 parameters like newline (eol), column-seperation-character (sep_char), quoting character (quote_char).
1422 If this spool runs out of entries and no style could be resolved, DBD::CSV dies causing this "Execution ERROR" which
1423 results in a "DBI-Error" afterwards.
1424 - - - - - - - - - - - - - - - - - - - - - - - - - -
1425
1426
1427 =head4 Check structure of source node
1428
1429 - - - - - - - - - - - - - - - - - - - - - - - - - -
1430 info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1431 critical: Data::Transfer::Sync->syncNodes: Can not synchronize: No ident found in source node, maybe try to "import" this node first.
1432 - - - - - - - - - - - - - - - - - - - - - - - - - -
1433 If lowlevel detection succeeds, but no other required informations are found, this message is issued.
1434 "Other informations" might be:
1435 - column-header-row completely missing
1436 - ident column is empty
1437 - - - - - - - - - - - - - - - - - - - - - - - - - -
1438
1439
1440 =head4 Modify structure of source node
1441
1442 - - - - - - - - - - - - - - - - - - - - - - - - - -
1443 info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1444 info: Data::Transfer::Sync->_prepareNode_MetaProperties( descent source )
1445 warning: Data::Transfer::Sync->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter...
1446 SQL ERROR: Command 'ALTER' not recognized or not supported!
1447
1448 SQL ERROR: Command 'ALTER' not recognized or not supported!
1449 - - - - - - - - - - - - - - - - - - - - - - - - - -
1450 The Engine found a node which structure does not match the required. It tries to alter this automatically - only when doing "import" -
1451 but the DBD driver (in this case DBD::CSV) gets in the way croaking not to be able to do this.
1452 This could also appear if your database connection has insufficient rights to modify the database structure.
1453 DBD::CSV croaks because it doesn't implement the ALTER command, so please edit your columns manually.
1454 Hint: Add columns with the names of your "ident" and "checksum" property specifications.
1455 - - - - - - - - - - - - - - - - - - - - - - - - - -
1456
1457
1458 =head4 Load source node by ident
1459
1460 - - - - - - - - - - - - - - - - - - - - - - - - - -
1461 info: Data::Transfer::Sync->_prepareNode_DummyIdent( descent source )
1462 pcritical: Data::Transfer::Sync->_modifyNode failed: "source" node is empty.
1463 - - - - - - - - - - - - - - - - - - - - - - - - - -
1464 The source node could not be loaded. Maybe the ident is missing. Please check manually.
1465 Hint: Like above, the ident and/or checksum columns may be missing....
1466 - - - - - - - - - - - - - - - - - - - - - - - - - -
1467
1468
1469 =head1 TODO
1470
1471 - sub _resolveIdentProvider
1472 - wrap _doModifySource and _doTransferTarget around a core function which can change virtually any type of node
1473 - split this module up into Sync.pm, Sync/Core.pm, Sync/Compare.pm and Sync/Compare/Checksum.pm
1474 - introduce _compareNodes as a core method and wrap it around methods in Sync/Compare/Checksum.pm
1475 - introduce Sync/Compare/MyComparisonImplementation.pm
1476 - some generic deferring method - e.g. "$self->defer(action)" - to be able to accumulate a bunch of actions for later processing
1477 - this implies everything done is _really_ split up into generic actions - how else would we defer them???
1478 - example uses:
1479 - fetch whole checksum list from node
1480 - remember source ident retransmits
1481 - remember: this is convenient - and maybe / of course faster - but we'll loose "per-node-atomic" operations
1482 - feature: mechanism to implicit inject checksum property to nodes (alter table / modify schema)
1483 - expand statistics / keep track of:
1484 - touched/untouched nodes
1485 - full sync
1486 - just do a push and a pull for now but use stats for touched nodes in between to speed up things
1487 - introduce some new metadata flags for a synchronization partner which is (e.g.) of "source" or "target":
1488 - isNewNodePropagator
1489 - isWriteProtected
1490
1491
1492 =cut
1493
1494 1;

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