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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.6 - (hide annotations)
Fri Feb 14 12:58:47 2003 UTC (21 years, 4 months ago) by joko
Branch: MAIN
Changes since 1.5: +14 -5 lines
+ re-enabled the erase-mechanism

1 joko 1.6 ## $Id: API.pm,v 1.5 2003/02/11 05:26: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: API.pm,v $
9 joko 1.6 ## Revision 1.5 2003/02/11 05:26:04 joko
10     ## + sub _tellWhatIWillDo
11     ## + re-enabled "branch to execution path for special targets" mechanism
12     ##
13 joko 1.5 ## Revision 1.4 2003/02/09 05:03:02 joko
14     ## + minor fix regarding namespace of api versioning extension module
15     ##
16 joko 1.4 ## Revision 1.3 2003/02/09 04:59:27 joko
17     ## + api versioning mechanism
18     ## + major structure changes
19     ## - refactored code to sister modules
20     ##
21 joko 1.3 ## Revision 1.2 2003/01/20 16:59:48 joko
22     ## + cosmetics and debugging
23     ##
24 joko 1.2 ## Revision 1.1 2003/01/19 01:23:04 joko
25     ## + new from Data/Transfer/Sync.pm
26     ##
27 joko 1.1 ## ----------------------------------------------------------------------------------------
28    
29    
30 joko 1.2 package Data::Transfer::Sync::API;
31    
32 joko 1.1 use strict;
33     use warnings;
34 joko 1.2
35 joko 1.3 use base qw( DesignPattern::Bridge );
36    
37 joko 1.1 use mixin::with qw( Data::Transfer::Sync );
38 joko 1.2
39 joko 1.1
40     # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - main
41 joko 1.2
42     use Data::Dumper;
43 joko 1.3 use Hash::Merge qw( merge );
44 joko 1.2
45 joko 1.3 use Data::Compare::Struct qw( isEmpty );
46 joko 1.2
47 joko 1.1 # get logger instance
48     my $logger = Log::Dispatch::Config->instance;
49 joko 1.2
50 joko 1.3
51     sub api_constructor {
52 joko 1.1 my $self = shift;
53 joko 1.3 $logger->debug( __PACKAGE__ . "->api_constructor: Loading API");
54     $self->_loadVersionExtensions();
55 joko 1.1 }
56 joko 1.2
57 joko 1.3
58     sub _loadVersionExtensions {
59 joko 1.1 my $self = shift;
60 joko 1.3 my $syncVersion = $self->{sync_version};
61     $syncVersion ||= '';
62     $logger->debug( __PACKAGE__ . "->loadVersionExtensions( version='$syncVersion' )");
63     #print Dumper($self);
64     #exit;
65 joko 1.4 my $module = "API::$syncVersion";
66 joko 1.3 $self->load($module);
67     }
68    
69    
70     sub configure {
71     my $self = shift;
72    
73     $logger->debug( __PACKAGE__ . "->configure");
74    
75     my @args = @_;
76    
77     if (!isEmpty(\@args)) {
78     my %properties = @_;
79     # merge args to properties
80     #map { $self->{$_} = $properties{$_}; } keys %properties;
81     #print Dumper($self);
82     #print Dumper(\%properties);
83     if ($self->{options}) {
84     my $options_new = merge($self->{options}, \%properties);
85     #print Dumper($options_new);
86     $self->{options} = $options_new;
87     #print Dumper($self->{options});
88     } else {
89     $self->{options} = \%properties;
90     }
91     $self->_init();
92     #$self->_initV1();
93     } else {
94     #print "no args!", "\n";
95 joko 1.1 }
96    
97 joko 1.3 #print Dumper($self);
98     #exit;
99    
100     $self->{state}->{configured} = 1;
101     return 1;
102 joko 1.1 }
103 joko 1.2
104 joko 1.3
105     sub setArguments {
106 joko 1.1 my $self = shift;
107 joko 1.3 my $args_raw = shift;
108     $self->{args_raw} = $args_raw;
109     }
110 joko 1.1
111 joko 1.3 sub readArguments {
112     my $self = shift;
113 joko 1.1
114 joko 1.3 my %syncConfig;
115     tie %syncConfig, 'Tie::IxHash';
116     %syncConfig = (
117     map => {
118     moduleName => $self->{args_raw}->{'mapping-module'},
119     },
120     source => {
121     dbKey => $self->{args_raw}->{source},
122     nodeType => $self->{args_raw}->{'source-type'},
123     nodeName => $self->{args_raw}->{'source-node'},
124     },
125     target => {
126     dbKey => $self->{args_raw}->{target},
127     nodeName => $self->{args_raw}->{'target-node'},
128     },
129     process => {
130     mode => $self->{args_raw}->{mode},
131     erase => $self->{args_raw}->{erase},
132     import => $self->{args_raw}->{import},
133     prepare => $self->{args_raw}->{prepare},
134     },
135     # metadata => {
136     # config => $self->{config_metadata},
137     # }
138     );
139 joko 1.1
140 joko 1.3 $self->{args} = \%syncConfig;
141 joko 1.1
142     }
143    
144 joko 1.2
145 joko 1.3 # TODO: some feature to show off the progress of synchronization (cur/max * 100)
146     sub syncNodes {
147 joko 1.1
148     my $self = shift;
149 joko 1.3 my $args = shift;
150    
151 joko 1.6 $logger->notice( "========================== " . __PACKAGE__ . "->syncNodes ==============");
152 joko 1.3 $logger->debug( __PACKAGE__ . "->syncNodes: starting" );
153 joko 1.1
154 joko 1.3 #print Dumper($self);
155 joko 1.1 #exit;
156    
157 joko 1.3 #print Dumper($self->{options});
158     $self->_prepareOptions();
159 joko 1.1
160 joko 1.6 # trace
161     #print Dumper($self->{options});
162     #exit;
163 joko 1.1
164 joko 1.3 if (!$self->checkOptions()) {
165     $logger->critical( __PACKAGE__ . "->syncNodes: 'Data::Transfer::Sync::checkOptions' failed.");
166 joko 1.1 return;
167     }
168    
169 joko 1.3 if (!$self->{state}->{configured}) {
170     $logger->critical( __PACKAGE__ . "->syncNodes: Synchronization object is not configured/initialized correctly." );
171 joko 1.1 return;
172     }
173    
174 joko 1.5 #print Dumper($args);
175    
176 joko 1.3 # remember arguments through the whole processing
177     $self->{args} = $args;
178 joko 1.1
179 joko 1.3 # hash to hold and/or fill in metadata required for the processing
180     $self->{meta} = {};
181 joko 1.1
182 joko 1.3 # hash to sum up results
183     # TODO: re-implement! (sync-statistics???)
184 joko 1.1
185 joko 1.6 #print Dumper($self->{options}->{process});
186    
187 joko 1.3 # detect synchronization method to determine which optical symbol (directed arrow) to use
188     my $mode = $self->{args}->{mode}; # V1
189     $mode ||= $self->{options}->{process}->{mode}; # V2
190     my $direction_arrow = $self->_getDirectedArrow($mode);
191 joko 1.1
192 joko 1.3 # determine code versions
193     my $code_metadata_version;
194     # first, try to use version supplied by mapping-metadata
195     $code_metadata_version = $self->{options}->{metadata}->{version};
196     # if not set, inherit from global 'sync_version'
197     $code_metadata_version ||= $self->{sync_version};
198    
199     # load additional code from versioned namespace into current instance
200     my $dynModule = "Metadata::$code_metadata_version";
201     $self->load($dynModule);
202    
203     # build metadata using versioned code and stuff
204     $self->options2metadata();
205     $self->options2metadata_accessor();
206    
207 joko 1.5 # branch to execution path for special targets
208     # detect for option 'handler' which could be a CODEref
209     if ($self->{options}->{handler} && ref $self->{options}->{handler} eq 'CODE') {
210     $logger->info( __PACKAGE__ . "->syncNodes: Running (special handler code - no generic sync!) on '$self->{options}->{target}->{dbKey}' with MODE $self->{options}->{process}->{mode}, NODE $self->{options}->{target}->{address}");
211     #print Dumper($self);
212     #exit;
213     # don't do this any more - it wasn't very nice somehow ...
214     #$self->{options}->{handler}->($self->{app}, $self->{options});
215     # .... now: better let the parent application scope handle this via callback
216     # not any more required for this: $self->{app} inside here (which isn't the app we mean here)
217     # required for this: getting the options out of here: establish some getter method! ($self->getOptions(...))
218     # so....
219     #$self->{__bridge}->{parent_ref}->
220    
221     # ahh okay, DesignPattern::Bridge moves closer to some Class::Inner!???
222     # so...
223     # similar like above - but it isn't very nice anyway ... (no privateness, but: who cares?)
224     #print Dumper($self->{__bridge});
225     # just take the global application instance and
226     # throw it into the context of the mapping module - this is heavy! ;-) (but again, who cares...)
227 joko 1.6 # TODO: handle this more abstract *sometime*
228 joko 1.5 #$self->{options}->{handler}->($self->{__bridge}->{parent}->{app}, $self->{options});
229     $self->{options}->{handler}->($self->{__bridge}->{parent}->{process}, $self->{options});
230    
231     return;
232     }
233    
234     # TODO: execution path branch V2!!!
235     # option1: wrap this via callback to parent scope (like current impl. mechanism)
236     # option2: branch directly from here (this needs refactoring of the sub handler)
237    
238 joko 1.3 # tracing
239     #print Dumper($self);
240     #exit;
241 joko 1.1
242 joko 1.3 $logger->info( __PACKAGE__ . "->syncNodes: source=$self->{meta}->{source}->{dbKey}/$self->{meta}->{source}->{nodeName} [$self->{meta}->{source}->{nodeType}] $direction_arrow target=$self->{meta}->{target}->{dbKey}/$self->{meta}->{target}->{nodeName} [$self->{meta}->{target}->{nodeType}]" );
243 joko 1.1
244 joko 1.3 return if !$self->buildFieldmapping();
245     return if !$self->_touchNodeSet();
246     return if !$self->_prepare_sync();
247 joko 1.5 $self->_tellWhatIWillDo();
248 joko 1.3
249     # tracing
250     #print Dumper($self);
251     #print Dumper($self->{args});
252     #print Dumper($self->{options});
253     #print Dumper($self->{meta});
254     #print Dumper($self->{metadata});
255     #exit;
256 joko 1.1
257 joko 1.3 # finally, tell the core to start the synchronization process
258     $self->_run();
259    
260     }
261 joko 1.1
262 joko 1.2
263 joko 1.5 my $c_string_default = '';
264     sub c_string {
265     my $value = shift;
266     $value ||= "[$c_string_default]";
267     return $value;
268     }
269    
270     sub _tellWhatIWillDo {
271     my $self = shift;
272    
273    
274     #return;
275    
276     # trace
277     #print Dumper($self->{meta});
278     #exit;
279    
280     $c_string_default = 'n/a';
281     my $source = c_string($self->{opt}->{'source'});
282     my $source_node = c_string($self->{opt}->{'source-node'});
283     my $source_type = c_string($self->{opt}->{'source-type'});
284     my $target = c_string($self->{opt}->{'target'});
285     my $target_node = c_string($self->{opt}->{'target-node'});
286     my $target_type = c_string($self->{opt}->{'target-type'});
287    
288     my $mapping_module = c_string($self->{opt}->{'mapping-module'});
289     my $mode = uc c_string($self->{opt}->{'mode'});
290    
291     #my $ql = "$mode INTO $source NODE $source_node TYPE $source_type SELECT NODE $target_node TYPE $target_type FROM $target USING MODULE $mapping_module;";
292     #$logger->notice( __PACKAGE__ . ": $ql" );
293     my $ql = <<EOT;
294    
295     FETCH DATA
296     FROM STORAGE $self->{meta}->{source}->{dbKey}
297     AT NODE $self->{meta}->{source}->{accessorName}.$self->{meta}->{source}->{nodeName}
298     USING IDENTIFIER $self->{meta}->{source}->{IdentProvider}->{method}.$self->{meta}->{source}->{IdentProvider}->{arg}
299     CONVERT DATA
300     CAST FROM $self->{meta}->{source}->{nodeType} TO $self->{meta}->{target}->{nodeType}
301     MAP ATTRIBUTES FROM @{$self->{meta}->{source}->{childnodes}} TO @{$self->{meta}->{target}->{childnodes}}
302     STORE DATA
303     TO STORAGE $self->{meta}->{target}->{dbKey}
304     AT NODE $self->{meta}->{target}->{accessorName}.$self->{meta}->{target}->{nodeName}
305     USING IDENTIFIER $self->{meta}->{target}->{IdentProvider}->{method}.$self->{meta}->{target}->{IdentProvider}->{arg}
306     EOT
307    
308    
309     $logger->notice( $ql );
310    
311     #exit;
312     return;
313    
314     my $actioning = ucfirst $self->{opt}->{'action'} . 'ing';
315    
316     # FIXME: this is weird!
317     my $long = <<EOT;
318    
319     - $actioning data of type $target_type and
320     filtered by $target_node from the storage named $target
321     to the storage named $source - filtered by $source_node.
322     - Will attempt to convert the data to $source_type.
323     EOT
324     chomp($long);
325     $logger->notice( __PACKAGE__ . ": $long" );
326    
327     }
328    
329    
330 joko 1.3 sub _prepareOptions {
331 joko 1.1
332     my $self = shift;
333 joko 1.3
334     my $opts = $self->{args};
335 joko 1.1
336     # patch options
337 joko 1.3 $opts->{source}->{nodeName} ||= '';
338     $opts->{target}->{nodeName} ||= '';
339 joko 1.1 $opts->{process}->{mode} ||= '';
340 joko 1.3 $opts->{process}->{erase} ||= 0;
341 joko 1.1 $opts->{process}->{prepare} ||= 0;
342 joko 1.3
343     # defaults (mostly for backward-compatibility to V1 -
344     # but code mungled here out of prepareOptions_V1 from Version::V1)
345     $opts->{metadata}->{syncMethod} ||= 'checksum'; # | timestamp
346     $opts->{source}->{ident} ||= 'storage_method:id';
347     $opts->{source}->{exclude} ||= [qw( cs )];
348     $opts->{target}->{ident} ||= 'property:oid';
349     #$map->{source_node} ||= $source_node_name;
350     #$map->{direction} ||= $opts->{mode}; # | PUSH | PULL | FULL
351 joko 1.1
352     # pre-check options
353     if (!$self->_preCheckOptions($opts)) {
354 joko 1.3 $logger->error( __PACKAGE__ . "->_prepareOptions: _preCheckOptions failed.");
355 joko 1.1 return;
356     }
357    
358     # inform user about option preparation
359 joko 1.3 $logger->debug( __PACKAGE__ . "->_prepareOptions( source.node='$opts->{source}->{nodeName}', target.node='$opts->{target}->{nodeName}', mode='$opts->{process}->{mode}', e='$opts->{process}->{erase}', p='$opts->{process}->{prepare}' )");
360 joko 1.1
361     # try to load mapping-metadata-container
362 joko 1.3 # How? Create a new instance of the given
363     # perl module/package name in ->{...}->{moduleName}.
364     # This instance is used later in the innards of the sync,
365     # that's why the module (class) should have a certain layout
366     # enabling the core to use it for gathering metadata while processing.
367     my $mapObject = DesignPattern::Object->fromPackage($opts->{map}->{moduleName});
368 joko 1.1
369     # try to resolve map from metadata-container
370    
371     # type of the item/node
372     my $source_nodeType = $opts->{source}->{nodeType};
373    
374     # check if mapping for certain node is contained in mapping object
375 joko 1.3 if (!$mapObject || !$mapObject->can($source_nodeType)) {
376     $logger->warning( __PACKAGE__ . "->_prepareOptions: Can't access mapping for source-type=\"$source_nodeType\" - please check \"$opts->{map}->{moduleName}\".");
377 joko 1.1 return;
378     }
379    
380     # get map
381     my $map = $mapObject->$source_nodeType;
382     #print Dumper($map);
383    
384 joko 1.3 my $map_version = $map->{metadata}->{version};
385     # FIXME: backward compatibility
386     if (!$map_version) {
387     $self->options_to_V2($map);
388     }
389    
390     # trace
391     #print Dumper($map);
392     #exit;
393     #print "ref: ", ref $map->{target}, "\n";
394     #print "target: ", $map->{target}, "\n";
395    
396 joko 1.1 # check here if "target" is actually a CODEref - in this case: resolve it - deprecated!!! ???
397     if (ref $map->{target}->{address} eq 'CODE') {
398     $map->{target}->{address} = $map->{target}->{address}->($source_nodeType);
399     }
400    
401     # resolve expressions (on nodename-level) here
402 joko 1.3 elsif ($map->{target}->{address} =~ m/^(code|expr):(.+?)$/) {
403 joko 1.1 my $target_dynamic_type = $1;
404     my $target_dynamic_expression = $2;
405     if (lc $target_dynamic_type eq 'code') {
406     $map->{target} = $mapObject->$target_dynamic_expression($map);
407     }
408     }
409    
410 joko 1.3 # merging - V1
411     #map { $opts->{$_} = $map->{$_}; } keys %$map;
412     # trace
413     #print Dumper($self->{options});
414    
415     # merging - V2
416    
417     # merge local map with local opts
418 joko 1.5
419     # delete undef'd items in $map
420    
421 joko 1.3 # enable cloning
422     # FIXME: do we really need cloning here? trade safety/encapsulation for speed?
423     Hash::Merge::set_clone_behavior(1);
424     Hash::Merge::set_behavior( 'RIGHT_PRECEDENT' );
425 joko 1.5 #Hash::Merge::set_behavior( 'STORAGE_PRECEDENT' );
426     #Hash::Merge::set_behavior( 'RETAINMENT_PRECEDENT' );
427     # TODO: add an option to Hash::Merge not to overwrite set items with undefined/empty/not assigned ones
428 joko 1.3 my $locals_merged = merge( $opts, $map );
429    
430     # trace
431     #print Dumper($opts);
432     #print Dumper($map);
433     #print Dumper($locals_merged);
434     #exit;
435    
436     # merge local-merged ones with instance-wide options
437     Hash::Merge::set_clone_behavior(0);
438     $self->{options} = merge( $self->{options}, $locals_merged );
439    
440     # trace
441     #print Dumper($self->{options});
442     #exit;
443    
444 joko 1.1
445     $self->{state}->{options_ready} = 1;
446    
447     return 1;
448    
449     }
450 joko 1.2
451    
452 joko 1.3 sub _preCheckOptions {
453    
454 joko 1.1 my $self = shift;
455 joko 1.3 my $opts = shift;
456    
457     # trace
458     #print Dumper($opts);
459     #exit;
460    
461     if (!$opts->{process}->{mode}) {
462     $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"--action=(load|save)\".");
463     return;
464 joko 1.1 }
465    
466 joko 1.3 # the type of the to-be-synced item
467     if (!$opts->{source}->{nodeType}) {
468     $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"source-type\".");
469     return;
470 joko 1.1 }
471 joko 1.3 # the name of the (container-) node the items are listed in
472     if (!$opts->{source}->{nodeName}) {
473     $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"source-node\".");
474     return;
475 joko 1.2 }
476 joko 1.1
477 joko 1.3 # a "map"-declaration which module to use for mapping- and/or lookup-purposes
478     if (!$opts->{map}) {
479     $logger->warning( __PACKAGE__ . "->_preCheckOptions: No mapping supplied - please check key 'map|mappings' in global configuration or specify additional argument '--mapping-module'.");
480 joko 1.1 return;
481     }
482 joko 1.3 if (!$opts->{map}->{moduleName}) {
483     $logger->warning( __PACKAGE__ . "->_preCheckOptions: Currently only perl-modules can provide mappings: Please specify one with '--mapping-module=My::Mapping::Module'.");
484     return;
485 joko 1.1 }
486    
487 joko 1.3 return 1;
488 joko 1.2
489     }
490 joko 1.1
491    
492 joko 1.2
493 joko 1.3 sub _prepare_sync {
494 joko 1.2
495     my $self = shift;
496 joko 1.1
497     # TODO:
498     # + if action == PUSH: start processing
499     # -+ if action == PULL: swap metadata and start processing
500     # - if action == FULL: start processing, then swap metadata and (re-)start processing
501    
502 joko 1.3 #print "dir: ", $self->{args}->{direction}, "\n";
503    
504 joko 1.1 # manipulate metainfo according to direction of synchronization
505 joko 1.3 if (lc $self->{options}->{process}->{mode} eq 'push') {
506     # just do it ... (don't modify any metadata)
507    
508     } elsif (lc $self->{options}->{process}->{mode} eq 'pull') {
509     # swap source and target metadata
510     # TODO: introduce different mechanism to make more then two partners (descents) possible
511 joko 1.1 ($self->{meta}->{source}, $self->{meta}->{target}) =
512     ($self->{meta}->{target}, $self->{meta}->{source});
513 joko 1.5 #($self->{options}->{source}, $self->{options}->{target}) =
514     # ($self->{options}->{target}, $self->{options}->{source});
515 joko 1.3
516     } elsif (lc $self->{options}->{process}->{mode} eq 'full') {
517     # TODO:
518     # do a pull first and a push afterwards
519     # this requires us to be called somehow recursively - just one recursion level ;-)
520    
521 joko 1.1 } else {
522 joko 1.3 # TODO: are there any other synchronization modes besides PULL, PUSH, FULL?
523    
524 joko 1.1 }
525    
526     # import flag means: prepare the source node to be syncable
527     # this is useful if there are e.g. no "ident" or "checksum" columns yet inside a DBI like (row-based) storage
528 joko 1.6 if ($self->{options}->{process}->{prepare}) {
529 joko 1.1 $self->_prepareNode_MetaProperties('source');
530     $self->_prepareNode_DummyIdent('source');
531     #return;
532     #$self->_erase_all($opts->{source_node});
533     }
534    
535     # erase flag means: erase the target
536     #if ($opts->{erase}) {
537 joko 1.6 if ($self->{options}->{process}->{erase}) {
538 joko 1.1 # TODO: move this method to the scope of the synchronization core and wrap it around different handlers
539     #print "ERASE", "\n";
540     $self->_erase_all('target');
541     }
542 joko 1.2
543     return 1;
544 joko 1.1
545 joko 1.3 }
546    
547     sub _getDirectedArrow {
548     my $self = shift;
549     my $mode = shift;
550     $mode ||= '';
551    
552     if (lc $mode eq 'push') {
553     return '->';
554     } elsif (lc $mode eq 'pull') {
555     return '<-';
556     } elsif (lc $mode eq 'full') {
557     return '<->';
558     } else {
559     return '';
560     }
561 joko 1.1 }
562    
563     1;

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