/[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.8 - (show annotations)
Thu Mar 27 15:31:15 2003 UTC (21 years, 3 months ago) by joko
Branch: MAIN
Changes since 1.7: +5 -2 lines
fixes to modules regarding new namespace(s) below Data::Mungle::*

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

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