<?php

/**
 * \AppserverIo\Appserver\ServletEngine\ServletManager
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <tw@appserver.io>
 * @copyright 2015 TechDivision GmbH <info@appserver.io>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/appserver-io/appserver
 * @link      http://www.appserver.io
 */

namespace AppserverIo\Appserver\ServletEngine;

use AppserverIo\Storage\GenericStackable;
use AppserverIo\Storage\StorageInterface;
use AppserverIo\Appserver\Core\AbstractEpbManager;
use AppserverIo\Psr\Di\ObjectManagerInterface;
use AppserverIo\Psr\Application\ApplicationInterface;
use AppserverIo\Psr\Servlet\ServletInterface;
use AppserverIo\Psr\Servlet\ServletContextInterface;
use AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface;
use AppserverIo\Psr\Servlet\Description\ServletDescriptorInterface;
use AppserverIo\Appserver\Application\Interfaces\ManagerSettingsInterface;
use AppserverIo\Appserver\Application\Interfaces\ManagerSettingsAwareInterface;

/**
 * The servlet manager handles the servlets registered for the application.
 *
 * @author    Tim Wagner <tw@appserver.io>
 * @copyright 2015 TechDivision GmbH <info@appserver.io>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/appserver-io/appserver
 * @link      http://www.appserver.io
 *
 * @property \AppserverIo\Storage\StorageInterface                                  $initParameters    The container for the init parameters
 * @property \AppserverIo\Appserver\ServletEngine\ResourceLocatorInterface          $resourceLocator   The resource locator for requested servlets
 * @property \AppserverIo\Appserver\ServletEngine\ResourceLocatorInterface          $servletLocator    The resource locator for the servlets
 * @property \AppserverIo\Storage\GenericStackable                                  $servletMappings   The container for the servlet mappings
 * @property \AppserverIo\Storage\StorageInterface                                  $servlets          The container for the servlets
 * @property \AppserverIo\Storage\StorageInterface                                  $sessionParameters The container for the session parameters
 * @property \AppserverIo\Appserver\Application\Interfaces\ManagerSettingsInterface $managerSettings   Settings for the servlet manager
 */
class ServletManager extends AbstractEpbManager implements ServletContextInterface, ManagerSettingsAwareInterface
{

    /**
     * Injects the resource locator that locates the requested servlet.
     *
     * @param \AppserverIo\Appserver\ServletEngine\ResourceLocatorInterface $resourceLocator The resource locator
     *
     * @return void
     */
    public function injectResourceLocator(ResourceLocatorInterface $resourceLocator)
    {
        $this->resourceLocator = $resourceLocator;
    }

    /**
     * Injects the container for the servlets.
     *
     * @param \AppserverIo\Storage\StorageInterface $servlets The container for the servlets
     *
     * @return void
     */
    public function injectServlets(StorageInterface $servlets)
    {
        $this->servlets = $servlets;
    }

    /**
     * Injects the container for the servlet mappings.
     *
     * @param \AppserverIo\Storage\GenericStackable $servletMappings The container for the servlet mappings
     *
     * @return void
     */
    public function injectServletMappings(GenericStackable $servletMappings)
    {
        $this->servletMappings = $servletMappings;
    }

    /**
     * Injects the container for the init parameters.
     *
     * @param \AppserverIo\Storage\StorageInterface $initParameters The container for the init parameters
     *
     * @return void
     */
    public function injectInitParameters(StorageInterface $initParameters)
    {
        $this->initParameters = $initParameters;
    }

    /**
     * Injects the container for the secured URL configurations.
     *
     * @param \AppserverIo\Storage\StorageInterface $securedUrlConfigs The container for the secured URL configurations
     *
     * @return void
     */
    public function injectSecuredUrlConfigs(StorageInterface $securedUrlConfigs)
    {
        $this->securedUrlConfigs = $securedUrlConfigs;
    }

    /**
     * Injects the container for the session parameters.
     *
     * @param \AppserverIo\Storage\StorageInterface $sessionParameters The container for the session parameters
     *
     * @return void
     */
    public function injectSessionParameters(StorageInterface $sessionParameters)
    {
        $this->sessionParameters = $sessionParameters;
    }

    /**
     * Injects the container for the error page configuration.
     *
     * @param \AppserverIo\Storage\StorageInterface $errorPages The container for the error page configuration
     *
     * @return void
     */
    public function injectErrorPages(StorageInterface $errorPages)
    {
        $this->errorPages = $errorPages;
    }

    /**
     * Injects the servlet manager settings.
     *
     * @param \AppserverIo\Appserver\Application\Interfaces\ManagerSettingsInterface $managerSettings The servlet manager settings
     *
     * @return void
     */
    public function injectManagerSettings(ManagerSettingsInterface $managerSettings)
    {
        $this->managerSettings = $managerSettings;
    }

