# HG changeset patch
# User Sergey
# Date 1247561677 -14400
# Node ID 03e58a454b208a22e4b4ca287c68fd43942a03d3
Создан репозитарий
diff -r 000000000000 -r 03e58a454b20 .hgignore
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,1 @@
+glob:.svn/
diff -r 000000000000 -r 03e58a454b20 Lib/BNFCompiler.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/BNFCompiler.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,666 @@
+package BNFCompiler;
+package BNFCompiler::DOM;
+package BNFCompiler::DOM::Builder;
+package BNFCompiler::DOM::Node;
+use strict;
+
+package BNFCompiler::EventParser;
+use strict;
+use lib '.';
+use Common;
+our @ISA = qw(Object);
+
+our $EventMapSchema = {
+ Description => 'Parser events',
+ Type => 'HASH',
+ Values => 'SCALAR'
+};
+
+BEGIN {
+ DeclareProperty(EventMap => ACCESS_READ);
+ DeclareProperty(CompiledEvents => ACCESS_NONE);
+ DeclareProperty(Handler => ACCESS_ALL);
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ $this->SUPER::CTOR(%args);
+}
+
+sub Compile {
+ my ($this) = @_;
+
+ delete $this->{$CompiledEvents};
+ while (my ($key,$val) = each %{$this->{$EventMap}}) {
+ $this->{$CompiledEvents}{$key} = qr/\G$val/;
+ }
+ 1;
+}
+
+sub Parse {
+ my ($this,$data) = @_;
+
+ my $StateData;
+ OUTER: while(pos($data) < length($data)) {
+ keys %{$this->{$CompiledEvents}};
+ while (my ($event,$match) = each %{$this->{$CompiledEvents}}) {
+ if ($data =~ m/($match)/gc) {
+ $StateData .= $1;
+ eval {
+ undef $StateData if $this->{$Handler}->($event,$StateData);
+ };
+ if ($@) {
+ die ["Invalid syntax","unexpected $event: $1",pos($data)];
+ }
+ next OUTER;
+ }
+ }
+ die ["Invalid syntax",substr($data,pos($data),10),pos($data)];
+ }
+
+ return 1;
+}
+
+# íåáîëüøàÿ óëîâêà, ïîñêîëüêó ref îò ðåãóëÿðíîãî âûðàæåíèÿ åñòü Regexp, ìîæíî ïîñòàâèòü õóêè
+package Regexp;
+use Data::Dumper;
+
+sub STORABLE_freeze {
+ my ($obj,$cloning) = @_;
+
+ return $obj;
+}
+
+sub STORABLE_attach {
+ my($class, $cloning, $serialized) = @_;
+ return qr/$serialized/;
+}
+
+package BNFCompiler;
+use Common;
+use Storable;
+use Data::Dumper;
+our @ISA = qw(Object);
+
+our $BNFSchema;
+my $ParseAgainstSchema;
+my $TransformDOMToBNF;
+
+BEGIN {
+ DeclareProperty(Schema => ACCESS_NONE);
+ DeclareProperty(SchemaCache => ACCESS_NONE);
+ DeclareProperty(Transform => ACCESS_NONE);
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+
+ $this->{$SchemaCache} .= '/' if ($this->{$SchemaCache} and not $this->{$SchemaCache} =~ /\/$/);
+}
+{
+ my $compiledBNFSchema;
+ sub LoadBNFSchema {
+ my ($this,%args) = @_;
+
+ my $CompileBNFText = sub {
+ my ($this,$text) = @_;
+
+ my %SchemaDOM;
+ foreach my $item (split /\n{2,}/, $text) {
+ next if not $item;
+ $compiledBNFSchema = CompileBNFSchema($BNFSchema) if not $compiledBNFSchema;
+ my $context = new BNFCompiler::DOM::Builder();
+ eval {
+ my $expr = &$ParseAgainstSchema($compiledBNFSchema,$item,$context);
+ die ["Unexpected expression", $expr] if $expr;
+ };
+ if ($@) {
+ if (ref $@ eq 'ARRAY') {
+ die new Exception(@{$@});
+ } else {
+ die $@;
+ }
+ }
+
+ $SchemaDOM{$context->Document->selectNodes('name')->text()} = &$TransformDOMToBNF($context->Document->selectNodes('def'));
+
+ }
+
+ $SchemaDOM{'separator'} = ['re:\\s+'];
+ $this->{$Schema} = CompileBNFSchema(\%SchemaDOM);
+ };
+
+ my $text;
+ if ($args{'file'}) {
+
+ my $fnameCached;
+ if ($this->{$SchemaCache}) {
+ my $fname = $args{'file'};
+ $fname =~ tr/\//_/;
+ $fnameCached = $this->{$SchemaCache}.$fname.'.cbs';
+ if ( -e $fnameCached && -f $fnameCached && ( -M $args{'file'} >= -M $fnameCached )) {
+ my $compiledSchema = retrieve($fnameCached);
+ if ($compiledSchema) {
+ $this->{$Schema} = $compiledSchema;
+ return 1;
+ } else {
+ unlink $fnameCached;
+ }
+ }
+ }
+ open my $hFile, '<', $args{'file'} or die new Exception("Failed to open file",$args{'file'},$!);
+ local $/ = undef;
+ my $text = <$hFile>;
+
+ $this->$CompileBNFText($text);
+
+ if ($fnameCached) {
+ store($this->{$Schema},$fnameCached);
+ }
+ } elsif ($args{'Schema'}) {
+ $this->{$Schema} = CompileBNFSchema($args{'Schema'});
+ return 1;
+ } elsif ($args{'text'}) {
+ $this->$CompileBNFText( $args{'text'} );
+ } else {
+ die new Exception("'file', 'text' or 'Schema' parameter required");
+ }
+
+ }
+}
+
+sub Parse {
+ my ($this, $string, %flags) = @_;
+
+ my $context = new BNFCompiler::DOM::Builder;
+
+ eval {
+ my $err;
+ $err = &$ParseAgainstSchema($this->{$Schema},$string,$context,\%flags) and die new Exception('Failed to parse',substr($err,0,80).' ...');
+ };
+ if ($@) {
+ if (ref $@ eq 'ARRAY') {
+ die new Exception(@{$@});
+ } else {
+ die $@;
+ }
+ }
+ if (not $this->{$Transform}) {
+ return $context->Document;
+ } else {
+ return $this->{$Transform}->($context->Document);
+ }
+}
+
+sub Dispose {
+ my ($this) = shift;
+ CleanSchema($this->{$Schema});
+ delete @$this{$Schema, $Transform};
+ $this->SUPER::Dispose;
+}
+
+sub CleanSchema {
+ my ($schema,$table) = @_;
+
+ UNIVERSAL::isa($schema,'ARRAY') or return;
+ $table or $table = { $schema, 1};
+
+ for(my $i=0; $i<@$schema;$i++) {
+ my $item = $schema->[$i];
+ if (ref $item) {
+ next if $table->{$item};
+ $table->{$item} = 1;
+ if (UNIVERSAL::isa($item,'ARRAY')) {
+ CleanSchema($item,$table);
+ } elsif( UNIVERSAL::isa($item,'HASH')) {
+ CleanSchema($item->{'syntax'},$table);
+ }
+ undef $schema->[$i];
+ }
+ }
+}
+
+
+sub OPT {
+ return bless [@_], 'OPT';
+}
+
+sub SWITCH {
+ return bless [@_], 'SWITCH';
+}
+
+sub REPEAT {
+ return bless [@_], 'REPEAT';
+}
+
+$TransformDOMToBNF = sub {
+ my ($nodeRoot) = @_;
+
+ return [grep $_, map {
+ my $nodeName = $_->nodeName;
+ if (not $nodeName ){
+ my $obj = $_;
+ $obj->text() if (not( grep { $obj->text() eq $_} ('{', '}', '[', ']') ) );
+ }elsif($nodeName eq 'name') {
+ $_->text();
+ } elsif ($nodeName eq 'separator') {
+ OPT('separator');
+ } elsif ($nodeName eq 'or_sep') {
+ # nothing
+ } elsif ($nodeName eq 'switch_part') {
+ &$TransformDOMToBNF($_);
+ } elsif ($nodeName eq 'class') {
+ my $class = $_->childNodes->[0]->text();
+
+ $class =~ s{(^<|>$|\\.|[\]\[])}{
+ my $char = { '>' => '', '<' => '', '[' => '\\[', ']' => '\\]', '\\\\' => '\\\\'}->{$1};
+ defined $char ? $char : ($1 =~ tr/\\// && $1);
+ }ge;
+ $class = '['.$class.']';
+ $class .= $_->childNodes->[1]->text() if $_->childNodes->[1];
+ 're:'.$class;
+ } elsif ($nodeName eq 'symbol') {
+ $_->text();
+ } elsif ($nodeName eq 'simple') {
+ @{&$TransformDOMToBNF($_)};
+ } elsif ($nodeName eq 'multi_def') {
+ @{&$TransformDOMToBNF($_)};
+ } elsif ($nodeName eq 'optional') {
+ my $multi_def = &$TransformDOMToBNF($_);
+ if ($multi_def->[scalar(@{$multi_def})-1] eq '...') {
+ pop @{$multi_def};
+ OPT(REPEAT(@{$multi_def}));
+ } else {
+ OPT(@{$multi_def});
+ }
+ } elsif ($nodeName eq 'switch') {
+ SWITCH(@{&$TransformDOMToBNF($_)});
+ } elsif ($nodeName eq 'def') {
+ @{&$TransformDOMToBNF($_)};
+ } else{
+ die "unknown nodeName: $nodeName";
+ }
+ } @{$nodeRoot->childNodes}];
+};
+
+$BNFSchema = {
+ syntax => ['name',OPT('separator'),'::=',OPT('separator'),'def'],
+ name => ['re:\\w+'],
+ class => ['re:<([^<>\\\\]|\\\\.)+>',OPT('re:\\*|\\+|\\?|\\{\\d+\\}')],
+ symbol => ['re:[^\\w\\d\\s\\[\\]{}<>\\\\|]+'],
+ separator => ['re:\\s+'],
+ simple => [
+ SWITCH(
+ 'name',
+ 'class',
+ 'symbol'
+ )
+ ],
+ multi_def => [
+ OPT('separator'), SWITCH('...',[SWITCH('simple','optional','switch'),OPT('multi_def')])
+ ],
+ optional => [
+ '[','multi_def', OPT('separator') ,']'
+
+ ],
+ keyword => [],
+ or_sep => ['|'],
+ switch_part => [OPT('separator'),SWITCH('simple','optional','switch'),OPT(REPEAT(OPT('separator'),SWITCH('simple','optional','switch'))),OPT('separator')],
+ switch => [
+ '{','switch_part',OPT(REPEAT('or_sep','switch_part')),'}'
+ ],
+ def => [REPEAT(OPT('separator'),SWITCH('simple','optional','switch'))]
+};
+
+my $CompileTerm;
+$CompileTerm = sub {
+ my ($term,$Schema,$cache,$ref) = @_;
+
+ my $compiled = ref $term eq 'ARRAY' ? ($ref or []) : bless (($ref or []), ref $term);
+
+ die new Exception("Invalid term type $term", $term, ref $term) if not grep ref $term eq $_, qw(ARRAY REPEAT SWITCH OPT);
+
+ foreach my $element (@{$term}) {
+ if (ref $element) {
+ push @{$compiled}, &$CompileTerm($element,$Schema,$cache);
+ } else {
+ if($element =~/^\w+$/) {
+ if (exists $Schema->{$element}) {
+ # reference
+ my $compiledUnit;
+ if (exists $cache->{$element}) {
+ $compiledUnit = $cache->{$element};
+ } else {
+ $compiledUnit = [];
+ $cache->{$element} = $compiledUnit;
+ &$CompileTerm($Schema->{$element},$Schema,$cache,$compiledUnit);
+ }
+
+ push @{$compiled},{ name => $element, syntax => $compiledUnit};
+ } else {
+ # simple word
+ push @{$compiled}, $element;
+ }
+ } elsif ($element =~ /^re:(.*)/){
+ # regexp
+ push @{$compiled},qr/\G(?:$1)/;
+ } else {
+ # char sequence
+ push @{$compiled},$element;
+ }
+ }
+ }
+
+ return $compiled;
+};
+
+sub CompileBNFSchema {
+ my($Schema) = @_;
+
+ my %Cache;
+ return &$CompileTerm($Schema->{'syntax'},$Schema,\%Cache);
+}
+
+my $CompiledSchema = CompileBNFSchema($BNFSchema);
+
+$ParseAgainstSchema = sub {
+ my ($Schema,$expression,$context,$flags,$level) = @_;
+
+ $level = 0 if not defined $level;
+ my $dbgPrint = $flags->{debug} ? sub {
+ print "\t" x $level, @_,"\n";
+ } : sub {};
+
+ foreach my $elem (@{$Schema}) {
+ my $type = ref $elem;
+ $expression = substr $expression,pos($expression) if $type ne 'Regexp' and pos($expression);
+
+ if ($type eq 'HASH') {
+ $context->NewContext($elem->{'name'});
+ &$dbgPrint("$elem->{name} ", join(',',map { ref $_ eq 'HASH' ? $_->{name} : $_ }@{$elem->{'syntax'}}));
+ eval {
+ $expression = &$ParseAgainstSchema($elem->{'syntax'},$expression,$context,$flags,$level+1);
+ };
+ if ($@) {
+ $context->EndContext(0);
+ &$dbgPrint("/$elem->{name} ", "0");
+ die $@;
+ } else {
+ &$dbgPrint("/$elem->{name} ", "1");
+ $context->EndContext(1);
+ }
+ } elsif ($type eq 'ARRAY') {
+ &$dbgPrint("entering ", join(',',map { ref $_ eq 'HASH' ? $_->{name} : $_ }@{$elem}));
+ $expression = &$ParseAgainstSchema($elem,$expression,$context,$flags,$level+1);
+ &$dbgPrint("success");
+ } elsif ($type eq 'OPT') {
+ if (defined $expression) {
+ &$dbgPrint("optional ",join(',',map { ref $_ eq 'HASH' ? $_->{name} : $_ }@{$elem}));
+ eval {
+ $expression = &$ParseAgainstSchema($elem,$expression,$context,$flags,$level+1);
+ };
+ if ($@) {
+ &$dbgPrint("failed");
+ undef $@;
+ } else {
+ &$dbgPrint("success");
+ }
+ }
+ } elsif ($type eq 'SWITCH') {
+ my $success = 0;
+ &$dbgPrint("switch");
+ LOOP_SWITCH: foreach my $subelem (@{$elem}) {
+ eval {
+ &$dbgPrint("\ttry ",join(',',map { ref $_ eq 'HASH' ? $_->{name} : $_ } @{(grep ref $subelem eq $_, qw(ARRAY SWITCH OPT REPEAT)) ? $subelem : [$subelem]}));
+ $expression = &$ParseAgainstSchema((grep ref $subelem eq $_, qw(ARRAY SWITCH OPT REPEAT)) ? $subelem : [$subelem],$expression,$context,$flags,$level+1);
+ $success = 1;
+ };
+ if ($@) {
+ undef $@;
+ } else {
+ last LOOP_SWITCH;
+ }
+ }
+ if ($success) {
+ &$dbgPrint("success");
+ } else {
+ &$dbgPrint("failed");
+ die ["syntax error",$expression,$elem];
+ }
+ } elsif ($type eq 'REPEAT') {
+ my $copy = [@{$elem}];
+ my $i = 0;
+ &$dbgPrint("repeat ",join(',',map { ref $_ eq 'HASH' ? $_->{name} : $_ }@{$elem}));
+ while (1) {
+ eval {
+ $expression = &$ParseAgainstSchema($copy,$expression,$context,$flags,$level+1);
+ $i++;
+ };
+ if ($@) {
+ if (not $i) {
+ &$dbgPrint("failed");
+ die $@;
+ }
+ &$dbgPrint("found $i matches");
+ undef $@;
+ last;
+ }
+ }
+ } elsif ($type eq 'Regexp') {
+ my $posPrev = pos($expression) || 0;
+ if ( $expression =~ m/($elem)/ ) {
+ $context->Data($1);
+ pos($expression) = $posPrev+length($1);
+ &$dbgPrint("Regexp: $1 $elem ", pos($expression));
+ } else {
+ &$dbgPrint("Regexp: $elem failed");
+ die ["syntax error",$expression,$elem,$posPrev];
+ pos($expression) = $posPrev;
+ }
+ } else {
+ if ((my $val = substr($expression, 0, length($elem),'')) eq $elem) {
+ &$dbgPrint("Scalar: $val");
+ $context->Data($elem);
+ } else {
+ &$dbgPrint("Scalar: failed $val expected $elem");
+ die ["syntax error",$val.$expression,$elem];
+ }
+ }
+
+ }
+
+ if (pos $expression) {
+ return substr $expression,(pos($expression) || 0);
+ } else {
+ return $expression;
+ }
+
+};
+
+package BNFCompiler::DOM::Node;
+use Common;
+our @ISA = qw(Object);
+
+sub NODE_TEXT { 1 }
+sub NODE_ELEM { 2 }
+
+BEGIN {
+ DeclareProperty(nodeName => ACCESS_READ);
+ DeclareProperty(nodeType => ACCESS_READ);
+ DeclareProperty(nodeValue => ACCESS_READ);
+ DeclareProperty(childNodes => ACCESS_READ);
+ DeclareProperty(isComplex => ACCESS_READ);
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ $args{'nodeType'} = NODE_ELEM if not $args{'nodeType'};
+ die new Exception("Invalid args. nodeName reqired.") if $args{'nodeType'} == NODE_ELEM and not $args{nodeName};
+
+ #for speed reason
+ #$this->SUPER::CTOR(%args);
+
+ $this->{$nodeName} = $args{'nodeName'} if $args{'nodeName'};
+ $this->{$nodeType} = $args{'nodeType'};
+ $this->{$nodeValue} = $args{'nodeValue'} if exists $args{'nodeValue'};
+
+ $this->{$isComplex} = 0;
+}
+
+sub insertNode {
+ my ($this,$node,$pos) = @_;
+
+ die new Exception("Invalid operation on text node.") if $this->{$nodeType} != NODE_ELEM;
+ die new Exception("Invalid node type",ref $node) if ref $node ne __PACKAGE__;
+
+ $this->{$childNodes} = [] if not $this->{$childNodes};
+
+ $pos = scalar(@{$this->{$childNodes}}) if not defined $pos;
+ die new Exception("Index out of range",$pos) if $pos > scalar(@{$this->{$childNodes}}) or $pos < 0;
+
+ splice @{$this->{$childNodes}},$pos,0,$node;
+ $this->{$isComplex} = 1 if not $this->{$isComplex} and $node->{$nodeType} == NODE_ELEM;
+
+ return $node;
+}
+
+sub removeNode {
+ my ($this,$node) = @_;
+
+ die new Exception("Invalid operation on text node.") if $this->{$nodeType} != NODE_ELEM;
+ @{$this->{$childNodes}} = grep { $_ != $node } @{$this->{$childNodes}};
+
+ return $node;
+}
+
+sub removeAt {
+ my ($this,$pos) = @_;
+
+ die new Exception("Invalid operation on text node.") if $this->{$nodeType} != NODE_ELEM;
+ die new Exception("Index out of range",$pos) if $pos >= scalar(@{$this->{$childNodes}}) or $pos < 0;
+
+ return splice @{$this->{$childNodes}},$pos,1;
+}
+
+sub selectNodes {
+ my ($this,$name) = @_;
+
+ die new Exception("Invalid operation on text node.") if $this->{$nodeType} != NODE_ELEM;
+
+ my @nodes = grep { $_->{$nodeType} == NODE_ELEM and $_->{$nodeName} eq $name } @{$this->{$childNodes}};
+
+ if (wantarray) {
+ return @nodes;
+ } else {
+ return shift @nodes;
+ }
+}
+
+sub text {
+ my $this = shift;
+
+ if ($this->{$nodeType} == NODE_TEXT) {
+ return $this->{$nodeValue};
+ } else {
+ my @texts;
+
+ foreach my $node (@{$this->{$childNodes}}) {
+ push @texts, $node->{$nodeValue} if ($node->{$nodeType}==NODE_TEXT);
+ }
+
+ if (wantarray) {
+ return @texts;
+ } else {
+ return join '',@texts;
+ }
+ }
+}
+
+package BNFCompiler::DOM::Builder;
+use Common;
+our @ISA=qw(Object);
+
+BEGIN {
+ DeclareProperty(Document => ACCESS_READ);
+ DeclareProperty(currentNode => ACCESS_NONE);
+ DeclareProperty(stackNodes => ACCESS_NONE);
+}
+
+sub CTOR {
+ my $this = shift;
+
+ $this->{$Document} = new BNFCompiler::DOM::Node(nodeName => 'Document', nodeType => BNFCompiler::DOM::Node::NODE_ELEM);
+ $this->{$currentNode} = $this->{$Document};
+}
+
+sub NewContext {
+ my ($this,$contextName) = @_;
+
+ push @{$this->{$stackNodes}},$this->{$currentNode};
+ $this->{$currentNode} = new BNFCompiler::DOM::Node(nodeName => $contextName, nodeType=> BNFCompiler::DOM::Node::NODE_ELEM);
+
+ return 1;
+}
+sub EndContext{
+ my ($this,$isNotEmpty) = @_;
+
+ if ($isNotEmpty) {
+ my $child = $this->{$currentNode};
+ $this->{$currentNode} = pop @{$this->{$stackNodes}};
+ $this->{$currentNode}->insertNode($child);
+ } else {
+ $this->{$currentNode} = pop @{$this->{$stackNodes}};
+ }
+}
+sub Data {
+ my ($this,$data) = @_;
+ $this->{$currentNode}->insertNode(new BNFCompiler::DOM::Node(nodeType=> BNFCompiler::DOM::Node::NODE_TEXT, nodeValue => $data));
+}
+
+package BNFCompiler::DOM;
+
+sub TransformDOMToHash {
+ my ($root,$options) = @_;
+
+ my %content;
+
+ if (not $root->childNodes) {
+ die;
+ }
+
+ foreach my $child (@{$root->childNodes}) {
+ if ($child->nodeType == BNFCompiler::DOM::Node::NODE_ELEM) {
+ my @newValue;
+ my $nodeName = $child->nodeName;
+ next if $nodeName eq 'separator' and $options->{'skip_spaces'};
+ if ($child->isComplex) {
+ $newValue[0] = TransformDOMToHash($child,$options);
+ } else {
+ @newValue = $child->text()
+ }
+
+ if ($options->{'use_arrays'}) {
+ push @{$content{$nodeName}},@newValue;
+ }
+
+ if (exists $content{$nodeName}) {
+ if (ref $content{$nodeName} eq 'ARRAY') {
+ push @{$content{$nodeName}}, @newValue;
+ } else {
+ $content{$nodeName} = [$content{$nodeName},@newValue];
+ }
+ } else {
+ $content{$nodeName} = $newValue[0] if scalar(@newValue) == 1;
+ $content{$nodeName} = \@newValue if scalar(@newValue) > 1;
+ }
+ } else {
+ next if $options->{'skip_text'};
+ push @{$content{'_text'}},$child->nodeValue();
+ }
+ }
+
+ return \%content;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/CDBI/Map.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/CDBI/Map.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,110 @@
+package CDBI::Map;
+use strict;
+use Common;
+
+BEGIN {
+ DeclareProperty _Cache => ACCESS_NONE;
+ DeclareProperty _HoldingType => ACCESS_NONE;
+}
+
+sub _KeyValuePairClass {
+ my $this = shift;
+ ($this->{$_HoldingType} = ref $this ) =~ s/^((?:\w+::)*)Map(\w+)$/${1}MapItem${2}/ unless $this->{$_HoldingType};
+ return $this->{$_HoldingType};
+}
+
+# ïðè çàãðóçêå êåøà íåëüçÿ ãðóçèòü KeyValuePair ïîñêîëüêó ïîëó÷àòñÿ öèêëè÷åñêèå ññûëêè:(
+sub GetCache {
+ my $this = shift;
+
+ if (not $this->{$_Cache}) {
+ $this->{$_Cache} = { map { $_->ItemKey, { id => $_->id, value => $_->Value} } $this->_KeyValuePairClass->search(Parent => $this) };
+ }
+
+ return $this->{$_Cache};
+}
+
+sub Keys {
+ my $this = shift;
+ return wantarray ? keys %{$this->GetCache} : [keys %{$this->GetCache}];
+}
+
+sub Item {
+ my ($this,$key,$value,%options) = @_;
+
+ die new Exception('A key must be specified') unless defined $key;
+
+ if (@_ > 2) {
+ # set
+ if (my $pairInfo = $this->GetCache->{$key}) {
+ # update
+ my $pair = $this->_KeyValuePairClass->retrieve($pairInfo->{id});
+ if (defined $value or $options{'keepnull'}) {
+ $pair->Value($value);
+ $pair->update;
+ $pairInfo->{value} = $value;
+ } else {
+ #delete
+ $pair->delete;
+ delete $this->GetCache->{$key};
+ }
+ } else {
+ if ( defined $value or $options{'keepnull'}) {
+ my $pair = $this->_KeyValuePairClass->insert( {Parent => $this, ItemKey => $key, Value => $value } );
+ $this->GetCache->{$key} = {id => $pair->id, value => $value };
+ }
+ }
+ return $value;
+ } else {
+ # get
+ if (my $pairInfo = $this->GetCache->{$key}) {
+ return $pairInfo->{value};
+ } else {
+ return undef;
+ }
+ }
+}
+
+sub Delete {
+ my ($this,$key) = @_;
+
+ if (my $pair = $this->GetCache->{$key} ) {
+ $pair->delete;
+ delete $this->GetCache->{$key};
+ return 1;
+ }
+ return 0;
+}
+
+sub Has {
+ my ($this,$key) = @_;
+
+ return exists $this->GetCache->{$key};
+}
+
+1;
+__END__
+=pod
+=head1 SYNOPSIS
+package App::CDBI;
+use base 'Class::DBI';
+
+#....
+
+package App::MapString;
+use base 'Class::DBI','CDBI::Map';
+
+#....
+
+
+my $Map = App::MapString->retrieve($id);
+print $Map->Item('key');
+$Map->Item('key','value');
+$Map->Delete('key');
+print "the $key is found" if $Map->Has($key);
+
+=head1 DESCRIPTION
+
+Provides a set of methods to manipulate with Maps;
+
+=cut
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/CDBI/Meta.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/CDBI/Meta.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,20 @@
+package CDBI::Meta::BindingAttribute;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+
+BEGIN {
+ public _direct property Binding => prop_get;
+ public _direct property Name => prop_get;
+}
+
+sub CTOR {
+ my ($this,$name,$binding) = @_;
+ $this->{$Binding} = $binding;
+ $this->{$Name} = $name;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/CDBI/Transform.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/CDBI/Transform.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,105 @@
+package CDBI::Transform::FormToObject;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object::Autofill Form::Transform );
+use IMPL::Class::Property;
+require IMPL::Exception;
+
+BEGIN {
+ public property Class => prop_all;
+ public property Namespace => prop_all;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->superCTOR(@_);
+
+ die new IMPL::InvalidArgumentException('Class is required') unless $this->Class;
+}
+
+sub TransformContainer {
+ my ($this,$container) = @_;
+
+ my $class;
+ if ($container->Name eq 'Form') {
+ $class = $this->Class;
+ } else {
+ $class = $this->_mk_class($container->Attributes->{'cdbi.class'}) or die new IMPL::Exception('cdbi.class isn\'t specified',$container->Id->Canonical);
+ }
+
+ my %data;
+
+ #my %columns = map {$_,1} $class->columns();
+
+ no strict 'refs';
+ my @accessors = map $_->accessor, $class->columns();# grep $columns{lc $_}, keys %{"${class}::"};
+
+ # ôîðìèðóåì èç êîíòåéíåðà ôîðìû äàííûå äëÿ îáúåêòà
+ foreach my $column ( @accessors, 'id' ) {
+ my ($val) = $container->GetChild($column);
+ $data{$column} = $this->Transform($val) if $val;
+ }
+
+ my $obj;
+ if ($data{id}) {
+ # edit value
+
+
+ $obj = $class->validateId($data{id});
+ my %filter = map { $_, $obj->$_()} @accessors;
+ $filter{$_} = $data{$_} foreach keys %data;
+ my ($newObj) = $class->lookup(\%data);
+ die new IMPL::DuplicateException('The object already exists', $class) if ($newObj and $newObj->id != $data{id});
+
+ $obj->$_($data{$_}) foreach keys %data;
+ $obj->update();
+ } else {
+ # new instance
+ die new IMPL::DuplicateException('The object already exists', $class) if $class->lookup(\%data);
+ $obj = $class->insert(\%data);
+ }
+ return $obj;
+}
+
+sub _mk_class {
+ my ($this,$name) = @_;
+
+ return unless $name;
+ return $name if $name =~ /::/;
+ return $this->Namespace ? $this->Namespace."::$name" : $name;
+}
+
+package CDBI::Transform::ObjectToForm;
+use base qw(IMPL::Transform);
+
+use IMPL::Class::Property;
+
+sub CTOR {
+ my $this = shift;
+
+ $this->superCTOR(
+ Default => \&TransformObject,
+ Plain => sub { my ($this,$val) = @_; return $val; }
+ );
+}
+
+sub TransformObject {
+ my ($this,$object) = @_;
+
+ return $object if not ref $object;
+
+ my %data;
+ foreach my $column ( (map $_->accessor,$object->columns()),'id') {
+ my $value = $object->$column();
+
+ if (ref $value eq 'HASH') {
+ $data{"$column/$_"} = $value->{$_} foreach keys %$value;
+ } else {
+ $data{$column} = $value;
+ }
+ }
+
+ return \%data;
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Common.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Common.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,282 @@
+package Common;
+use strict;
+no strict 'refs';
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT = qw(&ACCESS_NONE &ACCESS_READ &ACCESS_WRITE &ACCESS_ALL &DeclareProperty &DumpCaller &PropertyList &CloneObject);
+
+our $Debug;
+
+$Debug = 1 if not defined $Debug;
+
+my %ListProperties;
+my %GlobalContext;
+
+1;
+
+sub ACCESS_NONE () { 0 }
+sub ACCESS_READ () { 1 }
+sub ACCESS_WRITE () { 2 }
+sub ACCESS_ALL () {ACCESS_READ | ACCESS_WRITE}
+
+sub PropertyList {
+ return $ListProperties{ref($_[0]) || $_[0] || caller} || {};
+}
+
+sub DeclareProperty {
+ my ($attrName,$accessRights,%Mutators) = @_;
+
+ my $Package = caller;
+ my $Method = $Package.'::'.$attrName;
+ my $fldName;
+
+ my $getMutator = $Mutators{'get'};
+ my $setMutator = $Mutators{'set'};
+
+ ($fldName = $Method) =~ s/:+/_/g;
+
+ $ListProperties{$Package} = {} if not exists $ListProperties{$Package};
+ $ListProperties{$Package}->{$attrName} = $fldName;
+
+ if ($Debug) {
+ *$Method = sub {
+ my $this = shift;
+
+ die new Exception( 'too many args ['.scalar(@_).'\]' , "'$Method' called from: ".DumpCaller() ) if (@_ > 1);
+
+ my $Rights = $accessRights;
+ $Rights = ACCESS_ALL if $Package eq caller;
+
+ if (@_){
+ die new Exception("access denied 'write $Method'", "'$Method' called from: ".DumpCaller()) if not $Rights & ACCESS_WRITE;
+ if (defined $setMutator) {
+ &$setMutator($this,$fldName,shift);
+ } else {
+ $this->{$fldName} = $_[0];
+ }
+
+ } elsif (defined wantarray) {
+ die new Exception("access denied 'read $Method'", "'$Method' called from: ".DumpCaller()) if not $Rights & ACCESS_READ;
+ if (defined $getMutator) {
+ &$getMutator($this,$fldName);
+ } else {
+ if (wantarray){
+ if(ref $this->{$fldName} eq 'ARRAY' ) {
+ return @{$this->{$fldName}};
+ } elsif (not exists $this->{$fldName}) {
+ return;
+ } else {
+ return $this->{$fldName};
+ }
+ } else {
+ return $this->{$fldName};
+ }
+ }
+ } else {
+ undef;
+ }
+ };
+ *$Method = \$fldName;
+ } else {
+ *$Method = sub {
+ my $this = shift;
+ #return undef if @_ > 1;
+ #my $Rights = $accessRights;
+ #$Rights = ACCESS_ALL if $Package eq caller;
+
+ if (@_){
+ # return undef if not $Rights & ACCESS_WRITE;
+ if (defined $setMutator) {
+ &$setMutator($this,$fldName,shift);
+ } else {
+ $this->{$fldName} = shift;
+ }
+ } elsif (defined wantarray) {
+ # return undef if not $Rights & ACCESS_READ;
+ if (defined $getMutator) {
+ &$getMutator($this,$fldName);
+ } else {
+ if (wantarray){
+ if(ref $this->{$fldName} eq 'ARRAY' ) {
+ return @{$this->{$fldName}};
+ } elsif (not defined $this->{$fldName}) {
+ return;
+ } else {
+ return $this->{$fldName};
+ }
+ } else {
+ return $this->{$fldName};
+ }
+ }
+ } else {
+ undef;
+ }
+ };
+ *$Method = \$fldName;
+ }
+}
+
+sub DumpCaller {
+ return join(" ",(caller($_[0]))[1,2],(caller($_[0]+1))[3]);
+}
+
+sub Owner {
+ return undef if not tied $_[0];
+ return undef if not tied($_[0])->UNIVERSAL::can('owner');
+ return tied($_[0])->owner();
+};
+
+sub CloneObject {
+ my $object = shift;
+ if (ref $object == undef) {
+ return $object;
+ } elsif (ref $object eq 'SCALAR') {
+ return \CloneObject(${$object});
+ } elsif (ref $object eq 'ARRAY') {
+ return [map{CloneObject($_)}@{$object}];
+ } elsif (ref $object eq 'HASH') {
+ my %clone;
+ while (my ($key,$value) = each %{$object}) {
+ $clone{$key} = CloneObject($value);
+ }
+ return \%clone;
+ } elsif (ref $object eq 'REF') {
+ return \CloneObject(${$object});
+ } else {
+ if ($object->can('Clone')) {
+ return $object->Clone();
+ } else {
+ die new Exception('Object doesn\'t supports cloning');
+ }
+ }
+}
+
+package Exception;
+use base qw(IMPL::Exception);
+
+package Persistent;
+import Common;
+
+sub newSurogate {
+ my $class = ref($_[0]) || $_[0];
+ return bless {}, $class;
+}
+sub load {
+ my ($this,$context) = @_;
+ die new Exception("invalid deserialization context") if ref($context) ne 'ARRAY';
+ die new Exception("This is not an object") if not ref $this;
+
+ my %Props = (@{$context});
+ foreach my $BaseClass(@{ref($this).'::ISA'}) {
+ while (my ($key,$value) = each %{PropertyList($BaseClass)}) {
+ $this->{$value} = $Props{$value} if exists $Props{$value};
+ }
+ }
+
+ while (my ($key,$value) = each %{PropertyList(ref($this))}) {
+ $this->{$value} = $Props{$key} if exists $Props{$key};
+ }
+ return 1;
+}
+sub save {
+ my ($this,$context) = @_;
+
+ foreach my $BaseClass(@{ref($this).'::ISA'}) {
+ while (my ($key,$value) = each %{PropertyList($BaseClass)}) {
+ $context->AddVar($value,$this->{$value});
+ }
+ }
+
+ while (my ($key,$value) = each %{PropertyList(ref($this))}) {
+ $context->AddVar($key,$this->{$value});
+ }
+ return 1;
+}
+
+sub restore {
+ my ($class,$context,$surogate) = @_;
+ my $this = $surogate || $class->newNewSurogate;
+ $this->load($context);
+ return $this;
+}
+
+package Object;
+import Common;
+
+sub new {
+ my $class = shift;
+ my $self = bless {}, ref($class) || $class;
+ $self->CTOR(@_);
+ return $self;
+}
+
+sub cast {
+ return bless {}, ref $_[0] || $_[0];
+}
+
+our %objects_count;
+our %leaked_objects;
+
+sub CTOR {
+ my $this= shift;
+ $objects_count{ref $this} ++ if $Debug;
+ my %args = @_ if scalar (@_) > 0;
+ return if scalar(@_) == 0;
+
+ warn "invalid args in CTOR. type: ".(ref $this) if scalar(@_) % 2 != 0;
+ my @packages = (ref($this));
+ my $countArgs = int(scalar(@_) / 2);
+ #print "Set ", join(', ',keys %args), "\n";
+ LOOP_PACKS: while(@packages) {
+ my $package = shift @packages;
+ #print "\t$package\n";
+ my $refProps = PropertyList($package);
+ foreach my $name (keys %{$refProps}) {
+ my $fld = $refProps->{$name};
+ if (exists $args{$name}) {
+ $this->{$fld} = $args{$name};
+ #print "\t$countArgs, $name\n";
+ delete $args{$name};
+ $countArgs --;
+ last LOOP_PACKS if $countArgs < 1;
+ } else {
+ #print "\t-$name ($fld)\n";
+ }
+ }
+ push @packages, @{$package.'::ISA'};
+ }
+}
+
+sub Dispose {
+ my $this = shift;
+
+ if ($Debug and UNIVERSAL::isa($this,'HASH')) {
+ my @keys = grep { $this->{$_} and ref $this->{$_} } keys %{$this};
+ warn "not all fields of the object were deleted\n".join("\n",@keys) if @keys;
+ }
+
+ bless $this,'Object::Disposed';
+}
+
+our $MemoryLeakProtection;
+
+sub DESTROY {
+ if ($MemoryLeakProtection) {
+ my $this = shift;
+ warn sprintf("Object leaks: %s of type %s %s",$this,ref $this,UNIVERSAL::can($this,'_dump') ? $this->_dump : '');
+ }
+}
+
+package Object::Disposed;
+our $AUTOLOAD;
+sub AUTOLOAD {
+ return if $AUTOLOAD eq __PACKAGE__.'::DESTROY';
+ die new Exception('Object have been disposed',$AUTOLOAD);
+}
+
+END {
+ $MemoryLeakProtection = 0 if not $Debug;
+}
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Configuration.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Configuration.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,37 @@
+package Configuration;
+use strict;
+
+my $Configured = 0;
+
+sub import {
+ my ($class,$site) = @_;
+
+ if ($site and $site ne $Configured) {
+ Configure($site);
+ $Configured = $site;
+ } elsif (not $site and not $Configured) {
+ $Configured = 1;
+ require Configuration::Global;
+ }
+}
+
+our %virtualSite;
+
+sub Configure {
+ my $siteName = shift;
+ require Configuration::Global;
+
+ while ( my ($pattern,$configSite) = each %virtualSite) {
+ next if not $siteName =~ $pattern;
+ if (ref $configSite eq 'CODE') {
+ $configSite->();
+ } elsif (not ref $configSite and $configSite) {
+ require $configSite;
+ }
+ last;
+ }
+}
+
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/DOM.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,19 @@
+package DOM;
+require DOM::Site;
+use Common;
+
+my $GlobalSite;
+
+sub Site {
+ my $self = shift;
+
+ $GlobalSite = construct DOM::Site if not $GlobalSite;
+ return $GlobalSite;
+}
+
+sub Cleanup {
+ $GlobalSite->Dispose if $GlobalSite;
+ undef $GlobalSite;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Page.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Page.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,241 @@
+package DOM::Page;
+use Common;
+use Template::Context;
+use strict;
+
+our @ISA = qw(Object);
+our $AUTOLOAD;
+
+BEGIN {
+ DeclareProperty(Title => ACCESS_ALL);
+ DeclareProperty(NavChain => ACCESS_READ);
+ DeclareProperty(Menus => ACCESS_READ);
+ DeclareProperty(Properties => ACCESS_READ);
+ DeclareProperty(Template => ACCESS_READ);
+ DeclareProperty(TemplatesProvider => ACCESS_NONE);
+ DeclareProperty(Site => ACCESS_READ);
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ $this->{$Site} = $args{'Site'};
+ $this->{$TemplatesProvider} = $args{'TemplatesProvider'};
+ $this->{$Properties} = $args{'Properties'} || {};
+ $this->{$Title} = $args{'Template'}->Title() || $args{'Properties'}->{'Title'};
+ $this->{$Template} = $args{'Template'};
+ $this->{$NavChain} = $args{'NavChain'};
+ $this->{$Menus} = $args{'Menus'};
+}
+
+sub Render {
+ my ($this,$hOut) = @_;
+
+ my $context = new Template::Context({
+ VARIABLES => $this->{$Site}->Objects(),
+ LOAD_TEMPLATES => $this->{$TemplatesProvider}
+ });
+
+ print $hOut $this->{$Template}->process($context);
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ undef %$this;
+
+ $this->SUPER::Dispose;
+}
+
+sub Container {
+ my ($this) = @_;
+ my $nav = $this->{$NavChain};
+ return $nav->[@{$nav}-1];
+}
+
+sub AUTOLOAD {
+ my $this = shift;
+
+ my $name = $AUTOLOAD;
+ $name =~ s/.*://;
+
+ return $this->{$Properties}->{$name};
+}
+
+=pod
+Ìåíþ
+ [
+ Ýëåìåíò ìåíþ
+ {
+ Key => Êëþ÷ ïóíêòà ìåíþ, äëÿ áûñòðîãî îáðàùåíèÿ ê ýëåìåíòó è ñëèÿíèè ìåíþ
+ Name => Èìÿ ïóíêòà ìåíþ, êîòîðîå áóäåò âèäåëü ïîëüçîâàòåëü
+ Expand => ôëàã òîãî, ÷òî ìåíþ âûáðàíî
+ Value => {[ ýëåìåíò ìåíþ ...] | ÷òî-òî åùå, îáû÷íî óðë}
+ }
+ ]
+=cut
+
+package DOM::PageMenu;
+use Common;
+
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty('Items'); # ìàññèâ
+ DeclareProperty('Keys'); # êëþ÷è äëÿ ïóíêòîâ ìåíþ, åñëè òàêîâûå èìåþòñÿ
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ if (ref $args{'DATA'} eq 'ARRAY') {
+ foreach my $item (@{$args{'DATA'}}) {
+ if (ref $item eq 'HASH') {
+ $this->Append($item->{'Name'},_ProcessData($item->{'Value'}), Expand => $item->{'Expand'}, Key => $item->{'Key'}, Url => $item->{'Url'});
+ } elsif (ref $item eq 'ARRAY') {
+ $this->Append($item->[0],_ProcessData($item->[1]), Expand => $item->[2], Key => $item->[3], Url => $item->[4]);
+ }
+ }
+ }
+}
+
+sub Item {
+ my ($this,$index) = @_;
+
+ return $this->{$Items}[$index];
+}
+
+sub ItemByKey {
+ my ($this,$key) = @_;
+
+ return $this->{$Keys}->{$key};
+}
+
+sub InsertBefore {
+ my ($this,$index,$name,$data,%options) = @_;
+
+ my $item = {Name => $name, Value => _ProcessData($data), %options};
+ splice @{$this->{$Items}},$index,0,$item;
+
+ if ($options{'Key'}) {
+ $this->{$Keys}->{$options{'Key'}} = $item;
+ }
+}
+
+sub Append {
+ my ($this,$name,$data,%options) = @_;
+
+ my $item = {Name => $name, Value => _ProcessData($data), %options};
+
+ push @{$this->{$Items}},$item;
+
+ if ($options{'Key'}) {
+ $this->{$Keys}->{$options{'Key'}} = $item;
+ }
+}
+
+sub SubMenu {
+ my ($this,$path) = @_;
+ my $item = $this;
+ foreach my $key ( split /\/+/,$path ) {
+ $item = $item->{$Keys}->{$key};
+ if (not $item ) {
+ die new Exception('Item does\'t exist', $path, $key);
+ }
+ $item = $item->{Value};
+ if (not UNIVERSAL::isa($item,'DOM::PageMenu')) {
+ $item = ($this->{$Keys}->{$key}->{Value} = new DOM::PageMenu());
+ }
+ }
+
+ return $item;
+}
+
+sub Dump {
+ use Data::Dumper;
+
+ return Dumper(shift);
+}
+
+sub AppendItem {
+ my ($this,$item) = @_;
+
+ push @{$this->{$Items}},$item;
+
+ if ($item->{'Key'}) {
+ $this->{$Keys}->{$item->{'Key'}} = $item;
+ }
+}
+
+sub RemoveAt {
+ my ($this,$index) = @_;
+
+ my $item = splice @{$this->{$Items}},$index,1;
+
+ if ($item->{'Key'}) {
+ delete $this->{$Keys}->{$item->{'Key'}};
+ }
+
+ return 1;
+}
+
+sub ItemsCount {
+ my $this = shift;
+ return scalar(@{$this->{$Items}});
+}
+
+sub Sort {
+ my $this = shift;
+
+ $this->{$Items} = \sort { $a->{'Name'} <=> $b->{'Name'} } @{$this->{$Items}};
+
+ return 1;
+}
+
+sub as_list {
+ my $this = shift;
+ return $this->{$Items} || [];
+}
+
+sub Merge {
+ my ($this,$that) = @_;
+
+ foreach my $itemThat ($that->Items) {
+ my $itemThis = $itemThat->{'Key'} ? $this->{$Keys}->{$itemThat->{'Key'}} : undef;
+ if ($itemThis) {
+ $this->MergeItems($itemThis,$itemThat);
+ } else {
+ $this->AppendItem($itemThat);
+ }
+ }
+}
+
+sub MergeItems {
+ my ($this,$itemLeft,$itemRight) = @_;
+
+ while (my ($prop,$value) = each %{$itemRight}) {
+ if ($prop eq 'Value') {
+ if (UNIVERSAL::isa($itemLeft->{$prop},__PACKAGE__) && UNIVERSAL::isa($value,__PACKAGE__)) {
+ $itemLeft->{$prop}->Merge($value);
+ } else {
+ $itemLeft->{$prop} = $value if defined $value;
+ }
+ } else {
+ $itemLeft->{$prop} = $value if defined $value;
+ }
+ }
+
+ return 1;
+}
+
+sub _ProcessData {
+ my $refData = shift;
+
+ if (ref $refData eq 'ARRAY') {
+ return new DOM::PageMenu(DATA => $refData);
+ } else {
+ return $refData;
+ }
+}
+
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Providers/Form.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Providers/Form.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,49 @@
+package Configuration;
+our $DataDir;
+package DOM::Providers::Form;
+use strict;
+use Form;
+use Schema::Form;
+use Common;
+our @ISA = qw(Object);
+
+our $Encoding ;
+our $CacheDir ||= "${DataDir}Cache/";
+warn "The encoding for the DOM::Provider::Form isn't specified" if not $Encoding;
+$Encoding ||= 'utf-8';
+
+sub GetProviderInfo {
+ return {
+ Name => 'Form',
+ Host => 'DOM::Site',
+ Methods => {
+ LoadForm => \&SiteLoadForm
+ }
+ }
+}
+
+BEGIN {
+ DeclareProperty FormsEncoding => ACCESS_READ;
+ DeclareProperty DataCacheDir => ACCESS_READ;
+}
+
+sub SiteLoadForm {
+ my ($this,$site,$file,$form) = @_;
+ return $site->RegisterObject('Form',$this->LoadForm($file,$form));
+}
+
+sub LoadForm {
+ my ($this,$file, $formName) = @_;
+
+ my $formSchema = Schema::Form->LoadForms($file,$this->{$DataCacheDir},$this->{$FormsEncoding})->{$formName} or die new Exception('The form isn\'t found',$formName,$file);
+ return Form->new($formSchema);
+}
+
+sub construct {
+ my ($class) = @_;
+
+ return $class->new(FormsEncoding => $Encoding, DataCacheDir => $CacheDir);
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Providers/Gallery.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Providers/Gallery.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,200 @@
+use strict;
+package DOM::Gallery;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty(Id => ACCESS_READ);
+ DeclareProperty(Name => ACCESS_READ);
+ DeclareProperty(Description => ACCESS_READ);
+ DeclareProperty(Images => ACCESS_READ);
+ DeclareProperty(CurrentImage => ACCESS_READ);
+ DeclareProperty(NextImage => ACCESS_READ);
+ DeclareProperty(PrevImage => ACCESS_READ);
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Id} = $args{'Id'};
+ $this->{$Name} = $args{'Name'};
+ $this->{$Description} = $args{'Description'};
+}
+
+sub GroupList {
+ my ($this,$GroupCount, $option) = @_;
+
+ my @images = map { $this->{$Images}->{$_} } sort keys %{$this->{$Images}};
+
+ my @listGroups;
+ my $group;
+ for (my $i = 0; $i < $GroupCount; $i++ ) {
+ #last unless scalar(@images) or $option =~ /align/i;
+ push (@$group, shift(@images));
+ if ($i == $GroupCount - 1) {
+ push @listGroups, $group;
+ undef $group;
+ $i = -1;
+ last if not scalar(@images);
+ }
+ }
+
+ return \@listGroups;
+}
+
+sub SelectImage {
+ my ($this,$imageId) = @_;
+
+ my @images = sort keys %{$this->{$Images}};
+
+ for (my $i=0; $i <= @images; $i++) {
+ if ($images[$i] eq $imageId) {
+ $this->{$CurrentImage} = $this->{$Images}->{$images[$i]};
+ $this->{$PrevImage} = $i-1 >= 0 ? $this->{$Images}->{$images[$i-1]} : undef;
+ $this->{$NextImage} = $i+1 < @images ? $this->{$Images}->{$images[$i+1]} : undef;
+ return 1;
+ }
+ }
+ die new Exception("An image '$imageId' not found in the gallery '$this->{$Id}'");
+}
+
+sub AddImage {
+ my ($this,$image) = @_;
+
+ $this->{$Images}->{$image->Id()} = $image;
+}
+
+package DOM::Gallery::Image;
+use Common;
+our @ISA = qw(Object);
+BEGIN {
+ DeclareProperty(Id => ACCESS_READ);
+ DeclareProperty(Name => ACCESS_READ);
+ DeclareProperty(Gallery => ACCESS_READ);
+ DeclareProperty(URL => ACCESS_READ);
+ DeclareProperty(ThumbURL => ACCESS_READ);
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Id} = $args{'Id'} or die new Exception ('An Id should be specified for an image');
+ $this->{$Name} = $args{'Name'};
+ $this->{$Gallery} = $args{'Gallery'} or die new Exception('An Gallery should be specified for an image');
+ $this->{$URL} = $args{'URL'};
+ $this->{$ThumbURL} = $args{'ThumbURL'};
+}
+
+package DOM::Providers::Gallery;
+use Common;
+our @ISA = qw(Object);
+
+our $RepoPath;
+our $ImagesURL;
+our $Encoding;
+
+BEGIN {
+ DeclareProperty(GalleryCache => ACCESS_NONE);
+ DeclareProperty(Repository => ACCESS_NONE);
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Repository} = $args {'Repository'} or die new Exception('A path to an galleries repository should be specified');
+}
+
+sub GetProviderInfo() {
+ return {
+ Name => 'Gallery',
+ Host => 'DOM::Site',
+ Methods => {
+ LoadGallery => \&SiteLoadGallery #($this,$site,$galleryId)
+ }
+ };
+}
+
+sub SiteLoadGallery {
+ my ($this,$site,$galleryId) = @_;
+
+ my $gallery = $this->LoadGallery($galleryId);
+
+ $site->RegisterObject('Gallery',$gallery);
+
+ return $gallery;
+}
+
+sub LoadGallery {
+ my ($this,$galleryId) = @_;
+
+ die new Exception("Invalid Gallery Id: $galleryId") if $galleryId =~ /\\|\//;
+
+ my $galleryIdPath = $galleryId;
+ $galleryIdPath =~ s/\./\//g;
+
+ my $GalleryPath = $this->{$Repository} . $galleryIdPath .'/';
+
+ die new Exception("A gallery '$galleryId' isn't found",$GalleryPath) if not -d $GalleryPath;
+
+ open my $hDesc, "<:encoding($Encoding)", $GalleryPath.'index.htm' or die new Exception("Invalid gallery: $galleryId","Failed to open ${GalleryPath}index.htm: $!");
+
+ my $GalleryName;
+ while (<$hDesc>) {
+ if (/
(.+?)<\/title>/i) {
+ $GalleryName = $1;
+ last;
+ }
+ }
+ undef $hDesc;
+
+ my $ImagesPath = $GalleryPath.'images/';
+ my $ThumbsPath = $GalleryPath.'thumbnails/';
+
+ opendir my $hImages, $ImagesPath or die new Exception("Invalid gallery: $galleryId","Can't open images repository: $!");
+
+ my @imageIds = grep { -f $ImagesPath.$_ } readdir $hImages;
+
+ my %imageNames;
+
+ if (-f $GalleryPath.'description.txt') {
+ local $/="\n";
+ if (open my $hfile,"<:encoding($Encoding)",$GalleryPath.'description.txt') {
+ while (<$hfile>) {
+ chomp;
+ my ($id,$name) = split /\s*=\s*/;
+ $imageNames{$id} = $name;
+ }
+ }
+ }
+
+ undef $hImages;
+
+ if ($Common::Debug) {
+ foreach (@imageIds) {
+ warn "A tumb isn't found for an image: $_" if not -f $ThumbsPath.$_;
+ }
+ }
+
+ my $gallery = new DOM::Gallery(Id => $galleryId, Name => $GalleryName);
+
+ foreach my $imageId (@imageIds) {
+ $gallery->AddImage(new DOM::Gallery::Image(
+ Id => $imageId,
+ URL => $ImagesURL.$galleryIdPath.'/images/'.$imageId,
+ ThumbURL => $ImagesURL.$galleryIdPath.'/thumbnails/'.$imageId,
+ Gallery => $gallery,
+ Name => $imageNames{$imageId}
+ )
+ );
+ }
+
+ return $gallery;
+}
+
+sub construct {
+ my $self = shift;
+
+ return new DOM::Providers::Gallery( Repository => $RepoPath);
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Providers/Headlines.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Providers/Headlines.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,128 @@
+package DOM::Providers::Headlines::Headline;
+use Common;
+use Time::Local;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty(Id => ACCESS_READ);
+ DeclareProperty(DateModify => ACCESS_READ);
+ DeclareProperty(DateExpire => ACCESS_READ);
+ DeclareProperty(URL => ACCESS_READ);
+ DeclareProperty(Text => ACCESS_READ);
+ DeclareProperty(Channel => ACCESS_READ);
+}
+
+sub str2time {
+ my $str = shift;
+
+ if ($str =~ /^(\d{4})-(\d{2})-(\d{2})(?:\s(\d{2}):(\d{2}):(\d{2}))?$/) {
+ my ($year,$month,$day,$hh,$mm,$ss) = ($1,$2-1,$3,(defined $4 ? $4 : 0),(defined $5 ? $5 : 0),(defined $6 ? $6 : 0));
+ return timelocal($ss,$mm,$hh,$day,$month,$year);
+ } else {
+ die new Exception("A string '$str' isn't an ISO standard time");
+ }
+}
+
+sub IsActive {
+ my ($this) = @_;
+ my $timeExpire = str2time($this->{$DateExpire});
+
+ return ($timeExpire > time());
+}
+
+package DOM::Providers::Headlines::Collection;
+use Common;
+our @ISA = qw (Object);
+
+BEGIN {
+ DeclareProperty(Items => ACCESS_READ);
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ foreach my $headline (@{$args{'Items'}}) {
+ $this->{$Items}->{$headline->Id()} = $headline if ($headline->IsActive)
+ }
+}
+
+sub as_list {
+ my $this = shift;
+
+ return [ map { $this->{$Items}->{$_} } sort keys %{$this->{$Items}} ];
+}
+
+sub GenerateRandomSequence {
+ my ($count,$max) = @_;
+
+ my %hash;
+ $hash{rand()} = $_ foreach (0 .. $max - 1);
+ my @sequence = map { $hash{$_} } sort keys %hash;
+ return splice @sequence,0,$count;
+}
+
+sub Random {
+ my ($this,$count) = @_;
+
+ my $list = $this->as_list();
+
+ return [map { $list->[$_] } GenerateRandomSequence($count,scalar(@$list))];
+}
+
+sub Recent {
+ my ($this,$count) = @_;
+
+ my @result = sort { $b->DateModify() cmp $a->DateModify() } values %{$this->{$Items}};
+ splice @result,$count;
+
+ return \@result;
+}
+
+sub AddItem {
+ my ($this,$newItem) = @_;
+
+ $this->{$Items}->{$newItem->Id()} = $newItem;
+}
+
+package DOM::Providers::Headlines;
+use Common;
+use ObjectStore::Headlines;
+
+our $DBPath;
+our $Encoding;
+
+my %Channels;
+
+eval {
+ LoadHeadlines();
+};
+
+if ($@) {
+ my $err = $@;
+ if (ref $err eq 'Exception') {
+ die $err->ToString();
+ } else {
+ die $err;
+ }
+}
+
+
+sub GetProviderInfo {
+ return {
+ Name => 'Headlines',
+ Host => 'DOM::Site',
+ Objects => \%Channels
+ }
+}
+
+sub LoadHeadlines {
+ my $dsHeadlines = new ObjectStore::Headlines(DBPath => $DBPath, HeadlineClass => 'DOM::Providers::Headlines::Headline', Encoding => $Encoding);
+
+ foreach my $headline (@{$dsHeadlines->Search(Filter => sub { return $_[0]->IsActive(); } )}) {
+ my $channel = $headline->Channel() || 'main';
+ $Channels{$channel} = new DOM::Providers::Headlines::Collection() if not exists $Channels{$channel};
+ $Channels{$channel}->AddItem($headline);
+ }
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Providers/Page.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Providers/Page.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,258 @@
+use strict;
+
+package DOM::Providers::Page;
+use Template::Provider;
+#use PerfCounter;
+use DOM::Page;
+use Common;
+use Encode;
+
+our @ISA= qw(Object Exporter);
+
+our $UseIndexPage; #optional
+our $PagesPath; #required
+our $IncludesPath; #optional
+our $CacheSize; #optional
+our $CachePath; #optional
+our $Encoding; #optional
+our $AllowExtPath; #optional
+our $PageResolver; #optional
+
+
+BEGIN {
+ DeclareProperty('PageResolver');
+ DeclareProperty('PagesBase');
+ DeclareProperty('IndexPage');
+ DeclareProperty('TemplatesProvider');
+ DeclareProperty('PageEnc');
+}
+
+sub as_list {
+ return( map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : defined $_ ? $_ : () } @_ );
+}
+
+sub GetProviderInfo {
+ return {
+ Name => 'Page',
+ Host => 'DOM::Site',
+ Methods => {
+ LoadPage => \&SiteLoadPage,
+ ReleasePage => \&SiteReleasePage,
+ }
+ }
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$PageResolver} = $args{'PageResolver'};
+ $this->{$PagesBase} = $args{'TemplatesPath'};
+ $this->{$IndexPage} = $args{'IndexPage'} || 'index.html';
+ $this->{$PageEnc} = $args{'Encoding'};
+ $this->{$TemplatesProvider} = new Template::Provider( INCLUDE_PATH => [$this->{$PagesBase}, as_list($args{'IncludePath'}) ], COMPILE_DIR => $args{'CachePath'}, CACHE_SIZE => $args{'CacheSize'}, ENCODING => $args{'Encoding'}, ABSOLUTE => $AllowExtPath, RELATIVE => $AllowExtPath, INTERPOLATE => 1, PRE_CHOMP => 3);
+}
+
+sub ResolveId {
+ my ($this,$pageId) = @_;
+
+ if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'ResolveId')) {
+ return $this->{$PageResolver}->ResolveId($pageId);
+ } else {
+ return grep { $_ } split /\//,$pageId;
+ }
+}
+
+sub MakePageId {
+ my ($this,$raPath) = @_;
+
+ if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'MakeId')) {
+ return $this->{$PageResolver}->MakeId($raPath);
+ } else {
+ return join '/',@$raPath;
+ }
+}
+
+sub PageIdToURL {
+ my ($this,$pageId) = @_;
+
+ if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'PageIdToURL')) {
+ return $this->{$PageResolver}->PageIdToURL($pageId);
+ } else {
+ return '/'.$pageId;
+ }
+}
+
+sub SiteLoadPage {
+ my ($this,$site,$pageId) = @_;
+
+ return $site->RegisterObject('Page', $this->LoadPage($pageId, Site => $site));
+}
+sub LoadPage {
+ my ($this,$pageId,%args) = @_;
+
+ #StartTimeCounter('LoadPageTime');
+
+ my @pathPage = $this->ResolveId($pageId);
+
+ my $pageNode = $this->LoadNode(\@pathPage);
+
+ pop @pathPage;
+
+ my @pathNode;
+
+ # ïîñêîëüêó ïóòü óêàçàí îòíîñèòåëüíî êîðíåâîãî êîíòåéíåðà, òî íóæíî åãî äîáàâèòü â íà÷àëî
+ my @NavChain = map { push @pathNode, $_; $this->LoadNode(\@pathNode); } ('.',@pathPage);
+
+ if ($pageNode->{'Type'} eq 'Section') {
+ push @NavChain,$pageNode;
+ $pageNode = $this->LoadNode($pageNode->{'pathIndexPage'});
+ }
+
+ # ôîðìèðóåì ìåíþ ñòðàíèöû
+ my %PageMenus;
+ foreach my $MenuSet (map { $_->{'Menus'}} @NavChain, $pageNode->{'Menus'} ) {
+ foreach my $menuName (keys %$MenuSet) {
+ if ($PageMenus{$menuName}) {
+ $PageMenus{$menuName}->Merge($MenuSet->{$menuName});
+ } else {
+ $PageMenus{$menuName} = $MenuSet->{$menuName};
+ }
+ }
+ }
+
+ # ôîðìèðóåì êëþ÷åâûå ñëîâà è ñâîéñòâà
+ my @keywords;
+ my %Props;
+ foreach my $PropSet ( (map { $_->{'Props'}} @NavChain), $pageNode->{'Props'} ) {
+ if(ref $PropSet->{'Keywords'} eq 'ARRAY') {
+ push @keywords, @{$PropSet->{'Keywords'}};
+ } elsif (not ref $PropSet->{'Keywords'} and exists $PropSet->{'Keywords'}) {
+ push @keywords, $PropSet->{'Keywords'};
+ }
+
+ while (my ($prop,$value) = each %$PropSet) {
+ next if $prop eq 'Keywords';
+ $Props{$prop} = $value;
+ }
+ }
+
+ #StopTimeCounter('LoadPageTime');
+ # çàãðóæàåì øàáëîí
+
+ #StartTimeCounter('FetchTime');
+ my ($Template,$error) = $this->{$TemplatesProvider}->fetch($pageNode->{'TemplateFileName'});
+ die new Exception("Failed to load page $pageId",$Template ? $Template->as_string : 'Failed to parse') if $error;
+ #StopTimeCounter('FetchTime');
+
+ my $page = new DOM::Page(TemplatesProvider => $this->{$TemplatesProvider}, Properties => \%Props, Menus => \%PageMenus, NavChain => \@NavChain, Template => $Template, %args);
+ $page->Properties->{url} = $this->PageIdToURL($pageId);
+ return $page;
+}
+
+sub LoadNode {
+ my ($this,$refNodePath) = @_;
+
+ my $fileNameNode = $this->{$PagesBase} . join('/',grep $_, @$refNodePath);
+ my $fileNameMenus;
+ my $fileNameProps;
+
+ my %Node;
+
+ if ( -d $fileNameNode ) {
+ $Node{'Type'} = 'Section';
+ $fileNameMenus = $fileNameNode . '/.menu.pl';
+ $fileNameProps = $fileNameNode . '/.prop.pl';
+ } elsif ( -e $fileNameNode ) {
+ $Node{'Type'} = 'Page';
+ $Node{'TemplateFileName'} = join('/',@$refNodePath);;
+ $fileNameMenus = $fileNameNode . '.menu.pl';
+ $fileNameProps = $fileNameNode . '.prop.pl';
+ } else {
+ die new Exception("Page not found: $fileNameNode");
+ }
+
+ if ( -f $fileNameProps ) {
+ local ${^ENCODING};
+ my $dummy = '';
+ open my $hnull,'>>',\$dummy;
+ local (*STDOUT,*STDIN) = ($hnull,$hnull);
+ $Node{'Props'} = do $fileNameProps or warn "can't parse $fileNameProps: $@";
+ }
+
+ if ( -f $fileNameMenus ) {
+ local ${^ENCODING};
+ my $dummy = '';
+ open my $hnull,'>>',\$dummy;
+ local (*STDOUT,*STDIN) = ($hnull,$hnull);
+ $Node{'Menus'} = do $fileNameMenus or warn "can't parse $fileNameMenus: $@";
+ }
+
+ if ($Node{'Menus'}) {
+ my %Menus;
+ foreach my $menu (keys %{$Node{'Menus'}}) {
+ $Menus{$menu} = new DOM::PageMenu( DATA => $Node{'Menus'}->{$menu} );
+ }
+ $Node{'Menus'} = \%Menus;
+ }
+
+ $Node{'pathIndexPage'} = [@$refNodePath, $Node{'Props'}->{'IndexPage'} || $this->{$IndexPage}] if $Node{'Type'} eq 'Section';
+
+ return \%Node;
+}
+
+sub SiteReleasePage {
+ my ($this,$site) = @_;
+
+ my $page = $site->Objects()->{'Page'};
+ $page->Release() if $page;
+
+ return 1;
+}
+
+sub construct {
+ my $self = shift;
+
+ return new DOM::Providers::Page(TemplatesPath => $PagesPath, IncludePath => $IncludesPath, IndexPage => $UseIndexPage, CachePath => $CachePath, CacheSize => $CacheSize, Encoding => $Encoding);
+}
+
+sub DecodeData {
+ my ($Encoding, $data) = @_;
+
+ if (ref $data) {
+ if (ref $data eq 'SCALAR') {
+ my $decoded = Encode::decode($Encoding,$$data,Encode::LEAVE_SRC);
+ return \$decoded;
+ } elsif (UNIVERSAL::isa($data, 'HASH')) {
+ return {map {Encode::decode($Encoding,$_,Encode::LEAVE_SRC),DecodeData($Encoding,$data->{$_})} keys %$data };
+ } elsif (UNIVERSAL::isa($data, 'ARRAY')) {
+ return [map {DecodeData($Encoding,$_)} @$data];
+ } elsif (ref $data eq 'REF') {
+ my $decoded = DecodeData($Encoding,$$data);
+ return \$decoded;
+ } else {
+ die new Exception('Cant decode data type', ref $data);
+ }
+ } else {
+ return Encode::decode($Encoding,$data,Encode::LEAVE_SRC);
+ }
+}
+
+1;
+
+=pod
+Õðàíèëèùå øàáëîíîâ íà îñíîâå ôàéëîâîé ñèñòåìû.
+
+Õðàíèëèùå ñîñòîèò èç ðàçäåëîâ, êàæäûé ðàçäåë èìååò íàáîð ñâîéñòâ è ìåíþ
+Ñïåöèàëüíû ñâîéñòâà ðàçäåëîâ
+ Keywords Êëþ÷åâûå ñëîâà
+ Name Íàçâàíèå
+ IndexPage ñòðàíèöà ïî óìîë÷àíèþ
+
+Â ðàçäåëàõ íàõîäÿòñÿ ñòðàíèöû, êàæäàÿ ñòðàíèöà èìååò íàáîð ñâîéñòâ è ìåíþ
+
+Ïðè çàãðóçêå ñòðàíèöû ïîëíîñòüþ çàãðóæàþòñÿ âñå ðîäèòåëüñêèå êîíòåéíåðû,
+Ïðè ýòîì îäíîèìåííûå ìåíþ ñëèâàþòñÿ,
+Ñâîéñòâà keywords îáúåúåäèíÿþòñÿ,
+Åñëè èìÿ ñòðàíèöû íå çàäàíî, òî èñïîëüçóåòñÿ èìÿ ðàçäåëà
+
+=cut
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Providers/Perfomance.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Providers/Perfomance.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,15 @@
+use strict;
+
+package DOM::Providers::Perfomance;
+use PerfCounter;
+
+sub GetProviderInfo {
+ return {
+ Name => 'Perfomance',
+ Host => 'DOM::Site',
+ Objects => {
+ Counters => \%PerfCounter::Counters
+ }
+ }
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Providers/Security.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Providers/Security.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,19 @@
+use strict;
+
+package DOM::Providers::Security;
+use Security;
+
+sub GetProviderInfo {
+ return {
+ Name => 'Security',
+ Host => 'DOM::Site',
+ Objects => {
+ Session => \&GetSession
+ }
+ }
+}
+
+sub GetSession {
+ return Security->CurrentSession;
+}
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/DOM/Site.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DOM/Site.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,80 @@
+package DOM::Site;
+use strict;
+use Common;
+our @ISA = qw(Object);
+
+our $Name;
+our @Providers;
+our $AUTOLOAD;
+
+BEGIN {
+ DeclareProperty(Objects => ACCESS_READ);
+ DeclareProperty(Providers => ACCESS_NONE);
+}
+
+sub RegisterObject {
+ my ($this,$name,$object) = @_;
+
+ $this->{$Objects}->{$name} = $object;
+}
+
+sub RegisterProvider {
+ my ($this,$provider) = @_;
+
+ my $refInfo = $provider->GetProviderInfo();
+
+ if ($refInfo->{'Host'} eq __PACKAGE__) {
+ die new Exception("Provider $refInfo->{'Name'} already registered") if exists $this->{$Providers}->{$refInfo->{'Name'}};
+ while (my ($name,$method) = each %{$refInfo->{'Methods'}}) {
+ no strict 'refs';
+ no warnings 'redefine';
+ *{__PACKAGE__ . '::' . $name} = sub {
+ shift;
+ $method->($provider,$this,@_);
+ };
+ }
+
+ while (my ($name,$object) = each %{$refInfo->{'Objects'}}) {
+ $this->{$Objects}->{$refInfo->{'Name'}}->{$name} = $object;
+ }
+
+ $this->{$Providers}->{$refInfo->{'Name'}} = 1;
+ }
+}
+
+sub construct {
+ my $self = shift;
+
+ my $site = $self->new();
+ $site->RegisterObject(Site => { Name => $Name});
+ foreach my $provider (@Providers) {
+ my $providerFile = $provider;
+ $providerFile =~ s/::/\//g;
+ $providerFile .= '.pm';
+ eval {
+ require $providerFile;
+ };
+ if ($@) {
+ my $InnerErr = $@;
+ die new Exception("Failed to load provider $provider", $InnerErr);
+ }
+ if ($provider->can('construct')) {
+ $site->RegisterProvider($provider->construct());
+ } else {
+ $site->RegisterProvider($provider);
+ }
+ }
+ return $site;
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ UNIVERSAL::can($_,'Dispose') and $_->Dispose foreach values %{$this->{$Objects} || {}};
+
+ undef %$this;
+
+ $this->SUPER::Dispose;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/DateTime.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/DateTime.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,312 @@
+use strict;
+package DateTime::Span;
+package DateTime;
+use Common;
+use Time::Local;
+use Time::Zone;
+use Date::Format;
+our @ISA = qw(Object);
+
+use overload
+ '+' => \&opAdd,
+ '-' => \&opSubtract,
+ '<=>' => \&opCompare,
+ 'bool' => \&opAsBool,
+ 'fallback' => 1,
+ '""' => \&opAsString;
+
+BEGIN {
+ DeclareProperty UnixTime => ACCESS_READ;
+}
+
+sub CTOR {
+ my $this = shift;
+
+ if (@_ >= 2) {
+ my(%args) = @_;
+
+ $this->{$UnixTime} = $args{UnixTime} or die new Exception("A correct unix time value is required");
+ } else {
+ $this->{$UnixTime} = $this->ParseISOTime(shift,'+000');
+ }
+}
+
+sub ParseISOTime {
+ my ($class,$time,$timezone) = @_;
+
+ if ($time =~ /^(\d{4})-(\d{2})-(\d{2})(?:.(\d{2}):(\d{2}):(\d{2})(?:\.\d{3})?)?/ ) {
+ my ($yyyy,$mm,$dd,$hh,$MM,$SS) = ($1-1900,$2-1,$3,$4 || 0,$5 || 0,$6 || 0);
+ if ($timezone) {
+ return tz_offset($timezone) + timegm($SS,$MM,$hh,$dd,$mm,$yyyy);
+ } else {
+ return timelocal($SS,$MM,$hh,$dd,$mm,$yyyy);
+ }
+ } else {
+ die new Exception("The specified string isn\'t a correct ISO date",$time);
+ }
+}
+
+sub new_ISO {
+ my ($class,$ISOTime,$zone) = @_;
+ return $class->new(UnixTime => $class->ParseISOTime($ISOTime,$zone));
+}
+
+sub now {
+ my ($class) = @_;
+ return $class->new(UnixTime => time);
+}
+
+sub AsISOString {
+ my ($this,$zone) = @_;
+ return time2str("%Y-%m-%dT%H:%M:%S",$this->{$UnixTime},$zone);
+}
+
+sub AsFormatString {
+ my ($this,$format,$zone) = @_;
+ return time2str($format,$this->{$UnixTime},$zone);
+}
+
+sub opAdd {
+ my ($a,$b,$flag) = @_;
+
+ if (UNIVERSAL::isa($b,'DateTime::Span')) {
+ return new DateTime(UnixTime => $a->{$UnixTime} + $b->SecondsSpan);
+ } elsif (not ref $b){
+ return new DateTime(UnixTime => $a->UnixTime + $b);
+ } else {
+ die new Exception("Only a time span can be added to the DateTime object",$b);
+ }
+}
+
+sub GetDate {
+ my ($this) = @_;
+
+ return DateTime->new_ISO($this->AsFormatString('%Y-%m-%d'));
+}
+
+sub opSubtract {
+ my ($a,$b,$flag) = @_;
+
+ if (UNIVERSAL::isa($b,'DateTime')) {
+ return new DateTime::Span(Seconds => $a->{$UnixTime}-$b->{$UnixTime});
+ } elsif (UNIVERSAL::isa($b,'DateTime::Span')) {
+ return new DateTime(UnixTime => $flag ? $b->SecondsSpan - $a->UnixTime: $a->UnixTime - $b->SecondsSpan);
+ } elsif (not ref $b){
+ return new DateTime(UnixTime => $flag ? $b - $a->UnixTime : $a->UnixTime - $b);
+ } else {
+ die new Exception("Only an another DateTime object or a time span can be subtracted from the DateTime",$b);
+ }
+}
+
+sub opCompare {
+ my ($a,$b,$flag) = @_;
+
+ if (UNIVERSAL::isa($b,'DateTime')) {
+ return $flag ? $b->{$UnixTime} <=> $a->{$UnixTime} : $a->{$UnixTime} <=> $b->{$UnixTime};
+ } else {
+ die new Exception("Only a DateTime object can be compared to the DateTime object", $b);
+ }
+}
+
+sub opAsString {
+ my $this = shift;
+ $this->AsISOString('+000');
+}
+
+sub opAsBool {
+ 1;
+}
+
+package DateTime::Span;
+use Common;
+our @ISA = qw(Object);
+
+use overload
+ '-' => \&opSub,
+ '+' => \&opAdd,
+ '<=>' => \&opCmp,
+ 'fallback' => 1;
+
+BEGIN {
+ DeclareProperty SecondsSpan=>ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$SecondsSpan} = ($args{'Seconds'} || 0) + ($args{'Minutes'} || 0)*60 + ($args{'Hours'} || 0)*3600 + ($args{'Days'} || 0)*86400;
+}
+
+sub Days {
+ my ($this) = @_;
+
+ return int($this->{$SecondsSpan}/86400);
+}
+
+sub Hours {
+ my ($this) = @_;
+
+ return int($this->{$SecondsSpan}/3600);
+}
+sub Minutes {
+ my ($this) = @_;
+
+ return int($this->{$SecondsSpan}/60);
+}
+
+sub opAdd {
+ my ($a,$b,$flag) = @_;
+
+ if (UNIVERSAL::isa($b,'DateTime::Span')) {
+ return new DateTime::Span(Seconds => $a->{$SecondsSpan} + $b->{$SecondsSpan});
+ } elsif (not ref $b) {
+ return new DateTime::Span(Seconds => $a->{$SecondsSpan} + $b);
+ } else {
+ die new Exception("Only a time span can be added to the time span");
+ }
+}
+
+sub opSub {
+ my ($a,$b,$flag) = @_;
+
+ if (UNIVERSAL::isa($b,'DateTime::Span')) {
+ return new DateTime::Span(Seconds => $flag ? $b->{$SecondsSpan} - $a->{$SecondsSpan} : $a->{$SecondsSpan} - $b->{$SecondsSpan});
+ } elsif (not ref $b) {
+ return new DateTime::Span(Seconds => $flag ? $b - $a->{$SecondsSpan} : $a->{$SecondsSpan} - $b);
+ } else {
+ die new Exception("Only a time span can be subtracted from the time span");
+ }
+}
+
+sub opCmp {
+ my ($a,$b,$flag) = @_;
+
+ if (UNIVERSAL::isa($b,'DateTime::Span')) {
+ return $flag ? $b->{$SecondsSpan} <=> $a->{$SecondsSpan} : $a->{$SecondsSpan} <=> $b->{$SecondsSpan};
+ } elsif (not ref $b) {
+ return $flag ? $b <=> $a->{$SecondsSpan} : $a->{$SecondsSpan} <=> $b;
+ } else {
+ die new Exception("Only a time span can be compared to the time span");
+ }
+}
+
+package DateTime::TimeLine;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Timeline => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this) = @_;
+
+ $this->{$Timeline} = [ {Date => undef} ];
+}
+
+# ðåêóðñèâíî êîïèðóåò ïðîñòûå ñòðóêòóðû
+sub SimpleCopy {
+ my ($refObject,$cache) = @_;
+
+ return undef if not defined $refObject;
+
+ $cache ||= {};
+
+ if ($cache->{$refObject}) {
+ return $cache->{$refObject};
+ }
+
+ local $_;
+
+ if (ref $refObject eq 'HASH' ) {
+ return ($cache->{$refObject} = { map { $_, SimpleCopy($refObject->{$_},$cache) } keys %$refObject });
+ } elsif (ref $refObject eq 'ARRAY' ) {
+ return ($cache->{$refObject} = [ map { SimpleCopy($_,$cache) } @$refObject]);
+ } else {
+ return ($cache->{$refObject} = $refObject);
+ }
+}
+
+sub Split {
+ my ($this,$date) = @_;
+
+ die new Exception('Can\'t split the timeline with an undefined date') unless $date;
+
+ for (my $i = 0; $i < @{$this->{$Timeline}}; $i++) {
+ my $Elem = $this->{$Timeline}[$i];
+ if ($Elem->{Date} and $Elem->{Date} >= $date ) {
+ if ($Elem->{Date} == $date) {
+ return $Elem;
+ } else {
+ my $newElem = SimpleCopy($this->{$Timeline}[$i-1]);
+ $newElem->{Date} = $date;
+ use Data::Dumper;
+
+ splice @{$this->{$Timeline}},$i,0,$newElem;
+ return $newElem;
+ }
+ }
+ }
+ my $Elem = { Date => $date };
+ push @{$this->{$Timeline}},$Elem;
+ return $Elem;
+}
+
+sub Select {
+ my ($this,$start,$end) = @_;
+
+ my @result;
+
+ for (my $i=0; $i< @{$this->{$Timeline}}; $i++) {
+ my $Elem = $this->{$Timeline}[$i];
+ my $Next = $this->{$Timeline}[$i+1];
+ if (
+ (not $Elem->{Date} or not $start or $Elem->{Date} < $start)
+ and
+ (not $Next->{Date} or not $start or $Next->{Date} > $start)
+ ) {
+ # ------*++++(++++*----...--)---
+ push @result,$Elem;
+ } elsif (
+ $Elem->{Date}
+ and
+ (not $start or $Elem->{Date} >= $start)
+ and
+ (not $end or $Elem->{Date} < $end )
+ ) {
+ # ------*---(----*++...++*++)+++*----
+ push @result,$Elem;
+ } elsif ( $Elem->{Date} and $end and $Elem->{Date} >= $end) {
+ last;
+ }
+ }
+
+ return @result;
+}
+
+sub SelectStrict {
+ my ($this,$start,$end) = @_;
+ $this->Split($start);
+ $this->Split($end);
+ return grep {
+ $_->{Date}
+ and
+ $start ? $_->{Date} >= $start : 1
+ and
+ $end ? $_->{Date} < $end : 1
+ } @{$this->{$Timeline}};
+}
+
+sub SelectAsPeriod {
+ my ($this,$start,$end) = @_;
+
+ my @Dates = $this->Select($start,$end);
+ for (my $i = 0; $i< @Dates; $i++) {
+ $Dates[$i]->{Start} = $Dates[$i]->{Date};
+ $Dates[$i]->{End} = $Dates[$i+1] ? $Dates[$i+1]->{Date} : undef
+ }
+
+ return @Dates;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,15 @@
+package Deployment;
+use strict;
+
+our %DeploymentScheme;
+our %DeployMethod;
+
+sub isUpdateNeeded {
+
+}
+
+sub Update {
+
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,129 @@
+use strict;
+
+package Deployment::Batch;
+
+require URI::file;
+
+my %Provider;
+our $AUTOLOAD;
+
+our %Dirs;
+our %Context;
+
+$Context{DieOnError} = 1; # dies by default if the action fails to run
+
+our @history;
+
+# make all inc absolute;
+@INC = map { URI::file->new_abs($_)->dir } @INC;
+
+sub AUTOLOAD {
+ my $method = $AUTOLOAD;
+
+ shift if $_[0] eq __PACKAGE__;
+
+ my $class = "$method";
+
+ if (not $Provider{$method}) {
+ (my $file = "$class.pm") =~ s/::/\//g;
+ require $file;
+ $Provider{$method} = 1;
+ }
+
+ my $action = $class->new(@_);
+
+ push @history,$action;
+ if ($Context{Immediate}) {
+ $action->_Run or ($Context{DieOnError} ? die $_->LastError : return 0);
+ }
+
+ return 1;
+}
+
+sub SetDir {
+ shift if $_[0] eq __PACKAGE__;
+ my ($name,$dir) = @_;
+
+ $Dirs{$name} = URI::file->new_abs($dir);
+}
+
+sub Rollback {
+ return 1 if not @history;
+
+ $_->_Rollback or $_->Log('Rollback: ',$_->LastError) foreach reverse grep { $_->isProcessed } @history;
+ undef @history;
+ return 1;
+}
+
+sub Commit {
+ return 1 if not @history;
+
+ # during commit we are in the immediate mode
+ local $Context{Immediate} = 1;
+
+ $_->_Run or $_->Log('Run: ',$_->LastError) and Rollback() and last foreach grep { not $_->isProcessed } @history;
+ return 0 if not @history;
+ undef @history;
+ return 1;
+}
+
+sub DoPackage {
+ shift if $_[0] eq __PACKAGE__;
+ my ($package,$inline) = @_;
+
+ Log( "The package is required" ) and return 0 if not $package;
+ Log( "Processing $package" );
+ my $t0 = [Time::HiRes::gettimeofday];
+
+ if ($inline and $inline eq 'inline') {
+ $inline = 1;
+ } else {
+ $inline = 0;
+ }
+
+ if (not $inline) {
+ my %copy = %Context;
+ local %Context = %copy;
+ local @history = ();
+ $Context{Package} = $Context{PackageDir} ? URI::file->new($package)->abs($Context{PackageDir}) : URI::file->new_abs($package);
+ $Context{PackageDir} = URI::file->new('./')->abs($Context{Package});
+
+ undef $@;
+ do $package or Log("$package: ". ($@ || $!)) and Rollback() and Log("Rollback completed in ",Time::HiRes::tv_interval($t0)," s") and return 0;
+
+ Log("Commiting");
+ Commit or Log("Commit failed in ",Time::HiRes::tv_interval($t0)) and return 0;
+ Log("Commit successful in ",Time::HiRes::tv_interval($t0),' s');
+ return 1;
+ } else {
+ local $Context{Package} = $Context{PackageDir} ? URI::file->new($package)->abs($Context{PackageDir}) : URI::file->new_abs($package);
+ local $Context{PackageDir} = URI::file->new('./')->abs($Context{Package});
+
+ do $package or Log("$package: ". ($@ || $!)) and Rollback() and Log("Rollback completed in ",Time::HiRes::tv_interval($t0),' s') and return 0;
+
+ return 1;
+ }
+}
+
+sub Dir {
+ shift if $_[0] eq __PACKAGE__;
+ my $uriDir = $Dirs{$_[0]} or die "No such directory entry $_[0]";
+ shift;
+ return $uriDir->dir.join('/',@_);
+}
+
+sub PackageDir {
+ shift if $_[0] eq __PACKAGE__;
+ return $Context{PackageDir}->dir.join('/',@_);
+}
+
+sub Log {
+ shift if $_[0] eq __PACKAGE__;
+
+ if (my $hout = $Context{LogOutput}) {
+ print $hout 'DoPackage: ',@_,"\n";
+ }
+ 1;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch/Backup.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch/Backup.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,48 @@
+package Deployment::Batch::Backup;
+use base qw(Deployment::Batch::Generic);
+use Common;
+use File::Copy;
+
+BEGIN {
+ DeclareProperty Action => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,$actionName,$actionArg) = @_;
+
+ $this->{$Action} = { Name => $actionName, Arg => $actionArg };
+}
+
+sub Run {
+ my ($this) = @_;
+
+ my $tmpObj;
+
+ # we are in the immediate mode
+ if ($this->{$Action}{Name} eq 'File') {
+ $this->Log("Backup file: $this->{$Action}{Arg}");
+ if (-e $this->{$Action}{Arg}) {
+
+ Deployment::Batch->Temp( File => \$tmpObj ) or die "Failed to create temp file" ;
+
+ copy ($this->{$Action}{Arg}, $tmpObj->filename) or die "Failed to backup";
+ $this->{$Action}{Result} = $tmpObj->filename;
+ }
+ } else {
+ die "Don't know how to backup the $this->{$Action}{Name}";
+ }
+}
+
+sub Rollback {
+ my ($this) = @_;
+ if ($this->{$Action}{Name} eq 'File') {
+ $this->Log("Revert file: $this->{$Action}{Arg}");
+ if ($this->{$Action}{Result}) {
+ copy ($this->{$Action}{Result}, $this->{$Action}{Arg}) or die "Failed to backup";
+ } else {
+ unlink $this->{$Action}{Arg} if -f $this->{$Action}{Arg};
+ }
+ }
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch/CDBIUpdate.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch/CDBIUpdate.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,139 @@
+use strict;
+package Deployment::Batch::CDBIUpdate;
+use Common;
+use base qw(Deployment::Batch::Generic);
+
+use DBI;
+use Schema::DataSource;
+use Schema::DataSource::CDBIBuilder;
+
+
+BEGIN {
+ DeclareProperty DataSchemaFile => ACCESS_READ;
+ DeclareProperty DataSourceDir => ACCESS_READ;
+ DeclareProperty DSNamespace => ACCESS_READ;
+ DeclareProperty DBConnection => ACCESS_READ;
+ DeclareProperty DBTraitsClass => ACCESS_READ;
+ DeclareProperty SchemaPrev => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$DataSchemaFile} = $args{'Source'} or die new Exception('A data shema file is required');
+ $this->{$DataSourceDir} = $args{'Output'} or die new Exception('A directory for a data source is required');
+ $this->{$DSNamespace} = $args{'Namespace'} || 'DataSource';
+ $this->{$DBTraitsClass} = $args{'DBTraits'} or die new Exception('A DBTraitsClass is required');
+
+ (my $modname = $args{'DBTraits'}.'.pm') =~ s/::/\//g;
+ $this->Log("Loading DBTraits '$modname'");
+ require $modname;
+}
+
+sub Run {
+ my ($this) = @_;
+
+ $this->{$DBConnection} = $this->Context->{Connection};
+
+ my $prefix = $this->{$DSNamespace}.'::';
+
+ my $schemaDS = new Schema::DataSource(DataSourceBuilder => new Schema::DataSource::CDBIBuilder);
+ $schemaDS->BuildSchema($this->{$DataSchemaFile});
+
+ my $schemaDB = $schemaDS->DataSourceBuilder->BuildDBSchema();
+ (my $fname = $this->{$DataSourceDir}.$this->{$DSNamespace}.'.pm') =~ s/::/\//g;
+
+ # we are in the immediate mode, so the file will be backupped immediatelly;
+ $this->Log("Backup $fname");
+ Deployment::Batch->Backup( File => $fname );
+
+ $this->Log("Write the datasource '$this->{$DSNamespace}' to '$this->{$DataSourceDir}'");
+ $schemaDS->DataSourceBuilder->WriteModules($fname,$prefix);
+
+ if ($this->{$DBConnection}) {
+ $this->Log("Update the database '$this->{$DBConnection}[0]'");
+
+ $this->{$SchemaPrev} = $this->UpdateDBToSchema($schemaDB);
+
+ }
+ $schemaDB->Dispose;
+}
+
+sub Rollback {
+ my ($this) = @_;
+
+ if ($this->{$SchemaPrev}) {
+ $this->Log("Rallback the DB schema");
+ $this->UpdateDBToSchema($this->{$SchemaPrev})->Dispose;
+ $this->{$SchemaPrev}->Dispose;
+ delete $this->{$SchemaPrev};
+ }
+
+}
+
+sub UpdateDBToSchema {
+ my ($this,$schemaDB) = @_;
+ my $dbh = DBI->connect(@{$this->{$DBConnection}}) or die new Exception('Failed to connect to the database',@{$this->{$DBConnection}});
+ my $SchemaSource;
+
+ if (UNIVERSAL::can($this->{$DBTraitsClass},'GetMetaTable')) {
+ $SchemaSource = new Deployment::CDBI::SQLSchemeSource (MetaTable => $this->{$DBTraitsClass}->GetMetaTable($dbh));
+ } else {
+ die new Exception("Can't get a meta table",$this->{$DBTraitsClass});
+ }
+
+ my $schemaDBOld = $SchemaSource->ReadSchema($schemaDB->Name);
+
+ my $updater = $this->{$DBTraitsClass}->new(SrcSchema => $schemaDBOld, DstSchema => $schemaDB);
+ $updater->UpdateSchema();
+
+ $dbh->do($_) or die new Exception('Failed to execute the sql statement', $_) foreach $updater->Handler->Sql;
+
+ $SchemaSource->SaveSchema($schemaDB);
+ return $schemaDBOld;
+}
+
+sub DESTROY {
+ my $this = shift;
+
+ $this->{$SchemaPrev}->Dispose if $this->{$SchemaPrev};
+}
+
+package Deployment::CDBI::SQLSchemeSource;
+use Common;
+use Data::Dumper;
+use MIME::Base64;
+use Storable qw(nstore_fd fd_retrieve);
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty MetaTable => ACCESS_NONE;
+}
+
+sub ReadSchema {
+ my ($this,$name) = @_;
+
+ my $schema = decode_base64($this->{$MetaTable}->ReadProperty("db_schema_$name"));
+ if ($schema) {
+ open my $hvar,"<",\$schema or die new Exception("Failed to create a handle to the variable");
+ return fd_retrieve($hvar);
+ } else {
+ return new Schema::DB(Name => $name, Version => 0);
+ }
+}
+
+sub SaveSchema {
+ my ($this,$schema) = @_;
+
+ my $name = $schema->Name;
+
+ my $data = "";
+ {
+ open my $hvar,">",\$data or die new Exception("Failed to create a handle to the variable");
+ nstore_fd($schema,$hvar);
+ }
+
+ $this->{$MetaTable}->SetProperty("db_schema_$name",encode_base64($data));
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch/CopyFile.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch/CopyFile.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,45 @@
+use strict;
+package Deployment::Batch;
+our %Dirs;
+package Deployment::Batch::CopyFile;
+use base qw(Deployment::Batch::Generic);
+use File::Copy;
+require URI::file;
+use Common;
+
+BEGIN {
+ DeclareProperty Src => ACCESS_READ;
+ DeclareProperty Dst => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,$src,$dest,$Dir) = @_;
+
+ $src or die "Source file name is required";
+ $dest or die "Destination file name is reqiured";
+
+ my $uriSrc = URI::file->new($src)->abs($this->Context->{PackageDir});
+
+ my $uriDest = URI::file->new($dest);
+
+ $uriDest = $uriDest->abs(
+ ($Dir and $Dirs{$Dir}) ?
+ $Dirs{$Dir} :
+ $this->Context->{PackageDir}
+ );
+
+ $this->{$Src} = $uriSrc->file;
+ $this->{$Dst} = $uriDest->file;
+}
+
+sub Run {
+ my ($this) = @_;
+
+ $this->Log("Copy '$this->{$Src}' to '$this->{$Dst}'");
+
+ Deployment::Batch->Backup( File => $this->{$Dst} );
+
+ copy($this->{$Src},$this->{$Dst}) or die "copy failed: $!";
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch/CopyTree.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch/CopyTree.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,5 @@
+package Deployment::Batch::CopyTree;
+use base 'Deployment::Batch::Generic';
+use Common;
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch/CustomAction.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch/CustomAction.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,32 @@
+use strict;
+package Deployment::Batch::CustomAction;
+use base qw(Deployment::Batch::Generic);
+use Common;
+
+BEGIN {
+ DeclareProperty handlerRun => ACCESS_READ;
+ DeclareProperty handlerRollback => ACCESS_READ;
+ DeclareProperty Name => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$handlerRun} = $args{Run} || sub {};
+ $this->{$handlerRollback} = $args{Rollback} || sub {};
+ $this->{$Name} = $args{Name} || $this->SUPER::Name();
+}
+
+sub Run {
+ my ($this) = @_;
+
+ $this->{$handlerRun}->($this);
+}
+
+sub Rollback {
+ my ($this) = @_;
+
+ $this->{$handlerRollback}->($this);
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch/Generic.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch/Generic.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,87 @@
+use strict;
+package Deployment::Batch;
+our @history;
+
+package Deployment::Batch::Generic;
+use Common;
+use Time::HiRes;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty isProcessed => ACCESS_READ;
+ DeclareProperty LastError => ACCESS_READ;
+ DeclareProperty LocalHistory => ACCESS_NONE;
+}
+
+sub _Run {
+ my ($this) = @_;
+
+ undef $@;
+ local @history = ();
+ my $t0 = [Time::HiRes::gettimeofday];
+ eval {
+ $this->Run;
+ };
+ $this->Log("completed in ",Time::HiRes::tv_interval($t0)," s");
+
+ if ($@) {
+ $this->{$LastError} = $@;
+ Deployment::Batch::Rollback; # rallback nested actions
+ return 0;
+ }
+
+ $this->{$LocalHistory} = \@history;
+ $this->{$isProcessed} = 1;
+
+ return 1;
+}
+
+sub Name {
+ my $this = shift;
+ (my $mod = ref $this) =~ s/^(?:\w+\:\:)*(\w+)$/$1/;
+ return $mod;
+}
+
+sub _Rollback {
+ my ($this) = @_;
+
+ undef $@;
+ eval {
+ $this->Rollback;
+ };
+
+ if ($@) {
+ $this->{$LastError} = $@;
+ }
+
+ $this->{$isProcessed} = 0;
+
+ if ($this->{$LocalHistory}) {
+ local @history = @{$this->{$LocalHistory}};
+ Deployment::Batch::Rollback;
+ }
+
+ return 1;
+}
+
+sub Context {
+ my $this = shift;
+
+ return \%Deployment::Batch::Context;
+}
+
+sub Log {
+ my $this = shift @_;
+ if ($this->Context->{LogOutput}) {
+ my $out = $this->Context->{LogOutput};
+ print $out $this->Name,": ",@_,"\n";
+ }
+}
+
+sub Run {
+}
+
+sub Rollback {
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/Batch/Temp.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/Batch/Temp.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,52 @@
+use strict;
+package Deployment::Batch::Temp;
+use base qw(Deployment::Batch::Generic);
+use Common;
+use File::Temp;
+
+
+BEGIN {
+ DeclareProperty TmpObj => ACCESS_READ;
+ DeclareProperty Ref => ACCESS_NONE;
+ DeclareProperty TmpObjType => ACCESS_NONE;
+}
+
+sub CTOR {
+ my ($this,$type,$ref) = @_;
+
+ die "A reference to the temp object can be obtained only in the immediate mode" if $ref and not $this->Context->{Immediate};
+
+ $this->{$TmpObjType} = $type or die "The type of a temporary object should be specified";
+ $this->{$Ref} = $ref;
+}
+
+sub Run {
+ my ($this) = @_;
+
+ if ($this->{$TmpObjType} eq 'File') {
+ $this->{$TmpObj} = File::Temp->new;
+ if ($this->{$Ref}) {
+ ${$this->{$Ref}} = $this->{$TmpObj};
+ } else {
+ $this->Context('tmpfile') = $this->{$TmpObj}->filename;
+ }
+ } elsif ($this->{$TmpObjType} eq 'Dir') {
+ $this->{$TmpObj} = File::Temp->newdir;
+ if ($this->{$Ref}) {
+ ${$this->{$Ref}} = $this->{$TmpObj};
+ } else {
+ $this->Context('tmpdir') = $this->{$TmpObj}->dirname;
+ }
+ } else {
+ die "Don't know how to create a temporary $this->{$TmpObjType}";
+ }
+}
+
+sub DESTORY {
+ my ($this) = @_;
+
+ undef $this->{$TmpObj};
+}
+
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Deployment/CDBI.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Deployment/CDBI.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,101 @@
+use strict;
+package Deployment::CDBI;
+use Common;
+use DBI;
+use Schema::DataSource;
+use Schema::DataSource::CDBIBuilder;
+
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty DataSchemaFile => ACCESS_READ;
+ DeclareProperty DataSourceDir => ACCESS_READ;
+ DeclareProperty DSNamespace => ACCESS_READ;
+ DeclareProperty DBConnection => ACCESS_READ;
+ DeclareProperty DBTraitsClass => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$DataSchemaFile} = $args{'DataSchemaFile'} or die new Exception('A data shema file is required');
+ $this->{$DataSourceDir} = $args{'DataSourceDir'} or die new Exception('A directory for a data source is required');
+ $this->{$DSNamespace} = $args{'DSNamespace'} || 'DataSource';
+ $this->{$DBTraitsClass} = $args{'DBTraitsClass'} or die new Exception('A DBTraitsClass is required');
+ $this->{$DBConnection} = $args{'DBConnection'};
+}
+
+sub Update {
+ my ($this) = @_;
+
+ my $prefix = $this->{$DSNamespace}.'::';
+
+ my $schemaDS = new Schema::DataSource(DataSourceBuilder => new Schema::DataSource::CDBIBuilder);
+ $schemaDS->BuildSchema($this->{$DataSchemaFile});
+
+ my $schemaDB = $schemaDS->DataSourceBuilder->BuildDBSchema();
+ (my $fname = $this->{$DSNamespace} ) =~ s/::/\//g;
+ $schemaDS->DataSourceBuilder->WriteModules($this->{$DataSourceDir}.$fname.'.pm',$prefix);
+
+ if ($this->{$DBConnection}) {
+
+ my $dbh = DBI->connect(@{$this->{$DBConnection}}) or die new Exception('Failed to connect to the database',@{$this->{$DBConnection}});
+ my $SchemaSource;
+ if (UNIVERSAL::can($this->{$DBTraitsClass},'GetMetaTable')) {
+ $SchemaSource = new Deployment::CDBI::SQLSchemeSource (MetaTable => $this->{$DBTraitsClass}->GetMetaTable($dbh));
+ } else {
+ die new Exception("Can't get meta table");
+ }
+
+ my $schemaDBOld = $SchemaSource->ReadSchema($schemaDB->Name);
+
+ my $updater = $this->{$DBTraitsClass}->new(SrcSchema => $schemaDBOld, DstSchema => $schemaDB);
+ $updater->UpdateSchema();
+
+ $dbh->do($_) or die new Exception('Failed to execute the sql statement', $_) foreach $updater->Handler->Sql;
+
+ $SchemaSource->SaveSchema($schemaDB);
+
+ $schemaDBOld->Dispose;
+ }
+ $schemaDB->Dispose;
+}
+
+package Deployment::CDBI::SQLSchemeSource;
+use Common;
+use Data::Dumper;
+use MIME::Base64;
+use Storable qw(nstore_fd fd_retrieve);
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty MetaTable => ACCESS_NONE;
+}
+
+sub ReadSchema {
+ my ($this,$name) = @_;
+
+ my $schema = decode_base64($this->{$MetaTable}->ReadProperty("db_schema_$name"));
+ if ($schema) {
+ open my $hvar,"<",\$schema or die new Exception("Failed to create a handle to the variable");
+ return fd_retrieve($hvar);
+ } else {
+ return new Schema::DB(Name => $name, Version => 0);
+ }
+}
+
+sub SaveSchema {
+ my ($this,$schema) = @_;
+
+ my $name = $schema->Name;
+
+ my $data;
+ {
+ open my $hvar,">",\$data or die new Exception("Failed to create a handle to the variable");
+ nstore_fd($schema,$hvar);
+ }
+
+ $this->{$MetaTable}->SetProperty("db_schema_$name",encode_base64($data));
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Action.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Action.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,65 @@
+use strict;
+
+package Engine::Action;
+use Engine::CGI;
+use Common;
+use URI;
+use base qw(IMPL::Object IMPL::Object::Disposable IMPL::Object::Autofill IMPL::Object::EventSource);
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+
+our %Fallout;
+
+BEGIN {
+ public _direct property Package => prop_all;
+ public _direct property Method => prop_all;
+ public _direct property Output => prop_all;
+ public _direct property RequestURI => prop_all;
+ public _direct property Result => prop_all;
+ __PACKAGE__->CreateEvent('OnPreInvoke');
+ __PACKAGE__->CreateEvent('OnPastInvoke');
+}
+
+sub Invoke {
+ my ($this,$query) = @_;
+
+ eval {
+ die new Exception('A package isn\'t specified for the action',$this->RequestURI->as_string) if not $this->{$Package};
+
+ no strict 'refs';
+ eval "require ".$this->{$Package}.";" or die $@;
+
+ $this->OnPreInvoke();
+
+ $this->{$Package}->can($this->{$Method}) or
+ die new Exception("The method doesn't exists", $this->{$Method}, $this->{$Package})
+ if not ref $this->{$Method} eq 'CODE';
+
+ my $instance = $this->{$Package}->can('revive') ? $this->{$Package}->revive : $this->{$Package};
+ my $method = $this->{$Method};
+
+ $this->{$Result} = $instance->$method($query,$this);
+ $this->OnPastInvoke();
+ };
+
+ if($@) {
+ my $err = $@;
+ my $module = ref $this->{$Output} || $this->{$Output};
+ if(my $uri = $module ? ($Fallout{$module}->{ref $err} || $Fallout{$module}->{Default}) : undef) {
+ $this->{$RequestURI} = URI->new($uri,'http');
+ $this->{$Result} = $Common::Debug ? $err : undef;
+ } else {
+ die $err;
+ }
+ }
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ undef %$this;
+
+ $this->SUPER::Dispose;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Action/URICall.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Action/URICall.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,42 @@
+package Engine::Action::URICall;
+use strict;
+use Common;
+use Engine::Action;
+
+our $Namespace;
+
+our %MapOutput;
+our $DefaultMethod;
+
+%MapOutput = ( page => 'Engine::Output::Page' , xml => 'Engine::Output::Xml' ) if not %MapOutput;
+
+=pod
+ /module/submodule/method.format
+=cut
+
+sub ConstructAction {
+ my ($class,$uriRequest) = @_;
+
+ my @module = $uriRequest->path_segments;
+
+ my ($function,$format) = (((pop @module) or $DefaultMethod) =~ m/^(.*?)(?:\.(\w+))?$/);
+ @module = grep $_, @module;
+ my $module = @module ? ($Namespace ? $Namespace . '::' : '').join('::',@module) : $Namespace;
+
+ return new Engine::Action( Package => $module, Method => $function, Output => $class->MapOutput($format), RequestURI => $uriRequest);
+}
+
+sub MapOutput {
+ my ($class,$format) = @_;
+ my $module = $MapOutput{$format} or return undef;
+
+ eval "require $module;" or die new Exception('Failed to load output module',$module,$@);
+
+ if ($module->can('construct')) {
+ return $module->construct($format);
+ } else {
+ return $module;
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/CGI.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/CGI.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,67 @@
+use strict;
+package Engine::CGI;
+use base 'CGI';
+use Encode;
+use Common;
+
+BEGIN {
+ DeclareProperty Expires => ACCESS_ALL;
+}
+
+my $query;
+
+sub Query {
+ $query = new Engine::CGI unless $query;
+ return $query;
+}
+
+
+my $fcgi_loaded = 0;
+sub Accept {
+ my ($self) = shift;
+ require CGI::Fast unless $fcgi_loaded;
+ $fcgi_loaded = 1;
+
+ my $fquery = CGI::Fast->new();
+ $query = $fquery ? $self->new($fquery) : undef;
+ return $query;
+}
+
+sub as_list {
+ return( map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : defined $_ ? $_ : () } @_ );
+}
+
+sub header {
+ my ($this,%args) = @_;
+
+ $args{'-cookies'} = [as_list($args{'-cookies'}), values %{$this->{'cookies_list'}}] if $this->{'cookies_list'};
+ $args{'-expires'} = $this->{$Expires} || 'now';
+
+ $this->SUPER::header(%args);
+}
+
+sub SetCookies {
+ my ($this,@cookies) = @_;
+
+ foreach (@cookies) {
+ $this->{'cookies_list'}{$_->name} = $_;
+ }
+}
+
+sub param {
+ my ($this) = shift;
+ my $charset = $this->charset or die new Exception("Encoding is not defined");
+ if (wantarray) {
+ return map { Encode::is_utf8($_) ? $_ : Encode::decode($charset,$_,Encode::LEAVE_SRC) } $this->SUPER::param( map Encode::encode($charset,$_,Encode::LEAVE_SRC ), @_ );
+ } else {
+ my $val = $this->SUPER::param( map Encode::encode($charset,$_,Encode::LEAVE_SRC ), @_ );
+ return (Encode::is_utf8($val) ? $val : Encode::decode($charset,$val,Encode::LEAVE_SRC));
+ }
+}
+
+sub param_raw {
+ my $this = shift;
+ return $this->SUPER::param(@_);
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Output/JSON.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Output/JSON.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,56 @@
+package Configuration;
+our $HtDocsDir;
+
+package Engine;
+our $Encoding;
+
+package Engine::Output::JSON;
+use strict;
+use warnings;
+
+use Encode;
+use PerlIO;
+use IMPL::Exception;
+use JSON;
+
+sub CTX_TEMPLATE() { 1 }
+sub CTX_DATA() { 2 }
+
+my $context = CTX_DATA;
+our $Data;
+
+sub template() { $context = CTX_TEMPLATE }
+sub data() { $context = CTX_DATA }
+
+sub Print {
+ my ($class,$query,$action) = @_;
+
+ my @path = $action->RequestURI->path_segments;
+ shift @path;
+
+ my $result;
+
+ undef $@;
+ $Data = $action->Result;
+ eval {
+ my $fname = $HtDocsDir . join '/', @path;
+ if ($context == CTX_DATA) {
+ my $dummy = '';
+ open my $hstd, ">>", \$dummy or die new IMPL::Exception('Failed to create inmemory stream');
+ local (*STDIN,*STDOUT) = ($hstd,$hstd);
+ local ${^ENCODING};
+ $result = do $fname or die new IMPL::Exception('Failed to evalute the file', $@, $!,$fname);
+ } else {
+ die new IMPL::Exception('JSON templates not implemented');
+ }
+ };
+ if ($@) {
+ $result = { errorCode => 1, errorMessage => "$@"};
+ }
+
+ print $query->header(-status => 200, -type => 'text/javascript');
+ print to_json({ errorCode => 0, result => $result });
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Output/Page.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Output/Page.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,34 @@
+package Engine;
+our $Encoding;
+
+package Engine::Output::Page;
+use strict;
+
+use Common;
+use DOM;
+
+sub Print {
+ my ($class,$Query,$Action) = @_;
+
+ if (DOM::Site->can('LoadPage')) {
+ my $pageId = $Action->RequestURI->path;
+ DOM::Site->RegisterObject("Request",$Action);
+ my $Page = DOM::Site->LoadPage($pageId);
+ print $Query->header(-status => 200);
+ undef $@;
+ eval {
+ $Page->Properties->{Encoding} = $Engine::Encoding;
+ $Page->Render(*STDOUT);
+ };
+ if ($@) {
+ print $Query->start_html('Error processing template');
+ print $Query->p("Page: $pageId");
+ print $Query->p("Error: $@");
+ print $Query->end_html;
+ }
+ } else {
+ die new Exception('The site doesn\'t support page output');
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Output/Template.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Output/Template.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,55 @@
+package Engine;
+our $Encoding;
+
+package Engine::Output::Template;
+use strict;
+use Common;
+use Template;
+our @ISA = qw(Object);
+our %Formats;
+
+BEGIN {
+ DeclareProperty Include => ACCESS_READ;
+ DeclareProperty ContentType => ACCESS_READ;
+ DeclareProperty Encoding => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Include} = $args{Include} or die new Exception('An include diretory is required',$args{Format});
+ $this->{$ContentType} = $args{ContentType} or die new Exception('A content type must be specied',$args{Format});
+ $this->{$Encoding} = $args{Encoding};
+}
+
+sub Print {
+ my ($this,$Query,$Action) = @_;
+
+ my $template = new Template(
+ {
+ INCLUDE_PATH => $this->{$Include},
+ INTERPOLATE => 1,
+ RECURSION => 1,
+ ENCODING => $this->{$Encoding}
+ }
+ );
+
+ my @path = $Action->RequestURI->path_segments;
+ shift @path;
+ my $Template;
+ eval {
+ $Template = $template->context->template(join('/',@path));
+ };
+ print $Query->header(-type => 'text/html') and die new Exception('Failed to process a template', $@) if $@;
+ $Query->Expires($Template->Expires);
+ print $Query->header(-type => $this->{$ContentType});
+ print $template->context->process($Template,{Encoding => $Engine::Encoding, Data => $Action->Result, Query => $Query });
+}
+
+sub construct {
+ my ($class,$format) = @_;
+
+ $class->new(%{$Formats{$format}},Format => $format);
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Security.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Security.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,36 @@
+use strict;
+package Engine::Security;
+use Security::Auth;
+use Security;
+use Engine::Security::Auth;
+
+our @AuthMethods;
+my $AuthResult;
+my $AuthMod;
+my $AuthMethod;
+
+# use last auth method as default
+$AuthMethod = Engine::Security::Auth->new(%{$AuthMethods[$#AuthMethods]}) if @AuthMethods;
+
+sub AuthenticateContext {
+ Security->CurrentSession(undef); #prevent previous session from closing
+ foreach my $method (@AuthMethods) {
+ my $AuthObj = Engine::Security::Auth->new(%$method);
+ $AuthResult = $AuthObj->DoAuth();
+ # îáíîâèòü òåêóùèé êîíòåêñò áåçîïàñíîñòè, òîëüêî åñëè ýòî íåîáõîäèìî
+ $AuthObj->SetAuthResult($AuthResult) if $AuthResult->State == Security::AUTH_FAILED or $AuthResult->State == Security::AUTH_SUCCESS;
+ $AuthMethod = $AuthObj and last if $AuthResult->State != Security::AUTH_FAILED and $AuthResult->State != Security::AUTH_NOAUTH;
+ }
+ $AuthMod = $AuthMethod->AuthMod if $AuthMethod;
+}
+
+sub SetAuthResult {
+ shift;
+ $AuthMethod->SetAuthResult(@_) if $AuthMethod;
+}
+
+sub AuthMod {
+ return $AuthMethod ? $AuthMethod->AuthMod : undef;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Security/AccessDeniedException.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Security/AccessDeniedException.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,13 @@
+package Engine::Security::AccessDeniedException;
+use strict;
+use Common;
+our @ISA = qw(Exception);
+
+sub CTOR {
+ my ($this,$message,@args) = @_;
+
+ $this->SUPER::CTOR($message ? $message : 'Access denied',@args);
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Security/Auth.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Security/Auth.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,94 @@
+package Engine::Security::Auth;
+use strict;
+use Common;
+our @ISA = qw(Object);
+use Security;
+use Security::Auth;
+use Engine::Security::AccessDeniedException;
+
+BEGIN {
+ DeclareProperty ClientSecData => ACCESS_READ;
+ DeclareProperty SecPackage => ACCESS_READ;
+ DeclareProperty DataSource => ACCESS_READ;
+ DeclareProperty DefaultUser => ACCESS_READ;
+ DeclareProperty _AuthMod => ACCESS_NONE; # construct on demand
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+ eval "require $this->{$ClientSecData};" or warn $@;
+}
+
+sub DoAuth {
+ my ($this) = @_;
+
+ my $data = $this->{$ClientSecData}->ReadSecData($this);
+ my $SSID = $this->{$ClientSecData}->ReadSSID($this);
+
+ my $AuthResult;
+
+ if ($SSID) {
+ $AuthResult = $this->AuthMod->AuthenticateSession($SSID,$data);
+ } else {
+ $AuthResult = new Security::AuthResult(State => Security::AUTH_NOAUTH);
+ }
+
+ if ($AuthResult->State == Security::AUTH_SUCCESS) {
+ #warn "Session authenticated: ".$AuthResult->Session->User->Name;
+ } else {
+ #warn "Session is not authenticated: ".$AuthResult->State;
+ if ($this->{$DefaultUser}) {
+ $AuthResult = $this->AuthMod->AuthenticateUser($this->{$DefaultUser},undef);
+ }
+ }
+
+ return $AuthResult;
+}
+
+sub SetAuthResult {
+ my ($this,$AuthResult) = @_;
+
+ if ($AuthResult and $AuthResult->State == Security::AUTH_SUCCESS) {
+ $this->_CurrentSession($AuthResult->Session);
+ $this->{$ClientSecData}->WriteSecData($AuthResult->ClientSecData,$this);
+ } else {
+ $this->_CurrentSession(undef);
+ $this->{$ClientSecData}->WriteSecData(undef,$this);
+ }
+}
+
+sub _CurrentSession {
+ my ($this,$Session) = @_;
+
+ if (@_ >= 2) {
+ $this->AuthMod->DS->CloseSession(Security->CurrentSession) if Security->CurrentSession;
+
+ $this->{$ClientSecData}->WriteSSID($Session ? $Session->SSID : undef);
+ Security->CurrentSession($Session);
+ } else {
+ return Security->CurrentSession;
+ }
+}
+
+sub AuthMod {
+ my ($this) = @_;
+ if (not $this->{$_AuthMod}) {
+ if ($this->{$DataSource} and $this->{$SecPackage}) {
+ eval qq {
+ require $this->{$DataSource};
+ require $this->{$SecPackage};
+ } or warn $@;
+ $this->{$_AuthMod} = Security::Auth->new(
+ DS => $this->{$DataSource},
+ SecPackage => $this->{$SecPackage}
+ );
+ } else {
+ #construct default
+ $this->{$_AuthMod} = Security::Auth->construct;
+ }
+ }
+ return $this->{$_AuthMod};
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Security/Cookies.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Security/Cookies.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,27 @@
+use strict;
+package Engine::Security::Cookies;
+use Engine::CGI;
+use CGI::Cookie;
+
+sub ReadSecData {
+
+ return Engine::CGI::Query->cookie('SecData');
+}
+
+sub WriteSecData {
+ my ($class,$data) = @_;
+
+ Engine::CGI::Query->SetCookies(new CGI::Cookie(-name => 'SecData', -value => $data, -expires => '+1d'));
+}
+
+sub ReadSSID {
+ return Engine::CGI::Query->cookie('SSID');
+}
+
+sub WriteSSID {
+ my ($class,$data) = @_;
+
+ Engine::CGI::Query->SetCookies(new CGI::Cookie(-name => 'SSID', -value => $data, -expires => '+1d'));
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Engine/Security/IPSession.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Engine/Security/IPSession.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,48 @@
+package Engine::Security::IPSession;
+use strict;
+use Digest::MD5 qw(md5_hex);
+
+our %IPMap; # { IP_ADDR => {user => 'name', ClientSecData => 'ClientData', InitSecData => 'ServerData'} }
+
+sub ReadSecData {
+
+ return $IPMap{$ENV{REMOTE_ADDR} || ''} ? $IPMap{$ENV{REMOTE_ADDR} || ''}->{ClientSecData} : undef; # avoid from create hash item
+}
+
+sub WriteSecData {
+ my ($class,$data) = @_;
+ # does nothing
+}
+
+sub ReadSSID {
+ my ($class,$authEngineObj) = @_;
+
+ my $ip = $ENV{REMOTE_ADDR};
+ return undef if not $IPMap{$ip || ''};
+ my $SSID = md5_hex($ip);
+
+ if (not my $session = $authEngineObj->AuthMod->DS->LoadSession($SSID)) {
+ my $User = $authEngineObj->AuthMod->DS->FindUser($IPMap{$ip}->{user}) or warn "can't authenticate the $ip: user not found" and return undef;
+ $authEngineObj->AuthMod->DS->CreateSession($SSID,$User,$authEngineObj->AuthMod->SecPackage->NewAuthData($IPMap{$ip}->{InitSecData}));
+ } elsif ($session->User->Name ne $IPMap{$ip}->{user}) {
+ # update user
+ my $User = $authEngineObj->AuthMod->DS->FindUser($IPMap{$ip}->{user});
+ if ($User) {
+ $session->User($User);
+ } else {
+ warn "can't authenticate the $ip: user not found";
+ $authEngineObj->AuthMod->DS->CloseSession($session);
+ }
+ }
+
+ return $SSID;
+}
+
+sub WriteSSID {
+ my ($class,$data) = @_;
+
+ #do nothing
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Form.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,147 @@
+package Form;
+use strict;
+use Common;
+use base qw(Form::Container);
+use Form::ItemId;
+use Form::ValueItem;
+
+BEGIN {
+ DeclareProperty AutoCreate => ACCESS_ALL;
+ DeclareProperty isValidated => ACCESS_READ;
+ DeclareProperty isValid => ACCESS_READ;
+ DeclareProperty ValidationErrors => ACCESS_READ;
+ DeclareProperty MapFieldClasses => ACCESS_READ;
+ DeclareProperty LoadedFiledClasses => ACCESS_NONE;
+ DeclareProperty Bindings => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,$schema,$bind) = @_;
+
+ $this->SUPER::CTOR(
+ Schema => $schema->Body,
+ Id => Form::ItemId->new('Form',undef,Form::ItemId::Root->new()),
+ Form => $this
+ );
+ $this->{$MapFieldClasses} = {
+ SelectBox => 'Form::ValueItem::List',
+ RadioSelect => 'Form::ValueItem::List',
+ MultiCheckBox => 'Form::ValueItem::List'
+ };
+ $this->{$LoadedFiledClasses} = { 'Form::ValueItem' => 1 };
+ $this->{$Bindings} = $bind || {};
+ $this->{$isValid} = 0;
+ $this->{$isValidated} = 0;
+}
+
+sub NavigatePath {
+ my ($this,$path) = @_;
+
+ shift @$path if $path->[0]->Name eq 'Form'; # eat root node in Form/Item
+
+ return $this->SUPER::NavigatePath($path);
+}
+
+sub Item {
+ my ($this,$strId) = @_;
+
+ return $this->Navigate($this->MakeItemId($strId,undef));
+}
+
+sub MakeItemId {
+ my ($this,$Name,$BaseObject) = @_;
+
+ my $ItemId;
+ if ($BaseObject and $BaseObject->isa('Form::Item')) {
+ $ItemId = $BaseObject->Id;
+ } else {
+ $ItemId = new Form::ItemId::Root;
+ }
+
+ foreach my $item (split /\//,$Name) {
+ if ($item =~ /^(\w+?)(\d+)?$/) {
+ $ItemId = Form::ItemId->new($1,$2,$ItemId);
+ } else {
+ die new Exception('The invalid identifier',$Name);
+ }
+ }
+ return $ItemId;
+}
+
+sub CreateInstance {
+ my ($this,$schema,$ItemId,$parent) = @_;
+
+ my $obj;
+ if ($schema->isa('Schema::Form::Container')) {
+ $obj = new Form::Container(
+ Id => Form::ItemId->new($ItemId->Name,$ItemId->InstanceID,($parent ? $parent->Id : undef)),
+ Form => $this,
+ Parent => $parent,
+ Schema => $schema,
+ Attributes => {%{$schema->Attributes}}
+ );
+ } elsif ($schema->isa('Schema::Form::Field')) {
+ my $class = $this->{$MapFieldClasses}{$schema->Format->Name} || 'Form::ValueItem';
+ if (not $this->{$LoadedFiledClasses}{$class}) {
+ eval "require $class;" or die new Exception('Failed to load a module',$class,$@);
+ $this->{$LoadedFiledClasses}{$class} = 1;
+ }
+ $obj = $class->new(
+ Id => Form::ItemId->new($ItemId->Name,$ItemId->InstanceID,($parent ? $parent->Id : undef)),
+ Form => $this,
+ Parent => $parent,
+ Type => $schema->Format->Name,
+ Schema => $schema,
+ Attributes => {%{$schema->Attributes}}
+ );
+ } else {
+ die new Exception('Unexpected schema type', ref $schema);
+ }
+
+ return $obj;
+}
+
+sub Validate {
+ my ($this) = @_;
+
+ my @errors = $this->SUPER::Validate;
+ $this->{$isValidated} = 1;
+ if (@errors) {
+ $this->{$isValid} = 0;
+ $this->{$ValidationErrors} = \@errors;
+ } else {
+ $this->{$isValid} = 1;
+ delete $this->{$ValidationErrors};
+ }
+
+ return @errors;
+}
+
+sub SelectErrors {
+ my ($this,$parentId) = @_;
+
+ return [grep $_->Item->Parent->Id->Canonical eq $parentId, $this->ValidationErrors];
+}
+
+sub LoadValues {
+ my ($this,$rhValues) = @_;
+
+ $this->{$isValidated} = 0;
+ $this->{$isValid} = 0;
+
+ foreach my $key (keys %$rhValues) {
+ eval { $this->Item($key)->Value($rhValues->{$key}) };
+ undef $@;
+ }
+}
+
+
+sub Dispose {
+ my ($this) = @_;
+
+ delete @$this{$ValidationErrors,$MapFieldClasses,$LoadedFiledClasses,$Bindings};
+
+ $this->SUPER::Dispose;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Form/Container.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/Container.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,170 @@
+package Form::Container;
+use strict;
+use Common;
+use Form::Filter;
+use base qw(Form::Item);
+
+BEGIN {
+ DeclareProperty Schema => ACCESS_READ;
+ DeclareProperty Children => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ $args{Schema} or die new Exception('A schema is required');
+
+ $this->SUPER::CTOR(@args{qw(Id Form Parent Attributes)});
+ $this->{$Schema} = $args{Schema};
+}
+
+sub ResolveItem {
+ my ($this,$ItemId) = @_;
+
+ if (my $schemaChild = $this->{$Schema}->FindChild($ItemId->Name)) {
+ if ($schemaChild->isMulti) {
+ defined $ItemId->InstanceID or die new Exception('Instance id is required for a muti element');
+ if (my $child = $this->{$Children}{$ItemId->Name}[$ItemId->InstanceID]){
+ return $child;
+ } else {
+ return undef if not $this->Form->AutoCreate;
+ return $this->{$Children}{$ItemId->Name}[$ItemId->InstanceID] = $this->Form->CreateInstance($schemaChild,$ItemId,$this);
+ }
+
+ } else {
+ defined $ItemId->InstanceID and die new Exception('The child is a single element',$this->Id->Canonical,$ItemId->Name);
+ if(my $child = $this->{$Children}{$ItemId->Name}) {
+ return $child;
+ } else {
+ return undef if not $this->Form->AutoCreate;
+ return $this->{$Children}{$ItemId->Name} = $this->Form->CreateInstance($schemaChild,$ItemId,$this);
+ }
+ }
+ } else {
+ die new Exception('The requested item isn\'t exists in the schema', $this->Id->Canonical,$ItemId->Name);
+ }
+}
+
+sub isEmpty {
+ my ($this) = @_;
+
+ foreach my $child (values %{$this->{$Children} || {} }) {
+ if (ref $child eq 'ARRAY') {
+ foreach my $inst (@$child) {
+ return 0 if not $child->isEmpty;
+ }
+ } else {
+ return 0 if not $child->isEmpty;
+ }
+ }
+
+ return 1;
+}
+
+=pod
+Ïîëó÷àåò äî÷åðíèå êîíòåíåðû â âèäå ñïèñêà, ïðè òîì òîëüêî íå ïóñòûå êîíòåéíåðû.
+Åñëè äî÷åðííèé êîíòåéíåð íå ìíîæåñòâåííûé, òî ñïèñîê áóäåò ñîñòîÿòü èç îäíîãî ýëåìåíòà.
+=cut
+sub GetChild {
+ my ($this,$name) = @_;
+ return unless exists $this->{$Children}{$name};
+ return( grep $_, map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : $_ } $this->{$Children}{$name} );
+}
+
+=pod
+Âûïîëíÿåò ôèëüòðû ïî ñõåìå äëÿ ñåáÿ è âñåõ äåòåé.
+Ôèëüòðû îïðåäåëÿþòñÿ ïî ñõåìå è âûçûâàþòñÿ â ðàçëè÷íõ êîíòåêñòàõ
+
+* ñíà÷àëà äëÿ ãðóïïû,
+* ïîòîì äëÿ äåòèøåê, ïðè÷åì åñëè
+ * äåòèøêè ìíîæåñòâåííûå, òî
+ * ñíñ÷àëà äëÿ íàáîðà äåòèøåê, à ïîòîì
+ * äëÿ êàæäîãî â îòäåëüíîñòè
+=cut
+sub Validate {
+ my ($this,$rhDisableFilters) = @_;
+
+ $rhDisableFilters ||= {};
+
+ my @errors;
+
+ foreach my $filter (grep {$_->SUPPORTED_CONTEXT & (Form::Filter::CTX_SINGLE) and not exists $rhDisableFilters->{$_}} map {$_->Instance} $this->{$Schema}->Filters) {
+ my $result = $filter->Invoke($this,Form::Filter::CTX_SINGLE | Form::Filter::CTX_EXISTENT,$this->{$Schema});
+ if ($result->State == Form::FilterResult::STATE_SUCCESS_STOP) {
+ return ();
+ } elsif ($result->State == Form::FilterResult::STATE_ERROR) {
+ push @errors,$result;
+ }
+ }
+
+ CHILD_LOOP: foreach my $schemaChild ($this->{$Schema}->Children) {
+
+ if ($schemaChild->isMulti) {
+ my %DisableFilters;
+ foreach my $filter (grep {$_->SUPPORTED_CONTEXT & Form::Filter::CTX_SET} map {$_->Instance} $schemaChild->Filters) {
+
+ my $result = $filter->Invoke($this->{$Children}{$schemaChild->Name},Form::Filter::CTX_SET,$schemaChild,$this);
+ if ($result->State == Form::FilterResult::STATE_ERROR) {
+ push @errors,$result;
+ # íå ïðîâåðÿòü äðóãèå ôèëüòðû âîîáùå
+ next CHILD_LOOP;
+ } elsif ($result->State == Form::FilterResult::STATE_SUCCESS_STOP) {
+ # íå ïðîâåðÿòü äðóãèå ôèëüòðû âîîáùå
+ next CHILD_LOOP;
+ } elsif ($result->State == Form::FilterResult::STATE_SUCCESS_STAY) {
+ # íå ïðîâåðÿòü äàííûé ôèëüòð íà êàæäîì ýêçåìïëÿðå
+ $DisableFilters{$filter} = 1;
+ } else {
+ # STATE_SUCCESS - âñå îê
+ }
+ }
+
+ $_ and push @errors,$_->Validate(\%DisableFilters) foreach grep !$_->isEmpty, $this->GetChild($schemaChild->Name);
+
+ } else {
+ my %DisableFilters;
+
+ # ïðîâåðÿåì ôèëüòðû, êîòîðûå ìîãóò ïðèìåíÿòüñÿ íà íåñóùåñòâóþùåì çíà÷åíèè
+ foreach my $filter (grep { $_->SUPPORTED_CONTEXT & Form::Filter::CTX_SINGLE and not $_->SUPPORTED_CONTEXT & Form::Filter::CTX_EXISTENT} map {$_->Instance} $schemaChild->Filters) {
+ my $result = $filter->Invoke($this->{$Children}{$schemaChild->Name},Form::Filter::CTX_SINGLE,$schemaChild,$this);
+
+ if ($result->State == Form::FilterResult::STATE_ERROR) {
+ push @errors,$result;
+ # íå ïðîâåðÿòü äðóãèå ôèëüòðû âîîáùå
+ next CHILD_LOOP;
+ } elsif ($result->State == Form::FilterResult::STATE_SUCCESS_STOP) {
+ # íå ïðîâåðÿòü äðóãèå ôèëüòðû âîîáùå
+ next CHILD_LOOP;
+ } else {
+ # STATE_SUCCESS(_STAY) - âñå îê
+ $DisableFilters{$filter} = 1;
+ }
+ }
+
+ # åñëè çíà÷åíèå ñóùåñòâóåò, òî ïðèìåíÿåì îñòàâøèåñÿ ôèëüòðû
+ push @errors,$this->{$Children}{$schemaChild->Name}->Validate(\%DisableFilters) if $this->{$Children}{$schemaChild->Name};
+ }
+
+ }
+
+ return @errors;
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ foreach my $child (values %{ $this->{$Children} || {} }) {
+ if (ref $child eq 'ARRAY') {
+ foreach my $inst (@$child) {
+ $inst->Dispose;
+ }
+ } else {
+ die new IMPL::Exception("Child is null",%{ $this->{$Children} }) if not $child;
+ $child->Dispose;
+ }
+ }
+
+ delete @$this{$Schema,$Children};
+
+ $this->SUPER::Dispose;
+}
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Form/Filter.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/Filter.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,67 @@
+package Form::Filter;
+use strict;
+use Common;
+our @ISA = qw(Object);
+
+use constant {
+ CTX_SINGLE => 1, # çíà÷åíèå ïîëÿ
+ CTX_SET => 2, # ìíîæåñòâî çíà÷åíèé
+ CTX_EXISTENT => 4 # òîëüêî ñóùåñòâóþùèå çíà÷åíèÿ
+};
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Message => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,$name,$message) = @_;
+ $this->{$Name} = $name or die new Exception('A filter name is required');
+ $this->{$Message} = $message;
+}
+
+sub FormatMessage {
+ my ($this,$object) = @_;
+
+ (my $message = $object->Attributes->{$this->{$Name}} || $this->{$Message} || ($Common::Debug ? "$this->{$Name}: %name%" : '')) =~ s{%(\w+(?:\.\w+)*)%}{
+ my $value = $object->Attributes->{$1} || ($Common::Debug ? $object->Name.'.'.$1 : '');
+ }ge;
+
+ return $message;
+}
+
+package Form::FilterResult;
+use Common;
+our @ISA = qw(Object);
+
+use constant {
+ STATE_ERROR => 0, # îøèáî÷íîå çíà÷åíèå
+ STATE_SUCCESS => 1, # çíà÷åíèå êîððåêòíîå, ìîæíî ïðîäîëæàòü âûïîëíåíèå
+ STATE_SUCCESS_STOP => 2, # çíà÷åíèå êîððåêòíîå, âûïîëíåíèå îñòàëüíûõ ôèëüòðîâ íå òðåáóåòñÿ
+ STATE_SUCCESS_STAY => 3 # çíà÷åíèå êîððåêòíîå, âûïîëíåíèå äàííîãî ôèëüòðà áîëåå íå òðåáóåòñÿ
+};
+
+BEGIN {
+ DeclareProperty State => ACCESS_READ;
+ DeclareProperty Message => ACCESS_READ;
+ DeclareProperty Target => ACCESS_READ;
+ DeclareProperty Container => ACCESS_READ;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+
+ UNIVERSAL::isa($this->{$Target},'Form::Item') or UNIVERSAL::isa($this->{$Container},'Form::Container') or die new Exception("Invalid Target or Container property") if $this->{$State} == STATE_ERROR;
+}
+
+sub Item {
+ my $this = shift;
+
+ return ref $this->{$Target} ?
+ ($this->{$Target}->isa('Form::Item') ? $this->{$Target} : $this->{$Container}->Item( $this->{$Target}->isMulti ? $this->{$Target}->Name . '0' : $this->{$Target}->Name ) )
+ :
+ ($this->{$Target});
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Form/Filter/Depends.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/Filter/Depends.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,34 @@
+package Form::Filter::Depends;
+use base qw(Form::Filter);
+
+use Common;
+
+BEGIN {
+ DeclareProperty Fields => ACCESS_READ;
+}
+
+sub SUPPORTED_CONTEXT { Form::Filter::CTX_SINGLE | Form::Filter::CTX_SET }
+
+sub CTOR {
+ my ($this,$name,$message,@fields) = @_;
+
+ $this->SUPER::CTOR($name,$message);
+ $this->{$Fields} = \@fields;
+}
+
+sub Invoke {
+ my ($this,$object,$context,$schemaTarget) = @_;
+
+ foreach my $field (@{$this->{$Fields}}) {
+ my $objProv = $object->Navigate($object->Form->MakeItemId($field,$object->Parent));
+
+ if ( not $objProv or $objProv->isEmpty ) {
+ return new Form::FilterResult(State => Form::FilterResult::STATE_STOP);
+ }
+
+ }
+
+ return new Form::FilterResult(State => Form::FilterResult::STATE_SUCCESS_STAY);
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Form/Filter/Mandatory.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/Filter/Mandatory.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,34 @@
+package Form::Filter::Mandatory;
+use strict;
+use Common;
+use base qw(Form::Filter);
+
+sub SUPPORTED_CONTEXT { Form::Filter::CTX_SINGLE | Form::Filter::CTX_SET }
+
+sub Invoke {
+ my ($this,$target,$context,$schemaTarget,$parent) = @_;
+
+ my @list;
+ if ($context & Form::Filter::CTX_SET) {
+ @list = @{$target || []};
+ } elsif ($context & (Form::Filter::CTX_SINGLE | Form::Filter::CTX_EXISTENT)) {
+ @list = ($target);
+ }
+
+ foreach my $object (@list) {
+ if (defined $object and not $object->isEmpty) {
+ return Form::FilterResult->new(
+ State => Form::FilterResult::STATE_SUCCESS_STAY
+ );
+ }
+ }
+
+ return Form::FilterResult->new(
+ State => Form::FilterResult::STATE_ERROR,
+ Message => $this->FormatMessage($schemaTarget),
+ Target => $schemaTarget,
+ Container => $parent
+ );
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Form/Filter/Regexp.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/Filter/Regexp.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,38 @@
+package Form::Filter::Regexp;
+use strict;
+use Common;
+use Form::Filter;
+use base qw(Form::Filter);
+
+BEGIN {
+ DeclareProperty Regexp => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,@args) = @_;
+
+ $this->SUPER::CTOR(@args[0,1]);
+
+ my $re = $args[2] or die new Exception('A regular expression is required');
+
+ $this->{$Regexp} = qr/$re/;
+}
+
+sub SUPPORTED_CONTEXT { Form::Filter::CTX_SINGLE | Form::Filter::CTX_EXISTENT }
+
+sub Invoke {
+ my ($this,$object) = @_;
+
+ if ($object->isa('Form::ValueItem')) {
+ my $re = $this->{$Regexp};
+ if ($object->isEmpty or $object->Value =~ m/$re/) {
+ return new Form::FilterResult(State => Form::FilterResult::STATE_SUCCESS);
+ } else {
+ return new Form::FilterResult(Sate => Form::FilterResult::STATE_ERROR, Message => $this->FormatMessage($object), Target => $object );
+ }
+ } else {
+ die new Exception('Only a value items can be verified against a regular expression');
+ }
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Form/Item.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/Item.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,79 @@
+package Form::Item;
+use strict;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Parent => ACCESS_READ;
+ DeclareProperty Form => ACCESS_READ;
+ DeclareProperty Id => ACCESS_READ;
+ DeclareProperty Attributes => ACCESS_ALL;
+}
+
+sub CTOR {
+ my ($this,$id,$form,$parent,$attrib) = @_;
+
+ $this->{$Id} = $id or die new Exception('An Id i required for the form item');
+ $this->{$Form} = $form or die new Exception('A form is required for the form item');
+ $this->{$Parent} = $parent;
+ $this->{$Attributes} = $attrib || {};
+}
+
+sub Name {
+ my ($this) = @_;
+ return $this->{$Id}->Name;
+}
+
+sub Navigate {
+ my ($this,$ItemId) = @_;
+
+ $ItemId or die new Exception("An item id is undefined");
+
+ return $this->NavigatePath([$ItemId->ToNAVPath]);
+}
+
+sub Item {
+ my ($this,$strId) = @_;
+
+ return $this->Navigate($this->Form->MakeItemId($strId,$this));
+}
+
+sub NavigatePath {
+ my ($this,$refPath) = @_;
+
+ my $ItemId = shift @$refPath or die new Exception("An item id is undefined");
+ my $current;
+
+ if ($ItemId->isa('Form::ItemId::Prev')) {
+ $this->{$Parent} or die new Exception('Can\'t navigate to upper level');
+ $current = $this->{$Parent};
+ } elsif ($ItemId->isa('Form::ItemId::Root')) {
+ $current = $this->{$Form};
+ } else {
+ $current = $this->ResolveItem($ItemId);
+ }
+
+ if (@$refPath > 0) {
+ die new Exception('The item not found', $ItemId->Canonical) if not $current;
+ return $current->NavigatePath($refPath);
+ } else {
+ return $current;
+ }
+}
+
+sub ResolveItem {
+ my ($this,$ItemId) = @_;
+
+ die new Exception('Item not found',$ItemId->Name);
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ undef %$this;
+
+ $this->SUPER::Dispose;
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Form/ItemId.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/ItemId.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,46 @@
+package Form::ItemId;
+use strict;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Canonical => ACCESS_READ;
+ DeclareProperty InstanceID => ACCESS_READ;
+ DeclareProperty Parent => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,$name,$instance_id,$parent) = @_;
+
+ $this->{$Name} = $name or die new Exception('A name is required for the item id');
+ $this->{$InstanceID} = $instance_id;
+ $this->{$Parent} = $parent;
+
+ $this->{$Canonical} = ($parent && !$parent->isa('Form::ItemId::Root') ? $parent->Canonical.'/':'').$name.(defined $instance_id ? $instance_id : '');
+}
+
+sub ToNAVPath {
+ my ($this) = @_;
+
+ return ($this->{$Parent} ? ($this->{$Parent}->ToNAVPath,$this) : $this);
+}
+
+package Form::ItemId::Prev;
+our @ISA = qw(Form::ItemId);
+
+sub CTOR {
+ my ($this,$parent) = @_;
+ $this->SUPER::CTOR('(prev)',undef,$parent);
+}
+
+package Form::ItemId::Root;
+our @ISA = qw(Form::ItemId);
+
+sub CTOR {
+ my ($this,$parent) = @_;
+ $this->SUPER::CTOR('(root)',undef,$parent);
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Form/Transform.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/Transform.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,29 @@
+package Form::Transform;
+use strict;
+use warnings;
+use base qw(IMPL::Transform);
+
+sub CTOR {
+ my ($this) = @_;
+
+ $this->superCTOR(
+ Templates => {
+ 'Form::Container' => sub { my $this = shift; $this->TransformContainer(@_); },
+ 'Form' => sub { my $this = shift; $this->TransformContainer(@_); }
+ },
+ Default => \&TransformItem
+ );
+}
+
+sub TransformContainer {
+ my ($this,$container) = @_;
+}
+
+sub TransformItem {
+ my ($this,$item) = @_;
+ return $item->isEmpty ? undef : $item->Value;
+}
+
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Form/ValueItem.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/ValueItem.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,47 @@
+package Form::ValueItem;
+use strict;
+use base qw(Form::Item);
+use Common;
+use Form::Filter;
+
+BEGIN {
+ DeclareProperty Value => ACCESS_ALL;
+ DeclareProperty Type => ACCESS_READ;
+ DeclareProperty Schema => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->SUPER::CTOR(@args{qw(Id Form Parent Attributes)});
+ $this->{$Type} = $args{'Type'};
+ $this->{$Schema} = $args{'Schema'} or die new Exception('A field schema is required');
+}
+
+sub isEmpty {
+ my ($this) = @_;
+
+ return length $this->{$Value} ? 0 : 1;
+}
+
+sub Validate {
+ my ($this,$rhDisableFilters) = @_;
+
+ $rhDisableFilters ||= {};
+
+ my @errors;
+
+ foreach my $filter (grep {$_->SUPPORTED_CONTEXT & (Form::Filter::CTX_SINGLE) and not exists $rhDisableFilters->{$_}} map {$_->Instance} $this->{$Schema}->Filters) {
+ my $result = $filter->Invoke($this,Form::Filter::CTX_SINGLE | Form::Filter::CTX_EXISTENT,$this->{$Schema},$this->Parent);
+ if ($result->State == Form::FilterResult::STATE_SUCCESS_STOP) {
+ return ();
+ } elsif ($result->State == Form::FilterResult::STATE_ERROR) {
+ push @errors,$result;
+ }
+ }
+
+ return @errors;
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Form/ValueItem/List.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Form/ValueItem/List.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,107 @@
+package Form::ValueItem::List;
+use Common;
+use base qw(Form::ValueItem);
+
+BEGIN {
+ DeclareProperty ListValues => ACCESS_READ;
+ DeclareProperty CurrentItem => ACCESS_READ;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+
+ $this->{$ListValues} = [];
+
+ my $source = $this->Form->Bindings->{$this->Attributes->{source}};
+
+ if (ref $source eq 'CODE') {
+ $this->LoadList($source->());
+ } elsif (ref $source and (UNIVERSAL::isa($source,'HASH') or UNIVERSAL::isa($source,'ARRAY'))){
+ $this->LoadList($source);
+ } else {
+ if (not $source) {
+ warn "a source isn't specified for the listvalue ".$this->Id->Canonical;
+ } else {
+ warn "an unsupported source type ".(ref $source)." for the listvalue".$this->Id->Canonical;
+ }
+ }
+}
+
+sub Value {
+ my $this = shift;
+
+ if (@_) {
+ my $newValue = shift;
+
+ $this->{$CurrentItem}->{active} = 0 if $this->{$CurrentItem};
+
+ my ($item) = (defined $newValue ? grep {defined $_->{id} and $_->{id} eq $newValue} @{$this->{$ListValues}} : undef);
+
+ if ($item) {
+ $this->{$CurrentItem} = $item;
+ $item->{active} = 1;
+ return $this->SUPER::Value($newValue);
+ } else {
+ undef $this->{$CurrentItem};
+ return $this->SUPER::Value(undef);
+ }
+ } else {
+ return $this->SUPER::Value;
+ }
+}
+
+sub LoadList {
+ my ($this,$refList) = @_;
+
+ if (ref $refList and UNIVERSAL::isa($refList,'HASH')) {
+ $this->{$CurrentItem} = undef;
+ $this->{$ListValues} = [ sort { $a->{name} cmp $b->{name} } map { Form::ValueItem::List::Item->new($_,ref $refList->{$_} eq 'ARRAY' ? @{$refList->{$_}} : $refList->{$_})} keys %{$refList}];
+ $this->SUPER::Value(undef);
+ } elsif (ref $refList and UNIVERSAL::isa($refList,'ARRAY')) {
+ $this->{$CurrentItem} = undef;
+ $this->{$ListValues} = [map { Form::ValueItem::List::Item->new(ref $_ eq 'ARRAY' ? @$_ : $_ )} @$refList];
+ $this->SUPER::Value(undef);
+ } else {
+ die new Exception('An unexpected list type');
+ }
+}
+
+package Form::ValueItem::List::Item;
+use fields qw(
+ id
+ description
+ name
+ active
+);
+
+sub new {
+ my ($class,$id,$name,$desc) = @_;
+
+ my $this=fields::new($class);
+ $this->{id} = $id;
+ $this->{name} = $name;
+ $this->{description} = $desc;
+
+ return $this;
+}
+
+#compatibility with TToolkit
+
+sub Id {
+ $_[0]->{id};
+}
+
+sub Description {
+ $_[0]->{description};
+}
+
+sub Active {
+ $_[0]->{active};
+}
+
+sub Name {
+ $_[0]->{name};
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Class/Member.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Class/Member.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,37 @@
+package IMPL::Class::Member;
+use strict;
+use base qw(Exporter);
+our @EXPORT = qw(virtual public private protected);
+
+use IMPL::Class::Meta;
+require IMPL::Class::MemberInfo;
+
+use constant {
+ MOD_PUBLIC => 1,
+ MOD_PROTECTED => 2,
+ MOD_PRIVATE => 3
+};
+
+sub virtual($) {
+ $_[0]->Virtual(1);
+ $_[0];
+}
+
+sub public($) {
+ $_[0]->Access(MOD_PUBLIC);
+ $_[0]->Implement;
+ $_[0];
+}
+
+sub private($) {
+ $_[0]->Access(MOD_PRIVATE);
+ $_[0]->Implement;
+ $_[0];
+}
+
+sub protected($) {
+ $_[0]->Access(MOD_PROTECTED);
+ $_[0]->Implement;
+ $_[0];
+}
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Class/MemberInfo.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Class/MemberInfo.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,48 @@
+package IMPL::Class::MemberInfo;
+use strict;
+use base qw(IMPL::Object::Accessor);
+
+require IMPL::Exception;
+require IMPL::Class::Member;
+
+__PACKAGE__->mk_accessors(
+ qw(
+ Name
+ Access
+ Virtual
+ Class
+ Frozen
+ Implementor
+ Attributes
+ )
+);
+__PACKAGE__->PassThroughArgs;
+
+sub CTOR {
+ my $this = shift;
+
+ die new IMPL::Exception('The name is required for the member') unless $this->Name;
+ die new IMPL::Exception('The class is required for the member') unless $this->Class;
+
+ $this->Frozen(0);
+ $this->Virtual(0) unless defined $this->Virtual;
+ $this->Access(3) unless $this->Access;
+}
+
+sub Implement {
+ my ($this) = @_;
+ $this->Implementor->Make($this);
+ $this->Frozen(1);
+ $this->Class->set_meta($this);
+ return;
+}
+
+sub set {
+ my $this = shift;
+ if ($this->Frozen) {
+ die new IMPL::Exception('The member information can\'t be modified', $this->Name);
+ }
+ $this->SUPER::set(@_);
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Class/Meta.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Class/Meta.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,43 @@
+package IMPL::Class::Meta;
+use strict;
+
+my %class_meta;
+
+sub set_meta {
+ my ($class,$meta_data) = @_;
+ $class = ref $class if ref $class;
+
+ # òóò íåëüçÿ èñïîëüçîâàòü ñòàíäàðòíîå èñêëþ÷åíèå, ïîñêîëüêó äëÿ íåãî èñïîëüçóåòñÿ
+ # êëàññ IMPL::Object::Accessor, êîòîðûé íàñëåäóåòñÿ îò òåêóùåãî êëàññà
+ die "The meta_data parameter should be an object" if not ref $meta_data;
+
+ push @{$class_meta{$class}{ref $meta_data}},$meta_data;
+}
+
+sub get_meta {
+ my ($class,$meta_class,$predicate,$deep) = @_;
+ $class = ref $class if ref $class;
+ no strict 'refs';
+ my @result;
+
+ if ($deep) {
+ @result = map { $_->can('get_meta') ? $_->get_meta($meta_class,$predicate,$deep) : () } @{$class.'::ISA'};
+ }
+
+ if ($predicate) {
+ push @result,grep( &$predicate($_), map( @{$class_meta{$class}{$_}}, grep( $_->isa($meta_class), keys %{$class_meta{$class} || {}} ) ) );
+ } else {
+ push @result, map( @{$class_meta{$class}{$_} || []}, grep( $_->isa($meta_class), keys %{$class_meta{$class} || {}} ) );
+ }
+ wantarray ? @result : \@result;
+}
+
+=pod
+__PACKAGE_->set_meta($metaObject);
+__PACKAGE_->get_meta('MyMetaClass',sub {
+ my ($item) = @_;
+ $item->Name eq 'Something' ? 1 : 0
+} );
+=cut
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Class/Property.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Class/Property.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,35 @@
+package IMPL::Class::Property;
+use strict;
+use base qw(Exporter);
+BEGIN {
+ our @EXPORT = qw(property prop_get prop_set owner_set prop_none prop_all prop_list CreateProperty);
+}
+
+require IMPL::Class::Member;
+require IMPL::Class::PropertyInfo;
+
+sub import {
+ __PACKAGE__->export_to_level(1,@_);
+ IMPL::Class::Member->export_to_level(1,@_);
+}
+
+sub prop_get { 1 };
+sub prop_set { 2 };
+sub owner_set { 2 };
+sub prop_none { 0 };
+sub prop_all { 3 };
+sub prop_list { 4 };
+
+sub property($$;$) {
+ my ($propName,$mutators,$attributes) = @_;
+ my $Info = new IMPL::Class::PropertyInfo( {Name => $propName, Mutators => $mutators, Class => scalar(caller), Attributes => $attributes } );
+ return $Info;
+}
+
+sub CreateProperty {
+ my ($class,$propName,$mutators,$attributes) = @_;
+ my $Info = new IMPL::Class::PropertyInfo( {Name => $propName, Mutators => $mutators, Class => $class, Attributes => $attributes} );
+ return $Info;
+};
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Class/Property/Direct.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Class/Property/Direct.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,111 @@
+package IMPL::Class::Property::Direct;
+use strict;
+
+use base qw(IMPL::Object::Accessor Exporter);
+our @EXPORT = qw(_direct);
+
+use IMPL::Class::Property;
+require IMPL::Exception;
+
+__PACKAGE__->mk_accessors qw(ExportField);
+
+sub _direct($) {
+ my ($prop_info) = @_;
+ $prop_info->Implementor( IMPL::Class::Property::Direct->new({ExportField => 1}) );
+ return $prop_info;
+}
+
+my $access_private = "die new IMPL::Exception('Can\\'t access the private member',\$name,\$class,scalar caller) unless caller eq \$class;";
+my $access_protected = "die new IMPL::Exception('Can\\'t access the protected member',\$name,\$class,scalar caller) unless caller eq \$class;";
+
+my $accessor_get_no = 'die new IMPL::Exception(\'The property is write only\',$name,$class) unless $get;';
+my $accessor_set_no = 'die new IMPL::Exception(\'The property is read only\',$name,$class) unless $set;';
+my $accessor_set = 'return( $this->{$field} = @_ == 1 ? $_[0] : [@_] );';
+my $accessor_get = 'return( $this->{$field} );';
+my $list_accessor_set = 'return( @{ ($this->{$field} = ( (@_ == 1 and ref $_[0] eq \'ARRAY\') ? $_[0] : [@_] ) || [] ) } );';
+my $list_accessor_get = 'return( @{ $this->{$field} || [] } );';
+my $custom_accessor_get = 'unshift @_, $this and goto &$get;';
+my $custom_accessor_set = 'unshift @_, $this and goto &$set;';
+
+my %accessor_cache;
+sub mk_acessor {
+ my ($virtual,$access,$class,$name,$mutators,$field) = @_;
+
+ my ($get,$set) = ref $mutators ? ( $mutators->{get}, $mutators->{set}) : ($mutators & prop_get, $mutators & prop_set);
+ my $key = join '',$virtual,$access,$get ? 1 : 0,$set ? 1 : 0, (ref $mutators ? 'c' : ($mutators & prop_list() ? 'l' : 's'));
+ my $factory = $accessor_cache{$key};
+ if (not $factory) {
+ my $code =
+<can(\$name);
+ return \$this->\$method(\@_) unless \$method == \$accessor or caller->isa(\$class);
+VCALL
+ $code .= "$access_private\n" if $access == IMPL::Class::Member::MOD_PRIVATE;
+ $code .= "$access_protected\n" if $access == IMPL::Class::Member::MOD_PROTECTED;
+ my ($codeGet,$codeSet);
+ if (ref $mutators) {
+ $codeGet = $get ? $custom_accessor_get : $accessor_get_no;
+ $codeSet = $set ? $custom_accessor_set : $accessor_set_no;
+ } else {
+ if ($mutators & prop_list) {
+ $codeGet = $get ? $list_accessor_get : $accessor_get_no;
+ $codeSet = $set ? $list_accessor_set : $accessor_set_no;
+ } else {
+ $codeGet = $get ? $accessor_get : $accessor_get_no;
+ $codeSet = $set ? $accessor_set : $accessor_set_no;
+ }
+ }
+ $code .=
+<($class,$name,$set,$get, $field);
+}
+
+sub Make {
+ my ($self,$propInfo) = @_;
+
+ my $isExportField = ref $self ? ($self->ExportField || 0) : 0;
+ my ($class,$name,$virt,$access,$mutators) = $propInfo->get qw(Class Name Virtual Access Mutators);
+ (my $field = "${class}_$name") =~ s/::/_/g;
+
+ my $propGlob = $class.'::'.$name;
+
+ no strict 'refs';
+ *$propGlob = mk_acessor($virt,$access,$class,$name,$mutators,$field);
+ *$propGlob = \$field if $isExportField;
+
+ if (ref $mutators) {
+ $propInfo->canGet( $mutators->{get} ? 1 : 0);
+ $propInfo->canSet( $mutators->{set} ? 1 : 0);
+ } else {
+ $propInfo->canGet( ($mutators & prop_get) ? 1 : 0);
+ $propInfo->canSet( ($mutators & prop_set) ? 1 : 0);
+ }
+}
+
+sub FieldName {
+ my ($self,$propInfo) = @_;
+
+ my ($class,$name) = $propInfo->get qw(Class Name);
+ (my $field = "${class}_$name") =~ s/::/_/g;
+ return $field;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Class/PropertyInfo.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Class/PropertyInfo.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,38 @@
+package IMPL::Class::PropertyInfo;
+use strict;
+
+use base qw(IMPL::Class::MemberInfo);
+
+__PACKAGE__->mk_accessors(qw(Type Mutators canGet canSet));
+__PACKAGE__->PassThroughArgs;
+
+our @Implementors = ( ['IMPL::Object' => 'IMPL::Class::Property::Direct'] );
+
+my %LoadedModules;
+
+sub CTOR {
+ my $this = shift;
+
+ my $implementor = $this->Implementor($this->SelectImplementor());
+ if (my $class = ref $implementor ? undef : $implementor) {
+ if (not $LoadedModules{$class}) {
+ (my $package = $class.'.pm') =~ s/::/\//g;
+ require $package;
+ $LoadedModules{$class} = 1;
+ }
+ }
+
+ $this->Mutators(0) unless defined $this->Mutators;
+}
+
+sub SelectImplementor {
+ my ($this) = @_;
+
+ foreach my $item (@Implementors) {
+ return $item->[1] if $this->Class->isa($item->[0]);
+ }
+
+ die new IMPL::Exception('Can\'t find a property implementor for the specified class',$this->Class);
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Config.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Config.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,128 @@
+package IMPL::Config;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object IMPL::Object::Serializable IMPL::Object::Autofill);
+
+__PACKAGE__->PassThroughArgs;
+
+use IMPL::Class::Member;
+use IMPL::Class::PropertyInfo;
+use IMPL::Exception;
+
+use IMPL::Serialization;
+use IMPL::Serialization::XmlFormatter;
+
+sub LoadXMLFile {
+ my ($self,$file) = @_;
+
+ my $class = ref $self || $self;
+
+ my $serializer = new IMPL::Serializer(
+ Formatter => new IMPL::Serialization::XmlFormatter(
+ IdentOutput => 1,
+ SkipWhitespace => 1
+ )
+ );
+
+ open my $hFile,'<',$file or die new IMPL::Exception("Failed to open file",$file,$!);
+
+ my $obj;
+ eval {
+ $obj = $serializer->Deserialize($hFile);
+ };
+
+ if ($@) {
+ my $e=$@;
+ die new IMPL::Exception("Can't load the configuration file",$file,$e);
+ }
+ return $obj;
+}
+
+sub SaveXMLFile {
+ my ($this,$file) = @_;
+
+ my $serializer = new IMPL::Serializer(
+ Formatter => new IMPL::Serialization::XmlFormatter(
+ IdentOutput => 1,
+ SkipWhitespace => 1
+ )
+ );
+
+ open my $hFile,'>',$file or die new IMPL::Exception("Failed to open file",$file,$!);
+
+ $serializer->Serialize($hFile, $this);
+}
+
+sub xml {
+ my $this = shift;
+ my $serializer = new IMPL::Serializer(
+ Formatter => new IMPL::Serialization::XmlFormatter(
+ IdentOutput => 1,
+ SkipWhitespace => 1
+ )
+ );
+ my $str = '';
+ open my $hFile,'>',\$str or die new IMPL::Exception("Failed to open stream",$!);
+
+ $serializer->Serialize($hFile, $this);
+
+ undef $hFile;
+
+ return $str;
+}
+
+sub save {
+ my ($this,$ctx) = @_;
+
+ foreach my $info ($this->get_meta('IMPL::Class::PropertyInfo')) {
+ next if $info->Access != IMPL::Class::Member::MOD_PUBLIC; # save only public properties
+
+ my $name = $info->Name;
+ $ctx->AddVar($name => $this->$name()) if $this->$name();
+ }
+}
+
+1;
+__END__
+
+=pod
+
+=h1 SYNOPSIS
+
+package App::Config
+use base qw(IMPL::Config)
+
+use IMPL::Class::Property;
+use IMPL::Config::Class;
+
+BEGIN {
+ public property SimpleString => prop_all;
+ public property MyClass => prop_all;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->superCTOR(@_);
+
+ $this->MyClass(new IMPL::Config::Class(Type => MyClass)) unless $this->MyClass;
+}
+
+=head1 DESCRIPTION
+
+Ïîçâîëÿåò ñîõðàíèòü/çàãðóçèòü êîíôèãóðàöèþ. Òàêæå âñå êëàññû êîíôèãóðàöèè
+äîëæíû íàñëåäîâàòüñÿ îò äàííîãî êëàññà, è âñå Public ñâîéñòâà áóäóò
+àâòîìàòè÷åñêè ñîõðàíÿòüñÿ è âîññòàíàâëèâàòüñÿ.
+
+=head1 MEMBERS
+
+=item static LoadXMLFile($fileName)
+Ñîçäàåò èç XML ôàéëà ýêçåìïëÿð ïðèëîæåíèÿ
+
+=item SaveXMLFile($fileName)
+Ñîõðàíÿåò ïðèëîæåíèå â ôàéë
+
+=item xml
+Ñîõðàíÿåò êîíôèãóðàöèþ ïðèëîæåíèÿ â XML ñòðîêó
+
+=cut
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Config/Class.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Config/Class.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,52 @@
+package IMPL::Config::Class;
+use strict;
+use warnings;
+
+use base qw(IMPL::Config);
+use IMPL::Exception;
+use IMPL::Class::Property;
+
+BEGIN {
+ public property Type => prop_all;
+ public property Parameters => prop_all;
+ public property IsSingleton => prop_all;
+ private property _Instance => prop_all;
+}
+
+__PACKAGE__->PassThroughArgs;
+
+sub CTOR {
+ my $this = shift;
+
+ die new IMPL::Exception("A Type parameter is required") unless $this->Type;
+
+}
+
+sub _is_class {
+ no strict 'refs';
+ scalar keys %{"$_[0]::"} ? 1 : 0;
+}
+
+sub instance {
+ my $this = shift;
+
+ my $type = $this->Type;
+
+ if ($this->IsSingleton) {
+ if ($this->_Instance) {
+ return $this->_Instance;
+ } else {
+ my %args = (%{$this->Parameters || {}},@_);
+ eval "require $type" unless _is_class($type);
+ my $inst = $type->new(%args);
+ $this->_Instance($inst);
+ return $inst;
+ }
+ } else {
+ my %args = (%{$this->Parameters || {}},@_);
+ eval "require $type" unless _is_class($type);
+ return $type->new(%args);
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Config/Container.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Config/Container.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,40 @@
+package IMPL::Config::Container;
+use strict;
+use warnings;
+
+use base qw(IMPL::Config);
+use IMPL::Class::Property;
+
+BEGIN {
+ public property Chidren => prop_all;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->Chidren(\%args);
+}
+
+sub save {
+ my ($this,$ctx) = @_;
+
+ while (my ($key,$value) = each %{$this->Chidren}) {
+ $ctx->AddVar($key,$value);
+ }
+}
+
+our $AUTOLOAD;
+sub AUTOLOAD {
+ my $this = shift;
+
+ (my $prop = $AUTOLOAD) =~ s/.*?(\w+)$/$1/;
+
+ my $child = $this->Chidren->{$prop};
+ if (ref $child and $child->isa('IMPL::Config::Class')) {
+ return $child->instance(@_);
+ } else {
+ return $child;
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/DOM/Navigator.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/DOM/Navigator.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,11 @@
+package IMPL::DOM::Navigator;
+use strict;
+use warnings;
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw();
+
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/DOM/Node.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/DOM/Node.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,64 @@
+package IMPL::DOM::Node;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object IMPL::Object::Serializable IMPL::Object::Autofill);
+
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+use Scalar::Util qw(weaken);
+
+__PACKAGE__->PassThroughArgs;
+
+BEGIN {
+ public property nodeName => prop_get | owner_set;
+ public property isComplex => prop_get | owner_set;
+ public property nodeValue => prop_get | owner_set;
+ public property childNodes => prop_get | owner_set| prop_list;
+ public property parentNode => prop_get | owner_set;
+ private property _propertyMap => prop_all;
+}
+
+sub CTOR {
+ my $this = @_;
+
+ $this->_propertyMap({});
+}
+
+sub insertNode {
+ my ($this,$node,$pos) = @_;
+}
+
+sub removeNode {
+ my ($this,$node) = @_;
+}
+
+sub removeAt {
+ my ($this,$pos) = @_;
+}
+
+sub selectNodes {
+ my ($this,$name) = @_;
+}
+
+sub setParent {
+ my ($this,$parentNode) = @_;
+}
+
+sub text {
+ my ($this) = @_;
+}
+
+sub Property {
+ my $this = shift;
+ my $name = shift;
+
+ if (@_) {
+ # set
+ return $this->_propertyMap->{$name} = shift;
+ } else {
+ return $this->_propertyMap->{$name};
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Exception.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Exception.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,113 @@
+package IMPL::Exception;
+use strict;
+use overload
+ '""' => \&ToString,
+ 'fallback' => 1;
+use Carp qw(longmess shortmess);
+use Scalar::Util qw(refaddr);
+
+BEGIN {
+ require Error;
+}
+
+use base qw(IMPL::Object::Accessor Error);
+
+BEGIN {
+ __PACKAGE__->mk_accessors( qw(Message Args CallStack Source) );
+}
+
+sub indent {
+ my ($str,$level) = @_;
+ $level ||= 0;
+ $str = '' unless defined $str;
+ join ("\n", map( "\t"x$level.$_ , split(/\n/,$str) ) );
+}
+
+sub new {
+ my $class = shift;
+ $class = ref $class || $class;
+
+ my $this = $class->Error::new() or die "Failed to create an exception";
+
+ $this->callCTOR(@_);
+ $this->{-text} = $this->Message;
+
+ local $Carp::CarpLevel = 0;
+
+ $this->CallStack(longmess);
+ $this->Source(shortmess);
+
+ return $this;
+}
+
+sub CTOR {
+ my ($this,$message,@args) = @_;
+ $this->Message($message || '');
+ die new IMPL::Exception("Fatal erorr: cyclic structure in the exceptions were detected, do not use \$\@ while throwing the exception!") if grep ref $_ ? refaddr($this) == refaddr($_) : 0 , @args;
+ $this->Args([map defined $_ ? $_ : 'undef', @args]);
+}
+
+sub save {
+ my ($this,$ctx) = @_;
+
+ $ctx->AddVar(Message => $this->Message) if $this->Message;
+ $ctx->AddVar(Args => $this->Args) if @{$this->Args};
+ $ctx->AddVar(Source => $this->Source);
+ $ctx->AddVar(CallStack => $this->CallStack);
+}
+
+sub restore {
+ my ($class,$data,$instance) = @_;
+
+ my %args = @$data;
+
+ if ($instance) {
+ $instance->callCTOR($args{Message},@{$args{Args}});
+ } else {
+ $instance = $class->new($args{Message},@{$args{Args}});
+ }
+
+ $instance->Source($args{Source});
+ $instance->CallStack($args{CallStack});
+
+ return $instance;
+}
+
+sub ToString {
+ my $this = shift;
+
+ $this->toString();
+}
+
+sub toString {
+ my ($this,$notrace) = @_;
+ $this->Message . join("\n",'',map { my $s = $_; local $_; indent("$s",1) } @{$this->Args} ) . ( $notrace ? '' : "\n" . $this->CallStack);
+}
+
+package IMPL::InvalidOperationException;
+our @ISA = qw(IMPL::Exception);
+__PACKAGE__->PassThroughArgs;
+
+package IMPL::InvalidArgumentException;
+our @ISA = qw(IMPL::Exception);
+__PACKAGE__->PassThroughArgs;
+
+package IMPL::DuplicateException;
+our @ISA = qw(IMPL::Exception);
+__PACKAGE__->PassThroughArgs;
+
+package IMPL::NotImplementedException;
+our @ISA = qw(IMPL::Exception);
+__PACKAGE__->PassThroughArgs;
+
+package Exception;
+our @ISA = qw(IMPL::Exception);
+__PACKAGE__->PassThroughArgs;
+
+package IMPL::DeprecatedException;
+our @ISA = qw(IMPL::Exception);
+our %CTOR = (
+ 'IMPL::Exception' => sub { @_ ? @_ : "The method is deprecated" }
+);
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/ORM.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/ORM.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,123 @@
+package IMPL::ORM;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+use Scalar::Util qw(weaken refaddr);
+
+use IMPL::Exception;
+
+our $Depth = 1; # çàãðóæàòü îáúåêò + 1 óðîâåíü äåòåé
+our $UseProxy = 1;
+
+BEGIN {
+ private property _ObjectCache => prop_all;
+ private property _MapInstances => prop_all;
+ private property _WorkUnit => prop_all;
+ public property Schema => prop_all;
+}
+
+sub ObjectInfoById {
+ my ($this,$oid) = @_;
+
+ return $this->_ObjectCache->{$oid};
+}
+
+sub ObjectInfo {
+ my ($this,$inst) = @_;
+
+ die new IMPL::InvalidOperationException("This method can be used only for a reference") unless ref $inst;
+
+ return $this->_MapInstances->{refaddr $inst};
+}
+
+
+1;
+__END__
+=pod
+=head1 SYNOPSIS
+
+use IMPL::ORM::Sql;
+
+my $DB = new IMPL::ORM::Sql("connection string");
+
+local $IMPL::ORM::Depth = 1; # load childs only, no more
+
+my $artist = $DB->Lookup( Artist => { name => 'Beatles' } );
+
+my $order = new Order();
+$order->AddItem($_) foreach $artist->Albums->List;
+
+$DB->Save($order);
+
+my $label = $artist->Albums->Index(0)->Label;
+
+$DB->Populate($label); #load $label
+
+=head1 DESCRIPTION
+=head2 MEMBERS
+=level 4
+=back
+=head2 Variables
+=head2 INTERNALS
+=head3 Object Representation
+
+Êàæäûé êëàññ îòîáðàæàåìûé â èñòî÷íèê äàííûõ ïðåäñòàâëÿåòñÿ â âèäå íàáîðà
+ñóùíîñòåé, êàæäàÿ èç êîòîðûõ ïðåäñòàâëÿåò ñîñòîÿíèå áàçîâîãî êëàññà.
+
+Foo entityFoo
+ Bar entityBar
+ Baz entityBaz
+
+Ïðè ñîõðàíåíèè âèðòóàëüíûõ ñâîéñòâ êëàññîâ â ñîîòâåòñòâóþùèõ ñóùíîñòÿõ çàâîäèòñÿ
+äâà ïîëÿ - îäíî ïîä ñîõðàíåíèå ñîáñòâåííîãî çíà÷åíèÿ ñâîéñòâà, äðóãîå - äëÿ
+õðåíåèÿ âèðòóàëüíîãî çíà÷åíèÿ.
+
+Foo
+ public virtual property Name => prop_all, {Type => String};
+
+entityFoo
+ string m_Name - ñîáñòâåííîå çíà÷åíèå
+ string v_Name - âðòóàëüíîå çíà÷åíèå
+
+Êàæäûé ñîõðàíåííûé îáúåêò â áàçå èìååò ñîáñòâåííûé èäåíòèôèêàòîð.
+Íîâûå îáúåêòû èäåíòèôèêàòîðà íå èìåþò, äî òåõ ïîð ïîêà îíè íå áóäóò ñîõðàíåíû.
+
+=head3 Object Cache
+
+Äëÿ ó÷åòà îáúåêòîâ, êîòîðûå ïðèñóòñòâóþò â èñòî÷íèêå äàííûõ èñïîëüçóåòñÿ êåø
+îáúåêòîâ. Ñþäà ïîïàäàþò ïîëó÷åííûå èç áàçû îáúåêòû, à òàêæå âíîâü äîáàâëåííûå
+îáúåêòû.
+
+ObjectInfo => {
+ instance => weak ref
+ _id => data source dependent id
+ state => {persistent|null|new|deleted}
+ work_unit => ref to the work unit where object is acting
+}
+
+äàííàÿ ñòðóêòóðà äîñòóïíà ÷åðåç äâå ôóíêöèè ObjectInfoById è ObjectInfo
+
+=head3 Type mapping
+
+Èñòî÷íèê äàííûõ èìååò â ñåáå ñõåìó äàííûõ, êîòîðàÿ îïðåäåëÿåò íàáîð òèïîâ,
+õðàíèìûõ â äàííîì èñòî÷íèêå. Åñòü íåñêîëüêî âèäîâ îòîáðàæåíèÿ òèïîâ:
+
+=level 4
+
+=item 1
+
+Îòîáðàæåíèå êëàññîâ, êîãäà êëàññ ðàññìàðèâàåòñÿ â èäå íàáîðà ñâîéñòâ
+
+=item
+
+Îòîáðàæåíèå êëàññîâ â îäíî çíà÷åíèå (íàïðìåð ñòðîêó, äàííûå è ò.ï.)
+
+=item
+
+Êëàññû, êîòîðûå íà ïðÿìóþ ðàáîòàþò ñ èñòî÷íèêîì äàííûõ, òàêèå êàê êîëëåêöèè.
+
+=back
+
+=cut
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/ORM/Entity.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/ORM/Entity.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,13 @@
+package IMPL::ORM::Entity;
+use strict;
+use warnings;
+
+use base qw(IMPL::DOM::Node);
+use IMPL::Class::Property;
+
+# Name
+# Fields
+# Relations
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/ORM/MapInfo.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/ORM/MapInfo.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,22 @@
+use strict;
+use warnings;
+
+package IMPL::ORM::MapInfo;
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+
+BEGIN {
+ public property Entities => prop_all;
+ public property Cumulative => prop_all;
+}
+
+package IMPL::ORM::MapEntityInfo;
+use base qw(IMPL::Object IMPL::Object::Autofill);
+use IMPL::Class::Property;
+
+BEGIN {
+ public property Name => prop_all;
+ public property Fields => prop_all;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/ORM/Sql.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/ORM/Sql.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,11 @@
+package IMPL::ORM::Sql;
+use strict;
+use warnings;
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw();
+
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/ORM/WorkUnit.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/ORM/WorkUnit.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,8 @@
+package IMPL::ORM::WorkUnit;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object);
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Object.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Object.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,187 @@
+package IMPL::Object;
+use strict;
+
+use base qw(IMPL::Class::Meta);
+
+our $MemoryLeakProtection;
+my $Cleanup = 0;
+our $Debug;
+our %leaked_objects;
+
+my %cacheCTOR;
+
+
+sub new {
+ my $class = shift;
+ my $self = bless {}, ref($class) || $class;
+
+ $self->$_(@_) foreach @{$cacheCTOR{ref $self} || cache_ctor(ref $self)};
+
+ $self;
+}
+my $t = 0;
+sub cache_ctor {
+ my $class = shift;
+
+ no strict 'refs';
+ my @sequence;
+
+ my $refCTORS = *{"${class}::CTOR"}{HASH};
+
+ foreach my $super ( @{"${class}::ISA"} ) {
+ my $superSequence = $cacheCTOR{$super} || cache_ctor($super);
+
+ my $mapper = $refCTORS ? $refCTORS->{$super} : undef;
+ if (ref $mapper eq 'CODE') {
+ if ($mapper == *_pass_throgh_mapper{CODE}) {
+ push @sequence,@$superSequence;
+ } else {
+ push @sequence, sub {
+ my $this = shift;
+ $this->$_($mapper->(@_)) foreach @$superSequence;
+ };
+ }
+ } else {
+ warn "Unsupported mapper type, in '$class' for the super class '$super'" if $mapper;
+ push @sequence, sub {
+ my $this = shift;
+ $this->$_() foreach @$superSequence;
+ };
+ }
+ }
+
+ push @sequence, *{"${class}::CTOR"}{CODE} if *{"${class}::CTOR"}{CODE};
+
+ $cacheCTOR{$class} = \@sequence;
+ return \@sequence;
+}
+
+sub callCTOR {
+ my $self = shift;
+ my $class = ref $self;
+
+ $self->$_(@_) foreach @{$cacheCTOR{$class} || cache_ctor($class)};
+}
+
+sub surrogate {
+ bless {}, ref $_[0] || $_[0];
+}
+
+sub superCTOR {
+ my $this = shift;
+
+ warn "The mehod is deprecated, at " . caller;
+}
+
+sub toString {
+ my $self = shift;
+
+ return (ref $self || $self);
+}
+
+sub DESTROY {
+ if ($MemoryLeakProtection and $Cleanup) {
+ my $this = shift;
+ warn sprintf("Object leaks: %s of type %s %s",$this->can('ToString') ? $this->ToString : $this,ref $this,UNIVERSAL::can($this,'_dump') ? $this->_dump : '');
+ }
+}
+
+sub END {
+ $Cleanup = 1;
+ $MemoryLeakProtection = 0 unless $Debug;
+}
+
+sub _pass_throgh_mapper {
+ @_;
+}
+
+sub PassThroughArgs {
+ my $class = shift;
+ $class = ref $class || $class;
+ no strict 'refs';
+ no warnings 'once';
+ ${"${class}::CTOR"}{$_} = \&_pass_throgh_mapper foreach @{"${class}::ISA"};
+}
+
+package self;
+
+our $AUTOLOAD;
+sub AUTOLOAD {
+ goto &{caller(). substr $AUTOLOAD,4};
+}
+
+package supercall;
+
+our $AUTOLOAD;
+sub AUTOLOAD {
+ my $sub;
+ my $methodName = substr $AUTOLOAD,11;
+ no strict 'refs';
+ $sub = $_->can($methodName) and $sub->(@_) foreach @{caller().'::ISA'};
+}
+
+=pod
+=h1 SYNOPSIS
+
+package Foo;
+use base qw(IMPL::Object);
+
+sub CTOR {
+ my ($this,$arg) = @_;
+ print "Foo: $arg\n";
+}
+
+package Bar;
+use base qw(IMPL::Object);
+
+sub CTOR {
+ my ($this,$arg) = @_;
+ print "Bar: $arg\n";
+}
+
+package Baz;
+use base qw(Foo Bar);
+
+our %CTOR = (
+ Foo => sub { my %args = @_; $args{Mazzi}; },
+ Bar => sub { my %args = @_; $args{Fugi}; }
+);
+
+package Composite;
+use base qw(Baz Foo Bar);
+
+our %CTOR = (
+ Foo => undef,
+ Bar => undef
+);
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ print "Composite: $args{Text}\n";
+}
+
+package main;
+
+my $obj = new Composite(
+ Text => 'Hello World!',
+ Mazzi => 'Mazzi',
+ Fugi => 'Fugi'
+);
+
+# will print
+#
+# Foo: Mazzi
+# Bar: Fugi
+# Foo:
+# Bar:
+# Composite: Hello World!
+
+=h1 Description
+Áàçîâûé êëàññ äëÿ îáúåêòîâ. Ðåàëèçóåò ìíîæåñòâåííîå íàñëåäîâàíèå
+
+
+=h1 Members
+=cut
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Object/Accessor.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Object/Accessor.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,15 @@
+package IMPL::Object::Accessor;
+use strict;
+use base qw(IMPL::Object Class::Accessor IMPL::Class::Meta);
+
+sub new {
+ my $class = shift;
+ my $self = $class->Class::Accessor::new( @_ == 1 && ref $_[0] && UNIVERSAL::isa($_[0],'HASH') ? $_[0] : ());
+ $self->callCTOR(@_);
+ return $self;
+}
+
+sub surrogate {
+ $_[0]->Class::Accessor::new;
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Object/Autofill.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Object/Autofill.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,110 @@
+package IMPL::Object::Autofill;
+use strict;
+use IMPL::Class::Property;
+
+sub CTOR {
+ my $this = shift;
+ no strict 'refs';
+
+ my $fields = @_ == 1 ? $_[0] : {@_};
+
+ $this->_fill(ref $this,$fields);
+}
+
+sub _fill {
+ my ($this,$class,$fields) = @_;
+
+ $class->_autofill_method->($this,$fields);
+
+ no strict 'refs';
+ $this->_fill($_,$fields) foreach grep $_->isa('IMPL::Object::Autofill'), @{"${class}::ISA"};
+}
+
+sub DisableAutofill {
+ my $self = shift;
+
+ my $class = ref $self || $self;
+
+ *{"${class}::_impl_object_autofill"} = sub {};
+}
+
+sub _autofill_method {
+ my ($class) = @_;
+
+ $class = ref $class if ref $class;
+
+ # äëÿ àâòîçàïîëíåíèÿ íóæåí ñâîé ìåòîä âåðõíåãî óðîâíÿ
+ my $method;
+ {
+ no strict 'refs';
+ $method = ${$class.'::'}{_impl_object_autofill};
+ }
+
+ if ($method) {
+ return $method;
+ } else {
+ my $text = <can('get_meta')) {
+ # meta supported
+ foreach my $prop_info (grep {
+ my $mutators = $_->Mutators;
+ ref $mutators ? (exists $mutators->{set}) : ($mutators & prop_set || $_->Implementor->isa('IMPL::Class::Property::Direct'));
+ } $class->get_meta('IMPL::Class::PropertyInfo')) {
+ my $name = $prop_info->Name;
+ if (ref $prop_info->Mutators || !$prop_info->Implementor->isa('IMPL::Class::Property::Direct')) {
+ $text .= "\t\$this->$name(\$fields->{$name}) if exists \$fields->{$name};\n";
+ } else {
+ my $fld = $prop_info->Implementor->FieldName($prop_info);
+ if ($prop_info->Mutators & prop_list) {
+ $text .= "\t\$this->{$fld} = ref \$fields->{$name} ? \$fields->{$name} : [\$fields->{$name}] if exists \$fields->{$name};\n";
+ } else {
+ $text .= "\t\$this->{$fld} = \$fields->{$name} if exists \$fields->{$name};\n";
+ }
+ }
+ }
+ } else {
+ # meta not supported
+ #$text .= "\t".'$this->$_($fields->{$_}) foreach keys %$fields;'."\n";
+ }
+ $text .= "}\n\\&_impl_object_autofill;";
+ return eval $text;
+ }
+}
+
+1;
+
+__END__
+
+=pod
+=head1 SYNOPSIS
+package MyClass;
+use base qw(IMPL::Object IMPL::Object::Autofill);
+
+BEGIN {
+ private property PrivateData => prop_all;
+ public property PublicData => prop_get;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->superCTOR(@_);
+ # or eqvivalent
+ # $this->supercall::CTOR(@_);
+
+ print $this->PrivateData,"\n";
+ print $this->PublicData,"\n";
+}
+
+my $obj = new MyClass(PrivateData => 'private', PublicData => 'public', Other => 'some data');
+
+will print
+private
+public
+
+=cut
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Object/Disposable.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Object/Disposable.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,34 @@
+package IMPL::Object::Disposable;
+use strict;
+
+our $Strict = 1;
+
+sub Dispose {
+ my ($this) = @_;
+
+ bless $this, 'IMPL::Object::Disposed';
+}
+
+sub DESTROY {
+ my ($this) = @_;
+
+ warn sprintf('The object %s were marked as disposable but isn\'t disposed properly', $this->can('ToString') ? $this->ToString() : (ref $this || $this) );
+}
+
+sub superDispose {
+ my ($this) = @_;
+
+ my $package = caller;
+
+ no strict 'refs';
+
+ ($_.'::Dispose')->($this) foreach @{$package.'::ISA'};
+}
+
+package IMPL::Object::Disposed;
+our $AUTOLOAD;
+sub AUTOLOAD {
+ return if $AUTOLOAD eq __PACKAGE__.'::DESTROY';
+ die new IMPL::Exception('Object have been disposed',$AUTOLOAD);
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Object/EventSource.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Object/EventSource.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,133 @@
+package IMPL::Object::EventSource;
+use strict;
+require IMPL::Exception;
+use IMPL::Class::Property;
+
+sub CreateEvent {
+ my ($class,$event) = @_;
+
+ die new IMPL::Exception('A name is required for the event') unless $event;
+
+ (my $fullEventName = "$class$event") =~ s/:://g;
+
+ my $globalEventTable = new IMPL::Object::EventSource::EventTable($fullEventName);
+ my $propEventTable = $event.'Table';
+ public CreateProperty($class,$propEventTable,prop_all);
+ public CreateProperty($class,$event,
+ {
+ get => sub {
+ my $this = shift;
+ if (not defined wantarray and caller(1) eq $class) {
+ (ref $this ? $this->$propEventTable() || $globalEventTable : $globalEventTable)->Invoke($this);
+ } else {
+ if (ref $this) {
+ if (my $table = $this->$propEventTable()) {
+ return $table;
+ } else {
+ $table = new IMPL::Object::EventSource::EventTable($fullEventName,$globalEventTable);
+ $this->$propEventTable($table);
+ return $table;
+ }
+ } else {
+ return $globalEventTable;
+ }
+ }
+ },
+ set => sub {
+ (ref $_[0] ? $_[0]->$propEventTable() || $globalEventTable : $globalEventTable)->Invoke(@_);
+ }
+ }
+ );
+}
+
+sub CreateStaticEvent {
+ my ($class,$event) = @_;
+
+ die new IMPL::Exception('A name is required for the event') unless $event;
+
+ (my $fullEventName = "$class$event") =~ s/:://g;
+
+ my $globalEventTable = new IMPL::Object::EventSource::EventTable($fullEventName);
+
+ no strict 'refs';
+ *{"${class}::$event"} = sub {
+ my $class = shift;
+ if (not @_) {
+ if (not defined wantarray and caller(1) eq $class) {
+ $globalEventTable->Invoke($class);
+ } else {
+ return $globalEventTable;
+ }
+ } else {
+ $globalEventTable->Invoke(@_);
+ }
+ };
+}
+
+package IMPL::Object::EventSource::EventTable;
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+use Scalar::Util qw(weaken);
+
+use overload
+ '+=' => \&opSubscribe,
+ 'fallback' => 1;
+
+BEGIN {
+ public _direct property Name => prop_get;
+ public _direct property Handlers => { get => \&get_handlers };
+ private _direct property Next => prop_all;
+ private _direct property NextId => prop_all;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR();
+
+ $this->{$Handlers} = {};
+ $this->{$Name} = shift;
+ $this->{$Next} = shift;
+ $this->{$NextId} = 1;
+}
+
+sub get_handlers {
+ my $this = shift;
+ return values %{$this->{$Handlers}};
+}
+
+sub Invoke {
+ my $this = shift;
+
+ my $tmp;
+ $tmp = $_ and local($_) or &$tmp(@_) foreach values %{$this->{$Handlers}};
+
+ $this->{$Next}->Invoke(@_) if $this->{$Next};
+}
+
+sub Subscribe {
+ my ($this,$consumer,$nameHandler) = @_;
+
+ my $id = $this->{$NextId} ++;
+
+ if (ref $consumer eq 'CODE') {
+ $this->{$Handlers}{$id} = $consumer;
+ } else {
+ $nameHandler ||= $this->Name or die new IMPL::Exception('The name for the event handler method must be specified');
+ my $method = $consumer->can($nameHandler) or die new IMPL::Exception('Can\'t find the event handler method',$nameHandler,$consumer);
+
+ weaken($consumer) if ref $consumer;
+ $this->{$Handlers}{$id} = sub {
+ unshift @_, $consumer;
+ $consumer ? goto &$method : delete $this->{$Handlers}{$id};
+ };
+ }
+
+ return $id;
+}
+
+sub Remove {
+ my ($this,$id) = @_;
+ return delete $this->{$Handlers}{$id};
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Object/Meta.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Object/Meta.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,23 @@
+package IMPL::Object::Meta;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+
+BEGIN {
+ public _direct property Container => prop_get;
+}
+
+sub meta {
+ my $class = shift;
+ my $caller = caller;
+ my $meta = $class->surrogate();
+ $meta->{$Container} = $caller;
+ $meta->callCTOR(@_);
+ $caller->set_meta($meta);
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Object/Serializable.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Object/Serializable.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,54 @@
+package IMPL::Object::Serializable;
+use strict;
+use warnings;
+
+require IMPL::Exception;
+use IMPL::Class::Property;
+
+sub restore {
+ my ($class,$data,$refSurrogate) = @_;
+
+ if ($refSurrogate) {
+ $refSurrogate->callCTOR(@$data);
+ return $refSurrogate;
+ } else {
+ return $class->new(@$data);
+ }
+}
+
+sub save {
+ my ($this,$ctx,$predicate) = @_;
+
+ ($this->_get_save_method)->($this,$ctx);
+}
+
+sub _get_save_method {
+ my ($class) = @_;
+
+ $class = ref $class || $class;
+
+ no strict 'refs';
+ if (my $method = *{"${class}::_impl_auto_save"}{CODE}) {
+ return $method;
+ } else {
+ my $code = <AddVar('.$_->Name.' => ' .
+ ((not ref $_->Mutators and $_->Mutators & prop_list) ? ('[$this->'.$_->Class.'::'.$_->Name.'()]') : ('$this->'.$_->Class.'::'.$_->Name.'()')) .
+ ') if defined ' . '$this->'.$_->Class.'::'.$_->Name.'()' . ';', grep $_->canGet, $class->get_meta('IMPL::Class::PropertyInfo',undef,1);
+ $code .= <{$_}}{CODE}, keys %$table);
+ }
+}
+
+sub trap {
+ my ($class,$method) = @_;
+
+ return if not $Enabled;
+
+ no strict 'refs';
+ my $prevCode = \&{"${class}::${method}"};
+ my $proto = prototype $prevCode;
+
+ if (defined $proto and not $proto) {
+ return;
+ }
+ {
+ package IMPL::Profiler::Proxy;
+ no warnings 'redefine';
+ my $sub = sub {
+ my $t0 = [Time::HiRes::gettimeofday];
+ my @arr;
+ my $scalar;
+ my $entry = $prevCode;
+ my ($timeOwn,$timeTotal);
+ my $context = wantarray;
+ {
+ local $InvokeTime = 0;
+ #warn "\t"x$level,"enter ${class}::$method";
+ $level ++;
+ if ($context) {
+ @arr = &$entry(@_);
+ } else {
+ if (defined $context) {
+ $scalar = &$entry(@_);
+ } else {
+ &$entry(@_);
+ }
+ }
+ $timeTotal = Time::HiRes::tv_interval($t0);
+ $timeOwn = $timeTotal - $InvokeTime;
+ }
+ $InvokeInfo{"${class}::${method}"}{Count} ++;
+ $InvokeInfo{"${class}::${method}"}{Total} += $timeTotal;
+ $InvokeInfo{"${class}::${method}"}{Own} += $timeOwn;
+ $InvokeTime += $timeTotal;
+ $level --;
+ #warn "\t"x$level,"leave ${class}::$method";
+ return $context ? @arr : $scalar;
+ };
+ if ($proto) {
+ Scalar::Util::set_prototype($sub => $proto);
+ }
+ *{"${class}::${method}"} = $sub;
+ }
+
+}
+
+sub PrintStatistics {
+ my $hout = shift || *STDERR;
+ print $hout "-- modules --\n";
+ print $hout "$_\n" foreach sort keys %TrappedModules;
+ print $hout "\n-- stats --\n";
+ print $hout
+ pad($_,50),
+ pad("$InvokeInfo{$_}{Count}",10),
+ pad(sprintf("%.3f",$InvokeInfo{$_}{Own}),10),
+ pad(sprintf("%.3f",$InvokeInfo{$_}{Total}),10),
+ "\n"
+ foreach sort { $InvokeInfo{$b}{Own} <=> $InvokeInfo{$a}{Own} } keys %InvokeInfo;
+}
+
+sub ResetStatistics {
+ $InvokeTime = 0;
+ %InvokeInfo = ();
+}
+
+sub pad {
+ my ($str,$len) = @_;
+ if (length $str < $len) {
+ return $str.(' 'x ($len- length $str));
+ } else {
+ return $str;
+ }
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Profiler/Memory.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Profiler/Memory.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,57 @@
+package IMPL::Profiler::Memory;
+
+use strict;
+use Carp qw(longmess shortmess);
+use Scalar::Util qw(refaddr weaken isweak);
+
+my %instances;
+
+BEGIN {
+ *CORE::GLOBAL::bless = sub {
+ $_[1] |= caller unless $_[1];
+ my $ref = CORE::bless $_[0],$_[1];
+
+ my $id = refaddr($ref);
+
+ $instances{$id} = {
+ Class => $_[1],
+ WeakRef => $ref
+ };
+
+ weaken($instances{$id}{WeakRef});
+
+ return $ref;
+ }
+}
+
+sub DumpAlive {
+ my ($hout) = @_;
+ $hout = *STDOUT unless $hout;
+ print $hout "Alive objects table\n";
+ print $hout "-------------------\n";
+ while (my ($id,$info) = each %instances) {
+ delete $instances{$id} and next unless $info->{WeakRef};
+ print "$info->{Class} $id: $info->{WeakRef}\n";
+ }
+}
+
+sub StatClasses {
+ my ($hout) = @_;
+ $hout = *STDOUT unless $hout;
+ print $hout "Statistics by class\n";
+ print $hout "-------------------\n";
+ my %stat;
+ while (my ($id,$info) = each %instances) {
+ #$stat{$info->{Class}}{total} ++;
+ delete $instances{$id} and next unless $info->{WeakRef};
+ $stat{$info->{Class}}{alive} ++;
+ }
+
+ print $hout "$_ $stat{$_}{alive} \n" foreach sort keys %stat;
+}
+
+sub Clear {
+ undef %instances;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Resources.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Resources.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,59 @@
+package IMPL::Resources;
+use strict;
+use warnings;
+
+our $Encoding ||= 'utf-8';
+our %Files;
+
+my %Data;
+
+
+ foreach my $group (keys %Files) {
+ $Data{$group} = ParseResource($Files{$group});
+ }
+
+sub findFile {
+ my ($fname) = @_;
+
+ foreach my $dir (',',@INC) {
+ my $fullfname = "$dir/$fname";
+ return $fullfname if -f $fullfname;
+ }
+
+ return $fname;
+}
+
+sub ParseResource {
+ my ($fname) = @_;
+
+ open my $hRes, "<:encoding($Encoding)", findFile($fname) or die "Failed to open file $fname: $!";
+
+ my %Map;
+ my $line = 1;
+ while (<$hRes>) {
+ chomp;
+ $line ++ and next if /^\s*$/;
+
+ if (/^(\w+)\s*=\s*(.*)$/) {
+ $Map{$1} = $2;
+ } else {
+ die "Invalid resource format in $fname at $line";
+ }
+ $line ++;
+ }
+
+ return \%Map;
+}
+
+sub import {
+ my ($class,@groups) = @_;
+ my $caller = caller;
+ my %merged = map %{$Data{$_} || {} }, @groups;
+
+ no strict 'refs';
+ foreach my $item ( keys %merged ) {
+ *{"${caller}::ids_$item"} = sub { sprintf($merged{$item},@_) }
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/SVN.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/SVN.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,15 @@
+package IMPL::SVN;
+use strict;
+
+use base qw(IMPL::Object);
+use IMPL::Object::Property;
+
+BEGIN {
+ public virtual _direct property SvnClient => get;
+}
+
+sub UpdateBatch {
+ my ($this,$revstart,$revend) = @_;
+
+
+}
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Security.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Security.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,3 @@
+package IMPL::Security;
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Security/AuthResult.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Security/AuthResult.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,16 @@
+package IMPL::Security::AuthResult;
+use strict;
+
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+
+BEGIN {
+ public _direct property State => prop_get;
+ public _direct property Session => prop_get;
+ public _direct property ClientSecData => prop_get;
+ public _direct property AuthMod => prop_get;
+}
+
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Serialization.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Serialization.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,409 @@
+
+# 20060222
+# Ìîäóëü äëÿ ñåðèàëèçàöèè ñòðóêòóð äàííûõ
+# (ö) Sourcer, cin.sourcer@gmail.com
+# revision 3 (20090517)
+
+
+package IMPL::Serialization;
+use strict;
+
+package IMPL::Serialization::Context;
+use base qw(IMPL::Object);
+
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+use IMPL::Exception;
+use Scalar::Util qw(refaddr);
+
+BEGIN {
+ private _direct property ObjectWriter => prop_all; # îáúåêò, çàïèñûâàþùèé äàííûå â ïîòîê
+ private _direct property Context => prop_all; # êîíòåêñò (îáúåêòû êîòîðûå óæå ñåðèàëèçîâàíû, èõ èäåíòèôèêàòîðû)
+ private _direct property NextID => prop_all;# ñëåäóþùèé èäåíòèôèêàòîð äëÿ îáúåêòà
+
+ # ïðîöåäóðà, êîòîðàÿ çíàåò, êàê ñåðèàëèçîâûâàòü îïðåäåëåííûå òèïû. Ïåðâûì ïàðàìåòðîì
+ # ïîëó÷àåì ññûëêó íà IMPL::Serialization::Context, âòîðûì ïàðàìåòðîì ññûëêó íà îáúåêò
+ public _direct property Serializer => prop_all;
+
+ private _direct property State => prop_all; # ñîñòîÿíèå êîíòåêñòà ñåðèàëèçàöèè
+}
+
+# êîíòåêñò çàêðûò, ò.å. íèêàêîé îáúåêò íå íà÷àò
+sub STATE_CLOSED () { 0 }
+# êîíòåêñò îòêðûò, ò.å. îáúåêò íà÷àò, íî â íåì åùå íè÷åãî íå ëåæèò
+sub STATE_OPENED () { 1 }
+# êîíòåêñò îòêðûò è â íåãî ìîãóò áûòü äîáàâëåíû òîëüêî ïîäîáúåêòû
+sub STATE_COMPLEX () { 2 }
+# êîíòåêñò îòêðûò è â íåãî óæå íè÷åãî íå ìîæåò áûòü äîáàâëåíî, òàì ëåæàò äàííûå
+sub STATE_DATA () { 3 }
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$ObjectWriter} = $args{'ObjectWriter'};
+ #$this->{$Context} = {};
+ $this->{$NextID} = 1;
+ $this->{$Serializer} = ($args{'Serializer'} ? $args{'Serializer'} : \&DefaultSerializer );
+ $this->{$State} = STATE_CLOSED;
+
+ return 1;
+}
+
+sub AddVar {
+ my ($this,$sName,$Var) = @_;
+
+ die new Exception ('Invalid operation') if $this->{$State} == STATE_DATA;
+
+ if (not ref $Var) {
+ # íåìíîãî äóáëèðóåòñÿ òî, ÷òî ñíèçó, íî ýòî ðàäè òîãî, ÷òîáû îáúåêòû, êîòîðûå èäóò
+ # íå ïî ññûëêå, íå ïîëó÷àëè èäåíòèôèêàòîðà, èì îí íå íóæåí
+ my $prevState = $this->{$State};
+
+ $this->{$ObjectWriter}->BeginObject(name => $sName);#, type => 'SCALAR');
+ $this->{$State} = STATE_OPENED;
+
+ $this->{$Serializer}->($this,\$Var);
+
+ $this->{$ObjectWriter}->EndObject();
+
+ if ($prevState == STATE_OPENED) {
+ $this->{$State} = STATE_COMPLEX;
+ } else {
+ $this->{$State} = $prevState;
+ }
+ return 0;
+ }
+
+ my $PrevState = $this->{$State};
+
+ my $ObjID = $this->{$Context}->{refaddr $Var};
+ if ($ObjID) {
+ $this->{$ObjectWriter}->BeginObject(name => $sName, refid => $ObjID);
+ $this->{$ObjectWriter}->EndObject();
+ return $ObjID;
+ }
+
+ $ObjID = $this->{$NextID};
+ $this->{$NextID} = $ObjID + 1;
+
+ $this->{$Context}->{refaddr $Var} = $ObjID;
+
+ $this->{$ObjectWriter}->BeginObject(name => $sName, type => ref($Var), id => $ObjID);
+
+ $this->{$State} = STATE_OPENED;
+ $this->{$Serializer}->($this,$Var);
+
+ $this->{$ObjectWriter}->EndObject();
+
+ if ($PrevState == STATE_OPENED) {
+ $this->{$State} = STATE_COMPLEX;
+ } else {
+ $this->{$State} = $PrevState;
+ }
+
+ return $ObjID;
+}
+
+sub SetData {
+ my ($this,$Data,$Type) = @_;
+
+ die new Exception ('The object should be a scalar value') if ref $Data;
+ die new Exception ('Invalid operation') if $this->{$State} != STATE_OPENED;
+
+ $this->{$ObjectWriter}->SetData($Data,$Type);
+
+ $this->{$State} = STATE_DATA;
+
+ return 1;
+}
+
+sub DefaultSerializer {
+ my ($Context, $refObj) = @_;
+
+ if (ref($refObj) eq 'SCALAR') {
+ $Context->SetData($$refObj, 'SCALAR');
+ } elsif (ref($refObj) eq 'ARRAY') {
+ $Context->AddVar('item',$_) foreach @$refObj;
+ } elsif (ref($refObj) eq 'HASH') {
+ while (my ($key,$value) = each %$refObj) {
+ $Context->AddVar($key,$value);
+ }
+ } elsif (ref($refObj) eq 'REF') {
+ $Context->AddVar('ref',$$refObj);
+ } else {
+ if (ref $refObj and $refObj->UNIVARSAL::can('save')) {
+ $refObj->save($Context);
+ } else {
+ die new Exception('Cant serialize the object of the type: '.ref($refObj));
+ }
+ }
+
+ return 1;
+}
+
+package IMPL::Deserialization::Context;
+use base qw(IMPL::Object);
+
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+use IMPL::Exception;
+
+BEGIN {
+ # óæå äåñåðèàëèçîâàííûå îáúåêòû, õåø, êëþ÷ - èäåíòèôèêàòîð, çíà÷åíèå - ññûëêà.
+ private _direct property Context => prop_all;
+
+ # òåêóùèé îáúåêò. èíôîðìàöèÿ äëÿ äåñåðèàëèçàöèè
+ # {
+ # Type => 'typename',
+ # Name => 'object_name',
+ # Data => $Data,
+ # Id => 'object_id'
+ # }
+ private _direct property CurrentObject => prop_all;
+
+ # ñòåê îáúåêòîâ. ñþäà äîáàâëÿþòñÿ îïèñàíèÿ îáúåêòîâ ïî ìåðå âñòðå÷àíèÿ íîâûõ îáúåêòîâ.
+ private _direct property ObjectsPath => prop_all;
+
+ # ñþäà ïîïàäåò êîðåíü ãðàôà îáúåêòîâ
+ public _direct property Root => prop_get;
+
+ # ñîçäàåò îáúåêò è âîçâðàùàåò íà íåãî ññûëêó
+ # ObjectFactory($Type,$DeserializationData,$refSurogate)
+ # $Type - èìÿ òèïà äàííûõ
+ # $DeserializationData - ëèáî ññûëêà íà ìàññèâ ñ äàííûìè äëÿ äåñåðèàëèçàöèè ïîëåé,
+ # ëèáî ñêàëÿð ñîäåðæàùèé äàííûå.
+ # $refSurogate - ññûëêà íà ïðåäâàðèòåëüíî ñîçäàííûé, íå èíèöèàëèçèðîâàííûé îáúåêò.
+ # ìîæåò ïðèíèìàòü çíà÷åíèå undef
+ private _direct property ObjectFactory => prop_all;
+
+ # Ñîçäàåò íåèíèöèàëèçèðîâàííûå îáúåêòû.
+ # SurogateHelper($Type)
+ # $Type èìÿ òèïïà, ÷åé ñóðîãàò íóæíî ñîçäàòü.
+ private _direct property SurogateHelper => prop_all;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ $this->{$CurrentObject} = undef;
+ $this->{$Root} = undef;
+}
+
+sub OnObjectBegin {
+ my ($this,$name,$rhProps) = @_;
+
+ die new Exception("Invalid data from an ObjectReader","An object reader should pass a referense to a hash which contains attributes of an object") if (ref $rhProps ne 'HASH');
+ die new Exception("Trying to create second root object") if not $this->{$CurrentObject} and $this->{$Root};
+
+ if ($rhProps->{'refid'}) {
+ my $refObj = $this->{$Context}->{$rhProps->{'refid'}};
+ die new Exception("A reference to a not existing object found") if not $refObj;
+ my $rhCurrentObj = $this->{$CurrentObject};
+
+ die new Exception("Found a reference to an object as a root of an object's graph") if not $rhCurrentObj;
+
+ if ($rhCurrentObj->{'Data'}) {
+ die new Exception("Invalid serializaed data","Plain deserialization data for an object already exist") if not ref $rhCurrentObj->{'Data'};
+ push @{$rhCurrentObj->{'Data'}}, $name,$refObj;
+ } else {
+ $rhCurrentObj->{'Data'} = [$name,$refObj];
+ }
+
+ # ýòî çàòåì, ÷òî áóäåò âûçâàí OnObjectEnd äëÿ îáúåêòà, êîòîðûé áûë ïðîñòîé ññûëêîé. ò.î. ìû íå íàðóøèì ñòåê
+ push @{$this->{$ObjectsPath}},$rhCurrentObj;
+ $this->{$CurrentObject} = undef;
+
+ } else {
+ push @{$this->{$ObjectsPath}},$this->{$CurrentObject} if $this->{$CurrentObject};
+
+ $this->{$CurrentObject} = {
+ Name => $name,
+ Type => $rhProps->{'type'} || 'SCALAR',
+ Id => $rhProps->{'id'},
+ refId => $rhProps->{'refid'}
+ };
+ $this->{$Context}->{$rhProps->{'id'}} = $this->{$SurogateHelper} ? $this->{$SurogateHelper}->($rhProps->{'type'}) : DefaultSurogateHelper($rhProps->{'type'}) if defined $rhProps->{'id'};
+ }
+
+ return 1;
+}
+
+sub OnObjectData {
+ my ($this,$data) = @_;
+
+ my $rhObject = $this->{$CurrentObject};
+
+ die new Exception("Trying to set data for an object which not exists") if not $rhObject;
+
+ die new Exception("Deserialization data already exists for a current object", "ObjectName= $rhObject->{'Name'}") if $rhObject->{'Data'};
+
+ $rhObject->{'Data'} = $data;
+
+ return 1;
+}
+{
+ my $AutoId = 0;
+ sub OnObjectEnd {
+ my ($this,$name) = @_;
+
+ my $rhObject = $this->{$CurrentObject};
+ my $rhPrevObject = pop @{$this->{$ObjectsPath}};
+
+ # åñëè òåêóùèé îáúåêò íå îïðåäåëåí, à ïðåäûäóùèé - îïðåäåëåí, çíà÷èò òåêóùèé - ýòî ññûëêà
+ # ïðîñòî âîññòàíàâëèâàåì ïðåäûäóùèé â òåêóùèé è íè÷åãî áîëåå íå äåëàåì
+ if ((not defined($rhObject)) && $rhPrevObject) {
+ $this->{$CurrentObject} = $rhPrevObject;
+ return 1;
+ }
+
+ my $refObj = $this->{$ObjectFactory} ?$this->{$ObjectFactory}->($rhObject->{'Type'},$rhObject->{'Data'},$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef) : DefaultFactory($rhObject->{'Type'},$rhObject->{'Data'},$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef);
+
+ die new Exception("Trying to close a non existing oject") if not $rhObject;
+
+ my $Data;
+
+ if ($rhObject->{'Id'}) {
+ $this->{$Context}->{$rhObject->{'Id'}} = $refObj;
+ $Data = $refObj;
+ } else {
+ if (ref $refObj ne 'SCALAR') {
+ $rhObject->{Id} = "auto$AutoId";
+ $AutoId ++;
+ $this->{$Context}->{$rhObject->{'Id'}} = $refObj;
+ $Data = $refObj;
+ } else {
+ $Data = ${$refObj};
+ }
+ }
+
+ if (not $rhPrevObject) {
+ $this->{$Root} = $Data;
+ } else {
+ if ($rhPrevObject->{'Data'}) {
+ die new Exception("Trying append a reference to an object to the plain data") if not ref $rhPrevObject->{'Data'};
+ push @{$rhPrevObject->{'Data'}},$rhObject->{'Name'},$Data;
+ } else {
+ $rhPrevObject->{'Data'} = [$rhObject->{'Name'},$Data];
+ }
+ }
+
+ $this->{$CurrentObject} = $rhPrevObject;
+
+ return 1;
+ }
+}
+
+sub _is_class {
+ no strict 'refs';
+ scalar keys %{"$_[0]::"} ? 1 : 0;
+}
+
+sub DefaultSurogateHelper {
+ my ($Type) = @_;
+
+ if ($Type eq 'SCALAR' or $Type eq 'REF') {
+ my $var;
+ return \$var;
+ } elsif ($Type eq 'ARRAY') {
+ return [];
+ } elsif ($Type eq 'HASH') {
+ return {};
+ } else {
+ eval "require $Type" unless _is_class($Type);
+ if ($Type->UNIVERSAL::can('surrogate')) {
+ return $Type->surrogate();
+ } else {
+ return bless {}, $Type;
+ }
+ }
+}
+
+# deserialization context:
+# [
+# 'var_name',value,
+# ....
+# ]
+
+sub DefaultFactory {
+ my ($Type,$Data,$refSurogate) = @_;
+
+ if ($Type eq 'SCALAR') {
+ die new Exception("SCALAR needs a plain data for a deserialization") if ref $Data;
+ if ($refSurogate) {
+ $$refSurogate = $Data;
+ return $refSurogate;
+ } else {
+ return \$Data;
+ }
+ } elsif ($Type eq 'ARRAY') {
+ die new Exception("Invalid a deserialization context when deserializing ARRAY") if not ref $Data and defined $Data;
+ if (not ref $refSurogate) {
+ my @Array;
+ $refSurogate = \@Array;
+ }
+ for (my $i = 0; $i < scalar(@{$Data})/2; $i++) {
+ push @$refSurogate,$Data->[$i*2+1];
+ }
+ return $refSurogate;
+ } elsif ($Type eq 'HASH') {
+ die new Exception("Invalid a deserialization context when deserializing HASH") if not ref $Data and defined $Data;
+ if (not ref $refSurogate) {
+ $refSurogate = {};
+ }
+ for (my $i = 0; $i< @$Data; $i+= 2) {
+ $refSurogate->{$Data->[$i]} = $Data->[$i+1];
+ }
+ return $refSurogate;
+ } elsif ($Type eq 'REF') {
+ die new Exception("Invalid a deserialization context when deserializing REF") if not ref $Data and defined $Data;
+ if (not ref $refSurogate) {
+ my $ref = $Data->[1];
+ return \$ref;
+ } else {
+ $$refSurogate = $Data->[1];
+ return $refSurogate;
+ }
+ } else {
+ eval "require $Type" unless _is_class($Type);
+ if ( $Type->UNIVERSAL::can('restore') ) {
+ return $Type->restore($Data,$refSurogate);
+ } else {
+ die new Exception("Don't know how to deserialize $Type");
+ }
+ }
+}
+
+package IMPL::Serializer;
+use base qw(IMPL::Object);
+
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+use IMPL::Exception;
+
+BEGIN {
+ private _direct property Formatter => prop_all;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ $this->Formatter($args{'Formatter'}) or die new Exception("Omitted mandatory parameter 'Formatter'");
+}
+
+sub Serialize {
+ my $this = shift;
+ my ($hStream,$Object) = @_;
+ my $ObjWriter = $this->Formatter()->CreateWriter($hStream);
+ my $Context = new IMPL::Serialization::Context(ObjectWriter => $ObjWriter);
+ $Context->AddVar('root',$Object);
+ return 1;
+}
+
+sub Deserialize {
+ my $this = shift;
+ my ($hStream) = @_;
+ my $Context = new IMPL::Deserialization::Context();
+ my $ObjReader = $this->Formatter()->CreateReader($hStream,$Context);
+ $ObjReader->Parse();
+ return $Context->Root();
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Serialization/XmlFormatter.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Serialization/XmlFormatter.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,202 @@
+package IMPL::Serialization::XmlObjectWriter;
+use strict;
+
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+
+use IMPL::Serialization;
+use XML::Writer;
+use IMPL::Exception;
+
+sub CONTAINER_EMPTY () { 1 }
+sub CONTAINER_NORMAL () { 2 }
+
+BEGIN {
+ public _direct property Encoding => prop_all;
+ public _direct property hOutput => prop_all;
+ public _direct property IdentOutput => prop_all;
+
+ private _direct property CurrentObject => prop_all;
+ private _direct property ObjectPath => prop_all;
+ private _direct property XmlWriter => prop_all;
+ private _direct property IdentLevel => prop_all;
+ private _direct property IdentNextTag => prop_all;
+}
+
+sub new {
+ my $class = shift;
+ my $self = bless {}, ref($class) || $class;
+ $self->CTOR(@_);
+ return $self;
+}
+
+sub CTOR {
+ my $this = shift;
+ my %args = @_;
+ $this->{$hOutput} = $args{'hOutput'};
+ $this->{$Encoding} = $args{'Encoding'};
+ $this->{$CurrentObject} = undef;
+ $this->{$IdentOutput} = $args{'IdentOutput'};
+ $this->{$IdentLevel} = 0;
+ $this->{$IdentNextTag} = 0;
+ #$this->{$ObjectPath} = [];
+ return 1;
+}
+
+sub BeginObject {
+ my $this = shift;
+ my %args = @_;
+
+ if (not $this->{$CurrentObject}) {
+ my $xmlWriter = new XML::Writer(OUTPUT => $this->{$hOutput}, ENCODING => $this->{$Encoding});
+ $this->{$XmlWriter} = $xmlWriter;
+ $xmlWriter->xmlDecl();
+ }
+
+ push @{$this->{$ObjectPath}},$this->{$CurrentObject} if $this->{$CurrentObject};
+
+ my %ObjectProperties = %args;
+ delete $ObjectProperties{'name'};
+ delete $args{'container_type'};
+
+ $this->{$CurrentObject} = \%ObjectProperties;
+
+ my $tagname;
+ if (_CheckName($args{'name'})) {
+ $tagname = $args{'name'};
+ } else {
+ $tagname = 'element';
+ $ObjectProperties{'extname'} = $args{'name'};
+ }
+
+ if ($args{'refid'}) {
+ $this->{$XmlWriter}->characters("\n" . (' ' x $$this{$IdentLevel}) ) if $$this{$IdentNextTag};
+ $this->{$XmlWriter}->emptyTag($tagname,%ObjectProperties);
+ $ObjectProperties{'container_type'} = CONTAINER_EMPTY;
+ } else {
+ $this->{$XmlWriter}->characters("\n" . (' ' x $$this{$IdentLevel}) ) if $$this{$IdentNextTag};
+ $this->{$XmlWriter}->startTag($tagname,%ObjectProperties);
+ $ObjectProperties{'container_type'} = CONTAINER_NORMAL;
+ }
+
+ $this->{$IdentLevel} ++;
+ $this->{$IdentNextTag} = $this->{$IdentOutput};
+
+ return 1;
+}
+
+sub EndObject {
+ my $this = shift;
+
+ my $hCurrentObject = $this->{$CurrentObject} or return 0;
+
+ $this->{$IdentLevel} --;
+
+ if ( $hCurrentObject->{'container_type'} != CONTAINER_EMPTY ) {
+ $this->{$XmlWriter}->characters("\n" . (' ' x $$this{$IdentLevel}) ) if $$this{$IdentNextTag};
+ $this->{$XmlWriter}->endTag();
+ }
+
+ $this->{$IdentNextTag} = $this->{$IdentOutput};
+
+ $this->{$CurrentObject} = pop @{$this->{$ObjectPath}} if exists $this->{$ObjectPath};
+ $this->{$XmlWriter} = undef if (not $this->{$CurrentObject});
+
+ return 1;
+}
+
+sub SetData {
+ my $this = shift;
+ #my $hCurrentObject = $this->{$CurrentObject} or return 0;
+
+ if ($this->{$CurrentObject}->{'container_type'} == CONTAINER_NORMAL) {
+ $this->{$XmlWriter}->characters($_[0]) if defined $_[0];
+ $this->{$IdentNextTag} = 0;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+sub _CheckName {
+ return 0 if not $_[0];
+ return $_[0] =~ /^(_|\w|\d)+$/;
+}
+
+package IMPL::Serialization::XmlObjectReader;
+use base qw(XML::Parser);
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ die new Exception("Handler parameter is reqired") if not $args{'Handler'};
+ die new Exception("Handler parameter must be a reference") if not ref $args{'Handler'};
+
+ #my $this = $class->SUPER::new(Style => 'Stream', Pkg => 'Serialization::XmlObjectReader', 'Non-Expat-Options' => {hInput => $args{'hInput'} , Handler => $args{'Handler'}, SkipWhitespace => $args{'SkipWhitespace'} } );
+ my $this = $class->SUPER::new(Handlers => { Start => \&StartTag, End => \&EndTag, Char => \&Text} , 'Non-Expat-Options' => {hInput => $args{'hInput'} , Handler => $args{'Handler'}, SkipWhitespace => $args{'SkipWhitespace'} } );
+ return $this;
+}
+
+sub Parse {
+ my $this = shift;
+ $this->parse($this->{'Non-Expat-Options'}->{'hInput'});
+ return 1;
+}
+
+sub StartTag {
+ my $this = shift;
+ my $name = shift;
+ my %Attr = @_;
+ $name = $Attr{'extname'} if defined $Attr{'extname'};
+ $this->{'Non-Expat-Options'}->{'Handler'}->OnObjectBegin($name,\%Attr);
+ return 1;
+}
+
+sub EndTag {
+ my ($this,$name) = @_;
+ $this->{'Non-Expat-Options'}->{'Handler'}->OnObjectEnd($name);
+ return 1;
+}
+
+sub Text {
+ my ($this) = shift;
+ $_ = shift;
+ return 1 if $this->{'Non-Expat-Options'}->{'SkipWhitespace'} and /^\n*\s*\n*$/;
+ $this->{'Non-Expat-Options'}->{'Handler'}->OnObjectData($_);
+ return 1;
+}
+
+package IMPL::Serialization::XmlFormatter;
+use base qw(IMPL::Object);
+
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+
+BEGIN {
+ public _direct property Encoding => prop_all;
+ public _direct property SkipWhitespace => prop_all;
+ public _direct property IdentOutput => prop_all;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->Encoding($args{'Encoding'} || 'utf-8');
+ $this->SkipWhitespace($args{'SkipWhitespace'});
+ $this->IdentOutput($args{'IdentOutput'});
+
+ return 1;
+}
+
+sub CreateWriter {
+ my ($this,$hStream) = @_;
+ return new IMPL::Serialization::XmlObjectWriter(Encoding =>$this->Encoding() , hOutput => $hStream, IdentOutput => $this->IdentOutput());
+}
+
+sub CreateReader {
+ my ($this,$hStream,$refHandler) = @_;
+ return new IMPL::Serialization::XmlObjectReader(hInput => $hStream, Handler => $refHandler, SkipWhitespace => $this->SkipWhitespace());
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,32 @@
+package IMPL::Test;
+use strict;
+use warnings;
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(&test &shared);
+
+require IMPL::Test::Unit;
+use IMPL::Class::Member;
+
+sub test($$) {
+ my ($name,$code) = @_;
+ my $class = caller;
+
+ $class->set_meta(
+ new IMPL::Test::Unit::TestInfo( $name, $code )
+ );
+}
+
+sub shared($) {
+ my ($propInfo) = @_;
+
+ my $class = caller;
+
+ die new IMPL::Exception("Only properties could be declared as shared",$propInfo->Name) unless eval {$propInfo->isa('IMPL::Class::PropertyInfo')};
+ die new IMPL::Exception("You can't mark the readonly property as shared",$propInfo->Name) unless $propInfo->canSet;
+ die new IMPL::Exception("Only public properties could be declared as shared",$propInfo->Name) unless $propInfo->Access == IMPL::Class::Member::MOD_PUBLIC;
+
+ $class->set_meta(new IMPL::Test::Unit::SharedData($propInfo->Name));
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/BadUnit.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/BadUnit.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,49 @@
+package IMPL::Test::BadUnit;
+use strict;
+use warnings;
+
+use base qw(IMPL::Test::Unit);
+use IMPL::Class::Property;
+
+BEGIN {
+ public property UnitName => prop_all;
+ public property Message => prop_all;
+ public property Error => prop_all;
+}
+
+our %CTOR = (
+ 'IMPL::Test::Unit' => sub {
+ my ($unit,$message,$error) = @_;
+ return new IMPL::Test::Unit::TestInfo(
+ BadUnitTest => sub {
+ die new IMPL::Test::FailException($message,$unit,eval {$error->isa('IMPL::Exception')} ? $error->toString(1) : $error)
+ }
+ );
+ }
+);
+
+sub CTOR {
+ my ($this,$name,$message,$error) = @_;
+
+ $this->UnitName($name);
+ $this->Message($message);
+ $this->Error($error);
+}
+
+sub save {
+ my ($this,$ctx) = @_;
+
+ defined ($this->$_()) and $ctx->AddVar($_ => $this->$_()) foreach qw(UnitName Message);
+}
+
+sub restore {
+ my ($class,$data,$inst) = @_;
+
+ my %args = @$data;
+
+ $inst ||= $class->surrogate;
+ $inst->callCTOR(@args{qw(UnitName Message)});
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/FailException.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/FailException.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,22 @@
+package IMPL::Test::FailException;
+use strict;
+use warnings;
+
+use base qw(IMPL::Exception);
+
+__PACKAGE__->PassThroughArgs;
+
+sub toString {
+ my $this = shift;
+
+ $this->Message . join("\n",'',map IMPL::Exception::indent($_,1), @{$this->Args} );
+}
+
+sub save {
+ my ($this,$ctx) = @_;
+
+ $ctx->AddVar(Message => $this->Message);
+ $ctx->AddVar(Args => $this->Args) if @{$this->Args};
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/HarnessRunner.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/HarnessRunner.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,37 @@
+package IMPL::Test::HarnessRunner;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object IMPL::Object::Autofill IMPL::Object::Serializable);
+use IMPL::Class::Property;
+use Test::Harness;
+
+__PACKAGE__->PassThroughArgs;
+
+BEGIN {
+ public property Strap => prop_all;
+}
+
+sub CTOR {
+ my $this = shift;
+
+ die new IMPL::InvalidArgumentException("The Strap parameter must be specified") unless $this->Strap;
+}
+
+sub RunTests {
+ my ($this,@files) = @_;
+
+ local $Test::Harness::Strap = $this->Strap;
+
+ return runtests(@files);
+}
+
+sub ExecuteTests {
+ my ($this,%args) = @_;
+
+ local $Test::Harness::Strap = $this->Strap;
+
+ return Test::Harness::execute_tests(%args);
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/Plan.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/Plan.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,200 @@
+package IMPL::Test::Plan;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+
+use IMPL::Exception;
+use IMPL::Test::Result;
+use IMPL::Test::BadUnit;
+use Error qw(:try);
+
+use IMPL::Serialization;
+use IMPL::Serialization::XmlFormatter;
+
+BEGIN {
+ public property Units => prop_all | prop_list;
+ public property Results => prop_all | prop_list;
+ public property Listeners => prop_all | prop_list;
+ private property _Cache => prop_all | prop_list;
+ private property _Count => prop_all;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->Units(\@_);
+}
+
+sub restore {
+ my ($class,$data,$instance) = @_;
+
+ $instance ||= $class->surrogate;
+
+ $instance->callCTOR();
+
+ my %args = @$data;
+
+ $instance->Units($args{Units});
+ $instance->Results($args{Results}) if $args{Results};
+ $instance->Listeners($args{Listeners}) if $args{Listeners};
+}
+
+sub save {
+ my ($this,$ctx) = @_;
+
+ $ctx->AddVar(Units => [$this->Units]);
+ $ctx->AddVar(Results => [$this->Results]) if $this->Results;
+ $ctx->AddVar(Listeners => [$this->Listeners]) if $this->Listeners;
+}
+
+sub AddListener {
+ my ($this,$listener) = @_;
+
+ $this->Listeners($this->Listeners,$listener);
+}
+
+sub Prepare {
+ my ($this) = @_;
+
+ my $count = 0;
+ my @cache;
+
+ foreach my $Unit ($this->Units){
+ my %info;
+
+ $info{Unit} = $Unit;
+ try {
+ $info{Tests} = [map $Unit->new($_), $Unit->List];
+ } otherwise {
+ $info{Tests} = [$info{Unit} = new IMPL::Test::BadUnit($Unit->UnitName,"Failed to extract tests",$@)];
+ };
+ $count += @{$info{Tests}};
+ push @cache, \%info if @{$info{Tests}};
+ }
+
+ $this->_Count($count);
+ $this->_Cache(\@cache);
+}
+
+sub Count {
+ my ($this) = @_;
+ return $this->_Count;
+}
+
+sub Run {
+ my $this = shift;
+
+ die new IMPL::InvalidOperationException("You must call the prepare method before running the plan") unless $this->_Cache;
+
+ $this->_Tell(RunPlan => $this);
+
+ my @resultsTotal;
+
+ foreach my $info ($this->_Cache) {
+ $this->_Tell(RunUnit => $info->{Unit});
+
+ my $data;
+ undef $@;
+ eval {
+ $data = $info->{Unit}->StartUnit;
+ };
+
+ my @results;
+
+ if (not $@) {
+ foreach my $test (@{$info->{Tests}}) {
+ $this->_Tell(RunTest => $test);
+ my $result = $test->Run($data);
+ $this->_Tell(EndTest => $test,$result);
+ push @results,$result;
+ }
+ } else {
+ my $e = $@;
+ foreach my $test (@{$info->{Tests}}) {
+ $this->_Tell(RunTest => $test);
+ my $result = new IMPL::Test::Result(
+ Name => $test->Name,
+ State => IMPL::Test::Result::FAIL,
+ Exception => $e
+ );
+ $this->_Tell(EndTest => $test,$result);
+ push @results,$result;
+ }
+ }
+
+ eval {
+ $info->{Unit}->FinishUnit($data);
+ };
+
+ undef $@;
+
+ push @resultsTotal, { Unit => $info->{Unit}, Results => \@results};
+
+ $this->_Tell(EndUnit => $info->{Unit},\@results);
+ }
+
+ $this->Results(\@resultsTotal);
+ $this->_Tell(EndPlan => $this);
+}
+
+sub _Tell {
+ my ($this,$what,@args) = @_;
+
+ $_->$what(@args) foreach $this->Listeners;
+}
+
+sub SaveXML {
+ my ($this,$out) = @_;
+
+ my $h;
+
+ if (ref $out eq 'GLOB') {
+ $h = $out;
+ } elsif ($out and not ref $out) {
+ open $h, ">", $out or die new IMPL::Exception("Failed to open file",$out);
+ } else {
+ die new IMPL::InvalidOperationException("Invalid output specified");
+ }
+
+ my $s = new IMPL::Serializer(Formatter => new IMPL::Serialization::XmlFormatter( IdentOutput => 1, SkipWhitespace => 1) );
+ $s->Serialize($h,$this);
+}
+
+sub LoadXML {
+ my ($self,$in) = @_;
+
+ my $h;
+
+ if (ref $in eq 'GLOB') {
+ $h = $in;
+ } elsif ($in and not ref $in) {
+ open $h, ">", $in or die new IMPL::Exception("Failed to open file",$in);
+ } else {
+ die new IMPL::InvalidOperationException("Invalid input specified");
+ }
+
+ my $s = new IMPL::Serializer(Formatter => new IMPL::Serialization::XmlFormatter( IdentOutput => 1, SkipWhitespace => 1) );
+ return $s->Deserialize($h);
+}
+
+sub xml {
+ my $this = shift;
+ my $str = '';
+
+ open my $h,'>',\$str or die new IMPL::Exception("Failed to create stream");
+ $this->SaveXML($h);
+ undef $h;
+ return $str;
+}
+
+sub LoadXMLString {
+ my $self = shift;
+ my $str = shift;
+
+ open my $h,'<',\$str or die new IMPL::Exception("Failed to create stream");
+ return $self->LoadXML($h);
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/Result.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/Result.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,32 @@
+package IMPL::Test::Result;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object IMPL::Object::Autofill IMPL::Object::Serializable);
+use IMPL::Class::Property;
+
+__PACKAGE__->PassThroughArgs;
+
+use constant {
+ SUCCESS => 0,
+ FAIL => 1,
+ ERROR => 2
+};
+
+BEGIN {
+ public property Name => prop_all;
+ public property State => prop_all;
+ public property Exception => prop_all;
+ public property TimeExclusive => prop_all;
+ public property TimeInclusive => prop_all;
+}
+
+sub CTOR {
+ my ($this) = @_;
+
+ $this->TimeInclusive(0) unless defined $this->TimeInclusive;
+ $this->TimeExclusive(0) unless defined $this->TimeExclusive;
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/SkipException.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/SkipException.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,8 @@
+package IMPL::Test::SkipException;
+
+use base qw(IMPL::Test::FailException);
+
+__PACKAGE__->PassThroughArgs;
+
+1;
+
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/Straps.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/Straps.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,90 @@
+package IMPL::Test::Straps;
+use strict;
+use warnings;
+
+use base qw(Test::Harness::Straps IMPL::Object IMPL::Object::Autofill IMPL::Object::Serializable);
+use IMPL::Class::Property;
+
+__PACKAGE__->PassThroughArgs;
+
+BEGIN {
+ public property Executors => prop_all | prop_list;
+}
+
+sub new {
+ my $class = shift;
+ my $this = $class->Test::Harness::Straps::new();
+
+ $this->callCTOR(@_);
+
+ return $this;
+}
+
+sub surrogate {
+ my $class = shift;
+ return $class->Test::Harness::Straps::new();
+}
+
+sub analyze_file {
+ my($self, $file) = @_;
+
+ unless( -e $file ) {
+ $self->{error} = "$file does not exist";
+ return;
+ }
+
+ unless( -r $file ) {
+ $self->{error} = "$file is not readable";
+ return;
+ }
+
+ # *sigh* this breaks under taint, but open -| is unportable.
+ my $h = $self->ExecuteFile($file);
+ unless ($h) {
+ print "can't run $file. $!\n";
+ return;
+ }
+
+ my $results = $self->analyze_fh($file, $h);
+ my $exit = close $h;
+
+ $results->set_wait($?);
+ if ( $? && $self->{_is_vms} ) {
+ $results->set_exit($?);
+ }
+ else {
+ $results->set_exit( Test::Harness::Straps::_wait2exit($?) );
+ }
+ $results->set_passing(0) unless $? == 0;
+
+ $self->_restore_PERL5LIB();
+
+ return $results;
+}
+
+sub SelectExecutor {
+ my ($this,$file) = @_;
+
+ return $_->{Executor} foreach grep $file =~ /$_->{Re}/i, $this->Executors;
+}
+
+sub ExecuteFile {
+ my ($this,$file) = @_;
+
+ if (my $executor = $this->SelectExecutor($file)) {
+ return $executor->Execute($file);
+ }
+ return undef;
+}
+
+sub Execute {
+ my ($self,$file) = @_;
+
+ local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
+
+ open my $h,'-|',$self->_command_line($file) or return undef;
+
+ return $h;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/Straps/ShellExecutor.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/Straps/ShellExecutor.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,32 @@
+package IMPL::Test::Straps::ShellExecutor;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object IMPL::Object::Serializable);
+
+if ($^O =~ /win32/i) {
+ require Win32::Console;
+}
+
+sub Execute {
+ my ($this,$file) = @_;
+
+ my $h;
+
+ if ($^O =~ /win32/i) {
+ Win32::Console::OutputCP(65001);
+ unless ( open $h,'-|',$file ) {
+ return undef;
+ }
+ binmode $h,':encoding(utf-8)';
+ } else {
+ unless ( open $h,'-|',$file ) {
+ return undef;
+ }
+ }
+
+ return $h;
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/TAPListener.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/TAPListener.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,70 @@
+package IMPL::Test::TAPListener;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object IMPL::Object::Serializable);
+use IMPL::Class::Property;
+use IMPL::Test::Result;
+
+BEGIN {
+ private property _Output => prop_all;
+ private property _testNo => prop_all;
+}
+
+sub CTOR {
+ my ($this,$out) = @_;
+
+ $this->_Output($out || *STDOUT);
+ $this->_testNo(1);
+}
+
+sub RunPlan {
+ my ($this,$plan) = @_;
+
+ my $out = $this->_Output;
+
+ print $out "1..",$plan->Count,"\n";
+}
+
+sub EndPlan {
+
+}
+
+sub RunUnit {
+ my ($this,$unit) = @_;
+
+ my $out = $this->_Output;
+
+ print $out "#\n",join("\n",map "# $_", split /\n/, "Running unit: " . $unit->UnitName, ),"\n#\n";
+}
+
+sub EndUnit {
+
+}
+
+sub RunTest {
+
+}
+
+sub EndTest {
+ my ($this,$test,$result) = @_;
+
+ my $out = $this->_Output;
+ my $n = $this->_testNo;
+
+ $this->_testNo($n+1);
+
+ print $out (
+ $result->State == IMPL::Test::Result::SUCCESS ?
+ "ok $n " . join("\n# ", split(/\n/, $result->Name) )
+ :
+ "not ok $n " . (eval { $result->Exception->isa('IMPL::Test::SkipException') } ? '# SKIP ' : '') . join("\n# ", split(/\n/, $result->Name."\n".$result->Exception || '') )
+ ),"\n";
+
+}
+
+sub save {
+
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Test/Unit.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Test/Unit.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,143 @@
+package IMPL::Test::Unit;
+use strict;
+use warnings;
+
+use base qw(IMPL::Object);
+use IMPL::Class::Property;
+
+use Time::HiRes qw(gettimeofday tv_interval);
+
+use Error qw(:try);
+use IMPL::Test::Result;
+use IMPL::Test::FailException;
+use IMPL::Exception;
+
+BEGIN {
+ public property Name => prop_all;
+ public property Code => prop_all;
+}
+
+sub CTOR {
+ my ($this,$info) = @_;
+
+ die new IMPL::InvalidArgumentException("TestInfo should be supplied as an argument") unless $info;
+
+ $this->Name($info->Name || 'Annon');
+ $this->Code($info->Code)or die new IMPL::InvalidOperationException("Can't create test without entry point");
+}
+
+sub UnitName {
+ my ($self) = @_;
+ $self->toString;
+}
+
+sub Setup {
+ 1;
+}
+
+sub Cleanup {
+ 1;
+}
+
+sub StartUnit {
+ my $class = shift;
+
+ return {};
+}
+
+sub InitTest {
+ my ($this,$session) = @_;
+
+ $this->$_($session->{$_}) foreach map $_->DataList, $this->get_meta('IMPL::Test::Unit::SharedData');
+}
+
+sub FinishUnit {
+ my ($class,$session) = @_;
+
+ 1;
+}
+
+sub List {
+ my $self = shift;
+
+ return $self->get_meta('IMPL::Test::Unit::TestInfo',undef,1); # deep search with no criteria
+}
+
+sub Run {
+ my ($this,$session) = @_;
+
+ my $t = [gettimeofday];
+ return try {
+ $this->InitTest($session);
+ $this->Setup;
+ my $code = $this->Code;
+
+
+ my $t0 = [gettimeofday];
+ my $elapsed;
+
+ try {
+ $this->$code();
+ $elapsed = tv_interval ( $t0 );
+ } finally {
+ # we need to call Cleanup anyway
+ $this->Cleanup;
+ };
+
+ return new IMPL::Test::Result(
+ Name => $this->Name,
+ State => IMPL::Test::Result::SUCCESS,
+ TimeExclusive => $elapsed,
+ TimeInclusive => tv_interval ( $t )
+ );
+ } catch IMPL::Test::FailException with {
+ my $e = shift;
+ return new IMPL::Test::Result(
+ Name => $this->Name,
+ State => IMPL::Test::Result::FAIL,
+ Exception => $e,
+ TimeInclusive => tv_interval ( $t )
+ );
+ } otherwise {
+ my $e = shift;
+ return new IMPL::Test::Result(
+ Name => $this->Name,
+ State => IMPL::Test::Result::ERROR,
+ Exception => $e,
+ TimeInclusive => tv_interval ( $t )
+ );
+ }
+}
+
+package IMPL::Test::Unit::TestInfo;
+use base qw(IMPL::Object::Meta);
+use IMPL::Class::Property;
+
+require IMPL::Exception;
+
+BEGIN {
+ public property Name => prop_all;
+ public property Code => prop_all;
+}
+
+sub CTOR {
+ my ($this,$name,$code) = @_;
+
+ $this->Name($name);
+ $this->Code($code) or die new IMPL::InvalidArgumentException("The Code is a required parameter");
+}
+
+package IMPL::Test::Unit::SharedData;
+use base qw(IMPL::Object::Meta);
+use IMPL::Class::Property;
+
+BEGIN {
+ public property DataList => prop_all | prop_list;
+}
+
+sub CTOR {
+ my $this = shift;
+
+ $this->DataList(\@_);
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Transform.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Transform.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,65 @@
+package IMPL::Transform;
+use base qw(IMPL::Object IMPL::Object::Autofill);
+
+use IMPL::Class::Property;
+use IMPL::Class::Property::Direct;
+
+BEGIN {
+ protected _direct property Templates => prop_all;
+ protected _direct property Default => prop_all;
+ protected _direct property Plain => prop_all;
+}
+
+__PACKAGE__->PassThroughArgs;
+
+sub Transform {
+ my ($this,$object) = @_;
+
+ if (not ref $object) {
+ die new IMPL::Exception("There is no the template for a plain value in the transform") unless $this->{$Plain};
+ my $template = $this->{$Plain};
+ return $this->$template($object);
+ } else {
+
+ my $template = $this->MatchTemplate($object) || $this->Default or die new IMPL::Transform::NoTransformException(ref $object);
+
+ return $this->$template($object);
+ }
+}
+
+sub MatchTemplate {
+ my ($this,$object) = @_;
+ my $class = ref $object;
+
+ foreach my $tClass ( keys %{$this->Templates || {}} ) {
+ return $this->Templates->{$tClass} if ($tClass eq $class);
+ }
+}
+
+package IMPL::Transform::NoTransformException;
+use base qw(IMPL::Exception);
+
+1;
+
+__END__
+
+=pod
+=head1 SYNOPSIS
+
+my $obj = new AnyObject;
+
+my $t = new Transform (
+ AnyClass => sub {
+ my ($this,$object) = @_;
+ return new NewClass({ Name => $object->name, Document => $this->Transform($object->Data) })
+ },
+ DocClass => sub {
+ my ($this,$object) = @_;
+ return new DocPreview(Author => $object->Author, Text => $object->Data);
+ }
+);
+
+=head1 Summary
+Ïðåîáðàçóåò äàííûå ñîäåðæàùèåñÿ â ôîðìå â ðåàëüíûå îáúåêòû èñïîëüçóÿ ñïåöèàëüíîå ïðåîáðàçîâàíèå.
+
+=cut
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/IMPL/Tree/Batch.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/Tree/Batch.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,3 @@
+package IMPL::Tree::Batch;
+use strict;
+use base qw(IMP::Object);
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Mailer.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Mailer.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,69 @@
+package Mailer;
+use strict;
+
+use Encode qw (encode);
+use Encode::MIME::Header;
+use MIME::Base64 qw(encode_base64);
+use Email::Simple;
+
+our $SENDMAIL;
+
+sub DeliverMessage {
+ my $message = shift;
+
+ $message = shift if $message eq __PACKAGE__ or ref $message eq __PACKAGE__;
+
+ my $email = new Email::Simple($message);
+
+ $email->header_set('Content-Transfer-Encoding' => 'base64');
+ $email->header_set('MIME-Version' => '1.0') if !$email->header('MIME-Version');
+ $email->header_set('Content-Type' => 'text/plain; charset="utf-8"');
+ my $raw = $email->body();
+ utf8::encode($raw) if utf8::is_utf8($raw);
+ $email->body_set(encode_base64($raw));
+
+ foreach my $field ($email->header_names()) {
+ $email->header_set($field, map { encode('MIME-Header', utf8::is_utf8($_) ? $_ : Encode::decode('utf-8',$_) ) } $email->header($field) );
+ }
+
+ return SendMail($email,@_);
+}
+
+sub _find_sendmail {
+ return $SENDMAIL if defined $SENDMAIL;
+
+ my @path = split /:/, $ENV{PATH};
+ my $sendmail;
+ for (@path) {
+ if ( -x "$_/sendmail" ) {
+ $sendmail = "$_/sendmail";
+ last;
+ }
+ }
+ return $sendmail;
+}
+
+sub SendMail {
+ my ($message, %args) = @_;
+ my $mailer = _find_sendmail;
+
+ local *SENDMAIL;
+ if( $args{'TestFile'} ) {
+ open SENDMAIL, '>', $args{TestFile} or die "Failed to open $args{TestFile}: $!";
+ binmode(SENDMAIL);
+ print SENDMAIL "X-SendMail-Cmd: sendmail ",join(' ',%args),"\n";
+ } else {
+ my @args = %args;
+ die "sendmail not found" unless $mailer;
+ die "Found $mailer but cannot execute it"
+ unless -x $mailer;
+ open SENDMAIL, "| $mailer -t -oi @args"
+ or die "Error executing $mailer: $!";
+ }
+ print SENDMAIL $message->as_string
+ or die "Error printing via pipe to $mailer: $!";
+ close SENDMAIL;
+ return 1;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/ObjectStore/CDBI/Users.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/ObjectStore/CDBI/Users.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,100 @@
+#!/usr/bin/perl -w
+use strict;
+
+package ObjectStore::CDBI::Users;
+use Common;
+use Digest::MD5 qw(md5_hex);
+our @ISA = qw(Object);
+
+our $Namespace;
+our $DataModule;
+
+our $Prefix = $Namespace ? $Namespace.'::' : '';
+
+if ($DataModule) {
+ $DataModule =~ s/::/\//g;
+ $DataModule .= '.pm';
+ require $DataModule;
+}
+
+BEGIN {
+ DeclareProperty DSNamespace => ACCESS_NONE;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$DSNamespace} = $args{'DSNamespace'};
+}
+
+sub ClassName {
+ return $_[0]->{$DSNamespace} ? $_[0]->{$DSNamespace}. $_[1] : $_[1];
+}
+
+sub FindUser {
+ my ($this,$uname) = @_;
+
+ my @Users = $this->ClassName('Principal')->search(Name => $uname);
+ return shift @Users;
+}
+
+sub CreateUser {
+ my ($this,$uname,$description,$active) = @_;
+
+ if (my $user = $this->FindUser($uname)) {
+ die new Exception("The user is already exists",$uname);
+ } else {
+ return $this->ClassName('Principal')->insert({Name => $uname, Description => $description, Active => $active});
+ }
+}
+
+sub DeleteUser {
+ my ($this,$objUser) = @_;
+
+ $objUser->delete;
+}
+
+sub GetUserAuthData {
+ my ($this,$objUser,$objSecPackage) = @_;
+
+ my @Data = $this->ClassName('AuthData')->search(User => $objUser,Package => $objSecPackage->Name);
+ return $Data[0];
+}
+
+sub SetUserAuthData {
+ my ($this,$objUser,$objSecPackage,$objAuthData) = @_;
+
+ if (my $AuthData = $this->GetUserAuthData($objUser,$objSecPackage)) {
+ $AuthData->AuthData(objAuthData->SessionAuthData);
+ $AuthData->update;
+ } else {
+ $this->ClassName('AuthData')->insert({ User => $objUser, Package => $objSecPackage->Name, AuthData => $objAuthData->SessionAuthData});
+ }
+}
+
+sub CreateSession {
+ my ($this,$SSID,$objUser,$objAuthData) = @_;
+
+ my $session = $this->ClassName('Session')->insert({SSID => $SSID, User => $objUser, SecData => $objAuthData->SessionAuthData, LastUsage => DateTime->now() });
+ $session->autoupdate(1);
+ return $session;
+}
+
+sub CloseSession {
+ my ($this,$objSession) = @_;
+
+ $objSession->delete;
+}
+
+sub LoadSession {
+ my ($this,$SSID) = @_;
+ my @Data = $this->ClassName('Session')->search(SSID => $SSID);
+ if ($Data[0]) {
+ $Data[0]->autoupdate(1);
+ return $Data[0];
+ }
+}
+
+sub construct {
+ return __PACKAGE__->new(DSNamespace => $Prefix);
+}
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/PerfCounter.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/PerfCounter.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,161 @@
+package PerfCounter;
+use strict;
+use Common;
+use Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT = qw(&GetTimeCounter &StartTimeCounter &StopTimeCounter &SetDBIPerfCounter);
+
+our %Counters;
+
+sub Reset() {
+ $_->Reset foreach values %Counters;
+}
+
+sub GetTimeCounter {
+ my $counter = $Counters{$_[0]};
+ die new Exception("'$_[0]' already exists and isn't a time counter.") if ref $counter and ref $counter ne 'PerfInterval';
+ if (not ref $counter) {
+ $counter = new PerfInterval;
+ $Counters{$_[0]} = $counter;
+ }
+ return $counter;
+}
+
+sub StartTimeCounter {
+ my $counter = GetTimeCounter($_[0]);
+ if (not $counter->IsOpened) {
+ $counter->OpenInterval;
+ }
+}
+
+sub StopTimeCounter {
+ my $counter = GetTimeCounter($_[0]);
+ if ($counter->IsOpened) {
+ $counter->CloseInterval;
+ }
+}
+
+sub SetDBIPerfCounter{
+ my ($dbh,$name) = @_;
+ $name ||= 'DBI';
+ $Counters{$name} = DBIPerfomance->new(DBH => $dbh);
+}
+
+package PerfInterval;
+use Common;
+use Time::HiRes qw(gettimeofday tv_interval);
+
+sub new {
+ my $class = shift;
+ my $self = bless { StartTime => scalar(gettimeofday()) }, $class;
+ return $self;
+}
+
+sub CloseInterval {
+ my $this = shift;
+
+ if (not $this->{'EndTime'}) {
+ $this->{'EndTime'} = scalar(gettimeofday());
+ $this->{'Value'} += $this->{'EndTime'} - $this->{'StartTime'};
+ }
+
+ return $this->{'Value'};
+}
+
+sub Value {
+ my $this = shift;
+
+ if (not $this->{'EndTime'}) {
+ return sprintf ( '%.3f+',scalar(gettimeofday()) - $this->{'StartTime'});
+ } else {
+ return sprintf ( '%.3f',$this->{'Value'});
+ }
+}
+
+sub Add {
+ my ($this,$interval) = @_;
+
+ if(ref $interval eq 'PerfInterval') {
+ $this->{'Value'} += $interval->{'Value'};
+ } else {
+ $this->{'Value'} += $interval;
+ }
+
+ return $this->{'Value'};
+}
+
+sub IsOpened {
+ my $this = shift;
+ return( not $this->{'EndTime'} );
+}
+
+sub OpenInterval {
+ my $this = shift;
+
+ $this->{'StartTime'} = gettimeofday();
+ delete $this->{'EndTime'};
+
+ return 1;
+}
+
+sub Reset {
+ my ($this) = @_;
+
+ $this->CloseInterval();
+ $this->{'Value'} = 0;
+}
+
+package DBIPerfomance;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty DBH => ACCESS_READ;
+
+}
+
+sub CTOR {
+ my $this=shift;
+ $this->SUPER::CTOR(@_);
+
+
+ $this->DBH->{Profile} = 6;
+}
+
+sub Reset {
+ my $this = shift;
+ $this->DBH->{Profile} = 6;
+}
+
+sub Value {
+ my ($this,%opt) = @_;
+
+ my $infoSelect = { count => 0, time => 0};
+ my $infoUpdate = { count => 0, time => 0};
+ my $infoTotal;
+
+ foreach my $stmt (grep /^SELECT/i,keys %{$this->DBH->{Profile}->{Data} || {}}) {
+ $infoSelect->{'count'} += $this->DBH->{Profile}{Data}{$stmt}{execute}[0] || 0;
+ $infoSelect->{'time'} += $this->DBH->{Profile}{Data}{$stmt}{execute}[1] || 0;
+ }
+
+ foreach my $stmt (grep /^UPDATE/i,keys %{$this->DBH->{Profile}->{Data} || {}}) {
+ $infoUpdate->{'count'} += $this->DBH->{Profile}{Data}{$stmt}{execute}[0] || 0;
+ $infoUpdate->{'time'} += $this->DBH->{Profile}{Data}{$stmt}{execute}[1] || 0;
+ }
+
+ $infoTotal->{'count'} = $infoSelect->{'count'} + $infoUpdate->{'count'};
+ $infoTotal->{'time'} = $infoSelect->{'time'} + $infoUpdate->{'time'};
+
+ if ($opt{'extended'}) {
+ return ($infoSelect,$infoUpdate,$infoTotal);
+ } else {
+ return sprintf( '%i (%.2f)', $infoTotal->{count},$infoTotal->{time} );
+ }
+}
+
+sub Queries {
+ my ($this) = @_;
+ return [ map { "$this->{$DBH}{Profile}{Data}{$_}{execute}[0] x $_"} sort grep $_, keys %{$this->DBH->{Profile}->{Data}}];
+}
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,748 @@
+package Schema;
+package Schema::TypeName;
+package Schema::Type;
+package Schema::Template;
+package Schema::TemplateSpec;
+package Schema::Member;
+package Schema::Property;
+
+package Schema::TypeName;
+use Common;
+
+#our @ISA = qw(Object);
+
+# ìîæíî îïòèìèçèðîâàòü ïðîèçâîäèòåëüíîñòü, ñîçäàâàÿ îáúåêò ñêàëàÿð äëÿ ïðîñòûõ
+# èìåí è îáõåêò õåø äëÿ ñïåöèàëèçàöèé
+# ñäåëàíî
+
+sub new {
+ my $class = shift;
+ my $this;
+
+ my $name = shift;
+ my @list = map { ref $_ eq 'Schema::TypeName' ? $_ : new Schema::TypeName($_) } @_;
+
+ die new Exception('TypeName soud be a simple identifier',$name) if not $name =~ /^\w+$/;
+
+ if (@list) {
+ $this = bless {}, $class;
+ $this->{Name} = $name;
+ $this->{TemplateList} = \@list if @list;
+ } else {
+ $this = bless \$name, $class;
+ }
+
+ return $this;
+}
+
+sub Name {
+ my $this = shift;
+ return (UNIVERSAL::isa($this,'HASH') ? $this->{Name} : $$this);
+}
+
+sub Simple {
+ return $_[0]->Name;
+}
+
+# ñïèñîê ïàðàìåòðîâ òèïà
+sub TemplateList {
+ my $this = shift;
+ return (UNIVERSAL::isa($this,'HASH') ? (wantarray ? @{$this->{TemplateList}} : $this->{TemplateList} ) : (wantarray ? return () : undef));
+}
+
+# èìÿ òèïà ÿâëÿåòñÿ èìåíåì øàáëîíà
+sub isTemplateSpec {
+ my $this = shift;
+ return( UNIVERSAL::isa($this,'HASH') ? 1 : 0 );
+}
+
+sub CanonicalName {
+ my $this = shift;
+
+ if (UNIVERSAL::isa($this,'HASH')) {
+ if (my $result = $this->{SavedCanonicalName}) {
+ $result;
+ } else {
+ $result = $this->{Name};
+ $result .= '@'. join('#',map {ref $_ eq __PACKAGE__ ? $_->CanonicalName : $_} @{$this->{TemplateList}}) . '@@';
+ $this->{SavedCanonicalName} = $result;
+ }
+ } else {
+ $$this;
+ }
+}
+
+sub Canonical {
+ return $_[0]->CanonicalName;
+}
+
+# Íå ðåãèñòðèðóåò âíîâü ñîçäàííûõ òèïîâ â òàáëèöå
+# Ýòî èç-çà ñëó÷àÿ, êîãäà:
+# MyClass { Hash my_map; }, òîåñòü ïîëó÷åííûé òèï Hahs óæå ñïåöèàëèçèðîâàí è îí áóäåò ñðàçó èíñòàíòèíîðîâàí
+# DoNotCreate äëÿ ñïåöèàëèçàöèè øàáëîíà òîëüêî ñóùåñòâóþùèìè òèïàìè
+sub Resolve {
+ my ($this,$TypeTable,$DoNotCreate) = @_;
+
+ if (my $type = $TypeTable->ResolveType($this,$DoNotCreate)) {
+ # ïðåäïîëàãàåòñÿ, ÷òî ñõåìà àâòîìàòè÷åñêè ñîçäàåò ññûëêè âïåðåä íà íåîïðåäåëåííûå ïðîñòûå òèïû
+ return $type;
+ } else {
+ if ($this->isTemplateSpec) {
+ return new Schema::TemplateSpec($this->Name,map {ref $_ eq __PACKAGE__ ? $_->Resolve($TypeTable,$DoNotCreate) : Schema::TypeName->new($_)->Resolve($TypeTable,$DoNotCreate)} @{$this->{TemplateList}} );
+ } else {
+ die new Exception("Simple type not found", $this->Name);
+ }
+ }
+}
+
+package Schema::TypeTable;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty(Table => ACCESS_NONE);
+ DeclareProperty(NextTable => ACCESS_NONE);
+}
+
+sub CTOR {
+ my ($this,$nextTable) = @_;
+ $this->{$NextTable} = $nextTable;
+}
+
+sub ResolveType {
+ my ($this,$TypeName,@args) = @_;
+
+ if (my $Type = $this->{$Table}->{$TypeName->CanonicalName}) {
+ return $Type;
+ } elsif($this->{$NextTable}) {
+ return $this->{$NextTable}->ResolveType($TypeName,@args);
+ } else {
+ return undef;
+ }
+}
+
+sub RegisterType {
+ my ($this,$Type) = @_;
+
+ if (not $this->{$Table}->{$Type->Name->CanonicalName}) {
+ $this->{$Table}->{$Type->Name->CanonicalName} = $Type;
+ } else {
+ die new Exception("A type already registered",$Type->Name->CanonicalName);
+ }
+}
+
+sub _ListTypes {
+ my $this = shift;
+ return values %{$this->{$Table}};
+}
+
+sub Dispose {
+ my $this = shift;
+
+ $_->Dispose foreach values %{$this->{$Table} ? $this->{$Table} : {} };
+
+ delete $this->{$Table};
+
+ $this->SUPER::Dispose;
+}
+
+# Ñïåöèàëèçàöèÿ øàáëîíà - ýòî èìÿ ñïåöèàëèçèðóåìîãî øàáëîíà è ïàðàìåòðû, êîòîðûå áóäóò åìó ïåðåäàíû (âàæåí ïîðÿäîê ïàðàìåòðîâ)
+# Ñïåöèàëèçàöèÿ øàáëîíà ïàðàìåòðàìè ïîðàæäàåò ÷àñòè÷íî ñïåöèàëèçèðîâàííûé øàáëîí, êîòîðûé ïî ñóòè òàêæå ÿâëÿåòñÿ øàáëîíîì
+# Åñëè ñïåöèàëèçàöèÿ ïîëíàÿ, òî ìîæíî ñîçäàòü ýêçåìïëÿð øàáëîíà, òîåñòü ïîëíîöåííûé òèï
+package Schema::TemplateSpec;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty(Name => ACCESS_READ);
+ DeclareProperty(Parameters => ACCESS_READ);
+ DeclareProperty(TemplateList => ACCESS_READ);
+}
+
+sub CTOR {
+ my ($this,$templateName,@typeList) = @_;
+
+ my %Params;
+
+ $this->{$TemplateList} = \@typeList;
+
+ # âû÷èñëÿåì ïàðàìåòðû äàííîé ñïåöèàëèçàöèè
+ my @nameList;
+ foreach $typeItem (@typeList) {
+ map { $Params{$_->Name} = $_ } @{$typeItem->Parameters} if $typeItem->isTemplate;
+ push @nameList, $typeItem->Name;
+ }
+
+ $this->{$Parameters} = [ values %Params ];
+ $this->{$Name} = new Schema::TypeName($templateName,@nameList);
+}
+
+sub isTemplate {
+ 1;
+}
+
+sub canInstantinate {
+ my ($this) = @_;
+ if (@{$this->{$Parameters}}) {
+ 0;
+ } else {
+ 1;
+ }
+}
+
+sub Specialize {
+ my ($this,$refParams,$TypeTable) = @_;
+
+ my @specializedList = map {$_->isTemplate && !$_->canInstantinate ? $_->Specialize($refParams,$TypeTable) : $_ } @{$this->{$TemplateList}};
+
+ if ($TypeTable) {
+
+ my $TypeName = new Schema::TypeName($this->Name->Name,map {$_->Name} @specializedList);
+ my $templateSpec = $TypeTable->ResolveType($TypeName);
+ if (not $templateSpec) {
+ $templateSpec = new Schema::TemplateSpec($this->Name->Name,@specializedList);
+ $TypeTable->RegisterType($templateSpec);
+ }
+ return $templateSpec;
+ } else {
+ return new Schema::TemplateSpec($this->Name->Name,@specializedList);
+ }
+}
+
+# Ïàðàìåòð øàáëîíà
+# Ïî ñóòè ÿâëÿåòñÿ øàáëîíîì òèïà Param_Name -> T;
+package Schema::Parameter;
+
+sub new {
+ my $TypeName = new Schema::TypeName($_[1]);
+ bless \$TypeName,$_[0];
+}
+
+sub Name {
+ ${shift()};
+}
+
+sub Specialize {
+ my ($this,$refArgs) = @_;
+ return $refArgs->{$$this->Name};
+}
+
+sub isTemplate {
+ 1;
+}
+
+sub canInstantinate {
+ 0;
+}
+
+sub Parameters {
+ if (wantarray) {
+ shift;
+ } else {
+ [shift];
+ }
+}
+
+
+# ×ëåí êëàññà
+package Schema::Member;
+use Common;
+our @ISA = qw(Object);
+our $Abstract = 1;
+
+BEGIN {
+ DeclareProperty(Name => ACCESS_READ);
+}
+sub CTOR {
+ my($this,$name) = @_;
+
+ $this->{$Name} = $name;
+}
+
+# ×ëåí êëàññà - ñâîéñòâî.
+# Ñâîéñòâî ìîæåò áûòü øàáëîíîì, åñëè øàáëîíîì ÿâëÿåòñÿ åãî òèï
+package Schema::Property;
+use Common;
+our @ISA = qw(Schema::Member);
+
+BEGIN {
+ DeclareProperty(Type => ACCESS_READ);
+}
+
+sub CTOR {
+ my ($this,$name,$type) = @_;
+ $this->SUPER::CTOR($name);
+
+ $this->{$Type} = $type or die new Exception("A type for the property must be specified",$name);
+}
+
+sub isTemplate {
+ my $this = shift;
+ return $this->{$Type}->isTemplate;
+}
+
+sub canInstantinate {
+ my $this = shift;
+ return $this->{$Type}->canInstantinate;
+}
+
+sub Instantinate {
+ my ($this,$Schema) = @_;
+ return new Schema::Property($this->Name,$Schema->Instantinate($this->{$Type}));
+}
+
+sub Specialize {
+ my ($this,$refParams,$TypeTable) = @_;
+ return new Schema::Property($this->Name,$this->{$Type}->Specialize($refParams,$TypeTable));
+}
+
+# Òèï, îïèñûâàåò òèï îáúåêòà
+package Schema::Type;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty(Name => ACCESS_READ);
+ DeclareProperty(Schema => ACCESS_READ);
+ DeclareProperty(Members => ACCESS_READ);
+ DeclareProperty(BaseList => ACCESS_READ);
+ DeclareProperty(Attributes => ACCESS_READ); #hash of attributes
+}
+
+sub CTOR {
+ my ($this,$argSchema,$name) = @_;
+
+ $this->{$Name} = ref $name eq 'Schema::TypeName' ? $name : new Schema::TypeName($name);
+ $this->{$Schema} = $argSchema;
+}
+
+sub isTemplate {
+ 0;
+}
+
+sub Equals {
+ my ($this,$other) = @_;
+ if (UNIVERSAL::isa($other,'Schema::Type')) {
+ return ($this->Name->CanonicalName eq $other->Name->CanonicalName);
+ } else {
+ return 1;
+ }
+}
+
+sub CreateProperty {
+ my ($this,$PropName,$TypeName) = @_;
+
+ $PropType = $this->_ResolveType($TypeName);
+
+ return new Schema::Property($PropName,$PropType);
+}
+
+sub AddBase {
+ my ($this,$type) = @_;
+
+ $type = $this->_ResolveType($type);
+
+ not $type->isType($this) or die new Exception('Cant derive from the class which is derived from self', $this->Name->CanonicalName, $type->Name->CanonicalName);
+
+ push @{$this->{$BaseList}},$type;
+}
+
+sub isType {
+ my ($this,$type,$maxlevel) = @_;
+
+ return 0 if defined $maxlevel and $maxlevel < 0;
+ my $typeName = UNIVERSAL::isa($type,'Schema::Type') ? $type->Name : $type ;
+
+ return (
+ $this->{$Name}->CanonicalName eq $typeName->CanonicalName ?
+ 1
+ :
+ scalar (grep {$_->isType($typeName,defined $maxlevel ? $maxlevel - 1 : undef)} $this->BaseList)
+ );
+}
+
+sub ValidateType {
+ my ($this,$type) = @_;
+
+ die new Exception('Can\'t use an unspecialized template',$type->Name->CanonicalName) if ($type->isa('Schema::TypeTemplate'));
+
+ if ($type->isTemplate and not $type->canInstantinate) {
+ die new Exception('Cant use a not fully specialized template in a simple type',$type->Name->CanonicalName, $this->Name->Name) if not $this->isTemplate;
+
+ my %Params = map {$_->Name->Name() , 1} @{$this->Parameters};
+ my @Unresolved = grep {not $Params{$_->Name->Name}} @{$type->Parameters()};
+
+ die new Exception('Not all parameters can be rsolved',map {$_->Name->Name} @Unresolved) if @Unresolved;
+ }
+}
+
+sub InsertProperty {
+ my ($this,$PropName,$PropType) = @_;
+
+ $PropType = $this->_ResolveType($PropType);
+
+ my $prop = new Schema::Property($PropName,$PropType);
+
+ push @{$this->{$Members}}, $prop;
+
+ return $prop;
+}
+
+sub AddMember {
+ my ($this,$member) = @_;
+
+ push @{$this->{$Members}},$member;
+}
+
+sub GetTypeTable {
+ my $this = shift;
+ return $this->{$Schema};
+}
+
+sub _ResolveType {
+ my ($this,$type) = @_;
+ if ($type->isa('Schema::TypeName')) {
+ $type = $type->Resolve($this->GetTypeTable());
+ } elsif ($type->isa('Schema::Type') or $type->isa('Schema::TemplateSpec')) {
+ $this->ValidateType($type);
+ } else {
+ die new Exception('Invalid type',$type);
+ }
+
+ $type = $this->{$Schema}->Instantinate($type) if ($type->isTemplate and $type->canInstantinate and not $this->isTemplate);
+ return $type;
+}
+
+sub ListMembers {
+ my ($this,%options) = @_;
+
+ my @members;
+
+ if ($options{'foreign'}) {
+ push @members, $_->isa('Schema::Type') ? $_->ListMembers(%options) : () foreach @{$this->{$BaseList} ? $this->{$BaseList} : []};
+ }
+ push @members, @{$this->{$Members} ? $this->{$Members} : []};
+
+ return @members;
+}
+
+sub FindMembers {
+ my ($this,$memberName,%options) = @_;
+
+ my @members = grep { $_->Name eq $memberName} @{$this->{$Members} ? $this->{$Members} : []};
+
+ if ($options{'deep'}) {
+ push @members,$_->ListMembers(%options) foreach @{$this->{$BaseList} ? $this->{$BaseList} : []};
+ }
+
+ if(wantarray) {
+ return @members;
+ } else {
+ return shift @members;
+ }
+}
+
+sub SetAttributes {
+ my ($this,%attributes) = @_;
+
+ while (my ($key,$value) = each %attributes) {
+ $this->{$Attributes}{$key} = $value;
+ }
+}
+
+sub GetAttribute {
+ my ($this,$name) = @_;
+
+ return $this->{$Attributes}{$name};
+}
+
+sub _dump {
+ my ($this) = @_;
+ return $this->Name->CanonicalName;
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ undef %{$this};
+ $this->SUPER::Dispose;
+}
+
+# Øàáëîí - ïðàìåòðèçîâàííûé òèï
+package Schema::Template;
+use Common;
+our @ISA = qw(Schema::Type);
+
+BEGIN {
+ DeclareProperty(Parameters => ACCESS_READ);
+ DeclareProperty(LocalTypes => ACCESS_NONE);
+
+}
+
+sub CTOR {
+ my ($this,$Schema,$name,@args) = @_;
+ # ïàðàìåòðû íå ÿâëÿþòñÿ ÷à÷òüþ èìåíè
+ $this->SUPER::CTOR($Schema,$name);
+
+ $this->{$Parameters} = [ map {new Schema::Parameter($_) } @args ];
+ my $TypeTable = new Schema::TypeTable($Schema);
+ $TypeTable->RegisterType($_) foreach @{$this->{$Parameters} };
+ $this->{$LocalTypes} = $TypeTable;
+}
+
+sub GetTypeTable {
+ my ($this) = @_;
+ return $this->{$LocalTypes};
+}
+
+sub isTemplate {
+ 1;
+}
+
+sub Specialize {
+ my ($this,$refArgs,$TypeTable) = @_;
+
+ my @specializedList = map {$_->Specialize($refArgs)} @{$this->{$Parameters}};
+
+ # ñîçäàåì ñïåöèàëèçàöèþ øàáëîíà
+ my $specializedType;
+
+ if ($TypeTable) {
+ my $TypeName = new Schema::TypeName($this->Name->Name,map {$_->Name} @specializedList);
+
+ if(my $specializedType = $TypeTable->ResolveType($TypeName)) {
+ return $specializedType;
+ } else {
+ $specializedType = new Schema::TemplateSpec($this->Name->Name, @specializedList );
+ $TypeTable->RegisterType($specializedType);
+ return $specializedType;
+ }
+ } else {
+ return new Schema::TemplateSpec($this->Name->Name, @specializedList );
+ }
+}
+
+sub canInstantinate {
+ 0;
+}
+
+# ñîçäàíèå ýêçåìïëÿðà øàáëîíà.
+# Ñîçäàòü øàáëîí = ïîëíîñòüþ åãî ñïåöèàëèçèðîâàòü
+# Ïðèíèìàåò íàáîð ïàðàìåòðîâ øàáëîíà è ñîçäàåò íîâûé òèï èëè âîçâðàùàåò èç ñõåìû
+sub Instantinate {
+ my ($this,$refArgs,$instance) = @_;
+
+ my %ParamInstances;
+ my @TemplateListNames;
+
+ foreach my $param (@{$this->{$Parameters}}) {
+ my $type = $refArgs->{$param->Name->Name};
+ die new Exception("Parameter not specified",$param->Name->Name) if not $type;
+ if ($type->isTemplate) {
+ if ($type->canInstantinate) {
+ $type = $this->Schema->Instantinate($type);
+ } else {
+ die new Exception("Parameter must be a fully speciazlied type",$param->Name->Name);
+ }
+ }
+
+ $ParamInstances{$param->Name->Name} = $type;
+ push @TemplateListNames, $type->Name;
+ }
+
+ # ïàðàìåòðû ïðåäñòàâëÿþò ñîáîé ðåàëüíûå òèïû, ïåðåõîäèì ê ñîçäàíèþ òèïà
+ # äàííàÿ ôóíêöèÿ áåóñëîâíî ñîçäàåò íîâûé òèï, ýòó ôóíêöèþ èñïîëüçóåò ñõåì
+
+ $instance = $this->Schema->CreateType( new Schema::TypeName($this->Name->Name,@TemplateListNames) ) if not $instance;
+
+ $instance->SetAttributes(%{$this->Attributes}) if $this->Attributes;
+ $instance->SetAttributes(
+ TemplateInstance => {
+ Template => $this,
+ Parameters => \%ParamInstances
+ }
+ );
+
+ foreach my $Ancestor ($this->BaseList) {
+ $instance->AddBase(
+ $Ancestor->isTemplate ?
+ ( $Ancestor->canInstantinate ?
+ $this->Schema->Instantinate($Ancestor)
+ :
+ $this->Schema->Instantinate($Ancestor->Specialize(\%ParamInstances,$this->GetTypeTable))
+ )
+ :
+ $Ancestor
+ );
+ }
+
+ foreach my $Member ($this->Members) {
+ $instance->AddMember(
+ $Member->isTemplate ?
+ ($Member->canInstantinate ?
+ $Member->Instantinate($this->Schema)
+ :
+ $Member->Specialize(\%ParamInstances,$this->GetTypeTable)->Instantinate($this->Schema)
+ )
+ :
+ $Member
+ );
+ }
+
+ return $instance;
+}
+
+sub _ResolveType {
+ my ($this,$type) = @_;
+ if ($type->isa('Schema::TypeName')) {
+ $type = $type->Resolve($this->GetTypeTable());
+ if (not $this->{$LocalTypes}->ResolveType($type->Name)) {
+ $this->{$LocalTypes}->RegisterType($type);
+ }
+ } elsif ($type->isa('Schema::Type') or $type->isa('Schema::TemplateSpec')) {
+ $this->ValidateType($type);
+ } else {
+ die new Exception('Invalid type',$type);
+ }
+
+ return $type;
+}
+
+
+package Schema;
+use strict;
+use Common;
+our @ISA = qw(Schema::TypeTable);
+
+BEGIN {
+ DeclareProperty(PendingInstances => ACCESS_NONE);
+ DeclareProperty(UnresolvedTypes => ACCESS_NONE);
+}
+
+sub CTOR {
+
+}
+
+# Ñõåìà àâòîìàòè÷åñêè ñîçäàåò ññûëêè âïåðåä íà íåñóùåñòâóþùèå ïðîñòûå òèïû
+sub ResolveType {
+ my ($this,$TypeName,$DoNotCreate) = @_;
+
+ if (my $type = $this->SUPER::ResolveType($TypeName)) {
+ return $type;
+ } else {
+ if (not $TypeName->isTemplateSpec and not $DoNotCreate) {
+ $type = new Schema::Type($this,$TypeName);
+ $this->RegisterType($type);
+ $this->{$UnresolvedTypes}->{$TypeName->CanonicalName} = $TypeName;
+ return $type;
+ } else {
+ return undef;
+ }
+ }
+}
+
+sub CreateType {
+ my ($this,$TypeName) = @_;
+
+ $TypeName = new Schema::TypeName($TypeName) if ref $TypeName ne 'Schema::TypeName';
+
+ if (my $type = $this->SUPER::ResolveType($TypeName)) {
+ if ($this->{$UnresolvedTypes}->{$TypeName->CanonicalName}) {
+ delete $this->{$UnresolvedTypes}->{$TypeName->CanonicalName};
+ return $type;
+ } else {
+ die new Exception("Type already exists",$TypeName->CanonicalName);
+ }
+ } else {
+ $type = new Schema::Type($this,$TypeName);
+ $this->SUPER::RegisterType($type);
+ return $type;
+ }
+}
+
+sub CreateTemplate {
+ my ($this,$TemplateName,@ParamNames) = @_;
+
+ die new Exception("Parameters required for the template") if not @ParamNames;
+
+ if (ref $TemplateName eq 'Schema::TypeName') {
+ die new Exception('Template specialization is not valid name for a new template',$TemplateName->CanonicalName) if $TemplateName->isTemplateSpec;
+ } else {
+ $TemplateName = new Schema::TypeName($TemplateName);
+ }
+
+ if (my $type = $this->SUPER::ResolveType($TemplateName)) {
+ die new Exception('Type already exists');
+ } else {
+ $type = new Schema::Template($this,$TemplateName,@ParamNames);
+ $this->SUPER::RegisterType($type);
+ return $type;
+ }
+}
+
+# ñîçäàíèå ýêçåìïëÿðà øàáëîíà
+# ñîçäàåòñÿ íîâûé ïóñòîé òèï, äîáàâëÿåòñÿ â PendingInstances
+sub Instantinate {
+ my ($this,$TemplateSpec) = @_;
+
+ # ïðè ñïåöèàëèçàöèè íàïðìåð ýòîãî: T m_var; ïîëó÷èì äëÿ èíñòàíòèíèöèè real_type m_var; è íå ïðîâåðÿÿ îòäàäèì åãî íà ñïåöèàëèçàöèþ,
+ # âîò è îáðàáîòêà
+ return $TemplateSpec if not $TemplateSpec->isTemplate;
+
+ die new Exception('Only a template specialization can be instantinated') if ref $TemplateSpec ne 'Schema::TemplateSpec';
+ die new Exception('Only fully specialized template can be instantinated') if not $TemplateSpec->canInstantinate;
+
+ my $TypeName = $TemplateSpec->Name;
+
+ if (my $type = $this->SUPER::ResolveType($TypeName)) {
+ return $type;
+ } else {
+ $type = new Schema::Type($this,$TypeName);
+ $this->SUPER::RegisterType($type);
+ push @{$this->{$PendingInstances}},[$TemplateSpec,$type];
+ return $type;
+ }
+}
+
+sub Close {
+ my ($this) = @_;
+
+ if (keys %{$this->{$UnresolvedTypes}}) {
+ die new Exception('Some type definitions are absent',keys %{$this->{$UnresolvedTypes}});
+ }
+
+ if ($this->{$PendingInstances}) {
+ while( my $ref = shift @{$this->{$PendingInstances}} ) {
+ my ($spec,$instance) = @$ref;
+ if (my $typeTemplate = $this->SUPER::ResolveType( new Schema::TypeName($spec->Name->Name) )) {
+ die new Exception('Can\'t instantinate a specialization of the simple type',$instance->Name->CanonicalName) if not $typeTemplate->isTemplate;
+ if (scalar(@{$typeTemplate->Parameters}) == scalar(@{$spec->TemplateList})) {
+ my @Params = @{$typeTemplate->Parameters};
+ $typeTemplate->Instantinate({map { (shift @Params)->Name->Name, $_ } @{$spec->TemplateList}},$instance);
+ } else {
+ die new Exception('A template parameters doesn\'t match to the specialization list',$instance->Name->CanonicalName);
+ }
+ } else {
+ die new Exception('Can\'t instantinate a specialization, the specified template isn\'t found', $instance->Name->CanonicalName);
+ }
+ }
+
+ delete $this->{$PendingInstances};
+ }
+}
+
+sub EnumTypes {
+ my ($this,%options) = @_;
+
+ return grep { ($_->isTemplate and not $options{'skip_templates'}) or (not $_->isTemplate and not $options{'skip_classes'}) } $this->_ListTypes;
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ delete $this->{$UnresolvedTypes};
+
+ $this->SUPER::Dispose;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,57 @@
+use strict;
+package Schema::DB;
+use Common;
+use Schema::DB::Table;
+
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Version => ACCESS_READ;
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Tables => ACCESS_READ;
+}
+
+sub AddTable {
+ my ($this,$table) = @_;
+
+ if (UNIVERSAL::isa($table,'Schema::DB::Table')) {
+ $table->Schema == $this or die new Exception('The specified table must belong to the database');
+ not exists $this->{$Tables}->{$table->Name} or die new Exception('a table with the same name already exists in the database');
+ } elsif (UNIVERSAL::isa($table,'HASH')) {
+ not exists $this->{$Tables}->{$table->{'Name'}} or die new Exception('a table with the same name already exists in the database');
+ $table->{'Schema'} = $this;
+ $table = new Schema::DB::Table(%{$table});
+ } else {
+ die new Exception('Either a table object or a hash with table parameters is required');
+ }
+
+ $this->{$Tables}{$table->Name} = $table;
+}
+
+sub RemoveTable {
+ my ($this,$table) = @_;
+
+ my $tn = UNIVERSAL::isa($table,'Schema::DB::Table') ? $table->Name : $table;
+ $table = delete $this->{$Tables}{$tn} or die new Exception('The table doesn\'t exists',$tn);
+
+ # drop foreign keys
+ map { $_->Table->RemoveConstraint($_) } values %{$table->PrimaryKey->ConnectedFK} if $table->PrimaryKey;
+
+ # drop table contents
+ $table->Dispose();
+
+ return 1;
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ $_->Dispose foreach values %{$this->{$Tables}};
+
+ delete $this->{$Tables};
+
+ $this->SUPER::Dispose;
+}
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Column.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Column.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,56 @@
+package Schema::DB::Column;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Type => ACCESS_READ;
+ DeclareProperty CanBeNull => ACCESS_READ;
+ DeclareProperty DefaultValue => ACCESS_READ;
+ DeclareProperty Tag => ACCESS_READ;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+
+ $this->{$Name} or die new Exception('a column name is required');
+ $this->{$CanBeNull} = 0 if not exists $this->{$CanBeNull};
+ UNIVERSAL::isa($this->{$Type},'Schema::DB::Type') or die new Exception('a type is required for the column',$this->{$Name});
+}
+
+sub isEqualsStr {
+ my ($a,$b) = @_;
+
+ if (defined $a and defined $b) {
+ return $a eq $b;
+ } else {
+ if (defined $a or defined $b) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+}
+
+sub isEquals {
+ my ($a,$b) = @_;
+
+ if (defined $a and defined $b) {
+ return $a == $b;
+ } else {
+ if (defined $a or defined $b) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+}
+
+sub isSame {
+ my ($this,$other) = @_;
+
+ return ($this->{$Name} eq $other->{$Name} and $this->{$CanBeNull} == $other->{$CanBeNull} and isEqualsStr($this->{$DefaultValue}, $other->{$DefaultValue}) and $this->{$Type}->isSame($other->{$Type}));
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Constraint.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Constraint.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,48 @@
+package Schema::DB::Constraint;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Table => ACCESS_READ;
+ DeclareProperty Columns => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ die new Exception("The table argument must be an instance of a table object") if not UNIVERSAL::isa($args{'Table'},'Schema::DB::Table');
+ $this->{$Name} = $args{'Name'};
+ $this->{$Table} = $args{'Table'};
+ $this->{$Columns} = [map { ResolveColumn($this->Table,$_) } @{$args{'Columns'}}];
+}
+
+sub ResolveColumn {
+ my ($Table,$Column) = @_;
+
+ my $cn = UNIVERSAL::isa($Column,'Schema::DB::Column') ? $Column->Name : $Column;
+
+ my $resolved = $Table->Column($cn);
+ die new Exception("The column is not found in the table", $cn, $Table->Name) if not $resolved;
+ return $resolved;
+}
+
+sub HasColumn {
+ my ($this,@Columns) = @_;
+
+ my %Columns = map { $_, 1} @Columns;
+
+ return scalar(grep { $Columns{$_->Name} } $this->Columns) == scalar(@Columns);
+}
+
+sub UniqName {
+ my ($this) = @_;
+ return $this->{$Table}->Name.'_'.$this->{$Name};
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ delete @$this{$Table,$Columns};
+ $this->SUPER::Dispose;
+}
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Constraint/ForeignKey.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Constraint/ForeignKey.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,58 @@
+package Schema::DB::Constraint::ForeignKey;
+use strict;
+use Common;
+use base qw(Schema::DB::Constraint);
+
+BEGIN {
+ DeclareProperty ReferencedPrimaryKey => ACCESS_READ;
+ DeclareProperty OnDelete => ACCESS_READ;
+ DeclareProperty OnUpdate => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->SUPER::CTOR(%args);
+
+
+ die new Eexception("Referenced table must be an instance of a table object") if not UNIVERSAL::isa($args{'ReferencedTable'},'Schema::DB::Table');
+
+ die new Exception("Referenced columns must be a not empty list of columns") if not UNIVERSAL::isa($args{'ReferencedColumns'},'ARRAY') or not scalar(@{$args{'ReferencedColumns'}});
+
+ my @ReferencedColumns = map {Schema::DB::Constraint::ResolveColumn($args{'ReferencedTable'},$_)} @{$args{'ReferencedColumns'}};
+ my $ForeingPK = $args{'ReferencedTable'}->PrimaryKey or die new Exception('The referenced table doesn\'t have a primary key');
+
+ scalar (@ReferencedColumns) == scalar(@{$this->Columns}) or die new Exception('A foreing key columns doesn\'t match refenced columns');
+ my @ColumnsCopy = @ReferencedColumns;
+
+ die new Exception('A foreing key columns doesn\'t match refenced columns') if grep { not $_->Type->isSame((shift @ColumnsCopy)->Type)} $this->Columns;
+
+ @ColumnsCopy = @ReferencedColumns;
+ die new Exception('The foreign key must match to the primary key of the referenced table',$this->Name) if grep { not $_->Type->isSame(shift(@ColumnsCopy)->Type)} $ForeingPK->Columns;
+
+ $this->{$ReferencedPrimaryKey} = $ForeingPK;
+
+ $ForeingPK->ConnectFK($this);
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ $this->{$ReferencedPrimaryKey}->DisconnectFK($this) if not $this->{$ReferencedPrimaryKey}->isa('Object::Disposed');
+ delete $this->{$ReferencedPrimaryKey};
+
+ $this->SUPER::Dispose;
+}
+
+sub isSame {
+ my ($this,$other) = @_;
+
+ uc $this->OnDelete eq uc $other->OnDelete or return 0;
+ uc $this->OnUpdate eq uc $other->OnUpdate or return 0;
+
+ return $this->SUPER::isSame($other);
+}
+
+
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Constraint/Index.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Constraint/Index.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,14 @@
+package Schema::DB::Constraint::Index;
+use strict;
+use Common;
+use base qw(Schema::DB::Constraint);
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+
+ my %colnames;
+ not grep { $colnames{$_}++ } $this->Columns or die new Exception('Each column in the index can occur only once');
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Constraint/PrimaryKey.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Constraint/PrimaryKey.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,40 @@
+package Schema::DB::Constraint::PrimaryKey;
+use strict;
+use Common;
+use base qw(Schema::DB::Constraint::Index);
+
+BEGIN {
+ DeclareProperty ConnectedFK => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->SUPER::CTOR(%args);
+
+ $this->{$ConnectedFK} = {};
+}
+
+sub ConnectFK {
+ my ($this,$FK) = @_;
+
+ UNIVERSAL::isa($FK,'Schema::DB::Constraint::ForeignKey') or die new Exception('Aprimary key could be connected only to a foreign key');
+ not exists $this->{$ConnectedFK}->{$FK->UniqName} or die new Exception('This primary key already conneted with the specified foreing key',$FK->Name,$FK->Table->Name);
+
+ $this->{$ConnectedFK}->{$FK->UniqName} = $FK;
+}
+
+sub DisconnectFK {
+ my ($this,$FK) = @_;
+
+ delete $this->{$ConnectedFK}->{$FK->UniqName};
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ delete $this->{$ConnectedFK};
+ $this->SUPER::Dispose;
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Constraint/Unique.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Constraint/Unique.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,6 @@
+package Schema::DB::Constraint::PrimaryKey;
+use strict;
+use Common;
+use base qw(Schema::DB::Constraint::Index);
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Table.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Table.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,168 @@
+use strict;
+package Schema::DB::Table;
+use Carp;
+use Common;
+
+use Schema::DB::Column;
+use Schema::DB::Constraint;
+use Schema::DB::Constraint::PrimaryKey;
+use Schema::DB::Constraint::ForeignKey;
+
+our @ISA = qw(Object);
+
+srand time;
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Schema => ACCESS_READ;
+ DeclareProperty Columns => ACCESS_READ;
+ DeclareProperty Constraints => ACCESS_READ;
+ DeclareProperty ColumnsByName => ACCESS_NONE;
+ DeclareProperty PrimaryKey => ACCESS_READ;
+ DeclareProperty Tag => ACCESS_ALL;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Name} = $args{'Name'} or die new Exception('a table name is required');
+ $this->{$Schema} = $args{'Schema'} or die new Exception('a parent schema is required');
+}
+
+sub InsertColumn {
+ my ($this,$column,$index) = @_;
+
+ $index = ($this->{$Columns} ? scalar(@{$this->{$Columns}}) : 0) if not defined $index;
+
+ die new Exception("Index is out of range") if ($index < 0 || $index > ($this->{$Columns} ? scalar(@{$this->{$Columns}}) : 0));
+
+ if (UNIVERSAL::isa($column,'Schema::DB::Column')) {
+
+ } elsif (UNIVERSAL::isa($column,'HASH')) {
+ $column = new Schema::DB::Column(%{$column});
+ } else {
+ die new Exception("The invalid parameter");
+ }
+
+ if (exists $this->{$ColumnsByName}->{$column->Name}) {
+ die new Exception("The column already exists",$column->name);
+ } else {
+ $this->{$ColumnsByName}->{$column->Name} = $column;
+ splice @{$this->{$Columns}},$index,0,$column;
+ }
+
+ return $column;
+}
+
+sub RemoveColumn {
+ my ($this,$NameOrColumn,$Force) = @_;
+
+ my $ColName;
+ if (UNIVERSAL::isa($NameOrColumn,'Schema::DB::Column')) {
+ $ColName = $NameOrColumn->Name;
+ } elsif (not ref $NameOrColumn) {
+ $ColName = $NameOrColumn;
+ }
+
+ if (exists $this->{$ColumnsByName}->{$ColName}) {
+ my $index = 0;
+ foreach my $column(@{$this->{$Columns}}) {
+ last if $column->Name eq $ColName;
+ $index++;
+ }
+
+ my $column = $this->{$Columns}[$index];
+ if (my @constraints = $this->GetColumnConstraints($column)){
+ $Force or die new Exception('Can\'t remove column which is used in the constraints',@constraints);
+ $this->RemoveConstraint($_) foreach @constraints;
+ }
+
+ my $removed = splice @{$this->{$Columns}},$index,1;
+ delete $this->{$ColumnsByName}->{$ColName};
+ return $removed;
+ } else {
+ die new Exception("The column not found",$NameOrColumn->Name);
+ }
+}
+
+sub Column {
+ my ($this,$name) = @_;
+
+ return $this->{$ColumnsByName}->{$name};
+}
+
+sub ColumnAt {
+ my ($this,$index) = @_;
+
+ die new Exception("The index is out of range") if $index < 0 || $index >= ($this->{$Columns} ? scalar(@{$this->{$Columns}}) : 0);
+
+ return $this->{$Columns}[$index];
+}
+
+sub AddConstraint {
+ my ($this,$Constraint) = @_;
+
+ die new Exception('The invalid parameter') if not UNIVERSAL::isa($Constraint,'Schema::DB::Constraint');
+
+ $Constraint->Table == $this or die new Exception('The constaint must belong to the target table');
+
+ if (exists $this->{$Constraints}->{$Constraint->Name}) {
+ die new Exception('The table already has the specified constraint',$Constraint->Name);
+ } else {
+ if (UNIVERSAL::isa($Constraint,'Schema::DB::Constraint::PrimaryKey')) {
+ not $this->{$PrimaryKey} or die new Exception('The table already has a primary key');
+ $this->{$PrimaryKey} = $Constraint;
+ }
+
+ $this->{$Constraints}->{$Constraint->Name} = $Constraint;
+ }
+}
+
+sub RemoveConstraint {
+ my ($this,$Constraint,$Force) = @_;
+
+ my $cn = UNIVERSAL::isa($Constraint,'Schema::DB::Constraint') ? $Constraint->Name : $Constraint;
+ $Constraint = $this->{$Constraints}->{$cn} or die new Exception('The specified constraint doesn\'t exists',$cn);
+
+ if (UNIVERSAL::isa($Constraint,'Schema::DB::Constraint::PrimaryKey')) {
+ not scalar keys %{$this->{$PrimaryKey}->ConnectedFK} or die new Exception('Can\'t remove Primary Key unless some foreign keys referenses it');
+
+ delete $this->{$PrimaryKey};
+ }
+ $Constraint->Dispose;
+ delete $this->{$Constraints}->{$cn};
+ return $cn;
+}
+
+sub GetColumnConstraints {
+ my ($this,@Columns) = @_;
+
+ my @cn = map { UNIVERSAL::isa($_ ,'Schema::DB::Column') ? $_ ->Name : $_ } @Columns;
+ exists $this->{$ColumnsByName}->{$_} or die new Exception('The specified column isn\'t found',$_) foreach @cn;
+
+ return grep {$_->HasColumn(@cn)} values %{$this->{$Constraints}};
+}
+
+sub SetPrimaryKey {
+ my ($this,@ColumnList) = @_;
+
+ $this->AddConstraint(new Schema::DB::Constraint::PrimaryKey(Name => $this->{$Name}.'_PK', Table => $this,Columns => \@ColumnList));
+}
+
+sub LinkTo {
+ my ($this,$table,@ColumnList) = @_;
+ $table->PrimaryKey or die new Exception('The referenced table must have a primary key');
+ my $constraintName = $this->{$Name}.'_'.$table->Name.'_FK_'.join('_',map {ref $_ ? $_->Name : $_} @ColumnList);
+ $this->AddConstraint(new Schema::DB::Constraint::ForeignKey(Name => $constraintName, Table => $this,Columns => \@ColumnList, ReferencedTable => $table, ReferencedColumns => scalar($table->PrimaryKey->Columns)));
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ $_->Dispose() foreach values %{$this->{$Constraints}};
+
+ undef %{$this};
+ $this->SUPER::Dispose();
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Traits.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Traits.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,268 @@
+package Schema::DB::Traits;
+use strict;
+use Common;
+our @ISA = qw (Object);
+
+use constant {
+ STATE_NORMAL => 0,
+ STATE_UPDATED => 1,
+ STATE_CREATED => 2,
+ STATE_REMOVED => 3,
+ STATE_PENDING => 4
+} ;
+
+BEGIN {
+ DeclareProperty SrcSchema => ACCESS_NONE;
+ DeclareProperty DstSchema => ACCESS_NONE;
+ DeclareProperty PendingActions => ACCESS_READ;
+ DeclareProperty TableInfo => ACCESS_READ;
+ DeclareProperty Handler => ACCESS_READ;
+ DeclareProperty TableMap => ACCESS_NONE;
+ DeclareProperty KeepTables => ACCESS_ALL;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+
+ $this->{$SrcSchema} or die new Exception('A source schema is required');
+ $this->{$DstSchema} or die new Exception('A destination schema is required');
+ $this->{$Handler} or die new Exception('A handler is required to produce the update batch');
+
+ $this->{$TableInfo} = {};
+ $this->{$PendingActions} = [];
+
+}
+
+sub UpdateTable {
+ my ($this,$srcTable) = @_;
+
+ return 1 if $this->{$TableInfo}->{$srcTable->Name}->{'processed'};
+
+ my $dstTableName = $this->{$TableMap}->{$srcTable->Name} ? $this->{$TableMap}->{$srcTable->Name} : $srcTable->Name;
+ my $dstTable = $this->{$DstSchema}->Tables->{$dstTableName};
+
+ $this->{$TableInfo}->{$srcTable->Name}->{'processed'} = 1;
+
+ if (not $dstTable) {
+ $this->DropTable($srcTable) if not $this->{$KeepTables};
+ return 1;
+ }
+
+ if ( not grep {$srcTable->Column($_->Name)} $dstTable->Columns ) {
+
+ $this->{$TableInfo}->{$srcTable->Name}->{'NewName'} = $dstTable->Name if $srcTable->Name ne $dstTable->Name;
+
+ $this->DropTable($srcTable);
+ $this->CreateTable($dstTable);
+
+ return 1;
+ }
+
+ if ($srcTable->Name ne $dstTableName) {
+ $this->RenameTable($srcTable,$dstTableName);
+ }
+
+ my %dstConstraints = %{$dstTable->Constraints};
+
+ foreach my $srcConstraint (values %{$srcTable->Constraints}) {
+ if (my $dstConstraint = delete $dstConstraints{$srcConstraint->Name}) {
+ $this->UpdateConstraint($srcConstraint,$dstConstraint);
+ } else {
+ $this->DropConstraint($srcConstraint);
+ }
+ }
+
+ my $i = 0;
+ my %dstColumns = map { $_->Name, $i++} $dstTable->Columns ;
+
+ # ñíà÷àëà óäàëÿåì ñòîëáöû
+ # ïîòîì äîáàâëÿåì íåäîñòàþùèå è èçìåíÿåì ñòîëáöû â íóæíîì ïîðÿäêå
+
+ my @columnsToUpdate;
+
+ foreach my $srcColumn ($srcTable->Columns) {
+ if (defined (my $dstColumnIndex = delete $dstColumns{$srcColumn->Name})) {
+ push @columnsToUpdate, { Action => 'update', ColumnSrc => $srcColumn, ColumnDst => $dstTable->ColumnAt($dstColumnIndex), NewPosition => $dstColumnIndex};
+ } else {
+ $this->DropColumn($srcTable,$srcColumn);
+ }
+ }
+ push @columnsToUpdate, map { {Action => 'add', ColumnDst => $dstTable->ColumnAt($_), NewPosition => $_} } values %dstColumns;
+
+ foreach my $action (sort {$a->{'NewPosition'} <=> $b->{'NewPosition'}} @columnsToUpdate ) {
+ if ($action->{'Action'} eq 'update') {
+ $this->UpdateColumn($srcTable,@$action{'ColumnSrc','ColumnDst'},$dstTable,$action->{'NewPosition'}); # change type and position
+ }elsif ($action->{'Action'} eq 'add') {
+ $this->AddColumn($srcTable,$action->{'ColumnDst'},$dstTable,$action->{'NewPosition'}); # add at specified position
+ }
+ }
+
+ foreach my $dstConstraint (values %dstConstraints) {
+ $this->AddConstraint($dstConstraint);
+ }
+
+ $this->{$TableInfo}{$srcTable->Name}{'State'} = STATE_UPDATED;
+}
+
+sub UpdateConstraint {
+ my ($this,$src,$dst) = @_;
+
+ if (not ConstraintEquals($src,$dst)) {
+ if (UNIVERSAL::isa($src,'Schema::DB::Constraint::PrimaryKey')) {
+ $this->UpdateTable($_->Table) foreach values %{$src->ConnectedFK};
+ }
+ $this->DropConstraint($src);
+ $this->AddConstraint($dst);
+ } else {
+ $this->{$TableInfo}->{$this->MapTableName($src->Table->Name)}->{'Constraints'}->{$src->Name} = STATE_UPDATED;
+ }
+}
+
+sub ConstraintEquals {
+ my ($src,$dst) = @_;
+
+ ref $src eq ref $dst or return 0;
+
+ my @dstColumns = $dst->Columns;
+ scalar(@{$src->Columns}) == scalar(@{$dst->Columns}) and not grep { my $column = shift @dstColumns; not $column->isSame($_) } $src->Columns or return 0;
+
+ not UNIVERSAL::isa($src,'Schema::DB::Constraint::ForeignKey') or ConstraintEquals($src->ReferencedPrimaryKey,$dst->ReferencedPrimaryKey) or return 0;
+
+ 1;
+}
+
+sub UpdateSchema {
+ my ($this) = @_;
+
+ my %Updated = map { $this->UpdateTable($_); $this->MapTableName($_->Name) , 1; } values %{$this->{$SrcSchema}->Tables ? $this->{$SrcSchema}->Tables : {} };
+
+ $this->CreateTable($_) foreach grep {not $Updated{$_->Name}} values %{$this->{$DstSchema}->Tables};
+
+ $this->ProcessPendingActions();
+}
+
+sub RenameTable {
+ my ($this,$tblSrc,$tblDstName) = @_;
+
+ $this->{$Handler}->AlterTableRename($tblSrc->Name,$tblDstName);
+ $this->{$TableInfo}->{$tblSrc->Name}->{'NewName'} = $tblDstName;
+}
+
+sub MapTableName {
+ my ($this,$srcName) = @_;
+
+ $this->{$TableInfo}->{$srcName}->{'NewName'} ? $this->{$TableInfo}->{$srcName}->{'NewName'} : $srcName;
+}
+
+sub DropTable {
+ my ($this,$tbl) = @_;
+
+ if ($tbl->PrimaryKey) {
+ $this->UpdateTable($_->Table) foreach values %{$tbl->PrimaryKey->ConnectedFK};
+ }
+
+ $this->{$Handler}->DropTable($this->MapTableName($tbl->Name));
+ $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'State'} = STATE_REMOVED;
+ $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Constraints'} = {map {$_,STATE_REMOVED} keys %{$tbl->Constraints}};
+ $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Columns'} = {map { $_->Name, STATE_REMOVED} $tbl->Columns};
+
+ return 1;
+}
+
+sub CreateTable {
+ my ($this,$tbl) = @_;
+
+ # ñîçäàåì òàáëèöó, êðîìå âíåøíèõ êëþ÷åé
+ $this->{$Handler}->CreateTable($tbl,skip_foreign_keys => 1);
+
+ $this->{$TableInfo}->{$tbl->Name}->{'State'} = STATE_CREATED;
+
+ $this->{$TableInfo}->{$tbl->Name}->{'Columns'} = {map { $_->Name, STATE_CREATED } $tbl->Columns};
+ $this->{$TableInfo}->{$tbl->Name}->{'Constraints'} = {map {$_->Name, STATE_CREATED} grep { not UNIVERSAL::isa($_,'Schema::DB::Constraint::ForeignKey') } values %{$tbl->Constraints}};
+
+ $this->AddConstraint($_) foreach grep { UNIVERSAL::isa($_,'Schema::DB::Constraint::ForeignKey') } values %{$tbl->Constraints};
+
+ return 1;
+}
+
+sub AddColumn {
+ my ($this,$tblSrc,$column,$tblDst,$pos) = @_;
+
+ $this->{$Handler}->AlterTableAddColumn($this->MapTableName($tblSrc->Name),$column,$tblDst,$pos);
+ $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_CREATED;
+
+ return 1;
+}
+
+sub DropColumn {
+ my ($this,$tblSrc,$column) = @_;
+ $this->{$Handler}->AlterTableDropColumn($this->MapTableName($tblSrc->Name),$column->Name);
+ $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_REMOVED;
+
+ return 1;
+}
+
+sub UpdateColumn {
+ my ($this,$tblSrc,$srcColumn,$dstColumn,$tblDst,$pos) = @_;
+
+ if ($srcColumn->isSame($dstColumn) and $pos < @{$tblSrc->Columns} and $tblSrc->ColumnAt($pos) == $srcColumn) {
+ $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED;
+ return 1;
+ }
+
+ $this->{$Handler}->AlterTableChangeColumn($this->MapTableName($tblSrc->Name),$dstColumn,$tblDst,$pos);
+ $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED;
+
+ return 1;
+}
+
+sub DropConstraint {
+ my ($this,$constraint) = @_;
+
+ $this->{$Handler}->AlterTableDropConstraint($this->MapTableName($constraint->Table->Name),$constraint);
+ $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_REMOVED;
+
+ return 1;
+}
+
+sub IfUndef {
+ my ($value,$default) = @_;
+
+ return defined $value ? $value : $default;
+}
+
+sub AddConstraint {
+ my ($this,$constraint) = @_;
+
+ # ïåðåä äîáàâëåíèåì îãðàíè÷åíèÿ íóæíî óáåäèòüñÿ â òîì, ÷òî ñîçäàíû âñå íåîáõîäèìûå ñòîëáöû è ñîïóòñòâóþùèå
+ # îãðàíè÷åíèÿ (íàïðèìåð ïåðâè÷íûå êëþ÷è)
+
+ my $pending;
+
+ $pending = grep { my $column = $_; not grep { IfUndef($this->{$TableInfo}{$constraint->Table->Name}{'Columns'}{$column->Name}, STATE_NORMAL) == $_ } (STATE_UPDATED, STATE_CREATED) } $constraint->Columns;
+
+ if ($pending) {
+ push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]};
+ return 2;
+ } else {
+ if (UNIVERSAL::isa($constraint,'Schema::DB::Constraint::ForeignKey')) {
+ if (not grep { IfUndef($this->{$TableInfo}{$constraint->ReferencedPrimaryKey->Table->Name}{'Constraints'}{$constraint->ReferencedPrimaryKey->Name},STATE_NORMAL) == $_} (STATE_UPDATED, STATE_CREATED)) {
+ push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]};
+ return 2;
+ }
+ }
+ $this->{$Handler}->AlterTableAddConstraint($constraint->Table->Name,$constraint);
+ $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_CREATED;
+ }
+}
+
+sub ProcessPendingActions {
+ my ($this) = @_;
+
+ while (my $action = shift @{$this->{$PendingActions}}) {
+ $action->{'Action'}->($this,@{$action->{'Args'}});
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Traits/mysql.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Traits/mysql.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,549 @@
+package Schema::DB::Traits::mysql::Handler;
+use strict;
+use Common;
+our @ISA=qw(Object);
+
+BEGIN {
+ DeclareProperty SqlBatch => ACCESS_NONE;
+}
+
+sub formatTypeNameInteger {
+ my ($type) = @_;
+
+ return $type->Name.($type->MaxLength ? '('.$type->MaxLength.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : '');
+}
+
+sub formatTypeNameReal {
+ my ($type) = @_;
+
+ return $type->Name.($type->MaxLength ? '('.$type->MaxLength.', '.$type->Scale.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : '');
+}
+
+sub formatTypeNameNumeric {
+ my ($type) = @_;
+ $type->MaxLength or die new Exception('The length and precission must be specified',$type->Name);
+ return $type->Name.($type->MaxLength ? '('.$type->MaxLength.', '.$type->Scale.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : '');
+}
+
+sub formatTypeName {
+ my ($type) = @_;
+ return $type->Name;
+}
+
+sub formatTypeNameChar {
+ my ($type) = @_;
+
+ return (
+ $type->Name.'('.$type->MaxLength.')'. (UNIVERSAL::isa($type,'Schema::DB::Type::mysql::CHAR') ? $type->Encoding : '')
+ );
+}
+
+sub formatTypeNameVarChar {
+ my ($type) = @_;
+
+ return (
+ $type->Name.'('.$type->MaxLength.')'. (UNIVERSAL::isa($type,'Schema::DB::Type::mysql::VARCHAR') ? $type->Encoding : '')
+ );
+}
+
+sub formatTypeNameEnum {
+ my ($type) = @_;
+ die new Exception('Enum must be a type of either Schema::DB::Type::mysql::ENUM or Schema::DB::Type::mysql::SET') if not (UNIVERSAL::isa($type,'Schema::DB::Type::mysql::ENUM') or UNIVERSAL::isa($type,'Schema::DB::Type::mysql::SET'));
+ return (
+ $type->Name.'('.join(',',map {quote($_)} $type->Values).')'
+ );
+}
+
+sub quote{
+ if (wantarray) {
+ return map { my $str=$_; $str=~ s/'/''/g; "'$str'"; } @_;
+ } else {
+ return join '',map { my $str=$_; $str=~ s/'/''/g; "'$str'"; } @_;
+ }
+}
+
+sub quote_names {
+ if (wantarray) {
+ return map { my $str=$_; $str=~ s/`/``/g; "`$str`"; } @_;
+ } else {
+ return join '',map { my $str=$_; $str=~ s/`/``/g; "`$str`"; } @_;
+ }
+}
+
+sub formatStringValue {
+ my ($value) = @_;
+
+ if (ref $value) {
+ if (UNIVERSAL::isa($value,'Schema::DB::mysql::Expression')) {
+ return $value->as_string;
+ } else {
+ die new Exception('Can\'t format the object as a value',ref $value);
+ }
+ } else {
+ return quote($value);
+ }
+}
+
+
+sub formatNumberValue {
+ my ($value) = @_;
+
+ if (ref $value) {
+ if (UNIVERSAL::isa($value,'Schema::DB::mysql::Expression')) {
+ return $value->as_string;
+ } else {
+ die new Exception('Can\'t format the object as a value',ref $value);
+ }
+ } else {
+ $value =~ /^((\+|-)\s*)?\d+(\.\d+)?(e(\+|-)?\d+)?$/ or die new Exception('The specified value isn\'t a valid number',$value);
+ return $value;
+ }
+}
+
+
+my %TypesFormat = (
+ TINYINT => {
+ formatType => \&formatTypeNameInteger,
+ formatValue => \&formatNumberValue
+ },
+ SMALLINT => {
+ formatType => \&formatTypeNameInteger,
+ formatValue => \&formatNumberValue
+ },
+ MEDIUMINT => {
+ formatType => \&formatTypeNameInteger,
+ formatValue => \&formatNumberValue
+ },
+ INT => {
+ formatType => \&formatTypeNameInteger,
+ formatValue => \&formatNumberValue
+ },
+ INTEGER => {
+ formatType => \&formatTypeNameInteger,
+ formatValue => \&formatNumberValue
+ },
+ BIGINT => {
+ formatType => \&formatTypeNameInteger,
+ formatValue => \&formatNumberValue
+ },
+ REAL => {
+ formatType => \&formatTypeNameReal,
+ formatValue => \&formatNumberValue
+ },
+ DOUBLE => {
+ formatType => \&formatTypeNameReal,
+ formatValue => \&formatNumberValue
+ },
+ FLOAT => {
+ formatType => \&formatTypeNameReal,
+ formatValue => \&formatNumberValue
+ },
+ DECIMAL => {
+ formatType => \&formatTypeNameNumeric,
+ formatValue => \&formatNumberValue
+ },
+ NUMERIC => {
+ formatType => \&formatTypeNameNumeric,
+ formatValue => \&formatNumberValue
+ },
+ DATE => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ TIME => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ TIMESTAMP => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ DATETIME => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ CHAR => {
+ formatType => \&formatTypeNameChar,
+ formatValue => \&formatStringValue
+ },
+ VARCHAR => {
+ formatType => \&formatTypeNameVarChar,
+ formatValue => \&formatStringValue
+ },
+ TINYBLOB => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ BLOB => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ MEDIUMBLOB => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ LONGBLOB => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ TINYTEXT => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ TEXT => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ MEDIUMTEXT => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ LONGTEXT => {
+ formatType => \&formatTypeName,
+ formatValue => \&formatStringValue
+ },
+ ENUM => {
+ formatType => \&formatTypeNameEnum,
+ formatValue => \&formatStringValue
+ },
+ SET => {
+ formatType => \&formatTypeNameEnum,
+ formatValue => \&formatStringValue
+ }
+);
+
+
+=pod
+CREATE TABLE 'test'.'New Table' (
+ 'dd' INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+ `ff` VARCHAR(45) NOT NULL,
+ `ffg` VARCHAR(45) NOT NULL DEFAULT 'aaa',
+ `ddf` INTEGER UNSIGNED NOT NULL,
+ PRIMARY KEY(`dd`),
+ UNIQUE `Index_2`(`ffg`),
+ CONSTRAINT `FK_New Table_1` FOREIGN KEY `FK_New Table_1` (`ddf`)
+ REFERENCES `user` (`id`)
+ ON DELETE RESTRICT
+ ON UPDATE RESTRICT
+)
+ENGINE = InnoDB;
+=cut
+sub formatCreateTable {
+ my ($table,$level,%options) = @_;
+
+ my @sql;
+
+ # table body
+ push @sql, map { formatColumn($_,$level+1) } $table->Columns ;
+ if ($options{'skip_foreign_keys'}) {
+ push @sql, map { formatConstraint($_,$level+1) } grep {not UNIVERSAL::isa($_,'Schema::DB::Constraint::ForeignKey')} values %{$table->Constraints};
+ } else {
+ push @sql, map { formatConstraint($_,$level+1) } values %{$table->Constraints};
+ }
+
+ for(my $i = 0 ; $i < @sql -1; $i++) {
+ $sql[$i] .= ',';
+ }
+
+ unshift @sql, "CREATE TABLE ".quote_names($table->Name)."(";
+
+ if ($table->Tag) {
+ push @sql, ")";
+ push @sql, formatTableTag($table->Tag,$level);
+ $sql[$#sql].=';';
+ } else {
+ push @sql, ');';
+ }
+
+ return map { ("\t" x $level) . $_ } @sql;
+}
+
+sub formatDropTable {
+ my ($tableName,$level) = @_;
+
+ return "\t"x$level."DROP TABLE ".quote_names($tableName).";";
+}
+
+sub formatTableTag {
+ my ($tag,$level) = @_;
+ return map { "\t"x$level . "$_ = ".$tag->{$_} } grep {/^(ENGINE)$/i} keys %{$tag};
+}
+
+sub formatColumn {
+ my ($column,$level) = @_;
+ $level ||= 0;
+ return "\t"x$level.quote_names($column->Name)." ".formatType($column->Type)." ".($column->CanBeNull ? 'NULL' : 'NOT NULL').($column->DefaultValue ? formatValueToType($column->DefaultValue,$column->Type) : '' ).($column->Tag ? ' '.join(' ',$column->Tag) : '');
+}
+
+sub formatType {
+ my ($type) = @_;
+ my $format = $TypesFormat{uc $type->Name} or die new Exception('The unknown type name',$type->Name);
+ $format->{formatType}->($type);
+}
+
+sub formatValueToType {
+ my ($value,$type) = @_;
+
+ my $format = $TypesFormat{uc $type->Name} or die new Exception('The unknown type name',$type->Name);
+ $format->{formatValue}->($value);
+}
+
+sub formatConstraint {
+ my ($constraint,$level) = @_;
+
+ if (UNIVERSAL::isa($constraint,'Schema::DB::Constraint::ForeignKey')) {
+ return formatForeignKey($constraint,$level);
+ } else {
+ return formatIndex($constraint, $level);
+ }
+}
+
+sub formatIndex {
+ my ($constraint,$level) = @_;
+
+ my $name = quote_names($constraint->Name);
+ my $columns = join(',',map quote_names($_->Name),$constraint->Columns);
+
+ if (ref $constraint eq 'Schema::DB::Constraint::PrimaryKey') {
+ return "\t"x$level."PRIMARY KEY ($columns)";
+ } elsif ($constraint eq 'Schema::DB::Constraint::Unique') {
+ return "\t"x$level."UNIQUE $name ($columns)";
+ } elsif ($constraint eq 'Schema::DB::Constraint::Index') {
+ return "\t"x$level."INDEX $name ($columns)";
+ } else {
+ die new Exception('The unknown constraint', ref $constraint);
+ }
+
+}
+
+sub formatForeignKey {
+ my ($constraint,$level) = @_;
+
+ my $name = quote_names($constraint->Name);
+ my $columns = join(',',map quote_names($_->Name),$constraint->Columns);
+
+ not $constraint->OnDelete or grep { uc $constraint->OnDelete eq $_ } ('RESTRICT','CASCADE','SET NULL','NO ACTION','SET DEFAULT') or die new Exception('Invalid ON DELETE reference',$constraint->OnDelete);
+ not $constraint->OnUpdate or grep { uc $constraint->OnUpdate eq $_ } ('RESTRICT','CASCADE','SET NULL','NO ACTION','SET DEFAULT') or die new Exception('Invalid ON UPDATE reference',$constraint->OnUpdate);
+
+ my $refname = quote_names($constraint->ReferencedPrimaryKey->Table->Name);
+ my $refcolumns = join(',',map quote_names($_->Name),$constraint->ReferencedPrimaryKey->Columns);
+ return (
+ "\t"x$level.
+ "CONSTRAINT $name FOREIGN KEY $name ($columns) REFERENCES $refname ($refcolumns)".
+ ($constraint->OnUpdate ? 'ON UPDATE'.$constraint->OnUpdate : '').
+ ($constraint->OnDelete ? 'ON DELETE'.$constraint->OnDelete : '')
+ );
+}
+
+sub formatAlterTableRename {
+ my ($oldName,$newName,$level) = @_;
+
+ return "\t"x$level."ALTER TABLE ".quote_names($oldName)." RENAME TO ".quote_names($newName).";";
+}
+
+sub formatAlterTableDropColumn {
+ my ($tableName, $columnName,$level) = @_;
+
+ return "\t"x$level."ALTER TABLE ".quote_names($tableName)." DROP COLUMN ".quote_names($columnName).";";
+}
+
+=pod
+ALTER TABLE `test`.`user` ADD COLUMN `my_col` VARCHAR(45) NOT NULL AFTER `name2`
+=cut
+sub formatAlterTableAddColumn {
+ my ($tableName, $column, $table, $pos, $level) = @_;
+
+ my $posSpec = $pos == 0 ? 'FIRST' : 'AFTER '.quote_names($table->ColumnAt($pos-1)->Name);
+
+ return "\t"x$level."ALTER TABLE ".quote_names($tableName)." ADD COLUMN ".formatColumn($column) .' '. $posSpec.";";
+}
+
+=pod
+ALTER TABLE `test`.`manager` MODIFY COLUMN `description` VARCHAR(256) NOT NULL DEFAULT NULL;
+=cut
+sub formatAlterTableChangeColumn {
+ my ($tableName,$column,$table,$pos,$level) = @_;
+ my $posSpec = $pos == 0 ? 'FIRST' : 'AFTER '.quote_names($table->ColumnAt($pos-1)->Name);
+ return "\t"x$level."ALTER TABLE ".quote_names($tableName)." MODIFY COLUMN ".formatColumn($column).' '. $posSpec.";";
+}
+
+=pod
+ALTER TABLE `test`.`manager` DROP INDEX `Index_2`;
+=cut
+sub formatAlterTableDropConstraint {
+ my ($tableName,$constraint,$level) = @_;
+ my $constraintName;
+ if (ref $constraint eq 'Schema::DB::Constraint::PrimaryKey') {
+ $constraintName = 'PRIMARY KEY';
+ } elsif (ref $constraint eq 'Schema::DB::Constraint::ForeignKey') {
+ $constraintName = 'FOREIGN KEY '.quote_names($constraint->Name);
+ } elsif (UNIVERSAL::isa($constraint,'Schema::DB::Constraint::Index')) {
+ $constraintName = 'INDEX '.quote_names($constraint->Name);
+ } else {
+ die new Exception("The unknow type of the constraint",ref $constraint);
+ }
+ return "\t"x$level."ALTER TABLE ".quote_names($tableName)." DROP $constraintName;";
+}
+
+=pod
+ALTER TABLE `test`.`session` ADD INDEX `Index_2`(`id`, `name`);
+=cut
+sub formatAlterTableAddConstraint {
+ my ($tableName,$constraint,$level) = @_;
+
+ return "\t"x$level."ALTER TABLE ".quote_names($tableName)." ADD ".formatConstraint($constraint,0).';';
+}
+
+sub CreateTable {
+ my ($this,$tbl,%option) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatCreateTable($tbl,0,%option));
+
+ return 1;
+}
+
+sub DropTable {
+ my ($this,$tbl) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatDropTable($tbl,0));
+
+ return 1;
+}
+
+sub RenameTable {
+ my ($this,$oldName,$newName) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatAlterTableRename($oldName,$newName,0));
+
+ return 1;
+}
+
+sub AlterTableAddColumn {
+ my ($this,$tblName,$column,$table,$pos) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatAlterTableAddColumn($tblName,$column,$table,$pos,0));
+
+ return 1;
+}
+sub AlterTableDropColumn {
+ my ($this,$tblName,$columnName) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatAlterTableDropColumn($tblName,$columnName,0));
+
+ return 1;
+}
+
+sub AlterTableChangeColumn {
+ my ($this,$tblName,$column,$table,$pos) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatAlterTableChangeColumn($tblName,$column,$table,$pos,0));
+
+ return 1;
+}
+
+sub AlterTableAddConstraint {
+ my ($this,$tblName,$constraint) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatAlterTableAddConstraint($tblName,$constraint,0));
+
+ return 1;
+}
+
+sub AlterTableDropConstraint {
+ my ($this,$tblName,$constraint) = @_;
+
+ push @{$this->{$SqlBatch}},join("\n",formatAlterTableDropConstraint($tblName,$constraint,0));
+
+ return 1;
+}
+
+sub Sql {
+ my ($this) = @_;
+ if (wantarray) {
+ $this->SqlBatch;
+ } else {
+ return join("\n",$this->SqlBatch);
+ }
+}
+
+package Schema::DB::Traits::mysql;
+use Common;
+use base qw(Schema::DB::Traits);
+
+BEGIN {
+ DeclareProperty PendingConstraints => ACCESS_NONE;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $args{'Handler'} = new Schema::DB::Traits::mysql::Handler;
+ $this->SUPER::CTOR(%args);
+}
+
+sub DropConstraint {
+ my ($this,$constraint) = @_;
+
+ if (UNIVERSAL::isa($constraint,'Schema::DB::Constraint::Index')) {
+ return 1 if not grep { $this->TableInfo->{$this->MapTableName($constraint->Table->Name)}->{'Columns'}->{$_->Name} != Schema::DB::Traits::STATE_REMOVED} $constraint->Columns;
+ my @constraints = grep {$_ != $constraint } $constraint->Table->GetColumnConstraints($constraint->Columns);
+ if (scalar @constraints == 1 and UNIVERSAL::isa($constraints[0],'Schema::DB::Constraint::ForeignKey')) {
+ my $fk = shift @constraints;
+ if ($this->TableInfo->{$this->MapTableName($fk->Table->Name)}->{'Constraints'}->{$fk->Name} != Schema::DB::Traits::STATE_REMOVED) {
+ push @{$this->PendingActions}, {Action => \&DropConstraint, Args => [$constraint]};
+ $this->{$PendingConstraints}->{$constraint->UniqName}->{'attempts'} ++;
+
+ die new Exception('Can\'t drop the primary key becouse of the foreing key',$fk->UniqName) if $this->{$PendingConstraints}->{$constraint->UniqName}->{'attempts'} > 2;
+ return 2;
+ }
+ }
+ }
+ $this->SUPER::DropConstraint($constraint);
+}
+
+sub GetMetaTable {
+ my ($class,$dbh) = @_;
+
+ return Schema::DB::Traits::mysql::MetaTable->new( DBHandle => $dbh);
+}
+
+package Schema::DB::Traits::mysql::MetaTable;
+use Common;
+our @ISA=qw(Object);
+
+BEGIN {
+ DeclareProperty DBHandle => ACCESS_NONE;
+}
+
+sub ReadProperty {
+ my ($this,$name) = @_;
+
+ local $this->{$DBHandle}->{PrintError};
+ $this->{$DBHandle}->{PrintError} = 0;
+ my ($val) = $this->{$DBHandle}->selectrow_array("SELECT value FROM _Meta WHERE name like ?", undef, $name);
+ return $val;
+}
+
+sub SetProperty {
+ my ($this,$name,$val) = @_;
+
+ if ( $this->{$DBHandle}->selectrow_arrayref("SELECT TABLE_NAME FROM information_schema.`TABLES` T where TABLE_SCHEMA like DATABASE() and TABLE_NAME like '_Meta'")) {
+ if ($this->{$DBHandle}->selectrow_arrayref("SELECT name FROM _Meta WHERE name like ?", undef, $name)) {
+ $this->{$DBHandle}->do("UPDATE _Meta SET value = ? WHERE name like ?",undef,$val,$name);
+ } else {
+ $this->{$DBHandle}->do("INSERT INTO _Meta(name,value) VALUES ('$name',?)",undef,$val);
+ }
+ } else {
+ $this->{$DBHandle}->do(q{
+ CREATE TABLE `_Meta` (
+ `name` VARCHAR(255) NOT NULL,
+ `value` LONGTEXT NULL,
+ PRIMARY KEY(`name`)
+ );
+ }) or die new Exception("Failed to create table","_Meta");
+
+ $this->{$DBHandle}->do("INSERT INTO _Meta(name,value) VALUES (?,?)",undef,$name,$val);
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DB/Type.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DB/Type.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,42 @@
+use strict;
+package Schema::DB::Type;
+use Common;
+our @ISA=qw(Object);
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty MaxLength => ACCESS_READ;
+ DeclareProperty Scale => ACCESS_READ;
+ DeclareProperty Unsigned => ACCESS_READ;
+ DeclareProperty Zerofill => ACCESS_READ;
+ DeclareProperty Tag => ACCESS_READ;
+}
+
+sub CTOR {
+ my $this = shift;
+ $this->SUPER::CTOR(@_);
+
+ $this->{$Scale} = 0 if not $this->{$Scale};
+}
+
+sub isEquals {
+ my ($a,$b) = @_;
+
+ if (defined $a and defined $b) {
+ return $a == $b;
+ } else {
+ if (defined $a or defined $b) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+}
+
+sub isSame {
+ my ($this,$other) = @_;
+
+ return ($this->{$Name} eq $other->{$Name} and isEquals($this->{$MaxLength},$other->{$MaxLength}) and isEquals($this->{$Scale},$other->{$Scale}));
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DataSource.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DataSource.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,138 @@
+package Configuration;
+our $DataDir;
+package Schema::DataSource;
+use Common;
+use strict;
+use base qw(Object);
+
+use BNFCompiler;
+use Schema::DB;
+use Schema;
+use URI::file;
+
+BEGIN {
+ DeclareProperty ProcessedSchemas => ACCESS_NONE; #{ uri => schema }
+ DeclareProperty Types => ACCESS_READ; # Schema
+ DeclareProperty DataSourceBuilder => ACCESS_READ;
+ DeclareProperty Compiler => ACCESS_NONE;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$DataSourceBuilder} = $args{'DataSourceBuilder'} or die new Exception('A data source builder is required');
+ $this->{$Types} = new Schema;
+ $this->{$Compiler} = new BNFCompiler(SchemaCache => "${DataDir}Cache/",Transform => sub { BNFCompiler::DOM::TransformDOMToHash(@_,{skip_spaces => 1})} );
+ $this->{$Compiler}->LoadBNFSchema(file => 'Schema/schema.def');
+}
+
+sub as_list {
+ return( map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : defined $_ ? $_ : () } @_ );
+}
+
+sub ProcessSchema {
+ my ($this,$uriFile) = @_;
+
+ return 1 if $this->{$ProcessedSchemas}{$uriFile->as_string};
+
+ my $uriDir = URI::file->new('./')->abs($uriFile);
+ $this->{$ProcessedSchemas}->{$uriFile->as_string} = 1;
+
+ my $Schema = $this->ParseSchema($uriFile);
+
+ foreach my $item (as_list($Schema->{'header'}{'include_item'})) {
+ my $uriItem = URI::file->new($item->{'file_name'})->abs($uriDir);
+ $this->ProcessSchema($uriItem);
+ }
+
+ $this->ConstructTypes($Schema);
+
+}
+
+sub ParseSchema {
+ my ($this,$fileUri) = @_;
+
+ my $fileName = $fileUri->file;
+ open my $hfile,"$fileName" or die new Exception('Failed to read the file',$fileName,$!);
+ local $/ = undef;
+ my $Schema = $this->{$Compiler}->Parse(<$hfile>);
+
+ return $Schema;
+}
+
+sub ConstructTypes {
+ my ($this,$schema) = @_;
+ return if not $schema->{'class'};
+
+ foreach my $class (as_list($schema->{'class'})){
+ # îáúÿâëåíèå òèïà
+ my $type;
+ my $builder;
+ if ($class->{'type_definition'}{'args_list'}) {
+ $type = $this->{$Types}->CreateTemplate($class->{'type_definition'}{'name'},as_list($class->{'type_definition'}{'args_list'}{'name'}));
+ } else {
+ $type = $this->{$Types}->CreateType($class->{'type_definition'}{'name'});
+ }
+
+ $type->SetAttributes(ValueType => 1) if $class->{'value_type'};
+
+ my $mappingTip = $this->{$DataSourceBuilder}->GetClassMapping($type);
+
+
+ # îáðàáàòûâàåì ñïèñîê áàçîâûõ êëàññîâ
+
+ if ($class->{'base_types'}) {
+ foreach my $typename (as_list($class->{'base_types'}{'type'})) {
+ $type->AddBase(MakeTypeName($typename));
+ }
+ }
+
+ # îáðàáàòûâàåì ñïèñîê ñâîéñòâ
+ if ($class->{'property_list'}) {
+ foreach my $property (as_list($class->{'property_list'}{'property'})) {
+ $type->InsertProperty($property->{'name'},MakeTypeName($property->{'type'}));
+ if (my $mapping = $property->{'mapping'}) {
+ $mappingTip->PropertyMapping($property->{'name'},Column => $mapping->{'column_name'},DBType => $mapping->{'db_type'});
+ }
+ }
+ }
+ }
+}
+
+sub MakeTypeName {
+ my ($typename) = @_;
+
+ return new Schema::TypeName(
+ $typename->{'name'},
+ (
+ $typename->{'template_list'} ?
+ map { MakeTypeName($_) } as_list($typename->{'template_list'}{'type'})
+ :
+ ()
+ )
+ );
+}
+
+sub BuildSchema {
+ my ($this,$fileName) = @_;
+
+ my $uriFile = URI::file->new_abs($fileName);
+
+ $this->ProcessSchema($uriFile);
+
+ $this->{$Types}->Close();
+
+ foreach my $type ($this->{$Types}->EnumTypes(skip_templates => 1)) {
+ $this->{$DataSourceBuilder}->AddType($type);
+ }
+}
+
+sub DESTROY {
+ my ($this) = @_;
+
+ $this->{$Compiler}->Dispose;
+ $this->{$DataSourceBuilder}->Dispose;
+ $this->{$Types}->Dispose;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DataSource/CDBIBuilder.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DataSource/CDBIBuilder.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,326 @@
+use strict;
+package Schema::DataSource::CDBIBuilder;
+use Schema::DataSource::TypeMapping;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty ClassMappings => ACCESS_NONE;
+ DeclareProperty TypeMapping => ACCESS_READ;
+ DeclareProperty ValueTypeReflections => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$TypeMapping} = $args{'TypeMapping'} || Schema::DataSource::TypeMapping::Std->new;
+ $this->{$ValueTypeReflections} = { DateTime => 'DateTime'};
+}
+
+sub ReflectValueType {
+ my ($this,$Type) = @_;
+ return $this->{$ValueTypeReflections}{$Type->Name->Simple};
+}
+
+sub GetClassMapping {
+ my ($this,$type) = @_;
+
+ if (my $mapping = $this->{$ClassMappings}->{$type->Name->Canonical}) {
+ return $mapping;
+ } else {
+ $mapping = new Schema::DataSource::CDBIBuilder::ClassMapping(Class => $type,Parent => $this);
+ $this->{$ClassMappings}{$type->Name->Canonical} = $mapping;
+ return $mapping
+ }
+}
+
+sub EnumClassMappings {
+ my ($this) = @_;
+ return $this->{$ClassMappings} ? values %{$this->{$ClassMappings}} : ();
+}
+
+sub AddType {
+ my ($this,$type) = @_;
+ $this->GetClassMapping($type);
+}
+
+sub BuildDBSchema {
+ my ($this) = @_;
+
+ my $schemaDB = new Schema::DB(Name => 'auto', Version => time);
+
+ if ($this->{$ClassMappings}) {
+ $_->CreateTable($schemaDB) foreach values %{ $this->{$ClassMappings} };
+ $_->CreateConstraints($schemaDB) foreach values %{ $this->{$ClassMappings} };
+ }
+
+ return $schemaDB;
+}
+
+sub WriteModules {
+ my ($this,$fileName,$prefix) = @_;
+
+ my $text;
+ $text = <connection(\$DSN,\$User,\$Password);
+
+# initialize
+foreach my \$action (ref \$Init eq 'ARRAY' ? \@{\$Init} : \$Init) {
+ next unless \$action;
+
+ if (ref \$action eq 'CODE') {
+ \$action->(__PACKAGE__->db_Main);
+ } elsif (not ref \$action) {
+ __PACKAGE__->db_Main->do(\$action);
+ }
+}
+
+ModuleHeader
+
+ if ($this->{$ClassMappings}) {
+ $text .= join ("\n\n", map $_->GenerateText($prefix.'DBI',$prefix), sort {$a->Class->Name->Canonical cmp $b->Class->Name->Canonical } values %{ $this->{$ClassMappings} } );
+ }
+
+ $text .= "\n1;";
+
+ open my $out, ">$fileName" or die new Exception("Failed to open file",$fileName,$!);
+ print $out $text;
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ delete @$this{$ClassMappings,$TypeMapping,$ValueTypeReflections};
+
+ $this->SUPER::Dispose;
+}
+
+package Schema::DataSource::CDBIBuilder::ClassMapping;
+use Common;
+use Schema;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Table => ACCESS_READ;
+ DeclareProperty PropertyTables => ACCESS_READ;
+ DeclareProperty PropertyMappings => ACCESS_READ;
+
+ DeclareProperty Class => ACCESS_READ;
+ DeclareProperty Parent => ACCESS_NONE;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Class} = $args{'Class'} or die new Exception('The class must be specified');
+ $this->{$Parent} = $args{'Parent'} or die new Exception('The parent must be specified');
+
+}
+
+sub PropertyMapping {
+ my ($this,%args) = @_;
+ $this->{$PropertyMappings}{$args{'name'}} = { Column => $args{'Column'},DBType => $args{'DBType'} };
+}
+
+sub CreateTable {
+ my ($this,$schemaDB) = @_;
+
+ return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set';
+
+ # CreateTable
+ my $table = $schemaDB->AddTable({Name => $this->{$Class}->Name->Canonical});
+ $table->InsertColumn({
+ Name => '_id',
+ Type => $this->{$Parent}->TypeMapping->DBIdentifierType,
+ Tag => ['AUTO_INCREMENT']
+ });
+ $table->SetPrimaryKey('_id');
+ foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) {
+ if ($prop->Type->Name->Name eq 'Set') {
+ # special case for multiple values
+ my $propTable = $this->CreatePropertyTable($schemaDB,$prop);
+ $propTable->LinkTo($table,'parent');
+ } else {
+ $table->InsertColumn({
+ Name => $prop->Name,
+ Type => $this->{$Parent}->TypeMapping->MapType($prop->Type),
+ CanBeNull => 1
+ });
+ }
+ }
+ $this->{$Table} = $table;
+ return $table;
+}
+
+sub CreatePropertyTable {
+ my ($this,$schemaDB,$property) = @_;
+
+ my $table = $schemaDB->AddTable({Name => $this->{$Class}->Name->Canonical.'_'.$property->Name});
+ $table->InsertColumn({
+ Name => '_id',
+ Type => $this->{$Parent}->TypeMapping->DBIdentifierType,
+ Tag => ['AUTO_INCREMENT']
+ });
+ $table->SetPrimaryKey('_id');
+
+ $table->InsertColumn({
+ Name => 'parent',
+ Type => $this->{$Parent}->TypeMapping->DBIdentifierType
+ });
+
+ $table->InsertColumn({
+ Name => 'value',
+ Type => $this->{$Parent}->TypeMapping->MapType($property->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}),
+ CanBeNull => 1
+ });
+
+ $this->{$PropertyTables}->{$property->Name} = $table;
+
+ return $table;
+}
+
+sub CreateConstraints {
+ my ($this,$schemaDB) = @_;
+ return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set';
+
+ foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) {
+ if ($prop->Type->Name->Name eq 'Set' ) {
+ # special case for multiple values
+ if (not $prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}->GetAttribute('ValueType')) {
+ $this->{$PropertyTables}->{$prop->Name}->LinkTo(
+ $this->{$Parent}->GetClassMapping($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'})->Table,
+ 'value'
+ );
+ }
+ } elsif (not $prop->Type->GetAttribute('ValueType')) {
+ $this->{$Table}->LinkTo(
+ scalar($this->{$Parent}->GetClassMapping($prop->Type)->Table),
+ $prop->Name
+ );
+ }
+ }
+}
+
+sub GeneratePropertyTableText {
+ my ($this,$prop,$baseModule,$prefix) = @_;
+
+ my $packageName = $this->GeneratePropertyClassName($prop,$prefix);
+ my $tableName = $this->{$PropertyTables}->{$prop->Name}->Name;
+ my $parentName = $this->GenerateClassName($prefix);
+ my $text .= "package $packageName;\n";
+ $text .= "use base '$baseModule';\n\n";
+ $text .= "__PACKAGE__->table('`$tableName`');\n";
+ $text .= "__PACKAGE__->columns(Essential => qw/_id parent value/);\n";
+ $text .= "__PACKAGE__->has_a( parent => '$parentName');\n";
+
+ my $typeValue;
+ if ($prop->Type->Name->Simple eq 'Set') {
+ $typeValue = $prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'};
+ } else {
+ $typeValue = $prop->Type;
+ }
+ if ($typeValue->GetAttribute('ValueType')) {
+ if (my $reflectedClass = $this->{$Parent}->ReflectValueType($typeValue)) {
+ $text .= "__PACKAGE__->has_a( value => '$reflectedClass');\n";
+ }
+ } else {
+ my $foreignName = $this->{$Parent}->GetClassMapping($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'})->GenerateClassName($prefix);
+ $text .= "__PACKAGE__->has_a( value => '$foreignName');\n";
+ }
+
+ return $text;
+}
+
+sub GeneratePropertyClassName {
+ my ($this,$prop,$prefix) = @_;
+
+ my $packageName = $this->{$Class}->Name->Canonical;
+ $packageName =~ s/\W//g;
+ return $prefix.$packageName.$prop->Name.'Ref';
+}
+
+sub GenerateClassName {
+ my ($this,$prefix) = @_;
+ my $packageName = $this->{$Class}->Name->Canonical;
+ $packageName =~ s/\W//g;
+ return $prefix. $packageName;
+}
+
+sub GenerateText {
+ my ($this,$baseModule,$prefix) = @_;
+
+ return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set';
+
+ my @PropertyModules;
+ my $text;
+ my $packageName = $this->GenerateClassName($prefix);
+
+ my $tableName = $this->{$Table}->Name;
+ my $listColumns = join ',', map { '\''. $_->Name . '\''} $this->{$Table}->Columns;
+
+ $text .= "package $packageName;\n";
+ $text .= "use base '$baseModule'". ($this->{$Class}->Name->Name eq 'Map' ? ',\'CDBI::Map\'' : '' ).";\n\n";
+
+ $text .= "__PACKAGE__->table('`$tableName`');\n";
+ $text .= "__PACKAGE__->columns(Essential => $listColumns);\n";
+
+ foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) {
+ my $propName = $prop->Name;
+ if ($prop->Type->Name->Name eq 'Set') {
+ # has_many
+ push @PropertyModules, $this->GeneratePropertyTableText($prop,$baseModule,$prefix);
+ my $propClass = $this->GeneratePropertyClassName($prop,$prefix);
+ $text .= <has_many( ${propName}_ref => '$propClass');
+sub $propName {
+ return map { \$_->value } ${propName}_ref(\@_);
+}
+sub add_to_$propName {
+ return add_to_${propName}_ref(\@_);
+}
+ACCESSORS
+
+ } elsif (not $prop->Type->GetAttribute('ValueType')) {
+ # has_a
+ my $ForeignClass = $this->{$Parent}->GetClassMapping($prop->Type)->GenerateClassName($prefix);
+ $text .= "__PACKAGE__->has_a( $propName => '$ForeignClass');\n";
+ } else {
+ if (my $reflectedClass = $this->{$Parent}->ReflectValueType($prop->Type)) {
+ $text .= "__PACKAGE__->has_a( $propName => '$reflectedClass');\n";
+ }
+ }
+ }
+
+ # ñîçäàåì ñïèñîê äî÷åðíèõ êëàññîâ
+ foreach my $descedantMapping (grep {$_->{$Class}->isType($this->{$Class},1)} $this->{$Parent}->EnumClassMappings) {
+ next if $descedantMapping == $this;
+ $text .= "__PACKAGE__->might_have('m".$descedantMapping->GenerateClassName('')."' => '".$descedantMapping->GenerateClassName($prefix)."');\n";
+ }
+
+ # ñîçäàåì ññûëêè íà âñå êëàññû, êîòîðûå ìîãóò ññûëàòüñÿ íà íàø
+ # âèä ñâîéñòâà ññûëêè: refererClassProp
+ foreach my $referer (grep {not $_->Class->isTemplate} $this->{$Parent}->EnumClassMappings) {
+ next if $referer == $this;
+ foreach my $prop ( grep { $_->isa('Schema::Property') } $referer->{$Class}->ListMembers ) {
+ if($prop->Type->Equals($this->{$Class})) {
+ $text .= "__PACKAGE__->has_many('referer".$referer->GenerateClassName('').$prop->Name."' => '".$referer->GenerateClassName($prefix)."','".$prop->Name."');\n";
+ } elsif ($prop->Type->Name->Name eq 'Set' and $this->{$Class}->Equals($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}) ) {
+ # åñëè êëàññ áûë ïàðàìåòðîì ìíîæåñòâà è $prop->Type è åñòü ýòî ìíîæåñòâî
+ $text .= "__PACKAGE__->has_many('referer".$referer->GeneratePropertyClassName($prop,'')."value' => '".$referer->GeneratePropertyClassName($prop,$prefix)."','value');\n";
+ }
+ }
+ }
+
+ return (@PropertyModules,$text);
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/DataSource/TypeMapping.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/DataSource/TypeMapping.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,46 @@
+use strict;
+package Schema::DataSource::TypeMapping;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Mappings => ACCESS_NONE;
+ DeclareProperty DBIdentifierType => ACCESS_READ;
+ DeclareProperty DBValueType => ACCESS_READ;
+}
+
+sub MapType {
+ my ($this,$Type) = @_;
+
+ if (my $mapped = $this->{$Mappings}->{$Type->Name->Canonical}) {
+ return $mapped;
+ } elsif ($Type->Attributes and $Type->GetAttribute('ValueType')) {
+ return $this->{$DBValueType};
+ } else {
+ return $this->{$DBIdentifierType};
+ }
+}
+
+package Schema::DataSource::TypeMapping::Std;
+use Schema::DB::Type;
+our @ISA = qw(Schema::DataSource::TypeMapping);
+
+sub CTOR {
+ my ($this) = @_;
+ $this->SUPER::CTOR(
+ Mappings => {
+ Identifier => new Schema::DB::Type(Name => 'Integer'),
+ String => new Schema::DB::Type(Name => 'varchar', MaxLength => 255),
+ Integer => new Schema::DB::Type(Name => 'Integer'),
+ Float => new Schema::DB::Type(Name => 'Real'),
+ DateTime => new Schema::DB::Type(Name => 'DateTime'),
+ Bool => new Schema::DB::Type(Name => 'Tinyint'),
+ Blob => new Schema::DB::Type(Name => 'Blob'),
+ Text => new Schema::DB::Type(Name => 'Text')
+ },
+ DBIdentifierType => new Schema::DB::Type(Name => 'Integer'),
+ DBValueType => new Schema::DB::Type(Name => 'varchar', MaxLength => 255)
+ );
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/Form.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/Form.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,252 @@
+package Configuration;
+our $DataDir;
+package Schema::Form;
+use strict;
+use Storable;
+use Common;
+use URI::file;
+use BNFCompiler;
+use Schema::Form::Container;
+use Schema::Form::Field;
+use Schema::Form::Filter;
+use Schema::Form::Format;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Body => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Name} = $args{Name};
+
+}
+
+sub SetBody {
+ my ($this, $containerBody) = @_;
+ $this->{$Body} = $containerBody;
+}
+
+sub list {
+ return( map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : defined $_ ? $_ : () } @_ );
+}
+
+sub LoadForms {
+ my ($class,$File,$CacheDir,$Encoding) = @_;
+
+ $Encoding or die new Exception('An encoding must be specified for forms');
+
+ my $Compiler = new BNFCompiler(SchemaCache => "${DataDir}Cache/",Transform => sub { BNFCompiler::DOM::TransformDOMToHash(@_,{skip_spaces => 1})} );
+ $Compiler->LoadBNFSchema(file => 'Schema/form.def');
+
+ my %Context = (Compiler => $Compiler, Encoding => $Encoding);
+
+ $class->ProcessFile(URI::file->new_abs($File),URI::file->new_abs($CacheDir),\%Context);
+
+ $Compiler->Dispose;
+
+ return $Context{Forms};
+}
+
+sub ProcessFile {
+ my ($class,$uriFile,$uriCacheDir,$refContext) = @_;
+
+ return 1 if $refContext->{'Processed'}{$uriFile->as_string};
+ $refContext->{'Processed'}{$uriFile->as_string} = 1;
+
+ my $Data;
+ my $file = $uriFile->file;
+ my $fnameCached = $file;
+ $fnameCached =~ s/[\\\/:]+/_/g;
+ $fnameCached .= '.cfm';
+ $fnameCached = URI::file->new($fnameCached)->abs($uriCacheDir)->file;
+
+ if ( -e $fnameCached && -f $fnameCached && ( -M $file >= -M $fnameCached ) ) {
+ $Data = retrieve($fnameCached);
+ } else {
+ my $Compiler = $refContext->{'Compiler'};
+ local $/ = undef;
+ open my $hfile,"<:encoding($refContext->{Encoding})",$file or die new Exception('Failed to open file',$file);
+ $Data = $Compiler->Parse(<$hfile>);
+ store($Data,$fnameCached);
+ }
+
+
+ my $uriDir = URI::file->new('./')->abs($uriFile);
+
+ my $needRebuild = 0;
+
+ foreach my $inc (list $Data->{_include}) {
+ $needRebuild ||= $class->ProcessFile(URI::file->new($inc->{file_name})->abs($uriDir),$uriCacheDir,$refContext);
+ }
+
+ foreach my $use (list $Data->{_use}) {
+ $refContext->{Filters}{$use->{alias}} = { Class => join '', list $use->{mod_name} };
+ $refContext->{Require}{$use->{mod_name}} = 1;
+ }
+
+ foreach my $container (list $Data->{container}) {
+ if ($container->{type} eq 'Form') {
+ $class->ConstructForm($container,$refContext);
+ } elsif ($container->{type} eq 'Format') {
+ $class->ConstructFormat($container,$refContext);
+ } elsif ($container->{type} eq 'Filter') {
+ $class->ConstructFilter($container,$refContext);
+ }
+ }
+}
+
+sub ProcessContainer {
+ my ($class,$container,$refContext) = @_;
+}
+
+sub ConstructForm {
+ my ($class,$container,$refContext) = @_;
+
+ $container->{type} eq 'Form' or die new Exception("Unexpected container type");
+
+ not $refContext->{Forms}{$container->{name}} or die new Exception('The form is already exists',$container->{name});
+
+ my $Form = new Schema::Form(Name => $container->{name});
+
+ $Form->SetBody($class->ConstructGroup($container,$refContext));
+
+ $refContext->{Forms}{$Form->Name} = $Form;
+}
+
+sub ConstructGroup {
+ my($class,$container,$refContext) = @_;
+
+ my $Group = new Schema::Form::Container(
+ Name => $container->{name},
+ isMulti => ($container->{multi} ? 1 : 0)
+ );
+
+ foreach my $child (list $container->{body}{container}) {
+ my $obj;
+ if ($child->{type} eq 'Group') {
+ $obj = $class->ConstructGroup($child,$refContext);
+ } else {
+ $obj = $class->ConstructField($child,$refContext);
+ }
+ $Group->AddChild($obj);
+ }
+
+ foreach my $filter (list $container->{expression}) {
+ $Group->AddFilter($class->FilterInstance($filter,$refContext,$container->{name}));
+ }
+
+ foreach my $attr (list $container->{body}{body_property}) {
+ $Group->Attributes->{$attr->{complex_name}} = $class->ScalarExpression($attr->{expression},$container->{name});
+ }
+
+ return $Group;
+}
+
+sub ConstructField {
+ my ($class,$container,$refContext) = @_;
+
+ my $Format = $refContext->{Formats}{$container->{type}} or die new Exception('An undefined format name', $container->{type});
+
+ my $Field = Schema::Form::Field->new(
+ Name => $container->{name},
+ isMulti => ($container->{multi} ? 1 : 0),
+ Format => $Format
+ );
+
+ foreach my $filter (list $container->{expression}) {
+ $Field->AddFilter($class->FilterInstance($filter,$refContext,$container->{name}));
+ }
+
+ foreach my $attr (list $container->{body}{body_property}) {
+ $Field->Attributes->{ref $attr->{complex_name} ? join '', @{$attr->{complex_name}} : $attr->{complex_name}} = $class->ScalarExpression($attr->{expression},$container->{name});
+ }
+
+ return $Field;
+}
+
+sub FilterInstance {
+ my ($class,$expr,$refContext,$where) = @_;
+
+ my $filter = $expr->{instance} or die new Exception('Invalid filter syntax',$where);
+
+ my $filterClass = $refContext->{Filters}{$filter->{name}}{Class} or die new Exception('Using undefined filter name',$filter->{name},$where);
+
+ my @Args = map { $class->ScalarExpression($_,$where) } list $filter->{expression};
+
+ my $Filter = Schema::Form::Filter->new(
+ Name => $filter->{name},
+ Class => $filterClass,
+ Args => \@Args
+ );
+
+ if ($refContext->{Filters}{$filter->{name}}{Attributes}) {
+ while (my ($name,$value) = each %{$refContext->{Filters}{$filter->{name}}{Attributes}}) {
+ $Filter->Attributes->{$name} = $value;
+ }
+ }
+
+ return $Filter;
+}
+
+sub ScalarExpression {
+ my ($class,$expr,$where) = @_;
+
+ my $val;
+ if ($expr->{instance}) {
+ $val = $expr->{instance}{name};
+ } elsif ($expr->{string}) {
+ $val = join '', list $expr->{string};
+ $val =~ s/\\(.)/
+ if ($1 eq '"' or $1 eq '\\') {
+ $1;
+ } else {
+ "\\$1";
+ }
+ /ge;
+ } elsif ($expr->{number}) {
+ $val = join '', list $expr->{number};
+ } else {
+ die new Exception('Scalar expression required');
+ }
+
+ return $val;
+}
+
+sub ConstructFormat {
+ my ($class,$container,$refContext) = @_;
+
+ my $Format = Schema::Form::Format->new (
+ Name => $container->{name}
+ );
+
+ foreach my $filter (list $container->{expression}) {
+ $Format->AddFilter($class->FilterInstance($filter,$refContext,$container->{name}));
+ }
+
+ foreach my $attr (list $container->{body}{body_property}) {
+ $Format->Attributes->{ref $attr->{complex_name} ? join '', @{$attr->{complex_name}} : $attr->{complex_name}} = $class->ScalarExpression($attr->{expression},$container->{name});
+ }
+
+ $refContext->{Formats}{$Format->Name} = $Format;
+}
+
+sub ConstructFilter {
+ my ($class,$container,$refContext) = @_;
+
+ foreach my $attr (list $container->{body}{body_property}) {
+ $refContext->{Filters}{$container->{name}}{Attributes}{ref $attr->{complex_name} ? join '', @{$attr->{complex_name}} : $attr->{complex_name}} = $class->ScalarExpression($attr->{expression},$container->{name});
+ }
+}
+
+=pod
+Form schema - îïèñàíèå ôîðìû ââîäà è ïðàâèëà êîíòðîëÿ
+
+Form instance - çíà÷åíèÿ ýëåìåíòîâ ôîðìû
+
+=cut
+
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/Form/Container.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/Form/Container.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,41 @@
+package Schema::Form::Container;
+use Form::Container;
+use Common;
+use base qw(Schema::Form::Item);
+
+BEGIN {
+ DeclareProperty Children => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->SUPER::CTOR(@args{qw(Name isMulti Filters)});
+
+ $this->{$Children} = [];
+
+}
+
+sub AddChild {
+ my ($this,$child) = @_;
+
+ not grep { $_->Name eq $child->Name } $this->Children or die new Exception("The item already exists",$child->Name);
+
+ push @{$this->{$Children}},$child;
+}
+
+sub FindChild {
+ my ($this,$name) = @_;
+
+ my @result = grep { $_->Name eq $name} $this->Children;
+ return $result[0];
+}
+
+sub Dispose {
+ my ($this) = @_;
+
+ delete $this->{$Children};
+
+ $this->SUPER::Dispose;
+}
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/Form/Field.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/Form/Field.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,33 @@
+package Schema::Form::Field;
+use strict;
+use Common;
+use base qw(Schema::Form::Item);
+
+BEGIN {
+ DeclareProperty Format => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $args{'Format'} or die new Exception('A format is required for a field');
+
+ $args{'Attributes'} = { %{$args{Format}->Attributes},%{$args{Attributes} || {} } };
+
+ $this->SUPER::CTOR(@args{qw(Name isMulti Filters Attributes)});
+ $this->{$Format} = $args{'Format'};
+}
+
+=pod
+Ñíà÷àëà ïðèìåíèòü ôèëüòðû ôîðìàòà à ïîòîì ôèëüòðû ïîëÿ
+=cut
+sub Filters {
+ my ($this) = @_;
+
+ my @filters = $this->{$Format}->Filters;
+ push @filters,$this->SUPER::Filters;
+
+ return @filters;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/Form/Filter.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/Form/Filter.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,46 @@
+package Schema::Form::Filter;
+use strict;
+use Common;
+our @ISA = qw(Object);
+
+my %LoadedModules;
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Class => ACCESS_READ;
+ DeclareProperty Args => ACCESS_READ;
+ DeclareProperty Attributes => ACCESS_READ;
+ DeclareProperty _Instance => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Name} = $args{'Name'} or die new Exception('A filter name is required');
+ $this->{$Class} = $args{'Class'} or die new Exception('A filter class is required');
+ $this->{$Args} = $args{'Args'};
+ $this->{$Attributes} = {};
+}
+
+sub Create {
+ my ($this) = @_;
+
+ if (not $LoadedModules{$this->{$Class}}) {
+ eval "require $this->{$Class};" or die new Exception('Can\'t load the specified filter',$this->{$Name},$this->{$Class},$@);
+ $LoadedModules{$this->{$Class}} = 1;
+ }
+
+ return $this->{$Class}->new($this->{$Name},$this->{$Attributes}{'message'},$this->Args);
+}
+
+sub Instance {
+ my ($this) = @_;
+
+ if (my $instance = $this->{$_Instance}) {
+ return $instance;
+ } else {
+ return $this->{$_Instance} = $this->Create;
+ }
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/Form/Format.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/Form/Format.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,26 @@
+package Schema::Form::Format;
+use strict;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty Filters => ACCESS_READ;
+ DeclareProperty Attributes => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ $this->{$Name} = $args{'Name'} or die new Exception('A format name is required');
+ $this->{$Filters} = [];
+ $this->{$Attributes} = $args{'Attributes'} || {};
+}
+
+sub AddFilter {
+ my ($this,$filter) = @_;
+
+ push @{$this->{$Filters}},$filter;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Schema/Form/Item.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Schema/Form/Item.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,41 @@
+package Schema::Form::Item;
+use strict;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Name => ACCESS_READ;
+ DeclareProperty isMulti => ACCESS_READ;
+ DeclareProperty Filters => ACCESS_READ;
+ DeclareProperty Attributes => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,$name,$multi,$filters,$attributes) = @_;
+
+ $this->{$Name} = $name or die new Exception("A name is required for the item");
+ $this->{$isMulti} = defined $multi ? $multi : 0;
+ $this->{$Filters} = $filters || [];
+ $this->{$Attributes} = $attributes || {};
+}
+
+sub AddFilter {
+ my ($this,$filter) = @_;
+
+ push @{$this->{$Filters}}, $filter;
+}
+
+sub isMandatory {
+ my ($this) = @_;
+
+ return ( grep $_->Name eq 'mandatory', $this->Filters ) ? 1 : 0 ;
+}
+
+sub GetFirstFilter {
+ my ($this,$filterName) = @_;
+
+ my ($filter) = grep $_->Name eq $filterName, $this->Filters;
+ return $filter;
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Security.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Security.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,38 @@
+use strict;
+package Security;
+
+use constant {
+ AUTH_FAILED => 0,
+ AUTH_SUCCESS => 1,
+ AUTH_INCOMPLETE => 2,
+ AUTH_NOAUTH => 3
+};
+
+my $CurrentSession;
+
+sub CurrentSession {
+ my ($class,$newSession) = @_;
+
+ $CurrentSession = $newSession if @_>=2;
+ return $CurrentSession;
+}
+
+package Security::AuthResult;
+use Common;
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty State => ACCESS_READ;
+ DeclareProperty Session => ACCESS_READ;
+ DeclareProperty ClientSecData => ACCESS_READ;
+ DeclareProperty AuthMod => ACCESS_READ;
+}
+
+sub isSuccess {
+ my ($this) = @_;
+ return $this->{$State} == Security::AUTH_SUCCESS;
+}
+
+
+1;
+
diff -r 000000000000 -r 03e58a454b20 Lib/Security/Auth.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Security/Auth.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,108 @@
+package Security::Auth;
+use strict;
+use Common;
+use Security;
+use DateTime;
+use Digest::MD5 qw(md5_hex);
+our @ISA = qw(Object);
+
+our $Package;
+our $DataSource;
+
+srand time;
+
+BEGIN {
+ DeclareProperty DS => ACCESS_READ;
+ DeclareProperty SecPackage => ACCESS_READ;
+}
+
+{
+ my $i = 0;
+ sub GenSSID() {
+ return md5_hex(time,rand,$i++);
+ }
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+ $this->{$DS} = $args{'DS'} or die new Exception('A data source is required');
+ $this->{$SecPackage} = $args{'SecPackage'} or die new Exception('A security package is required');
+}
+
+sub AuthenticateUser {
+ my ($this,$Name,$SecData) = @_;
+
+ my $User = $this->{$DS}->FindUser($Name);
+ if (not $User or not $User->Active ) {
+ return new Security::AuthResult (
+ State => Security::AUTH_FAILED,
+ AuthModule => $this
+ );
+ } else {
+
+
+ if (my $StoredData = $this->{$DS}->GetUserAuthData($User,$this->{$SecPackage})) {
+ my $AuthData = $this->{$SecPackage}->ConstructAuthData($StoredData->AuthData);
+ if ((my $status = $AuthData->DoAuth($SecData)) != Security::AUTH_FAILED) {
+ $AuthData = $this->{$SecPackage}->NewAuthData(GenSSID);
+ return new Security::AuthResult (
+ State => $status,
+ Session => $this->{$DS}->CreateSession(GenSSID,$User,$AuthData),
+ ClientSecData => $AuthData->ClientAuthData,
+ AuthModule => $this
+ )
+ } else {
+ return new Security::AuthResult (
+ State => Security::AUTH_FAILED,
+ AuthModule => $this
+ );
+ }
+ } else {
+ # the user isn't allowed to authenticate using this method
+ return new Security::AuthResult (
+ State => Security::AUTH_FAILED,
+ AuthModule => $this
+ );
+ }
+ }
+}
+
+sub AuthenticateSession {
+ my ($this,$SSID,$SecData) = @_;
+
+ my $Session = $this->{$DS}->LoadSession($SSID) or return new Security::AuthResult(State => Security::AUTH_FAILED);
+
+ my $AuthData = $this->{$SecPackage}->ConstructAuthData($Session->SecData);
+ if ((my $status = $AuthData->DoAuth($SecData)) != Security::AUTH_FAILED) {
+ $Session->SecData($AuthData->SessionAuthData);
+ $Session->LastUsage(DateTime->now());
+ return new Security::AuthResult(State => $status, Session => $Session, ClientSecData => $AuthData->ClientAuthData, AuthModule => $this);
+ } else {
+ $this->{$DS}->CloseSession($Session);
+ return new Security::AuthResult(State => Security::AUTH_FAILED, AuthModule => $this);
+ }
+}
+
+sub CreateUser {
+ my ($this,$uname,$description,$active,$secData) = @_;
+
+ my $user = $this->{$DS}->CreateUser($uname,$description,$active);
+ $this->{$DS}->SetUserAuthData($user,$this->{$SecPackage},$this->{$SecPackage}->NewAuthData($secData));
+
+ return $user;
+}
+
+sub try_construct {
+ my $package = shift;
+ return $package->can('construct') ? $package->construct() : $package;
+}
+
+sub construct {
+ $Package or die new Exception('A security package is reqiured');
+ $DataSource or die new Exception('A data source is required');
+ eval "require $DataSource;" or die new Exception('Failed to load the data source module',$@) if not ref $DataSource;
+ eval "require $Package;" or die new Exception('Failed to load the security package module',$@) if not ref $Package;
+ return __PACKAGE__->new(DS => try_construct($DataSource), SecPackage => try_construct($Package));
+}
+
+1;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Lib/Security/Auth/Simple.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Security/Auth/Simple.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,73 @@
+package Security::Auth::Simple;
+use strict;
+use Common;
+
+our $Strict;
+
+our @ISA = qw(Object);
+
+sub Name {
+ return 'Simple';
+}
+
+sub ConstructAuthData {
+ my ($class,$SecData) = @_;
+ return new Security::Auth::Simple::AuthData(DataMD5 => $SecData);
+}
+
+sub NewAuthData {
+ my ($class,$SecData) = @_;
+ return new Security::Auth::Simple::AuthData(Data => $SecData);
+
+}
+
+package Security::Auth::Simple::AuthData;
+use Common;
+use Security;
+use Security::Auth;
+use Digest::MD5 qw(md5_hex);
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty Data => ACCESS_READ;
+ DeclareProperty DataMD5 => ACCESS_READ;
+}
+
+sub CTOR {
+ my ($this,%args) = @_;
+
+ if ($args{'Data'}) {
+ $args{'DataMD5'}= $args{'Data'} ? md5_hex($args{'Data'}) : undef ;
+ $this->{$Data} = $args{'Data'};
+ }
+ $this->{$DataMD5} = $args{'DataMD5'};
+}
+
+sub DoAuth {
+ my ($this,$SecData) = @_;
+
+ if (not ($this->{$DataMD5} or $SecData) or $this->{$DataMD5} eq md5_hex($SecData)) {
+ if ($Strict) {
+ $this->{$Data} = Security::Auth::GenSSID;
+ $this->{$DataMD5} = md5_hex($this->{$Data});
+ } else {
+ $this->{$Data} = $SecData;
+ }
+ return Security::AUTH_SUCCESS;
+ } else {
+ return Security::AUTH_FAILED;
+ }
+}
+
+sub SessionAuthData {
+ my ($this) = @_;
+
+ return $this->{$DataMD5};
+}
+
+sub ClientAuthData {
+ my ($this) = @_;
+ return $this->{$Data};
+}
+
+1;
diff -r 000000000000 -r 03e58a454b20 Lib/Security/Authz.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/Security/Authz.pm Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,33 @@
+package Security::Authz;
+use Common;
+use Security;
+
+our @ISA = qw(Object);
+
+BEGIN {
+ DeclareProperty User => ACCESS_READ;
+}
+
+sub _CurrentUser {
+ my ($class) = @_;
+
+ if (ref $class) {
+ return $class->{$User};
+ } else {
+ if (Security->CurrentSession) {
+ Security->CurrentSession->User;
+ } else {
+ return undef;
+ }
+ }
+}
+
+sub demand {
+ my ($class,@Roles) = @_;
+
+ return 0 if not $class->_CurrentUser;
+
+ my %UserRoles = map { $_->Name, 1 } $class->_CurrentUser->Roles;
+
+ return not grep {not $UserRoles{$_}} @Roles;
+}
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Schema/form.def
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Schema/form.def Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,39 @@
+syntax ::= {{_include|_use}|container}[ {{_include|_use}|container} ...]
+
+name ::=<\w>+
+
+file_name ::=<\w./>+
+
+mod_name ::= <\w>+[::<\w>+...]
+
+_include ::= include file_name ;
+
+_use ::= use alias mod_name ;
+
+alias ::= <\w>+
+
+type ::=<\w>+
+
+multi ::=*
+
+container ::=type [multi] name[ : expression [, expression ...]] [body];
+
+instance ::= name[ ( expression [, expression ...])]
+
+string ::=[{<^\\">+|<\\><\w\W>}...]
+
+number ::=[{+|-}] <0-9>+[.<0-9>+[e[-]<0-9>+]]
+
+bin_op ::={+|-|&|<|>|=}
+
+un_op ::=!
+
+expression ::= {"string"|number|instance|(expression)|{"string"|number|instance|(expression)} bin_op expression|un_op expression}
+
+body ::= <{>
+ [{body_property|container} ...]
+<}>
+
+complex_name ::= <\w>+[.<\w>+...]
+
+body_property ::= complex_name = expression;
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Schema/query.def
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Schema/query.def Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,27 @@
+syntax ::= select expr_list from var_defs where condition
+
+name ::= <\w>+
+
+fqdn ::= name[.name...]
+
+string ::= '[{<^'>+|<'>{2}}...]'
+
+number ::= [{+|-}] <\d>+
+
+math_op ::= {+|-|*|/}
+
+compare_op ::= {<\>>|<\<>|==|!=}
+
+log_op ::= {OR|AND}
+
+not_op ::= NOT
+
+expr ::= {string|number|fqdn} [math_op {string|number|fqdn|( expr )} ...]
+
+expr_list ::= expr [, expr ...]
+
+type ::= name [<\<>type [, type ...]<\>>]
+
+condition ::= [not_op] expr compare_op expr [log_op {condition|( condition )} ...]
+
+var_defs ::= name as type [, name as type ...]
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Schema/schema.def
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Schema/schema.def Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,43 @@
+syntax ::= header[ class ...]
+
+name ::= <\w>+
+
+column_name ::= {<\w>+|<[><^[]>+<]>}
+
+type ::= name [<\<> template_list <\>>]
+
+type_definition ::= name [<\<> args_list <\>>]
+
+args_list ::= name [, name ...]
+
+db_type ::= <\w>+[(<\d>+[,<\d>+])]
+
+template_list ::= type[, type ...]
+
+mapping ::= column_name [as db_type]
+
+property ::= type name[ =<\>> mapping]
+
+comment ::= #<^\n>*<\n>[ #<^\n>*<\n>...]
+
+property_list ::= property ; [comment] [property ; [comment] ...]
+
+base_types ::= type [, type ...]
+
+value_type ::= value
+
+class ::=
+[comment][value_type ]type_definition [: base_types] <{>
+ [comment]
+ [property_list]
+<}>
+
+header_value ::= {*<^;>+ {;<\n>| header_value}|<^\n>+[;]}
+
+header_prop ::= name = header_value
+
+file_name ::=<\w./>+
+
+include_item ::= include ( file_name )[;]
+
+header ::=[ {header_prop|include_item} ...]
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 Schema/type.def
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Schema/type.def Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,3 @@
+syntax ::= name [<\<>syntax [, syntax ...]<\>>]
+
+name ::= <\w>+
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 _test/object.t
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/_test/object.t Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,33 @@
+#!/usr/bin/perl -w
+use strict;
+use lib '../Lib';
+
+package Foo;
+use base qw(IMPL::Object);
+
+sub CTOR {
+ my ($this,%args) = @_;
+ print "CTOR Foo $args{Name}\n";
+}
+
+sub Hello {
+ print "Hello";
+}
+
+package Bar;
+use base qw(Foo);
+
+__PACKAGE__->PassThroughArgs;
+
+sub CTOR {
+ print "CTOR Bar\n";
+}
+
+package main;
+
+my $obj = new Bar ( Name => 'Tom') ;
+
+Hello $obj;
+
+no strict 'refs';
+print "$_\n" foreach sort keys %{'Bar::'};
\ No newline at end of file
diff -r 000000000000 -r 03e58a454b20 impl.kpf
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/impl.kpf Tue Jul 14 12:54:37 2009 +0400
@@ -0,0 +1,475 @@
+
+
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+ 1
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+
+
+
+
+
+ 9011
+
+
+ _test/object.t
+
+ Perl
+
+
+
+ application/x-www-form-urlencoded
+ GET
+ 1
+ 0
+ 0
+
+
+ enabled
+
+
+ default
+
+