Solstice::Controller::Application::Main - Controls the lifcycle of Solstice requests.


Solstice documentation  | view source Contained in the Solstice distribution.

Index


NAME

Top

Solstice::Controller::Application::Main - Controls the lifcycle of Solstice requests.

SYNOPSIS

Top

DESCRIPTION

Top

Superclass

Solstice::Controller::Application

Export

No symbols exported.

Methods

runApp() =cut

sub runApp { my $self = shift; my $screen = shift;

    $self->_setScreen($screen);

    # session, user, js and so on
    # Could enforce url level auth
    return unless $self->_denyUnstableBrowser();
    return unless $self->_initializeSession();

    return if $self->requiresURLAuth();

    return unless $self->_isLoginValid();
    return unless $self->_checkJavascript();

    return unless $self->_initializeState();

    return unless $self->loadApplicationForNamespace();
    return unless $self->_handleButtonAction();
    $self->_setPreClickState(Solstice::Session->new()->getStateTracker()->getState());

    return unless $self->_handlePreClick();

    return unless $self->_paintPreClickView();
    $self->_getPreClickController()->finalize();

    return TRUE;
}

sub requiresURLAuth { my $self = shift;

    my $config   = $self->getConfigService();

    my $auth_controller = Solstice::Controller::Application::Auth->new();
    if ($self->_getRequiresAuth() ){
        if (!$auth_controller->hasAuthenticated()) {
            return $auth_controller->requiresUserLogin();
        }
        elsif (!$auth_controller->hasUserAuth()) {
            $self->_paintView(Solstice::View::DeniedUser->new());
            return TRUE;
        }
    }
    return FALSE;
}

sub _paintPreClickView { my $self = shift;

    my $preclick_controller = $self->_getPreClickController();
    my $view = $preclick_controller->getView();
    $view->setError($preclick_controller->getController()->getError());

    return $self->_paintView($view);
}







sub _paintView { my $self = shift; my $view = shift;

    my $application = $self->_getApplication();
    my $server  = Solstice::Server->new();

    my $screen = $self->_getScreen();

    $view->sendHeaders();

    if($view->isDownloadView()){
        $server->printHeaders();
        $view->printData();

    }else{ ### The traditional view
        my $config = $self->getConfigService();

        ### Init the Application View
        my $app_view = Solstice::View::Application->new($application, 1);
        $app_view->setEscapeFrames($self->_getEscapeFrames());
        $app_view->setDocumentTitle($self->_getDocumentTitle());
        $app_view->setSessionAppKey('__'.Solstice::NamespaceService->new()->getNamespace().'__');
        #$app_view->addChildView('solstice_popin_container', Solstice::View::PopIn->new());

        ### Now create the boilerplate View
        my $boiler_class;
        if ($self->_getBoilerplateView()) {
            $boiler_class = $self->_getBoilerplateView();
            $self->loadModule($boiler_class);

        }elsif ($config->getBoilerplateView()) {
            $boiler_class = $config->getBoilerplateView();
            $self->loadModule($boiler_class);

        } else {
            $boiler_class = 'Solstice::View::Boilerplate';
        }

        my $boiler_view = $boiler_class->new($application);

        $boiler_view->setViewTopNav($self->_getViewTopNav()) if $boiler_view->can('setViewTopNav');

        $app_view->addChildView('boilerplate', $boiler_view);
        $boiler_view->addChildView('content', $view);

        $app_view->addChildView('developer_toolbar', Solstice::View::Developer::Toolbar->new($application));

        # Paint the App View - the content is returned by the screen 
        # scalar ref
        $app_view->paint($screen);
    }
    return  TRUE;
}

sub _setDevelopmentOptions { my $self = shift;

    my $application = $self->_getApplication();
    my $preference_service = $self->getPreferenceService($application->getNamespace());
    $Solstice::View::use_wireframes = $preference_service->getPreference('display_wire_frames');
    $Solstice::LangService::display_tags = $preference_service->getPreference('display_lang_tags');

    return TRUE;
}

