view Lib/IMPL/Config.pm @ 194:4d0e1962161c

Replaced tabs with spaces IMPL::Web::View - fixed document model, new features (control classes, document constructor parameters)
author cin
date Tue, 10 Apr 2012 20:08:29 +0400
parents d1676be8afcc
children d63f9a92d6d4
line wrap: on
line source

package IMPL::Config;
use strict;
use warnings;

use parent qw(IMPL::Object::Accessor IMPL::Object::Serializable IMPL::Object::Autofill);

__PACKAGE__->PassThroughArgs;

use File::Spec();

use IMPL::Class::Member;
use IMPL::Class::PropertyInfo;
use IMPL::Exception;

use IMPL::Serialization;
use IMPL::Serialization::XmlFormatter;

our $ConfigBase ||= '';

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) = @_;
    
    my $val;

    $val = $this->rawGet($_) and $ctx->AddVar($_ => $val) foreach map $_->Name, $this->get_meta(
        'IMPL::Class::PropertyInfo',
        sub {
            $_->Access == IMPL::Class::Member::MOD_PUBLIC and
            $_->canGet;
        },
        1);    
}

sub spawn {
    my ($this,$file) = @_;
    unless ($file) {
        ($file = ref $this || $this) =~ s/:+/./g;
        $file .= ".xml";
    }
    return $this->LoadXMLFile( File::Spec->catfile($ConfigBase,$file) );
}

sub get {
    my $this = shift;
    
    if (@_ == 1) {
        my $obj = $this->SUPER::get(@_);
        return UNIVERSAL::isa($obj,'IMPL::Config::Activator') ? $obj->activate : $obj;
    } else {
        my @objs = $this->SUPER::get(@_);    
        return map UNIVERSAL::isa($_,'IMPL::Config::Activator') ? $_->activate : $_, @objs ;    
    }
}

sub rawGet {
    my $this = shift;
    return $this->SUPER::get(@_);
}

sub Exists {
    $_[0]->SUPER::get($_[1]) ? 1 : 0;
}

1;
__END__

=pod

=head1 NAME

C<IMPL::Config> - базовый класс для настраиваемого приложения.

=head1 SYNOPSIS

=begin code

# define application

package MyApp;
use parent qw(IMPL::Config);

use IMPL::Class::Property;
use IMPL::Config::Class;

BEGIN {
    public property SimpleString => prop_all;
    public property DataSource => prop_all; 
}

sub CTOR {
    my $this = shift;
    
    $this->DataSource(
        new IMPL::Config::Activator(
            factory => 'MyDataSource',
            parameters=>{
                host => 'localhost',
                user => 'dbuser'
            }
        )
    ) unless $this->Exists('DataSource');
}

# using application object

my $app = spawn MyApp('default.xml');

$app->Run();

=end code

Ниже приведен пример файла C<default.xml> содержащего настройки приложения

=begin code xml

<app type='MyApp'>
    <SimpleString>The application</SimpleString>
    <DataSource type='IMPL::Config::Activator'>
        <factory>MyDataSourceClass</factory>
        <parameters type='HASH'>
            <host>localhost</host>
            <user>dbuser</user>
        </parameters>
    </DataSource>
</app>

=end code xml

=head1 DESCRIPTION

C<[Serializable]>

C<[Autofill]>

C<use parent IMPL::Object::Accessor>

Базовый класс для приложений. Использует подход, что приложение
является объектом, состояние которого предтавляет собой конфигурацию,
а методы - логику.

Данный класс реализует функционал десериализации (и сериализации) экземпляра
приложения из XML документа. Для этого используется механизм C<IMPL::Serialization>.
При этом используются опции C<IMPL::Serialization::XmlFormatter> C<IdentOutput> и
C<SkipWhitespace> для записи документа в легко читаемом виде.

Поскольку в результате восстановления приложения восстанавливаются все элементы
из файла конфигурации, то это может потребовать значительных ресурсов для
создания частей, которые могут никогда не понадобиться. Например, не требуется инициализация
источника данных для передачи пользователю статических данных, сохраненных на диске.

Для решения этой проблемы используются специальные объекты C<IMPL::Config::Activator>.

Если у приложения описано свойство, в котором хранится C<IMPL::Config::Activator>, то
при первом обращении к такому свойству, будет создан объект вызовом метода
C<< IMPL::Config::Activator->activate() >> и возвращен как значение этого свойства.
Таким образом реализуется прозрачная отложенная активация объектов, что позволяет
экономить ресурсы. 

=head1 MEMBERS

=over

=item C<[static] LoadXMLFile($fileName) >

Создает из XML файла C<$fileName> экземпляр приложения

=item C<SaveXMLFile($fileName)>

Сохраняет приложение в файл C<$fileName>

=item C<[get] xml >

Сохраняет конфигурацию приложения в XML строку.

=item C<[static,operator] spawn($file)>

Синоним для C<LoadXMLFile>, предполагается использование как оператора.

=item C<rawGet($propname,...)>

Метод для получения значений свойств приложения. Данный метод позволяет избежать
использование активации объектов через C<IMPL::Config::Activator>.

=back

=cut