from Common import * from WebKit.Servlet import Servlet import sys from types import ClassType, BuiltinFunctionType import ImportSpy as imp # ImportSpy provides find_module and load_module import threading class ServletFactory(Object): """ ServletFactory is an abstract class that defines the protocol for all servlet factories. Servlet factories are used by the Application to create servlets for transactions. A factory must inherit from this class and override uniqueness(), extensions() and servletForTransaction(). Do not invoke the base class methods as they all raise AbstractErrors. Each method is documented below. """ def __init__(self, application): """ Stores a reference to the application in self._app, because subclasses may or may not need to talk back to the application to do their work. """ Object.__init__(self) self._app = application self._cacheClasses = self._app.setting("CacheServletClasses",1) def name(self): """ Returns the name of the factory. This is a convenience for the class name. """ return self.__class__.__name__ def uniqueness(self): """ Returns a string to indicate the uniqueness of the ServletFactory's servlets. The Application needs to know if the servlets are unique per file, per extension or per application. Return values are 'file', 'extension' and 'application'. *** NOTE: Application only supports 'file' uniqueness at this point in time. """ raise AbstractError, self.__class__ def extensions(self): """ Return a list of extensions that match this handler. Extensions should include the dot. An empty string indicates a file with no extension and is a valid value. The extension '.*' is a special case that is looked for a URL's extension doesn't match anything. """ raise AbstractError, self.__class__ def servletForTransaction(self, transaction): """ Returns a new servlet that will handle the transaction. This method should do no caching (e.g., it should really create the servlet upon each invocation) since caching is already done at the Application level. """ raise AbstractError, self.__class__ def flushCache(self): """ Clear any caches and start fesh. """ raise AbstractError, self.__class__ def importAsPackage(self, transaction, serverSidePathToImport): """ Imports the module at the given path in the proper package/subpackage for the current request. For example, if the transaction has the URL 'http://localhost/WebKit.cgi/MyContextDirectory/MySubdirectory/MyPage' and path = 'some/random/path/MyModule.py' and the context is configured to have the name "MyContext" then this function imports the module at that path as MyContext.MySubdirectory.MyModule . Note that the context name may differ from the name of the directory containing the context, even though they are usually the same by convention. Note that the module imported may have a different name from the servlet name specified in the URL. This is used in PSP. """ debug=0 # Pull out the full server side path and the context path request = transaction.request() path = request.serverSidePath() contextPath = request.serverSideContextPath() fullname = request.contextName() ## There is no context, so import the module standalone and give it a unique name if fullname == None: remainder = serverSidePathToImport remainder = string.replace(remainder, '\\', '_') remainder = string.replace(remainder, '/','_') fullmodname = string.replace(remainder,'.','_') if debug: print __file__, "fullmodname=",fullmodname if len(fullmodname) > 100: fullmodname=fullmodname[:-50] modname=os.path.splitext(os.path.basename(serverSidePathToImport))[0] fp, pathname, stuff = imp.find_module(modname, [os.path.dirname(serverSidePathToImport)]) module = imp.load_module(fullmodname, fp, pathname, stuff) return module # First, we'll import the context's package. directory, contextDirName = os.path.split(contextPath) self._importModuleFromDirectory(fullname, contextDirName, directory, isPackageDir=1) directory = contextPath # Now we'll break up the rest of the path into components. remainder = path[len(contextPath)+1:] remainder = string.replace(remainder, '\\', '/') remainder = string.split(remainder, '/') # Import all subpackages of the context package for name in remainder[:-1]: fullname = fullname + '.' + name self._importModuleFromDirectory(fullname, name, directory, isPackageDir=1) directory = os.path.join(directory, name) # Finally, import the module itself as though it was part of the package # or subpackage, even though it may be located somewhere else. moduleFileName = os.path.basename(serverSidePathToImport) moduleDir = os.path.dirname(serverSidePathToImport) name, ext = os.path.splitext(moduleFileName) fullname = fullname + '.' + name module = self._importModuleFromDirectory(fullname, name, moduleDir, forceReload=1) return module def _importModuleFromDirectory(self, fullModuleName, moduleName, directory, isPackageDir=0, forceReload=0): """ Imports the given module from the given directory. fullModuleName should be the full dotted name that will be given to the module within Python. moduleName should be the name of the module in the filesystem, which may be different from the name given in fullModuleName. Returns the module object. If forceReload is true then this reloads the module even if it has already been imported. If isPackageDir is true, then this function creates an empty __init__.py if that file doesn't already exist. """ debug = 0 if debug: print __file__, fullModuleName, moduleName, directory if not forceReload: module = sys.modules.get(fullModuleName, None) if module is not None: return module fp = None try: if isPackageDir: # Check if __init__.py is in the directory -- if not, make an empty one. packageDir = os.path.join(directory, moduleName) initPy = os.path.join(packageDir, '__init__.py') if not os.path.exists(initPy): file = open(initPy, 'w') file.write('#') file.close() fp, pathname, stuff = imp.find_module(moduleName, [directory]) module = imp.load_module(fullModuleName, fp, pathname, stuff) finally: if fp is not None: fp.close() return module class PythonServletFactory(ServletFactory): """ This is the factory for ordinary, Python servlets whose extensions are empty or .py. The servlets are unique per file since the file itself defines the servlet. """ def __init__(self,app): ServletFactory.__init__(self,app) self._cache = {} self._lock = threading.RLock() def uniqueness(self): return 'file' def extensions(self): return ['.py'] def flushCache(self): self._cache = {} # def old_servletForTransaction(self, transaction): # path = transaction.request().serverSidePath() # globals = {} # execfile(path, globals) # from types import ClassType # name = os.path.splitext(os.path.split(path)[1])[0] # assert globals.has_key(name), 'Cannot find expected servlet class named "%s".' % name # theClass = globals[name] # assert type(theClass) is ClassType # assert issubclass(theClass, Servlet) # return theClass() def servletForTransaction(self, transaction): request = transaction.request() path = request.serverSidePath() name = os.path.splitext(os.path.split(path)[1])[0] # Use a lock to prevent multiple simultaneous imports of the same module self._lock.acquire() try: if not self._cache.has_key(path): self._cache[path] = {} if os.path.getmtime(path) > self._cache[path].get('mtime', 0): # Import the module as part of the context's package module = self.importAsPackage(transaction, path) assert module.__dict__.has_key(name), 'Cannot find expected servlet class named %s in %s.' % (repr(name), repr(path)) # Pull the servlet class out of the module theClass = getattr(module, name) # new-style classes aren't ClassType, but they # are okay to use. They are subclasses of # type. But type isn't a class in older # Python versions, it's a builtin function. # So we test what type is first, then use # isinstance only for the newer Python # versions if type(type) is BuiltinFunctionType: assert type(theClass) is ClassType else: assert type(theClass) is ClassType \ or isinstance(theClass, type) assert issubclass(theClass, Servlet) self._cache[path]['mtime'] = os.path.getmtime(path) self._cache[path]['class'] = theClass else: theClass = self._cache[path]['class'] if not self._cacheClasses: del self._cache[path] return theClass() finally: self._lock.release() # def import_servletForTransaction(self, transaction): # path = transaction.request().serverSidePath() # name = os.path.splitext(os.path.split(path)[1])[0] # if not self.cache.has_key(name): self.cache[name]={} # if os.path.getmtime(path) > self.cache[name].get('mtime',0): # if not os.path.split(path)[0] in sys.path: sys.path.append(os.path.split(path[0])) # module_obj=__import__(name) # reload(module_obj)#force reload # inst = module_obj.__dict__[name]() # self.cache[name]['mtime']=os.path.getmtime(path) # else: # inst = sys.modules[name].__dict__[name]() # return inst