###
#
# Copyright Alan Kennedy. 
# 
# You may contact the copyright holder at this uri:
# 
# http://www.xhaus.com/contact/modjy
# 
# The licence under which this code is released is the Apache License v2.0.
# 
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
# 
# You may also read a copy of the license at the following web address.
# 
# http://modjy.xhaus.com/LICENSE.txt
#
###

import sys
import synchronize

from java.io import File

from modjy_exceptions import *

class modjy_publisher:

    def init_publisher(self):
        self.cache = None
        if self.params['app_directory']:
            self.app_directory = self.expand_relative_path(self.params['app_directory'])
        else:
            self.app_directory = self.servlet_context.getRealPath('/')
        self.params['app_directory'] = self.app_directory
        if self.app_directory is not None and not self.app_directory in sys.path:
            sys.path.append(self.app_directory)

    def map_uri(self, req, environ):
        source_uri = '%s%s%s' % (self.app_directory, File.separator, self.params['app_filename'])
        callable_name = self.params['app_callable_name']
        if self.params['callable_query_name']:
            query_string = req.getQueryString()
            if query_string:
                for name_val in query_string.split('&'):
                    if name_val.find('=') != -1:
                        name, value = name_val.split('=', 1)
                    else:
                        name, value = name_val, ''
                    if name == self.params['callable_query_name']:
                        callable_name = value
            else:
                callable_name = ''
        return source_uri, callable_name

    def get_app_object(self, req, environ):
        environ["SCRIPT_NAME"] = "%s%s" % (req.getContextPath(), req.getServletPath())
        path_info = req.getPathInfo() or ""
        environ["PATH_INFO"] = path_info
        environ["PATH_TRANSLATED"] = File(self.app_directory, path_info).getPath()

        if self.params['app_import_name']:
            return self.get_app_object_importable(self.params['app_import_name'])
        else:
            if self.cache is None:
                self.cache = {}
            return self.get_app_object_old_style(req, environ)

    get_app_object = synchronize.make_synchronized(get_app_object)

    def get_app_object_importable(self, importable_name):
        self.log.debug("Attempting to import application callable '%s'\n" % (importable_name, ))
        # Under the importable mechanism, the cache contains a single object
        if self.cache is None:
            application, instantiable, method_name = self.load_importable(importable_name.strip())
            if instantiable and self.params['cache_callables']:
                application = application()
            self.cache = application, instantiable, method_name
        application, instantiable, method_name = self.cache
        self.log.debug("Application is " + str(application))
        if instantiable and not self.params['cache_callables']:
            application = application()
            self.log.debug("Instantiated application is " + str(application))
        if method_name is not None:
            if not hasattr(application, method_name):
                self.log.fatal("Attribute error application callable '%s' as no method '%s'" % (application, method_name))
                self.raise_exc(ApplicationNotFound, "Attribute error application callable '%s' as no method '%s'" % (application, method_name))
            application = getattr(application, method_name)
            self.log.debug("Application method is " + str(application))
        return application

    def load_importable(self, name):
        try:
            instantiable = False ; method_name = None
            importable_name = name
            if name.find('()') != -1:
                instantiable = True
                importable_name, method_name = name.split('()')
                if method_name.startswith('.'):
                    method_name = method_name[1:]
                if not method_name:
                    method_name = None
            module_path, from_name = importable_name.rsplit('.', 1)
            imported = __import__(module_path, globals(), locals(), [from_name])
            imported = getattr(imported, from_name)
            return imported, instantiable, method_name
        except (ImportError, AttributeError), aix:
            self.log.fatal("Import error import application callable '%s': %s\n" % (name, str(aix)))
            self.raise_exc(ApplicationNotFound, "Failed to import app callable '%s': %s" % (name, str(aix)))

    def get_app_object_old_style(self, req, environ):
        source_uri, callable_name = self.map_uri(req, environ)
        source_filename = source_uri
        if not self.params['cache_callables']:
            self.log.debug("Caching of callables disabled")
            return self.load_object(source_filename, callable_name)
        if not self.cache.has_key( (source_filename, callable_name) ):
            self.log.debug("Callable object not in cache: %s#%s" % (source_filename, callable_name) )
            return self.load_object(source_filename, callable_name)
        app_callable, last_mod = self.cache.get( (source_filename, callable_name) )
        self.log.debug("Callable object was in cache: %s#%s" % (source_filename, callable_name) )
        if self.params['reload_on_mod']:
            f = File(source_filename)
            if f.lastModified() > last_mod:
                self.log.info("Source file '%s' has been modified: reloading" % source_filename)
                return self.load_object(source_filename, callable_name)
        return app_callable

    def load_object(self, path, callable_name):
        try:
            app_ns = {} ; execfile(path, app_ns)
            app_callable = app_ns[callable_name]
            f = File(path)
            self.cache[ (path, callable_name) ] = (app_callable, f.lastModified())
            return app_callable
        except IOError, ioe:
            self.raise_exc(ApplicationNotFound, "Application filename not found: %s" % path)
        except KeyError, k:
            self.raise_exc(NoCallable, "No callable named '%s' in %s" % (callable_name, path))
        except Exception, x:
            self.raise_exc(NoCallable, "Error loading jython callable '%s': %s" % (callable_name, str(x)) )

