/usr/local/CPAN/Web-App/Web/App.pm
package Web::App;
# $Id: App.pm,v 1.36 2009/03/23 00:44:49 apla Exp $
our $VERSION = '1.20';
use Class::Easy;
use Data::Dumper;
use IO::Easy;
use Web::App::Config;
use Web::App::Request;
use Web::App::Response;
use Web::App::Session;
has 'root';
has 'config';
has 'int';
has 'session';
has 'request';
has 'response';
has 'project', is => 'rw';
our $app = {};
1;
sub new {
my $class = shift;
my $params = {@_};
bless $app, $class;
debug "process initialization";
my $t = timer ('project init');
my $config_file;
if ($params->{project}) {
my $project = $params->{project};
die "can't use package $project"
unless try_to_use ($project);
# modules always in lib for Web::App
$app->{root} = $project->root;
$app->{project} = $project;
# !!! dirty xml hack
$config_file = $project->root->append ('etc', $project->id . '-web-app.xml')
unless -f $config_file;
} else {
$app->{root} = IO::Easy->new ($params->{'root'});
$config_file = $params->{'config'} || 'etc/config.xml';
}
$t->lap ('config loading');
# ÐнализиÑÑÐµÑ Ð²Ñ
одÑÑий запÑоÑ, пÑÐ¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ Ð¾Ð±ÑÑÑ Ð°Ð±ÑÑÑакÑнÑÑ
# обÑабоÑÐºÑ Ð·Ð°Ð¿ÑоÑа
debug "creating Web::App object in $app->{root}";
debug 'loading configuration';
my $config = Web::App::Config->get ($app, $config_file);
$app->{config} = $config;
$t->lap ('modules loading');
$config->init_modules;
$t->end;
return $app;
}
# accessors here
sub home {
shift->{root};
}
sub app {
$app;
}
sub receive_request {
my $self = shift;
# initialization
my $request = $self->{request} = Web::App::Request->new ($app);
my $response = $self->{response} = Web::App::Response->new;
$request->handle ($self);
$response->{data}->{request} = $request; # for presentation
my $screen = $self->request->screen;
# TODO CHANGE DESCRIPTION
# we don't init session because some session
# internals must be preloaded
my $session = Web::App::Session->detect;
}
sub handler {
my $self = shift;
my $r = Web::App::Request->new ($app);
return $r;
}
sub var {
my $self = shift;
return $self->response->data;
}
sub expand_params {
my $self = shift;
my $params = shift;
my $session = $self->session;
my $request = $self->request;
my $form = $request->params;
my $dirs = {
'data-dir' => $self->root . '/var/db/sharedwork',
'root' => $self->root,
'path_info' => $request->path_info,
'session_id' => $session->id,
'screen_id' => $request->screen->id,
'dir_info' => $request->dir_info,
'file_name' => $request->file_name,
'file_extension' => $request->file_extension,
'base_uri' => $request->base_uri,
'var' => $self->var,
'form' => {map {
$_ => $form->{$_}->[0]
} grep {! /CGI\:\:Minimal/} keys %$form},
};
my $counter = 1;
foreach my $match (@{$request->screen_matches}) {
$dirs->{$counter} = $match;
$counter++;
}
if (defined ref $params and ref $params eq 'HASH') {
foreach my $key (keys %$params) {
#supports xslt notation: {$aaa}
# 3-letters
my $val = $params->{$key};
my $pos = index ($val, '{$');
while ($pos > -1) {
my $end = index ($val, '}', $pos);
my $str = substr ($val, $pos + 2, $end - $pos - 2);
# warn "found replacement: key => $key, requires => \$$str\n";
my $fix;
if (index ($str, '/') > -1) { # treat as path
# warn join ', ', keys %{$self->var};
$fix = Web::App::Config::path_to_val ($dirs, $str);
} else { # scalar
$fix = $dirs->{$str};
}
# warn "value for replace is: $fix\n";
if ($pos == 0 and $end == (length ($val) - 1)) {
$val = $fix;
} else {
substr ($val, $pos, $end - $pos + 1, $fix);
}
$pos = index ($val, '{$', $end);
}
$params->{$key} = $val;
# warn ("key is: $key, param is: $1");
}
} else { # what this?
$params =~ s/(?:\$\{|\{\$)([\w\-_0-9]+)\}/$dirs->{$1}/g;
return $params;
}
}
sub process_request {
my $self = shift;
my $request = $self->request;
my $response = $self->response;
my $screen = $self->request->screen;
# adding processors from config for current screen into request
my $processors = [];
if ($request->data_available) {
$processors = $screen->process_calls;
} else {
$processors = $screen->init_calls;
}
push @{$request->processors}, @$processors
if defined $processors;
$request->presentation ($screen->{'presentation'});
while (my $processor = $request->next_processor) {
last unless defined $processor;
my $processor_params = {%$processor}; # copy
my $result_place = delete $processor_params->{place};
$self->expand_params ($processor_params);
my $processor_call = $processor_params->{sub};
debug "launch '$processor_call'";
my ($pack, $method) = split '->', $processor_call;
if ($pack =~ /^\$([^:]+)$/) {
$pack = $app->$1;
}
my $result = eval {
$pack->$method ($self, $processor_params);
};
if (defined $result and $result) {
die "you must supply place for results"
unless $result_place;
die "you can't override $result_place"
if exists $app->var->{$result_place};
$app->var->{$result_place} = $result;
}
# eval "$processor_call (\$self, \$processor_params)";
critical "after '$processor_call' launch: $@"
if $@;
}
debug "processors finished";
my $location = $self->{'redirect-to'};
# !!! need to be replaced for correct headers output.
debug Dumper $response->data
if $Class::Easy::DEBUGIMMEDIATELY;
if ($location) {
if ($Class::Easy::DEBUGIMMEDIATELY) {
# print "Location: <a href='$location'>$location</a>\n\n";
debug "actual headers are below";
} else {
$self->response->headers->header ('Location' => $location);
}
}
}
sub handle_request ($$) {
my $class = shift;
my $r = shift;
my $self = $class->app;
delete $self->{'redirect-to'};
my $t = timer ('request retrieval');
$self->receive_request;
$t->lap ('accessors');
my $request = $self->request;
my $screen = $request->screen;
my $session = $self->session;
$t->lap ('authentication');
if ($screen->authenticated ($session)) {
$t->lap ('processors work');
$self->process_request;
} else {
debug "screen not authenticated";
$self->clear_process_queue;
$self->set_presentation_screen ('login');
}
$t->lap ('presentation');
my $content;
my $status;
my $can_set_status = $request->can ('set_status');
if ($self->redirected) {
$request->set_status (302)
if $can_set_status;
$self->send_headers;
} else {
$request->set_status (200)
if $can_set_status;
$self->prepare_presenter;
$content = $self->run_presenter;
$self->send_headers;
$request->send_content ($content);
}
$t->end;
debug "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< request finished";
$t->total;
$request->done_status;
}
sub prepare_presenter {
my $app = shift;
# maybe processor changed presentation
my $presentation = $app->request->presentation;
my $presenter = $app->config->presenters->{$presentation->{'type'}};
$app->response->presenter ($presenter);
$presenter->headers;
}
sub debug_log { # TODO: more optimal way without copying
my $self = shift;
my $result = $Web::App::LOG;
$Web::App::LOG = '';
my $presentation = $self->request->presentation;
my $presentation_type = $presentation->{'type'};
# we must prettify log for html
my $presenters = $self->config->presenters;
my $presenter;
if ($presentation_type) {
$presenter = $presenters->{$presentation_type};
} else {
if ($self->response->headers->content_type =~ /text\/html/) {
$presenter = $presenters->{'xslt'};
warn "we hacked into xslt";
}
}
if ($presenter and $presenter->can ('wrap_log')) {
return $presenter->wrap_log ($result);
} else {
return $result;
}
}
sub send_headers {
my $app = shift;
my $request = $app->request;
return if $request->headers_sent;
my $headers = $app->response->headers;
$request->send_headers ($headers);
debug "headers are: ", $headers->as_string;
$request->headers_sent (1);
}
sub set_presentation {
my $self = shift;
my $presentation = shift;
$self->request->presentation ($presentation);
}
sub set_presentation_screen {
my $self = shift;
my $screen_name = shift;
my $screen = $self->config->screen ($screen_name)->{'?'};
$self->request->presentation ($screen->{'presentation'});
$self->request->screen ($screen);
}
sub clear_process_queue {
my $self = shift;
debug 'requested for clearing processor queue, processed';
$self->request->processors ([]);
}
sub redirect_to_screen {
my $self = shift;
my $screen = shift;
my $request = $self->request;
return unless $request->type eq 'CGI';
# TODO CRITICAL: fix for proto (https) and port
my $base_uri = $request->base_uri;
my $host = $request->host;
if ($self->project and exists $self->project->config->{'hostname'}) {
$host = $self->project->config->{'hostname'};
}
if ( $request->{'session-id'} ) {
my $session_id = $request->{'session-id'};
$self->{'redirect-to'} = "http://$host$base_uri/$session_id\@$screen";
} else {
$self->{'redirect-to'} = "http://$host$base_uri/$screen";
}
}
sub redirect {
my $self = shift;
my $url = shift;
debug "requested redirect to uri: $url";
my $request = $self->request;
$self->{'redirect-to'} = $url;
}
sub redirected {
my $self = shift;
my $status = 0;
$status = 1 if exists $self->{'redirect-to'} and $self->{'redirect-to'} ne '';
return $status;
}
sub run_presenter {
my $self = shift;
my $presentation = $self->request->presentation;
debug "presenter: " . $presentation->{'type'}
. (defined $presentation->{'file'} ? " in " . $presentation->{'file'} : '');
my $presenter = $self->response->presenter;
critical "maybe you want to register presenter, because i nothing knows about '$presentation->{type}'"
unless defined $presenter;
my $data = $self->response->data;
my $content;
eval {
$content = $presenter->process ($self, $data, %$presentation);
};
debug $@ if $@;
return $content;
}
1;