view lib/IMPL/Config.pm @ 409:f7eeafbd33da ref20150831

sync
author cin
date Sun, 13 Sep 2015 19:30:49 +0300
parents c6e90e02dd17
children
line wrap: on
line source

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

use Carp qw(carp);

use IMPL::lang qw(is);
use IMPL::Exception;
use IMPL::Const qw(:access);
use IMPL::declare {
	require => {
		PropertyInfo => 'IMPL::Class::PropertyInfo',
		XmlFormatter => 'IMPL::Serialization::XmlFormatter',
		Serializer => '-IMPL::Serializer',
		Activator => '-IMPL::Config::Activator',
		
		Exception => 'IMPL::Exception',
		IOException => '-IMPL::IOException'
	},
    base => [
        'IMPL::Object::Accessor' => undef,
        'IMPL::Object::Serializable' => undef,
        'IMPL::Object::Autofill' => '@_'
    ]
};

use File::Spec();


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

sub LoadXMLFile {
    my ($self,$file) = @_;
    
    my $class = ref $self || $self;
    
    my $serializer = Serializer->new(
        formatter => XmlFormatter->new(
            IdentOutput => 1,
            SkipWhitespace => 1
        )
    );
    
    open my $hFile,'<',$file or die IOException->new("Failed to open file",$file,$!);
    
    my $obj;
    eval {
        $obj = $serializer->Deserialize($hFile);
    };
    
    if ($@) {
        my $e=$@;
        die Exception->new("Can't load the configuration file",$file,$e);
    }
    return $obj;
}

sub SaveXMLFile {
    my ($this,$file) = @_;
    
    my $serializer = Serializer->new(
        formatter => XmlFormatter->new(
            IdentOutput => 1,
            SkipWhitespace => 1
        )
    );
    
    open my $hFile,'>',$file or die IOException->new("Failed to open file",$file,$!);
    
    $serializer->Serialize($hFile, $this);
}

sub xml {
    my $this = shift;
    my $serializer = Serializer->new(
        formatter => XmlFormatter->new(
            IdentOutput => 1,
            SkipWhitespace => 1
        )
    );
    my $str = '';
    open my $hFile,'>',\$str or die IOException->new("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(
        PropertyInfo,
        sub {
            $_->access == ACCESS_PUBLIC and
            $_->setter;
        },
        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 is($obj,Activator) ? $obj->activate : $obj;
    } else {
        my @objs = $this->SUPER::get(@_);    
        return map is($_,Activator) ? $_->activate : $_, @objs ;    
    }
}

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

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

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

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

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

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

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

sub ConfigFile {
	shift;
	File::Spec->catfile($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