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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.10 - (show annotations)
Sat Jun 19 01:43:03 2004 UTC (20 years ago) by joko
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +89 -80 lines
code from _prepareOptions now split into _prepareMap and _mergeOptions

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

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