sub _handlePreClick { my $self = shift;

    my $application = $self->_getApplication();
    my $state_tracker = Solstice::Session->new()->getStateTracker();
    my $selected_button = $self->getButtonService()->getSelectedButton();

    #most of the time a preclick will have been set, if not, pull from state tracker the
    #default for this url or pageflow
    my $preclick_controller = $self->_getPreClickController();
    unless( $preclick_controller ){
        $preclick_controller = $state_tracker->getController($application);
    }

    if (defined $selected_button) {
        $preclick_controller = $self->_preClickFallbacks($preclick_controller);
    }

    if ($preclick_controller->getRequiresAuth() &&
        $self->_requiresAuthentication()) {
            return FALSE;
    }

    unless ($preclick_controller->getController()){
        my $ref = ref $preclick_controller ;
        die("$ref did not set a controller in it's constructor.  Solstice cannot continue\n");
    }
    $preclick_controller->initialize();
    $self->_setPreClickController($preclick_controller);
    return TRUE;
}

sub _preClickFallbacks { my $self = shift; my $preclick_controller = shift;

    my $application = $self->_getApplication();
    my $state_tracker = Solstice::Session->new()->getStateTracker();

    #If we do not have a transition, eg for a bad back button or default state,
    #we cannot check for freshen
    my $transition = $self->_getTransition();

    if ( $transition && $transition->requiresFresh() ) {
        if (!$preclick_controller->freshen()) {
            return $self->_preClickFallbacks($state_tracker->failFreshen());
        }
    }
    if (!$preclick_controller->validPreConditions()) {
        return $self->_preClickFallbacks($state_tracker->failValidPreConditions($application));
    }

    return $preclick_controller;
}

sub _handleButtonAction { my $self = shift;

    my $state_tracker = Solstice::Session->new()->getStateTracker();
    # Get the user action and destination application
    my $selected_button = $self->getButtonService()->getSelectedButton();

    if ($self->getConfigService()->getDevelopmentMode()) {
        $self->_setDevelopmentOptions();
    }

    if(!defined $selected_button){
        return TRUE;
    }

    $self->_setPostClickState($state_tracker->getState()); # for logging only

    my $action = $selected_button->getAction();

    #if the button clicked was a navigation-only link (a "StaticButton")
    #we do not want to act as if any action was selected
    if($action eq '__nav_only__'){
        return TRUE;
    }

    $self->_setAction($action);
    my $session = Solstice::Session->new();

    # __bad_back_button__ is the action on the ajax submit that blocks the back button, 
    # getIllegalSession() traps the person who manages to click a button on a bad screen
    if ($action eq '__bad_back_button__' || $session->getIllegalSession()) {
        return $self->_handleBadBackButton();
    }
    if ($action eq "__set_preference__") {
        return $self->_handlePreferenceButton();
    }
    if ($action eq "__requires_auth__") {
        return $self->_handleLoginButton();
    }

    # Clear the session history if the user should not be able to use
    # the back button after this transition.
    if ( !$state_tracker->canUseBackButton($action) || $self->_getDisableBackButton() ) {
        #Clear the session history so this can't be backed over.
        $session->deleteSubsessionChain();
    }

    $self->_findTransition($action);

    return FALSE if ($self->_requiresPostClickAuthentication());

    # Returns false if validation fails - stay on the current state.
    if ($self->_runPostClickLifeCycle()) {

        if ($self->_getIsGlobalTransition()) {
            $self->_runGlobalTransition();
        }else{
            #run standard transition
            $state_tracker->transition($action);
        }

        #reset application incase we changed pageflows
        $state_tracker->getState() =~ /^(.*?):/;
        my $namespace = $1;
        $self->_setNamespace($namespace);
        $self->loadApplicationForNamespace();
    }

    return TRUE;
}

