view Lib/IMPL/Config.pm @ 263:0f59b2de72af

*fixed IMPL::DOM::Schema circular module references *modified IMPL::Object::Singleton, added auto-activation *code cleanups, docs
author sergey
date Wed, 09 Jan 2013 05:17:44 +0400
parents 891c04080658
children 4ddb27ff4a0b
line wrap: on
line source

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

use IMPL::declare {
    base => [
        'IMPL::Object::Accessor' => undef,
        'IMPL::Object::Serializable' => undef,
        'IMPL::Object::Autofill' => '@_'
    ]
};

use File::Spec();

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

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

our $ConfigBase ||= '';
our $AppBase;

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;
}

sub AppBase {
	shift;
	File::Spec->catdir($AppBase,@_);
}

sub ConfigBase {
	shift;
	File::Spec->catdir($ConfigBase,@_);
}

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