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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (hide 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 joko 1.11 ## $Id: Sync.pm,v 1.10 2002/12/19 01:07:16 joko Exp $
2 joko 1.1 ##
3     ## Copyright (c) 2002 Andreas Motl <andreas.motl@ilo.de>
4     ##
5     ## See COPYRIGHT section in pod text below for usage and distribution rights.
6     ##
7     ## ----------------------------------------------------------------------------------------
8 joko 1.2 ## $Log: Sync.pm,v $
9 joko 1.11 ## Revision 1.10 2002/12/19 01:07:16 joko
10     ## + fixed output done via $logger
11     ##
12 joko 1.10 ## Revision 1.9 2002/12/16 07:02:34 jonen
13     ## + added comment
14     ##
15 jonen 1.9 ## Revision 1.8 2002/12/15 02:03:09 joko
16     ## + fixed logging-messages
17     ## + additional metadata-checks
18     ##
19 joko 1.8 ## Revision 1.7 2002/12/13 21:49:34 joko
20     ## + sub configure
21     ## + sub checkOptions
22     ##
23 joko 1.7 ## Revision 1.6 2002/12/06 04:49:10 jonen
24     ## + disabled output-puffer here
25     ##
26 jonen 1.6 ## 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 joko 1.5 ## Revision 1.4 2002/12/03 15:54:07 joko
32     ## + {import}-flag is now {prepare}-flag
33     ##
34 joko 1.4 ## Revision 1.3 2002/12/01 22:26:59 joko
35     ## + minor cosmetics for logging
36     ##
37 joko 1.3 ## 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 joko 1.2 ## Revision 1.1 2002/11/29 04:45:50 joko
46     ## + initial check in
47     ##
48 joko 1.1 ## 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 joko 1.7 #use Hash::Merge qw( merge );
60    
61 joko 1.1 use misc::HashExt;
62     use libp qw( md5_base64 );
63     use libdb qw( quotesql hash2Sql );
64 joko 1.2 use Data::Transform::Deep qw( hash2object refexpr2perlref );
65 joko 1.1 use Data::Compare::Struct qw( getDifference isEmpty );
66    
67     # get logger instance
68     my $logger = Log::Dispatch::Config->instance;
69    
70 jonen 1.6 $| = 1;
71 joko 1.1
72     sub new {
73     my $invocant = shift;
74     my $class = ref($invocant) || $invocant;
75 joko 1.7 my $self = {};
76 joko 1.1 $logger->debug( __PACKAGE__ . "->new(@_)" );
77     bless $self, $class;
78 joko 1.7 $self->configure(@_);
79 joko 1.1 return $self;
80     }
81    
82    
83 joko 1.7 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 joko 1.1 sub _init {
98     my $self = shift;
99 joko 1.7
100     $self->{configured} = 1;
101 joko 1.1
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 joko 1.7 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 joko 1.10 $logger->notice( __PACKAGE__ . "->prepareOptions( source_node $opts->{source_node} mode $opts->{mode} erase $opts->{erase} prepare $opts->{prepare} )");
131 joko 1.7
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 joko 1.1 # 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 joko 1.7 if (!$self->{configured}) {
218     $logger->critical( __PACKAGE__ . "->syncNodes: Synchronization object is not configured/initialized correctly." );
219     return;
220     }
221    
222 joko 1.1 # 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 joko 1.7 # TODO: refactor!!! take this list from already established/given metadata
245 joko 1.1 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 joko 1.7 #print Dumper($self->{meta});
300    
301 joko 1.1 $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 joko 1.2 # 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 joko 1.1 foreach (@{$self->{args}->{mapping}}) {
308 joko 1.2 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 joko 1.1 }
327    
328 joko 1.7 #print Dumper($self->{meta});
329    
330 joko 1.1 # check partners/nodes: does partner exist / is node available?
331     foreach my $partner (keys %{$self->{meta}}) {
332 joko 1.8
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 joko 1.1 my $node = $self->{meta}->{$partner}->{node};
354     if (!$self->{meta}->{$partner}->{storage}->existsChildNode($node)) {
355 joko 1.8 $logger->critical( __PACKAGE__ . "->syncNodes: Could not reach node \"$node\" at partner \"$partner\"." );
356 joko 1.1 return;
357     }
358 joko 1.8
359 joko 1.1 }
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 joko 1.4 if ($self->{args}->{prepare}) {
383 joko 1.1 $self->_prepareNode_MetaProperties('source');
384     $self->_prepareNode_DummyIdent('source');
385     #return;
386     #$self->_erase_all($opts->{source_node});
387     }
388    
389 joko 1.2 # 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 joko 1.1 $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 joko 1.5 # get reference to node list from convenient method provided by CORE-HANDLE
422 joko 1.1 #$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 joko 1.5 #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 joko 1.1 return;
502     }
503    
504     #print "statload", "\n";
505     #print "ident: ", $self->{node}->{source}->{ident}, "\n";
506 joko 1.5 #print Dumper($self->{node});
507 joko 1.1
508     my $statOK = $self->_statloadNode('target', $self->{node}->{source}->{ident});
509 joko 1.5
510     #print Dumper($self->{node});
511 joko 1.1
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 joko 1.5 #print Dumper($self->{node});
598 joko 1.1 $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 joko 1.5 print "r" if $self->{verbose};
624 joko 1.1 #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 joko 1.3 my $msg = "statistics: $tc";
638 joko 1.1
639     if ($tc->{error_per_row}) {
640     $msg .= "\n";
641 joko 1.2 $msg .= "errors from \"error_per_row\":" . "\n";
642 joko 1.1 $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 jonen 1.9 # refactor this as some core-function to do a generic dump resolving data-encapsulations of e.g. Set::Object
655 joko 1.1 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 joko 1.5
667     #print Dumper($val);
668    
669 joko 1.1 if (ref $val eq 'Set::Object') {
670     #print "========================= SET", "\n";
671 joko 1.5 #print Dumper($val);
672 joko 1.1 #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 joko 1.5
679 joko 1.1 } else {
680     $item->{$key} = $val;
681     }
682 joko 1.5
683 joko 1.1 }
684     push @data, $item;
685     $count++;
686     }
687    
688 joko 1.5 #print "Dump:", Dumper(@data), "\n";
689 joko 1.1
690     $Data::Dumper::Indent = 0;
691     my $result = Dumper(@data);
692     $Data::Dumper::Indent = 2;
693     return $result;
694 joko 1.5
695 joko 1.1 }
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 joko 1.11 # 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 joko 1.1 # md5-based fingerprint, base64 encoded (from Digest::MD5)
724 joko 1.11 $self->{node}->{$descent}->{checksum} = md5_base64($dump) . '==';
725 joko 1.1 # 32-bit integer "hash" value (maybe faster?) (from DBI)
726 joko 1.11 #$self->{node}->{$descent}->{checksum} = DBI::hash($dump, 1);
727 joko 1.1
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 joko 1.2 $self->{node}->{source}->{propcache} = {};
794     $self->{node}->{target}->{propcache} = {};
795    
796 joko 1.1 # 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 joko 1.2
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 joko 1.1
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 joko 1.2 }
843     elsif ($storage_type eq 'Tangram') {
844     # iso? utf8 already possible?
845    
846 joko 1.1 } 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 joko 1.5
930     #print Dumper($self->{meta});
931 joko 1.1
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 joko 1.7
945     #print Dumper($map);
946     #delete $map->{cs};
947    
948 joko 1.1 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 joko 1.7 #$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 joko 1.1
961     # transfer data
962     my $sqlHandle = $self->{meta}->{$descent}->{storage}->sendCommand($sql_main);
963    
964 joko 1.7 #exit;
965    
966 joko 1.1 # 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 joko 1.5 #print Dumper($object);
1042 joko 1.1 $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 joko 1.5 #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 joko 1.1
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 joko 1.5
1149     # patch for DBD::CSV
1150     if ($ident && $ident eq 'Null') {
1151     return;
1152     }
1153 joko 1.1
1154 joko 1.7 #print "yai!", "\n";
1155    
1156     my $query = {
1157 joko 1.1 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 joko 1.7 };
1165    
1166     #print Dumper($query);
1167    
1168     my $result = $self->{meta}->{$descent}->{storage}->sendQuery($query);
1169 joko 1.1
1170     my $entry = $result->getNextEntry();
1171 joko 1.7
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 joko 1.1 my $status = $result->getStatus();
1178    
1179 joko 1.5 #print Dumper($status);
1180    
1181 joko 1.1 # TODO: enhance error handling (store inside tc)
1182     #if (!$row) {
1183     # print "\n", "row error", "\n";
1184     # next;
1185     #}
1186 joko 1.5
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 joko 1.7 $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - hard error (that's ok): $status->{err}" );
1192 joko 1.5 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 joko 1.1 # was:
1205     # $self->{node}->{$descent}->{ident} = $ident;
1206     # is:
1207 joko 1.5 # TODO: re-resolve ident from entry via metadata "IdentProvider" here - like elsewhere
1208 joko 1.1 $self->{node}->{$descent}->{ident} = $ident;
1209     $self->{node}->{$descent}->{payload} = $entry;
1210 joko 1.5
1211 joko 1.1 }
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 joko 1.5
1235     #print Dumper($map);
1236     #print Dumper($self->{node});
1237     #exit;
1238    
1239 joko 1.1 $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 joko 1.5
1321     #print Dumper($map);
1322     #print Dumper($crit);
1323    
1324 joko 1.1 $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 joko 1.2 my $descent = shift;
1340     return 'source' if $descent eq 'target';
1341     return 'target' if $descent eq 'source';
1342 joko 1.1 return '';
1343 joko 1.2 }
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 joko 1.1 }
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