/[cvs]/nfo/perl/libs/Data/Storage.pm
ViewVC logotype

Diff of /nfo/perl/libs/Data/Storage.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.4 by joko, Sun Oct 27 18:35:07 2002 UTC revision 1.9 by joko, Sun Dec 1 22:15:45 2002 UTC
# Line 4  Line 4 
4  #  #
5  # See COPYRIGHT section in pod text below for usage and distribution rights.  # See COPYRIGHT section in pod text below for usage and distribution rights.
6  #  #
7  #################################  ############################################
8  #  #
9  #  $Log$  #  $Log$
10    #  Revision 1.9  2002/12/01 22:15:45  joko
11    #  - sub createDb: moved to handler
12    #
13    #  Revision 1.8  2002/11/29 04:48:23  joko
14    #  + updated pod
15    #
16    #  Revision 1.7  2002/11/17 06:07:18  joko
17    #  + creating the handler is easier than proposed first - for now :-)
18    #  + sub testAvailability
19    #
20    #  Revision 1.6  2002/11/09 01:04:58  joko
21    #  + updated pod
22    #
23    #  Revision 1.5  2002/10/29 19:24:18  joko
24    #  - reduced logging
25    #  + added some pod
26    #
27  #  Revision 1.4  2002/10/27 18:35:07  joko  #  Revision 1.4  2002/10/27 18:35:07  joko
28  #  + added pod  #  + added pod
29  #  #
# Line 23  Line 40 
40  #  Revision 1.1  2002/10/10 03:43:12  cvsjoko  #  Revision 1.1  2002/10/10 03:43:12  cvsjoko
41  #  + new  #  + new
42  #  #
43  #################################  ############################################
44    
 # aim_V1: should encapsulate Tangram, DBI, DBD::CSV and LWP:: to access them in an unordinary way ;)  
 # aim_V2: introduce a generic layered structure, refactor *SUBLAYER*-stuff, make (e.g.) this possible:  
 #               - Perl Data::Storage[DBD::CSV]  ->  Perl LWP::  ->  Internet HTTP/FTP/*  ->  Host Daemon  ->  csv-file  
