/[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.7 - (show annotations)
Fri Feb 21 08:00:24 2003 UTC (21 years, 4 months ago) by joko
Branch: MAIN
Changes since 1.6: +18 -4 lines
debugging

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

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