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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (hide annotations)
Sun Jan 19 02:05:42 2003 UTC (21 years, 5 months ago) by joko
Branch: MAIN
Changes since 1.1: +5 -143 lines
- removed pod-documentation: now in Data/Transfer/Sync.pod

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

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