    /**
     * Has been automatically invoked by the container after the application
     * instance has been created.
     *
     * @param \AppserverIo\Psr\Application\ApplicationInterface $application The application instance
     *
     * @return void
     * @see \AppserverIo\Psr\Application\ManagerInterface::initialize()
     */
    public function initialize(ApplicationInterface $application)
    {

        // register the annotation registries
        $application->registerAnnotationRegistries();

        // parse the object descriptors
        $this->parseObjectDescriptors();

        // register the servlets
        $this->registerServlets($application);
    }

    /**
     * Finds all servlets which are provided by the webapps and initializes them.
     *
     * @param \AppserverIo\Psr\Application\ApplicationInterface $application The application instance
     *
     * @return void
     *
     * @throws \AppserverIo\Appserver\ServletEngine\InvalidServletMappingException
     */
    public function registerServlets(ApplicationInterface $application)
    {

        // load the object manager instance
        /** @var \AppserverIo\Psr\Di\ObjectManagerInterface $objectManager */
        $objectManager = $this->getApplication()->search(ObjectManagerInterface::IDENTIFIER);

        // register the beans located by annotations and the XML configuration
        /** \AppserverIo\Psr\Deployment\DescriptorInterface $objectDescriptor */
        foreach ($objectManager->getObjectDescriptors() as $descriptor) {
            // check if we've found a servlet descriptor and register the servlet
            if ($descriptor instanceof ServletDescriptorInterface) {
                $this->registerServlet($descriptor);
            }
        }
    }

    /**
     * Register the servlet described by the passed descriptor.
     *
     * @param \AppserverIo\Psr\Servlet\Description\ServletDescriptorInterface $descriptor The servlet descriptor
     *
     * @return void
     */
    public function registerServlet(ServletDescriptorInterface $descriptor)
    {

        try {
            // prepend the url-pattern - servlet mapping to the servlet mappings
            foreach ($descriptor->getUrlPatterns() as $pattern) {
                $this->addServletMapping($pattern, $descriptor->getName());
            }

            // register's the servlet's references
            $this->registerReferences($descriptor);

        } catch (\Exception $e) {
            // log the exception
            $this->getApplication()->getInitialContext()->getSystemLogger()->critical($e->__toString());
        }
    }

    /**
     * Lifecycle callback that'll be invoked after the application has been started.
     *
     * @param \AppserverIo\Psr\Application\ApplicationInterface $application The application instance
     *
     * @return void
     * @see \AppserverIo\Psr\Application\ManagerInterface::postStartup()
     */
    public function postStartup(ApplicationInterface $application)
    {

        // register the annotation registries
        $application->registerAnnotationRegistries();

        // load the object manager
        /** @var \AppserverIo\Psr\Di\ObjectManagerInterface $objectManager */
        $objectManager = $application->search(ObjectManagerInterface::IDENTIFIER);

        // register the beans found by annotations and the XML configuration
        /** \AppserverIo\Psr\Deployment\DescriptorInterface $objectDescriptor */
        foreach ($objectManager->getObjectDescriptors() as $descriptor) {
            // if we found a singleton session bean with a startup callback instanciate it
            if ($descriptor instanceof ServletDescriptorInterface) {
                // instantiate the servlet
                $instance = $this->get($servletName = $descriptor->getName());

                // initialize the servlet configuration
                $servletConfig = new ServletConfiguration();
                $servletConfig->injectServletContext($this);
                $servletConfig->injectServletName($servletName);

                // append the init params to the servlet configuration
                foreach ($descriptor->getInitParams() as $paramName => $paramValue) {
                    $servletConfig->addInitParameter($paramName, $paramValue);
                }

                // initialize the servlet
                $instance->init($servletConfig);

                // the servlet is added to the dictionary using the complete request path as the key
                $this->addServlet($servletName, $instance);
            }
        }
    }

    /**
     * Returns all servlets
     *
     * @return array The servlets collection
     */
    public function getServlets()
    {
        return $this->servlets;
    }

    /**
     * Returns the servlet mappings found in the
     * configuration file.
     *
     * @return \AppserverIo\Storage\GenericStackable The servlet mappings
     */
    public function getServletMappings()
    {
        return $this->servletMappings;
    }

    /**
     * Returns the resource locator for the servlets.
     *
     * @return \AppserverIo\Appserver\ServletEngine\ResourceLocatorInterface The resource locator for the servlets
     */
    public function getServletLocator()
    {
        return $this->servletLocator;
    }

    /**
     * Returns the servlet with the passed name.
     *
     * @param string $key The name of the servlet to return
     *
     * @return \AppserverIo\Psr\Servlet\ServletInterface The servlet instance
     */
    public function getServlet($key)
    {
        if ($this->servlets->has($key)) {
            return $this->servlets->get($key);
        }
    }

    /**
     * Returns the servlet for the passed URL mapping.
     *
     * @param string $urlMapping The URL mapping to return the servlet for
     *
     * @return \AppserverIo\Psr\Servlet\ServletInterface The servlet instance
     */
    public function getServletByMapping($urlMapping)
    {
        if (isset($this->servletMappings[$urlMapping])) {
            return $this->getServlet($this->servletMappings[$urlMapping]);
        }
    }

