/[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.9 - (show annotations)
Tue May 13 08:16:44 2003 UTC (21 years, 2 months ago) by joko
Branch: MAIN
Changes since 1.8: +9 -3 lines
minor update: modified header

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

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