package Gestinanna::SiteConfiguration; use Storable (); use XML::LibXML; use Gestinanna::POF; use Gestinanna::Request; use Gestinanna::Workflow::Factory; use strict; =begin testing # new __OBJECT__ = __PACKAGE__ -> __METHOD__; isa_ok(__OBJECT__, '__PACKAGE__'); =end testing =cut sub new { my $class = shift; return bless { @_ } => $class; } =begin testing # parse_config __OBJECT__ -> parse_config(<<'EOXML'); test-1.0 testing-1.1 testinging-1.2 Gestinanna::XSM::Auth Gestinanna::XSM::Authz Gestinanna::XSM::ContentProvider Gestinanna::XSM::Diff Gestinanna::XSM::Digest Gestinanna::XSM::Gestinanna Gestinanna::XSM::POF Gestinanna::XSM::SMTP EOXML is(__OBJECT__ -> package, "__PACKAGE__::Test"); =end testing =cut sub parse_config { my($self, $conf) = @_; return unless $conf !~ m{^\s*$}; my $parser = XML::LibXML -> new; my $doc = $parser -> parse_string($conf); my $root = $doc -> getDocumentElement; my %conf; $self -> {config} = \%conf; $conf{package} = $root -> getAttribute('package'); $conf{anonymous_id} = $root -> getAttribute('anonymous-id'); # my $tagpath = $root -> findnodes('tagpath/*'); # have my %tagpaths = ( test => [ ], 'pre-production' => [ ], production => [ ], ); foreach my $part ($tagpath -> get_nodelist) { my $type = $part -> nodeName; next unless exists $tagpaths{$type}; push @{$tagpaths{$type}}, map { $_ -> textContent } $part -> findnodes('tag'); } $conf{tagpaths} = \%tagpaths; # $conf{default_theme} = $root -> findvalue('themes/@default'); my $themes = $root -> findnodes('themes/theme'); my %themes; foreach my $theme ($themes -> get_nodelist) { my $xml = join("\n", map { $_ -> toString} ($theme -> childNodes)); $xml =~ s{^\s*}{}; $xml =~ s{\s*$}{}; $themes{$theme -> findvalue('@id')} = $xml; } $conf{themes} = \%themes; foreach my $type (qw(data content security)) { my $dts = $root -> findnodes($type . '-type'); my %types; foreach my $dt ($dts -> get_nodelist) { $types{$dt -> getAttribute('id')} = $dt -> getAttribute('class'); } $conf{$type . '_types'} = \%types; } foreach my $provider (qw(data content)) { my %providers; my $dps = $root -> findnodes($provider.'-provider'); $self -> {parsing_provider_type} = $provider; foreach my $dp ($dps -> get_nodelist) { my $type = $dp -> getAttribute('type'); my $config = $self -> parse_provider_config($dp); $providers{$type} = $config if $config; } $conf{$provider . "_provider"} = \%providers; delete $self -> {parsing_provider_type}; } $conf{session} = { cookie => { map { $_ => $root -> findvalue("session/cookie/\@$_") } qw( name secure expires ) }, store => { map { $_ => $root -> findvalue("session/store/\@$_") } qw( store lock generate serialize ) }, }; my($dp) = $root -> findnodes('workflow'); if($dp) { foreach my $attr (@Gestinanna::Workflow::Factory::XML_ATTRIBUTES) { $conf{workflow}{params}{$attr} = $dp -> getAttribute($attr); } $conf{workflow}{config} = Gestinanna::Workflow::Factory -> parse_config( site => $self, params => $conf{workflow}{params}, nodes => [ ($dp -> childNodes) ] ); } warn "Workflow config: ", Data::Dumper -> Dump([$conf{workflow}]); # $self -> {config} = \%conf; } sub store_config { my($self) = @_; my $parser = XML::LibXML -> new; my $dom = $parser -> createDocument( "1.0", "UTF8" ); my $config = $self -> {config}; my $root = $dom -> createElement( 'configuration' ); $root -> setAttribute(package => $config -> {package}) if defined $config -> {package}; $root -> setAttribute('anonymous-id' => $config -> {anonymous_id}) if defined $config -> {anonymous_id}; $dom -> setDocumentElement( $root ); my $session = $dom -> createElement( 'session' ); foreach my $part (qw(cookie store)) { my $node = $dom -> createElement( $part ); foreach my $attr (keys %{$config -> {session} -> {$part}||{}}) { $node -> setAttribute($attr => $config -> {session} -> {$part} -> {$attr}) if defined $config -> {session} -> {$part} -> {$attr}; } $session -> appendChild( $node ); } $root -> appendChild( $session ); my $tagpath = $dom -> createElement( 'tagpath' ); foreach my $type (keys %{$config -> {tagpaths} || {}}) { my $parent_node = $dom -> createElement( $type ); foreach my $tag (@{$config -> {tagpaths} -> {$type} || []}) { $parent_node -> appendTextChild( 'tag', $tag ); } $tagpath -> appendChild($parent_node); } $root -> appendChild( $tagpath ); my $themes = $dom -> createElement( 'themes' ); $themes -> setAttribute(default => $config -> {default_theme}) if defined $config -> {default_theme}; foreach my $theme (keys %{$config -> {themes} || {}}) { my $theme_node = $dom -> createElement( 'theme' ); $theme_node -> setAttribute(id => $theme); if($config -> {themes} -> {$theme}) { my $fragment = $parser->parse_xml_chunk($config -> {themes} -> {$theme}); $theme_node -> appendChild( $_ ) for $fragment -> childNodes; } $themes -> appendChild( $theme_node ); } $root -> appendChild($themes); foreach my $provider (qw(data content security)) { foreach my $id (keys %{$config -> {$provider . '_types'} || {}}) { my $node = $dom -> createElement( $provider . '-type' ); $node -> setAttribute( id => $id ); $node -> setAttribute( class => $config -> {$provider . '_types'} -> {$id} ); $root -> appendChild( $node ); } foreach my $dp (keys %{$config -> {"${provider}_provider"} || {}}) { my $dp_root = $dom -> createElement( "${provider}-provider" ); $self -> {storing_provider_type} = $provider; $self -> store_provider_config( $dp_root, $config -> {"${provider}_provider"} -> {$dp} ); $dp_root -> setAttribute( type => $dp ); $root -> appendChild( $dp_root ); delete $self -> {storing_provider_type}; } } return $dom -> toString(1); } sub parse_provider_config { my($self, $dp) = @_; my $type = $self -> {parsing_provider_type} . '_types'; my $type_attr = $self -> {parsing_provider_type} . '-type'; my $types = $self -> _types($self -> {parsing_provider_type}); my $data_type = $dp -> getAttribute($type_attr); my $class = $types -> {$data_type}; eval { eval "require $class;" }; return if $@; my %params; no strict 'refs'; foreach my $attr (@{"${class}::XML_ATTRIBUTES"}, 'resource') { $params{$attr} = $dp -> getAttribute($attr); } my $config = { }; $config = $class -> parse_config( site => $self, params => \%params, nodes => [ ($dp -> childNodes) ] ) if $class && $class -> can('parse_config'); return { params => \%params, class => $class, type => $data_type, config => $config, }; } sub store_provider_config { my($self, $root, $config) = @_; my $type = $self -> {storing_provider_type} . "_types"; my $attr = $self -> {storing_provider_type} . "-type"; my $types = $self -> {$type}; my $class = $types -> {$config->{type}}; eval { eval "require $class;" }; return if $@; $class -> store_config( site => $self, params => $config -> {params}, config => $config -> {config}, root => $root, ) if $class -> can('store_config'); foreach my $attr (@{"${class}::XML_ATTRIBUTES"}) { $root -> setAttribute($attr, $config -> {params} -> {$attr}) if defined $config -> {params} -> {$attr}; } } sub build_object_class { my($self, %params) = @_; my($package, $config) = @params{qw(class config)}; # passes it along to the right perl class my $class = $self -> {data_types} -> {$config -> {'data-type'}}; eval { eval "require $class;" }; return if $@; $class -> build_object_class( site => $self, class => $package, config => $config -> {config}, params => $config -> {params}, ); } ### ### accessors ### # these need to reference both the global and local configs sub parent { $_[0] -> {parent} } sub anonymous_id { return $_[0] -> {config} -> {anonymous_id} if $_[0] -> {config} -> {anonymous_id} ne ''; return $_[0] -> {parent} -> anonymous_id if defined $_[0] -> {parent}; } sub new_cookie { # returns an Apache::Cookie object my $self = shift; my $r; $r = shift if ref $_[0]; my $config = $self -> {config}; my $parent = $self -> {parent}; my $cookie; if($parent) { $cookie = $parent -> new_cookie($r); } else { if($r) { require Apache::Cookie; $cookie = Apache::Cookie -> new($r); } else { require CGI::Cookie; $cookie = CGI::Cookie -> new; } } # allow override of parent if we went to parent foreach my $field (qw(name expires secure)) { my $v = $self -> session_cookie_field($field); $cookie -> $field($v) if defined($v) && $v ne ''; } $cookie -> value($_[0]) if @_; return $cookie; } sub session_cookie_field { my($self, $field) = @_; return $self -> {config} -> {session} -> {cookie} -> {$field} if $self -> {config} -> {session} -> {cookie} -> {$field} ne ''; return $self -> {parent} -> session_cookie_field($field) if $self -> {parent}; } sub session_cookie { my $self = shift; my $name = $self -> session_cookie_field('name'); return unless defined $name && $name ne ''; my $cookies; if(Gestinanna::Request -> in_mod_perl) { $cookies = Apache::Cookie -> fetch; } else { $cookies = CGI::Cookie -> fetch; } return $cookies -> {$name}; } sub session_params { my $self = shift; my $config = $self -> {config}; my $parent = $self -> {parent}; my $params = { }; $params = $parent -> session_params if $parent; foreach my $p (qw(store lock generate serialize)) { $params -> {ucfirst $p} = $config -> {session} -> {store} -> {$p} if $config -> {session} -> {store} -> {$p}; } return $params; } =begin testing # package is(__OBJECT__ -> package, "__PACKAGE__::Test"); =end testing =cut sub package { my $self = shift; my $global_package; if($self -> {parent}) { $global_package = $self -> {parent} -> package; } if(defined $global_package && $global_package =~ m{::$}) { return $global_package . $self -> {config} -> {package}; } return $self -> {config} -> {package} if defined $self -> {config} -> {package}; return $global_package; } sub default_theme { my $self = shift; return $self -> {config} -> {default_theme} if defined $self -> {config} -> {default_theme} && $self -> {config} -> {default_theme} ne ''; return $self -> {parent} -> default_theme if defined $self -> {parent}; } foreach my $type (qw(data content)) { eval qq{ sub ${type}_providers { \$_[0] -> _providers('@{[$type]}') } }; } sub _providers { my($self, $type) = @_; my $providers = { }; $providers = $self -> {parent} -> _providers($type) if $self -> {parent}; my $local = $self -> {config} -> {"${type}_provider"}; foreach my $id (keys %$local) { $providers -> {$id} = Storable::dclone($local -> {$id}); } return $providers; } foreach my $type (qw(data content security)) { eval qq{ sub ${type}_types { \$_[0] -> _types('@{[$type]}') } }; } sub _types { my($self, $type) = @_; my $types = { }; $types = $self -> {parent} -> _types($type) if $self -> {parent}; my $local = $self -> {config} -> {"${type}_types"}; @{$types}{keys %$local} = values %$local; return $types; } sub site_path { my $self = shift; return $self -> {_site_path} if ref $self -> {_site_path}; return $self -> {_site_path} = [ $self -> {site}, @{$self -> {parent} -> site_path} ] if $self -> {parent}; return $self -> {_site_path} = [ $self -> {site} ]; } =begin testing # tag_path is_deeply([ __OBJECT__ -> tag_path('production') ], [q(test-1.0)]); is_deeply([ __OBJECT__ -> tag_path('pre-production') ], [q(testing-1.1)]); is_deeply([ __OBJECT__ -> tag_path('test') ], [q(testinging-1.2)]); =end testing =cut sub tag_path { my($self, $type) = @_; my @tags = @{$self -> {config} -> {tagpaths} -> {$type} || []}; push @tags, $self -> {parent} -> tag_path($type) if $self -> {parent}; return @tags; } =begin testing # factory_class is(__OBJECT__ -> __METHOD__, "__PACKAGE__::Test::POF"); =end testing =cut sub factory_class { $_[0] -> package . "::POF" }; =begin testing # workflow_factory_class is(__OBJECT__ -> __METHOD__, "__PACKAGE__::Test::Workflows"); =end testing =cut sub workflow_factory_class { $_[0] -> package . "::Workflows" }; =begin testing # provider_class is(__OBJECT__ -> __METHOD__(data => 'view'), "__PACKAGE__::Test::DataProvider::view"); =end testing =cut sub provider_class { my $type = $_[2]; $type =~ s{[^A-Za-z0-9_]}{_}g; return( ($_[0] -> package) . "::" . ucfirst($_[1]) . "Provider::$type"); }; =begin testing # factory my $factory = __OBJECT__ -> factory( tag_path => 'test', ); is_deeply($factory -> {tag_path}, [qw( testinging-1.2 testing-1.1 test-1.0 )]); =end testing =cut sub factory { my($self, %params) = @_; my $factory_class = $self -> factory_class; # need to populate factory classes and match resources, etc. ... # a lot of this will need to be taken from the Apache::Gestinanna stuff, probably my @tags = ( ); my $tag_level = 0; $tag_level |= 1 if $params{tag_path} eq 'pre-production'; $tag_level |= 2 if $params{tag_path} eq 'test'; $tag_level |= 4 if $params{actor} && $params{tag_path} eq 'personal'; push @tags, $params{actor} -> object_id if $tag_level >= 4; push @tags, $self -> tag_path('test') if $tag_level >= 2; push @tags, $self -> tag_path('pre-production') if $tag_level >= 1; push @tags, $self -> tag_path('production'); my %tag_seen; @tags = grep { !$tag_seen{$_}++ } @tags; return $factory_class -> new( _factory => ( _resources => $params{resources}, site => $self, tag_path => \@tags, ($params{actor} ? ( actor => $params{actor} ) : ( ) ), ) ); } =begin testing # workflow_factory =end testing =cut sub workflow_factory { my($self, %params) = @_; if($self -> {config} -> {workflow}) { my $class = $self -> workflow_factory_class; if(!UNIVERSAL::isa($class, "Gestinanna::Workflow::Factory")) { no strict 'refs'; require Gestinanna::Workflow::Factory; push @{"${class}::ISA"}, 'Gestinanna::Workflow::Factory'; $class -> instance -> config($self -> {config} -> {workflow}); $class -> instance -> add_config( persister => { name => 'default', class => 'Gestinanna::Workflow::Persister', source => $params{'workflow-store-provider'}, }, ); } return $class -> instance; } if($self -> {parent}) { return $self -> {parent} -> workflow_factory(%params); } } =begin testing # build_factory __OBJECT__ -> build_factory; my $factory = __OBJECT__ -> factory_class; my $f_class; my $class; foreach my $type (qw( xsm document portal view site uri-map user actor username xslt folder )) { $class = __OBJECT__ -> provider_class(data => $type); $f_class = undef; eval { $f_class = $factory -> get_factory_class($type); }; ok(!$@, "Data provider exists for type $type"); if($f_class =~ m{::Object$}) { is($f_class, "${class}::Object", "Data provider for type $type is a class"); ok(UNIVERSAL::VERSION("${class}::Tag"), "${class}::Tag is defined"); ok(UNIVERSAL::VERSION("${class}::Description"), "${class}::Description is defined"); } else { is($f_class, $class, "Data provider for type $type is a class"); } ok(UNIVERSAL::VERSION($class), "$class is defined"); } =end testing =cut sub build_factory { my $self = shift; no strict 'refs'; my $factory_class = $self -> factory_class; @{"${factory_class}::ISA"} = qw(Gestinanna::POF); my $data_providers = $self -> data_providers; foreach my $type (keys %{$data_providers}) { my $provider_class = $self -> provider_class(data => $type); my $class = $data_providers -> {$type} -> {class}; #eval "require $provider_class;"; next unless $class -> can('build_object_class'); $class -> build_object_class( params => $data_providers -> {$type} -> {params}, class => $provider_class, config => $data_providers -> {$type} -> {config}, site => $self, ) or next; my $file = $provider_class; $file =~ s{::}{/}g; $INC{$file . ".pm"} = 1; @{"${provider_class}::VERSION"} = 1; if(UNIVERSAL::can($provider_class, 'add_factory_types')) { $provider_class -> add_factory_types($factory_class, $type); my $method; $provider_class -> $method( type => $type, factory => $factory_class, params => $data_providers -> {$type} -> {params}, config => $data_providers -> {$type} -> {config}, site => $self, ) if $method = $provider_class -> can('set_factory_resources'); } else { $factory_class -> add_factory_type($type => $provider_class); $factory_class -> set_resources($type => $provider_class -> resource_requirements( params => $data_providers -> {$type} -> {params}, config => $data_providers -> {$type} -> {config}, site => $self, ) ) if $provider_class -> can('resource_requirements'); } } } 1; __END__ =head1 NAME Gestinanna::SiteConfiguration - Site configuration information manager =head1 SYNOPSIS my $site = Gestinanna::SiteConfiguration -> new( parent => $parent_site_config ); $site -> parse_config( $xml_string ); $cookie = $site -> new_cookie; # session cookie $factory = $site -> factory( $resources ); $package = $site -> package $params = $site -> session_params; Questionable: $content_provider = $site -> get_content_provider( $type , ... ) $data_provider = $site -> get_data_provider( $type, ... ) =head1 DESCRIPTION This package manages the configuration information for a single site. The constructor can take a site configuration object for the global configuration, which provides information that may be overridden by the site-specific configuration.