    /**
     * Registers a servlet under the passed key.
     *
     * @param string                                    $key     The servlet to key to register with
     * @param \AppserverIo\Psr\Servlet\ServletInterface $servlet The servlet to be registered
     *
     * @return void
     */
    public function addServlet($key, ServletInterface $servlet)
    {
        $this->servlets->set($key, $servlet);
    }

    /**
     * Adds an URL mapping for a servlet.
     *
     * @param string $pattern     The URL pattern we want the servlet to map to
     * @param string $servletName The servlet name to map
     *
     * @return void
     */
    public function addServletMapping($pattern, $servletName)
    {
        $this->servletMappings[$pattern] = $servletName;
    }

    /**
     * Return the resource locator instance.
     *
     * @return \AppserverIo\Appserver\ServletEngine\ResourceLocatorInterface The resource locator instance
     */
    public function getResourceLocator()
    {
        return $this->resourceLocator;
    }

    /**
     * Registers the init parameter under the passed name.
     *
     * @param string $name  Name to register the init parameter with
     * @param string $value The value of the init parameter
     *
     * @return void
     */
    public function addInitParameter($name, $value)
    {
        $this->initParameters->set($name, $value);
    }

    /**
     * Returns the init parameter with the passed name.
     *
     * @param string $name Name of the init parameter to return
     *
     * @return null|string
     */
    public function getInitParameter($name)
    {
        if ($this->initParameters->has($name)) {
            return $this->initParameters->get($name);
        }
    }

    /**
     * Registers the error page under the passed error code.
     *
     * @param string $errorCodePattern The error code for the page
     * @param string $errorLocation    The error page location
     *
     * @return void
     */
    public function addErrorPage($errorCodePattern, $errorLocation)
    {
        $this->errorPages->set($errorCodePattern, $errorLocation);
    }

    /**
     * Returns the container with the error page configuration.
     *
     * @return \AppserverIo\Storage\StorageInterface The container with the error page configuration
     */
    public function getErrorPages()
    {
        return $this->errorPages;
    }

    /**
     * Returns the webapps security context configurations.
     *
     * @return array The security context configurations
     * @throws \AppserverIo\Appserver\ServletEngine\OperationNotSupportedException Is thrown if this method has been invoked
     */
    public function getSecuredUrlConfigs()
    {
        throw new OperationNotSupportedException(sprintf('%s not yet implemented', __METHOD__));
    }

    /**
     * Registers the session parameter under the passed name.
     *
     * @param string $name  Name to register the session parameter with
     * @param string $value The value of the session parameter
     *
     * @return void
     */
    public function addSessionParameter($name, $value)
    {
        $this->sessionParameters->set($name, $value);
    }

    /**
     * Returns the session parameter with the passed name.
     *
     * @param string $name Name of the session parameter to return
     *
     * @return null|string
     */
    public function getSessionParameter($name)
    {
        if ($this->sessionParameters->has($name)) {
            return $this->sessionParameters->get($name);
        }
    }

    /**
     * Returns TRUE if we've at least one session parameter configured, else FALSE.
     *
     * @return boolean TRUE if we've at least one session parameter configured, else FALSE
     */
    public function hasSessionParameters()
    {
        return sizeof($this->sessionParameters) > 0;
    }

    /**
     * Return's the servlet manager settings.
     *
     * @return \AppserverIo\Appserver\Application\Interfaces\ManagerSettingsInterface The servlet manager settings
     */
    public function getManagerSettings()
    {
        return $this->managerSettings;
    }

    /**
     * Tries to locate the resource related with the request.
     *
     * @param \AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface $servletRequest The request instance to return the servlet for
     * @param array                                                     $args           The arguments passed to the servlet constructor
     *
     * @return \AppserverIo\Psr\Servlet\ServletInterface The requested servlet
     * @see \AppserverIo\Appserver\ServletEngine\ServletLocator::locate()
     */
    public function locate(HttpServletRequestInterface $servletRequest, array $args = array())
    {

        // load the servlet path => to locate the servlet
        $servletPath = $servletRequest->getServletPath();

        // if a session cookie has been sent, initialize the session manager and the session
        if ($manager = $this->getApplication()->search(SessionManagerInterface::IDENTIFIER)) {
            $requestedSessionName = $manager->getSessionSettings()->getSessionName();
            if ($servletRequest->hasCookie($requestedSessionName)) {
                $servletRequest->getCookie($requestedSessionName)->getValue();
            }
        }

        // return the instance
        return $this->lookup($servletPath, $args);
    }

    /**
     * Runs a lookup for the servlet with the passed class name and
     * session ID.
     *
     * @param string $servletPath The servlet path
     * @param array  $args        The arguments passed to the servlet constructor
     *
     * @return \AppserverIo\Psr\Servlet\ServletInterface The requested servlet
     */
    public function lookup($servletPath, array $args = array())
    {
        return $this->getResourceLocator()->locate($this, $servletPath, $args);
    }

    /**
     * Returns the identifier for the servlet manager instance.
     *
     * @return string
     * @see \AppserverIo\Psr\Application\ManagerInterface::getIdentifier()
     */
    public function getIdentifier()
    {
        return ServletContextInterface::IDENTIFIER;
    }
}