sub _requiresPostClickAuthentication { my $self = shift; my $transition = $self->_getTransition(); my $state_tracker = Solstice::Session->new()->getStateTracker(); my $application = $self->_getApplication(); my $postclick_controller = $state_tracker->getController($application); $self->setPostClickController($postclick_controller);

    if ($postclick_controller->getRequiresAuth() &&
        $self->_requiresAuthentication()) {
            return TRUE;
    }
    return FALSE;
}

sub _requiresAuthentication { my $self = shift;

    my $auth_controller = Solstice::Controller::Application::Auth->new();

    if (!$auth_controller->hasUserAuth() ) {
        $auth_controller->getAuthentication();
        return TRUE;
    }
    return FALSE;
}

sub _runPostClickLifeCycle { my $self = shift;

    my $transition      = $self->_getTransition();
    my $state_tracker   = Solstice::Session->new()->getStateTracker();
    my $application     = $self->_getApplication();
    my $postclick_controller = $self->_getPostClickController();

    if ($transition->requiresRevert()) {
        if (!$postclick_controller->revert()) {
            $self->_setPreClickController($state_tracker->failRevert($application));
            return FALSE;
        }
    }

    if ($transition->requiresUpdate()) {
        if (!$postclick_controller->update()) {
            $self->_setPreClickController($state_tracker->failUpdate($application));
            return FALSE;
        }
    }
    if ($transition->requiresValidation()) {
        if (!$postclick_controller->validate()) {

            if (my $controller = $state_tracker->failValidation($application)) {
                $self->_setPreClickController($controller);
            } else {
                $self->_setPreClickController($postclick_controller);
            }
            return FALSE;
        }
    }

    if ($transition->requiresCommit()) {
        if (!$postclick_controller->commit()) {
            $self->_setPreClickController($state_tracker->failCommit($application));
            return FALSE;
        }
    }
    return TRUE;
}




sub _runGlobalTransition { my $self = shift;

    my $transition = $self->_getTransition();
    my $target_pageflow = $transition->getTargetPageFlow();
    my $machine = Solstice::State::Machine->new();
    my $application = $self->_getApplication();
    my $state_tracker = Solstice::Session->new()->getStateTracker();

    #assume main page flow if none is specified
    my $pageflow = $application->getNamespace().'::'.((defined $target_pageflow) ? $target_pageflow : 'Main');

    my $state;
    if ("Solstice::State::FlowTransition" eq ref($transition)) {
       $pageflow = $transition->getPageFlowName();
       my $current_pageflow = $machine->getPageFlow($pageflow);
       $state = $current_pageflow->getEntrance();
    }else{
        $state_tracker->clearPageFlowStack();
    }
    $state_tracker->startApplication($pageflow, $transition->getName());
    $state_tracker->_setState($transition->getTargetState()||$state);

    return;
}

sub _findTransition { my $self = shift; my $action = shift;

    my $state_tracker = Solstice::Session->new()->getStateTracker();

    if( my $transition = $state_tracker->getTransition($action)){
        $self->_setTransition($transition);
        $self->_setIsGlobalTransition(FALSE);

    }elsif( $transition = $state_tracker->getPageFlowGlobalTransition($action, $self->_getNamespace())){
        $self->_setTransition($transition);
        $self->_setIsGlobalTransition(TRUE)

    }elsif( $transition = $state_tracker->getGlobalTransition($action, $self->_getNamespace())){
        $self->_setTransition($transition);
        $self->_setIsGlobalTransition(TRUE);

   }else{
        my $state = $state_tracker->getState();
        die "Could not transition from state $state via action $action\n"
   }
}

sub _handleBadBackButton { my $self = shift;

    my $state_tracker   = Solstice::Session->new()->getStateTracker();
    my $application     = $self->_getApplication();

    if( $state_tracker->getBackErrorMessage() ){
        my $ns = Solstice::NamespaceService->new()->getAppNamespace();
        $self->getMessageService()->addInfoMessage(
            $self->getLangService($ns)->getMessage($state_tracker->getBackErrorMessage())
        );
    }else{
        $self->getMessageService()->addInfoMessage(
            $self->getLangService()->getMessage('generic_back_error')
        );
    }

    $self->_setPreClickController($state_tracker->getController($application));

    return TRUE;
}

