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

Legend:
Removed from v.1.2  
changed lines
  Added in v.1.10

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