45    
46  BEGIN {  BEGIN {
47  $Data::Storage::VERSION = 0.01;    $Data::Storage::VERSION = 0.02;
48  }  }
49    
50    
51  =head1 NAME  =head1 NAME
52    
53  Data::Storage - Interface for accessing various Storage implementations for Perl in an independent way  Data::Storage - Interface for accessing various Storage implementations for Perl in an independent way
54    
55    
56    =head1 AIMS
57    
58      - should encapsulate Tangram, DBI, DBD::CSV and LWP:: to access them in an unordinary (more convenient) way ;)
59      - introduce a generic layered structure, refactor *SUBLAYER*-stuff, make (e.g.) this possible:
60        Perl Data::Storage[DBD::CSV]  ->  Perl LWP::  ->  Internet HTTP/FTP/*  ->  Host Daemon  ->  csv-file
61      - provide generic synchronization mechanisms across arbitrary/multiple storages based on ident/checksum
62        maybe it's possible to have schema-, structural- and semantical modifications synchronized???
63    
64    
65  =head1 SYNOPSIS  =head1 SYNOPSIS
66    
67    ... the basic way:  =head2 BASIC ACCESS
68    
69    =head2 ADVANCED ACCESS
70    
71    ... via inheritance:    ... via inheritance:
72        
# Line 58  Data::Storage - Interface for accessing Line 84  Data::Storage - Interface for accessing
84      $self->{storage}->insert($proxyObj);      $self->{storage}->insert($proxyObj);
85    
86    
87    =head2 SYNCHRONIZATION
88    
89      my $nodemapping = {
90        'LangText' => 'langtexts.csv',
91        'Currency' => 'currencies.csv',
92        'Country'  => 'countries.csv',
93      };
94    
95      my $propmapping = {
96        'LangText' => [
97          [ 'source:lcountrykey'  =>  'target:country' ],
98          [ 'source:lkey'         =>  'target:key' ],
99          [ 'source:lvalue'       =>  'target:text' ],
100        ],
101        'Currency' => [
102          [ 'source:ckey'         =>  'target:key' ],
103          [ 'source:cname'        =>  'target:text' ],
104        ],
105        'Country' => [
106          [ 'source:ckey'         =>  'target:key' ],
107          [ 'source:cname'        =>  'target:text' ],
108        ],
109      };
110    
111      sub syncResource {
112    
113        my $self = shift;
114        my $node_source = shift;
115        my $mode = shift;
116        my $opts = shift;
117        
118        $mode ||= '';
119        $opts->{erase} ||= 0;
120        
121        $logger->info( __PACKAGE__ . "->syncResource( node_source $node_source mode $mode erase $opts->{erase} )");
122      
123        # resolve metadata for syncing requested resource
124        my $node_target = $nodemapping->{$node_source};
125        my $mapping = $propmapping->{$node_source};
126        
127        if (!$node_target || !$mapping) {
128          # loggger.... "no target, sorry!"
129          print "error while resolving resource metadata", "\n";
130          return;
131        }
132        
133        if ($opts->{erase}) {
134          $self->_erase_all($node_source);
135        }
136      
137        # create new sync object
138        my $sync = Data::Transfer::Sync->new(
139          storages => {
140            L => $self->{bizWorks}->{backend},
141            R => $self->{bizWorks}->{resources},
142          },
143          id_authorities        =>  [qw( L ) ],
144          checksum_authorities  =>  [qw( L ) ],
145          write_protected       =>  [qw( R ) ],
146          verbose               =>  1,
147        );
148        
149        # sync
150        # todo: filter!?
151        $sync->syncNodes( {
152          direction       =>  $mode,                 # | +PUSH | +PULL | -FULL | +IMPORT | -EXPORT
153          method          =>  'checksum',            # | -timestamp | -manual
154          source          =>  "L:$node_source",
155          source_ident    =>  'storage_method:id',
156          source_exclude  =>  [qw( id cs )],
157          target          =>  "R:$node_target",
158          target_ident    =>  'property:oid',
159          mapping         =>  $mapping,
160        } );
161    
162      }
163    
164    
165  =head2 NOTE  =head2 NOTE
166    
167  This module heavily relies on DBI and Tangram, but adds a lot of additional bugs and quirks.  This module heavily relies on DBI and Tangram, but adds a lot of additional bugs and quirks.
168  Please look at their documentation and this code for additional information.  Please look at their documentation and/or this code for additional information.
169    
170    
171    =head1 REQUIREMENTS
172    
173      For full functionality:
174        DBI              from CPAN
175        DBD::mysql       from CPAN
176        Tangram 2.04     from CPAN         (hmmm, 2.04 won't do in some cases)
177        Tangram 2.05     from http://...   (2.05 seems okay but there are also additional patches from our side)
178        Class::Tangram   from CPAN
179        DBD::CSV         from CPAN
180        MySQL::Diff      from http://adamspiers.org/computing/mysqldiff/
181        ... and all their dependencies
182    
183  =cut  =cut
184    
# Line 75  use strict; Line 191  use strict;
191  use warnings;  use warnings;
192    
193  use Data::Storage::Locator;  use Data::Storage::Locator;
194    use Data::Dumper;
195    
196    # TODO: actually implement level (integrate with Log::Dispatch)
197    my $TRACELEVEL = 0;
198    
199  # get logger instance  # get logger instance
200  my $logger = Log::Dispatch::Config->instance;  my $logger = Log::Dispatch::Config->instance;
# Line 83  sub new { Line 203  sub new {
203    my $invocant = shift;    my $invocant = shift;
204    my $class = ref($invocant) || $invocant;    my $class = ref($invocant) || $invocant;
205    #my @args = normalizeArgs(@_);    #my @args = normalizeArgs(@_);
206      
207    my $arg_locator = shift;    my $arg_locator = shift;
208    my $arg_options = shift;    my $arg_options = shift;
209      
210    #my $self = { STORAGEHANDLE => undef, @_ };    #my $self = { STORAGEHANDLE => undef, @_ };
211    my $self = { STORAGEHANDLE => undef, locator => $arg_locator, options => $arg_options };    my $self = { STORAGEHANDLE => undef, locator => $arg_locator, options => $arg_options };
212    $logger->debug( __PACKAGE__ . "[$self->{locator}->{type}]" . "->new(@_)" );    #$logger->debug( __PACKAGE__ . "[$self->{locator}->{type}]" . "->new(@_)" );
213      $logger->debug( __PACKAGE__ . "[$arg_locator->{type}]" . "->new(@_)" );
214    return bless $self, $class;    return bless $self, $class;
215  }  }
216    
# Line 100  sub AUTOLOAD { Line 221  sub AUTOLOAD {
221    #     - Deep recursion on subroutine "Data::Storage::AUTOLOAD"    #     - Deep recursion on subroutine "Data::Storage::AUTOLOAD"
222    #     - Deep recursion on subroutine "Data::Storage::Handler::Abstract::AUTOLOAD"    #     - Deep recursion on subroutine "Data::Storage::Handler::Abstract::AUTOLOAD"
223    #     - Deep recursion on anonymous subroutine at [...]    #     - Deep recursion on anonymous subroutine at [...]
224    # we also might filter log messages caused by logging itself in "advanced logging of AUTOLOAD calls"    # we also might filter log messages caused by logging to itself in "advanced logging of AUTOLOAD calls"
225        
226    my $self = shift;    my $self = shift;
227    our $AUTOLOAD;    our $AUTOLOAD;
# Line 111  sub AUTOLOAD { Line 232  sub AUTOLOAD {
232    my $method = $AUTOLOAD;    my $method = $AUTOLOAD;
233    $method =~ s/^.*:://;    $method =~ s/^.*:://;
234    
235    # advanced logging of AUTOLOAD calls    # advanced logging of AUTOLOAD calls ...
236      my $logstring = "";    # ... nice but do it only when TRACING (TODO) is enabled
237      $logstring .= __PACKAGE__ . "[$self->{locator}->{type}]" . "->" . $method;      if ($TRACELEVEL) {
238      #print "count: ", $#_, "\n";        my $logstring = "";
239      #$logstring .= Dumper(@_) if ($#_ != -1);        $logstring .= __PACKAGE__ . "[$self->{locator}->{type}]" . "->" . $method;
240      my $tabcount = int( (80 - length($logstring)) / 10 );        #print "count: ", $#_, "\n";
241      $logstring .= "\t" x $tabcount . "(AUTOLOAD)";        #$logstring .= Dumper(@_) if ($#_ != -1);
242      # TODO: only ok if logstring doesn't contain        my $tabcount = int( (80 - length($logstring)) / 10 );
243      #            e.g. "Data::Storage[Tangram]->insert(SystemEvent=HASH(0x5c0034c))          (AUTOLOAD)"        $logstring .= "\t" x $tabcount . "(AUTOLOAD)";
244      # but that would be way too specific as long as we don't have an abstract handler for this  ;)        # TODO: only ok if logstring doesn't contain
245      $logger->debug( $logstring );        #            e.g. "Data::Storage[Tangram]->insert(SystemEvent=HASH(0x5c0034c))          (AUTOLOAD)"
246          # but that would be _way_ too specific as long as we don't have an abstract handler for this  ;)
247    # filtering AUTOLOAD calls        $logger->debug( $logstring );
248          #print join('; ', @_);
249        }
250        
251      # filtering AUTOLOAD calls and first-time-touch of the actual storage impl
252    if ($self->_filter_AUTOLOAD($method)) {    if ($self->_filter_AUTOLOAD($method)) {
253        #print "_accessStorage\n";
254      $self->_accessStorage();      $self->_accessStorage();
255      $self->{STORAGEHANDLE}->$method(@_);      $self->{STORAGEHANDLE}->$method(@_);
256    }    }
# Line 155  sub normalizeArgs { Line 281  sub normalizeArgs {
281  sub _accessStorage {  sub _accessStorage {
282    my $self = shift;    my $self = shift;
283    # TODO: to some tracelevel!    # TODO: to some tracelevel!
284    $logger->debug( __PACKAGE__ .  "[$self->{locator}->{type}]" . "->_accessStorage()" );    if ($TRACELEVEL) {
285        $logger->debug( __PACKAGE__ .  "[$self->{locator}->{type}]" . "->_accessStorage()" );
286      }
287    if (!$self->{STORAGEHANDLE}) {    if (!$self->{STORAGEHANDLE}) {
288      $self->_createStorageHandle();      $self->_createStorageHandle();
289    }    }
# Line 163  sub _accessStorage { Line 291  sub _accessStorage {
291    
292  sub _createStorageHandle {  sub _createStorageHandle {
293    my $self = shift;    my $self = shift;
   
294    my $type = $self->{locator}->{type};    my $type = $self->{locator}->{type};
295    $logger->debug( __PACKAGE__ .  "[$type]" . "->_createStorageHandle()" );    $logger->debug( __PACKAGE__ .  "[$type]" . "->_createStorageHandle()" );
296    
297    my $pkg = "Data::Storage::Handler::" . $type . "";    my $pkg = "Data::Storage::Handler::" . $type . "";
298        
299    # propagate args to handler    # try to load perl module at runtime
300    # needs some more thoughts! (not only "dbi" to Tangram, when (in future) db is not more the common case)    my $evalstr = "use $pkg;";
301    if ($type eq 'DBI') {    eval($evalstr);
302      use Data::Storage::Handler::DBI;    if ($@) {
303      #my @args = %{$self->{locator}->{dbi}};      $logger->error( __PACKAGE__ .  "[$type]" . "->_createStorageHandle(): $@" );
304      my @args = %{$self->{locator}};      return;
     # create new storage handle  
     $self->{STORAGEHANDLE} = $pkg->new( @args );  
   }  
   if ($type eq 'Tangram') {  
     use Data::Storage::Handler::Tangram;  
     #$self->{STORAGEHANDLE} = $pkg->new( dsn => $self->{locator}->{dbi}->{dsn} );  
     #my @args = %{$self->{locator}->{dbi}};  
     my @args = %{$self->{locator}};  
     # create new storage handle  
     $self->{STORAGEHANDLE} = $pkg->new( @args );  
   
     #$self->{STORAGEHANDLE_UNDERLYING} = $self->{STORAGEHANDLE}->getUnderlyingStorage();  
     #$self->{STORAGEHANDLE_UNDERLYING}->_configureCOREHANDLE();  
305    }    }
306        
307      # build up some additional arguments to pass on
308      #my @args = %{$self->{locator}};
309      my @args = ();
310    
311      # - create new storage handle object
312      # - propagate arguments to handler
313      # - pass locator by reference to be able to store status- or meta-information in it
314      $self->{STORAGEHANDLE} = $pkg->new( locator => $self->{locator}, @args );
315    
316  }  }
317    
318  sub addLogDispatchHandler {  sub addLogDispatchHandler {
# Line 221  sub addLogDispatchHandler { Line 344  sub addLogDispatchHandler {
344  }  }
345    
346  sub removeLogDispatchHandler {  sub removeLogDispatchHandler {
347      my $self = shift;
348        my $self = shift;    my $name = shift;
349        my $name = shift;    #my $logger = shift;
350        #my $logger = shift;    $logger->remove($name);
   
       $logger->remove($name);  
   
351  }  }
352    
353  sub getDbName {  sub getDbName {
# Line 245  sub testDsn { Line 365  sub testDsn {
365    if ( my $dbh = DBI->connect($dsn, '', '', {    if ( my $dbh = DBI->connect($dsn, '', '', {
366                                                        PrintError => 0,                                                        PrintError => 0,
367                                                        } ) ) {                                                        } ) ) {
368        
369        # TODO: REVIEW
370      $dbh->disconnect();      $dbh->disconnect();
371        
372      return 1;      return 1;
373    } else {    } else {
374      $logger->error( __PACKAGE__ .  "[$self->{locator}->{type}]" . "->testDsn(): " . "DBI-error: " . $DBI::errstr );      $logger->warning( __PACKAGE__ .  "[$self->{locator}->{type}]" . "->testDsn(): " . "DBI-error: " . $DBI::errstr );
375    }    }
376  }  }
377    
378  sub createDb {  sub testAvailability {
379    my $self = shift;    my $self = shift;
380    my $dsn = $self->{locator}->{dbi}->{dsn};    my $status = $self->testDsn();
381      $self->{locator}->{status}->{available} = $status;
382    $logger->debug( __PACKAGE__ .  "->createDb( dsn $dsn )" );    return $status;
   
   $dsn =~ s/database=(.+?);//;  
   my $database_name = $1;  
   
   my $ok;  
     
   if ( my $dbh = DBI->connect($dsn, '', '', {  
                                                       PrintError => 0,  
                                                       } ) ) {  
     if ($database_name) {  
       if ($dbh->do("CREATE DATABASE $database_name;")) {  
         $ok = 1;  
       }  
     }  
     $dbh->disconnect();  
   }  
     
   return $ok;  
     
383  }  }
384    
385    
386  sub dropDb {  sub dropDb {
387    my $self = shift;    my $self = shift;
388    my $dsn = $self->{locator}->{dbi}->{dsn};    my $dsn = $self->{locator}->{dbi}->{dsn};
# Line 297  sub dropDb { Line 402  sub dropDb {
402          $ok = 1;          $ok = 1;
403        }        }
404      }      }
405    
406      $dbh->disconnect();      $dbh->disconnect();
407    
408    }    }
409        
410    return $ok;    return $ok;
# Line 314  __END__ Line 421  __END__
421    
422  =head1 DESCRIPTION  =head1 DESCRIPTION
423    
424  Data::Storage is module for a accessing various "data structures" stored inside  Data::Storage is a module for accessing various "data structures" stored inside
425  various "data containers". It sits on top of DBI and/or Tangram.  various "data containers". It sits on top of DBI and/or Tangram.
426    
427    
# Line 329  License or the Artistic License, as spec Line 436  License or the Artistic License, as spec
436    
437  =head1 ACKNOWLEDGEMENTS  =head1 ACKNOWLEDGEMENTS
438    
439  Larry Wall and the C<perl5-porters> for Perl,  Larry Wall for Perl, Tim Bunce for DBI, Jean-Louis Leroy for Tangram and Set::Object,
440  Tim Bunce for DBI, Jean-Louis Leroy for Tangram and Set::Object,  Sam Vilain for Class::Tangram, Jochen Wiedmann and Jeff Zucker for DBD::CSV and related,
441  Sam Vilain for Class::Tangram.  Adam Spiers for MySQL::Diff and all contributors.
442    
443    
444  =head1 SUPPORT / WARRANTY  =head1 SUPPORT / WARRANTY
# Line 342  Data::Storage is free software. IT COMES Line 449  Data::Storage is free software. IT COMES
449  =head1 TODO  =head1 TODO
450    
451    
452  =head2 Handle the following errors/cases:  =head2 BUGS
453    
454    "DBI-Error [Tangram]: DBD::mysql::st execute failed: Unknown column 't1.requestdump' in 'field list'"
455    
456  =head3 "DBI-Error [Tangram]: DBD::mysql::st execute failed: Unknown column 't1.requestdump' in 'field list'"    ... occours when operating on object-attributes not introduced yet:
457      this should be detected and appended/replaced through:
458      "Schema-Error detected, maybe (just) an inconsistency.
459      Please check if your declaration in schema-module "a" matches structure in database "b" or try to run"
460      db_setup.pl --dbkey=import --action=deploy
461    
     ... occours when operating on object-attributes not introduced yet:  
     this should be detected and appended/replaced through:  
     "Schema-Error detected, maybe (just) an inconsistency.  
     Please check if your declaration in schema-module "a" matches structure in database "b" or try to run"  
     db_setup.pl --dbkey=import --action=deploy  
462    
463  =head3 Compare schema (structure diff) with database ...  Compare schema (structure diff) with database ...
464    
465    ... when issuing "db_setup.pl --dbkey=import --action=deploy"    ... when issuing "db_setup.pl --dbkey=import --action=deploy"
466    on a database with an already deployed schema, use an additional "--update" then    on a database with an already deployed schema, use an additional "--update" then
# Line 373  Data::Storage is free software. IT COMES Line 481  Data::Storage is free software. IT COMES
481      R retrieve  ->  no, not subject of this aspect since it is about deployment only      R retrieve  ->  no, not subject of this aspect since it is about deployment only
482      U update    ->  yes, just by user-interaction; maybe automatically if it can be determined that data wouldn't be lost      U update    ->  yes, just by user-interaction; maybe automatically if it can be determined that data wouldn't be lost
483      D delete    ->  yes, just by user-interaction      D delete    ->  yes, just by user-interaction
484    It's all about not to be able to loose data simply while this is in alpha stage.    
485      It's all about not to be able to loose data simply while this is in pre-alpha stage.
486      And loosing data by being able to modify and redeploy schemas easily is definitely quite easy.
487      
488      As we can see, creations of Classes and new Class variables is handled
489      automatically and this is believed to be the most common case under normal circumstances.
490    
491    
492  =head2 Introduce some features:  =head2 FEATURES
493    
494    Get this stuff together with UML (Unified Modeling Language) and/or standards from ODMG.    - Get this stuff together with UML (Unified Modeling Language) and/or standards from ODMG.
495    Make it possible to load/save schemas in XMI (XML Metadata Interchange),    - Make it possible to load/save schemas in XMI (XML Metadata Interchange),
496    which seems to be most commonly used today, perhaps handle objects with OIFML.      which seems to be most commonly used today, perhaps handle objects with OIFML.
497    Integrate/bundle this with a web-/html-based UML modeling tool or      Integrate/bundle this with a web-/html-based UML modeling tool or
498    some other interesting stuff like the "Co-operative UML Editor" from Uni Darmstadt. (web-/java-based)      some other interesting stuff like the "Co-operative UML Editor" from Uni Darmstadt. (web-/java-based)
499    Enable Round Trip Engineering. Keep code and diagrams in sync. Don't annoy/bother the programmer.    - Enable Round Trip Engineering. Keep code and diagrams in sync. Don't annoy/bother the programmers.
500      - Add support for some more handlers/locators to be able to
501         access the following standards/protocols/interfaces/programs/apis transparently:
502        +  DBD::CSV (via Data::Storage::Handler::DBI)
503       (-) Text::CSV, XML::CSV, XML::Excel
504        -  MAPI
505        -  LDAP
506        -  DAV (look at PerlDAV: http://www.webdav.org/perldav/)
507        -  Mbox (use formail for seperating/splitting entries/nodes)
508        -  Cyrus (cyrdeliver - what about cyrretrieve (export)???)
509        -  use File::DiffTree, use File::Compare
510        -  Hibernate
511        -  "Win32::UserAccountDb"
512        -  "*nix::UserAccountDb"
513        -  .wab - files (Windows Address Book)
514        -  .pst - files (Outlook Post Storage?)
515        -  XML (e.g. via XML::Simple?)
516      - Move to t3, look at InCASE
517    
518    
519  =head3 Links:  =head3 LINKS / REFERENCES
520    
521      Specs:
522      UML 1.3 Spec: http://cgi.omg.org/cgi-bin/doc?ad/99-06-08.pdf      UML 1.3 Spec: http://cgi.omg.org/cgi-bin/doc?ad/99-06-08.pdf
523      XMI 1.1 Spec: http://cgi.omg.org/cgi-bin/doc?ad/99-10-02.pdf      XMI 1.1 Spec: http://cgi.omg.org/cgi-bin/doc?ad/99-10-02.pdf
524      XMI 2.0 Spec: http://cgi.omg.org/docs/ad/01-06-12.pdf      XMI 2.0 Spec: http://cgi.omg.org/docs/ad/01-06-12.pdf
525      ODMG: http://odmg.org/      ODMG: http://odmg.org/
526      OIFML: http://odmg.org/library/readingroom/oifml.pdf      OIFML: http://odmg.org/library/readingroom/oifml.pdf
     Co-operative UML Editor: http://www.darmstadt.gmd.de/concert/activities/internal/umledit.html  
527    
528    further readings:    CASE Tools:
529        Rational Rose (commercial): http://www.rational.com/products/rose/
530        Together (commercial): http://www.oi.com/products/controlcenter/index.jsp
531        InCASE - Tangram-based Universal Object Editor
532        Sybase PowerDesigner: http://www.sybase.com/powerdesigner
533      
534      UML Editors:
535        Fujaba (free, university): http://www.fujaba.de/
536        ArgoUML (free): http://argouml.tigris.org/
537        Poseidon (commercial): http://www.gentleware.com/products/poseidonDE.php3
538        Co-operative UML Editor (research): http://www.darmstadt.gmd.de/concert/activities/internal/umledit.html
539        Metamill (commercial): http://www.metamill.com/
540        Violet (university, research, education): http://www.horstmann.com/violet/
541        PyUt (free): http://pyut.sourceforge.net/
542        (Dia (free): http://www.lysator.liu.se/~alla/dia/)
543        UMLet (free, university): http://www.swt.tuwien.ac.at/umlet/index.html
544        Voodoo (free): http://voodoo.sourceforge.net/
545        Umbrello UML Modeller: http://uml.sourceforge.net/
546    
547      UML Tools:
548        http://www.objectsbydesign.com/tools/umltools_byPrice.html
549    
550      Further readings:
551      http://www.google.com/search?q=web+based+uml+editor&hl=en&lr=&ie=UTF-8&oe=UTF-8&start=10&sa=N      http://www.google.com/search?q=web+based+uml+editor&hl=en&lr=&ie=UTF-8&oe=UTF-8&start=10&sa=N
552      http://www.fernuni-hagen.de/DVT/Aktuelles/01FHHeidelberg.pdf      http://www.fernuni-hagen.de/DVT/Aktuelles/01FHHeidelberg.pdf
553      http://www.enhyper.com/src/documentation/      http://www.enhyper.com/src/documentation/
# Line 403  Data::Storage is free software. IT COMES Line 555  Data::Storage is free software. IT COMES
555      http://citeseer.nj.nec.com/vilain00diagrammatic.html      http://citeseer.nj.nec.com/vilain00diagrammatic.html
556      http://archive.devx.com/uml/articles/Smith01/Smith01-3.asp      http://archive.devx.com/uml/articles/Smith01/Smith01-3.asp
557    
   maybe useful for / to be integrated with:  
     ArapXML: http://xml.coverpages.org/ni2001-09-24-b.html  

Legend:
Removed from v.1.4  
changed lines
  Added in v.1.9

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