sub _handlePreferenceButton { my $self = shift;

    my $selected_button = $self->getButtonService()->getSelectedButton();
    my $application     = $self->_getApplication();
    my $state_tracker   = Solstice::Session->new()->getStateTracker();

    my $key = $selected_button->getPreferenceKey();
    my $value = $selected_button->getPreferenceValue();

    if (defined $key) {
        my $preference_service = $self->getPreferenceService($application->getNamespace());
        $preference_service->setPreference($key, $value);
    }

    my $controller = $state_tracker->getController($application);
    $controller->update();
    $self->_setPreClickController($controller);

    # This ensures that dev toolbar prefs get picked up immediately
    if ($self->getConfigService()->getDevelopmentMode()) {
        $self->_setDevelopmentOptions();
    }

    return TRUE;
}

sub _handleLoginButton { my $self = shift;

    my $selected_button = $self->getButtonService()->getSelectedButton();
    my $application     = $self->_getApplication();
    my $state_tracker   = Solstice::Session->new()->getStateTracker();

    my $controller = $state_tracker->getController($application);
    $controller->setRequiresAuth(TRUE);
    $self->_setPreClickController($controller);

    return TRUE;
}

sub _denyUnstableBrowser { my $self = shift;

    # Do we support this browser?
    if( $self->_isBrowserStable() ){
        return TRUE;
    }else{
        $self->_paintView(Solstice::View::DeniedBrowser->new());
        return FALSE;
    }
}

sub _initializeSession { my $self = shift;

    # Initialize session
    my $session = Solstice::Session->new;

    # Before we do anything else, make sure we have a proper session
    # to work with
    unless ($session->hasSession()) {
        if($self->_getRequireSession()){
            $self->_forceCreateSession();
            return FALSE;
        }else{
            $self->_passiveCreateSession();
        }
    }
    # Okay, we have a useful session - since this is a user-pageview
    # controller, we'll need a subsession too
    $session->loadSubsession();
    return TRUE;
}

sub _isLoginValid { my $self = shift;

    # Is the login name valid?
    if (my $user = $self->getUserService()->getOriginalUser()) {
        # Is this really the first place where we need to load the login realm module?
        $self->loadModule($user->getLoginRealm());
        unless ($user->getLoginRealm()->isValidLogin($user->getLoginName())) {
            $self->_paintView(Solstice::View::DeniedUser->new());
            return FALSE;
        }
    }
    return TRUE;
}

sub _checkJavascript { my $self = shift;

    my $session = Solstice::Session->new();
    # Check that javascript flag has been set
    if($self->_getRequireSession()){
        if (!defined $session->hasJavascript()) {
            my $has_js = param('has_js');
            if (!defined $has_js) {
                $self->_checkHasJavascript();
                return FALSE;
            }
            $session->setHasJavascript($has_js);
            Solstice::ButtonService->new()->setHasJavascript($has_js);
        }
        Solstice::JavaScriptService->new()->setHasJavascript($session->hasJavascript());
    }else{
        #if we aren't doing a session bounce we just have to assume
        #TODO: this exposes that our JS detection method is kinda weak. Can we do better?
        Solstice::JavaScriptService->new()->setHasJavascript(TRUE);
    }

    return TRUE;
}

sub _initializeState { my $self = shift;

    my $session = Solstice::Session->new();
    my $namespace = Solstice::NamespaceService->new()->getNamespace();

    my $pageflow = $self->_getPageFlow() || 'Main';
    my $initial_state = $namespace.'::'.$self->_getInitialState();
    $pageflow = $namespace.'::'.$pageflow;

    ### Init the App's State Machine
    my $state_tracker = $session->getStateTracker();

    #if we are just navigating via a button, we want to keep our subsesion
    #but we need to refresh our state to whatever this url thinks is default
    my $clear_state = FALSE;
    my $selected_button = $self->getButtonService()->getSelectedButton();
    if($selected_button && $selected_button->getAction() eq '__nav_only__'){
        $clear_state = TRUE;
    }

    if (!$state_tracker || $clear_state) {
        $state_tracker = new Solstice::State::Tracker();
        $state_tracker->startApplication($pageflow, $initial_state);
        $session->setStateTracker($state_tracker);
    }

    #if there was a state tracker and we were ina pageflow, we may need
    #to capture the name of that pageflow - if we are in the url's default
    #pageflow, this will be essentially a no-op
    $state_tracker->getState() =~ /(.*?):/;
    $namespace = $1;
    $self->_setNamespace($namespace);

    return TRUE;
}

_passiveCreateSession($screen)

Adds a header to the current request that will set a session cookie, but does not force a browser bounce - The session cookie will be send on subsequent requests if it's accepted.

_forceCreateSession($screen)

Write a cookie to the browser, and do a meta refresh back to the page. Can't be a 300 redirect, it needs to be a full round trip to the client for the cookie to be processed properly.

_checkHasJavascript()

Round trip to the client, to verify if javascript is enabled or not.

_isBrowserStable()

Check browser/OS combination

_getAccessorDefinition() =cut

sub _getAccessorDefinition { return [ { name => 'RequiresFreshen', key => '_require_freshen', type => 'Boolean', private_get => TRUE, }, { name => 'ViewTopNav', key => '_view_top_nav', type => 'Boolean', private_get => TRUE, }, { name => 'BoilerplateView', key => '_boilerplate_view', type => 'String', private_get => TRUE, }, { name => 'RequiresAuth', type => 'Boolean', private_get => TRUE, }, { name => 'RequireSession', key => '_require_session', type => 'Boolean', private_get => TRUE, }, { name => 'InitialState', key => '_initial_state', type => 'String', private_get => TRUE, }, { name => 'PageFlow', key => '_page_flow', type => 'String', private_get => TRUE, }, { name => 'EscapeFrames', key => '_escape_frames', type => 'Boolean', private_get => TRUE, }, { name => 'DisableBackButton', key => '_disable_back_button', type => 'Boolean', private_get => TRUE, }, { name => 'DocumentTitle', key => '_document_title', type => 'String', private_get => TRUE, }, { name => 'PostClickState', key => '_post_click_state', type => 'String', }, { name => 'PreClickState', key => '_pre_click_state', type => 'String', }, { name => 'PostClickController', key => '_post_click_controller', type => 'Solstice::Controller', private_get => TRUE, }, { name => 'PreClickController', key => '_pre_click_controller', type => 'Solstice::Controller', private_get => TRUE, }, { name => 'Action', key => '_action', type => 'String', }, { name => 'Screen', key => '_screen', type => 'SCALAR', private_get => TRUE, }, { name => 'Namespace', key => '_namespace', type => 'String', private_get => TRUE, }, { name => 'Application', key => '_application', type => 'Solstice::Application', private_get => TRUE, }, { name => 'IsGlobalTransition', key => '_is_global_trans', type => 'Boolean', private_get => TRUE, }, { name => 'Transition', key => '_transition', type => 'Solstice::State::Transition', private_get => TRUE, },

    ];
}




Private Methods

Modules Used

Solstice::Controller::Application, Solstice::Session, Solstice::View::HelpPane (Solstice::View::HelpPane), Solstice::View::InvalidPreConditions, Solstice::View::Navigation (Solstice::View::Navigation), Solstice::CGI,

AUTHOR

Top

Catalyst Group, <catalyst@u.washington.edu>

VERSION

Top

$Revision: 3375 $

COPYRIGHT

Top


Solstice documentation  | view source Contained in the Solstice distribution.