Attempt to optimize Zope 3 startup time by caching processed ZCML directives on
disk.  This is a prototype, full of ugly hacks, not fit for production.

Patch by Marius Gedminas and Stephan Richter.  It's diffed against Zope 3 trunk
in svn and touches multiple svn externals.

Overall strategy: take the ZCML configuration context object's list of actions
and pickle it into zcml.cache.  During next startup load the pickle instead of
parsing ZCMLs.  This shaves off about 30% of Zope startup time when I tested it
on my laptop (unpickling costs about 0.7 seconds, while full ZCML parsing takes
1.7 seconds, so having a cache brings down the startup time from ~3 to ~2
seconds).

In real life you'd want a separate cache for each zcml file, and you'd want to
compare mtimes and automatically update the cache when the corresponding ZCML
file changes.

We had to make some changes to get actions to work:

  * BTree iterators had to be made pickleable so that security declarations for
    xxBTreeIterator would work

  * buddydemo.stubpostal.Lookup had to be converted to a new-style class

  * nested classes can't be pickled so we had to move
    zope.app.component.metaconfigure.ProxyView to top level.

  * dynamically-generated classes can't be pickled so we had to change
    view, browser view, browse page, all browser form directives to avoid them.
    I have some reservations about the changes made to view directives, because
    now we create a new view *class* for every request

  * some global registries need to stay singletons, but though configuration
    actions like to pickle bound methods, so I had to add __reduce__ to them.
    I'm not at all confident I caught all of them.

  * we had to make the magic zope.app.menus module even more magic so that we
    could pickle and unpickle dynamically created interfaces

  * we had to explicitly import zope.app.publisher.browser.menumeta in the
    zcml code to register the fake menus module in sys.modules; this probably
    breaks layering

  * we had to avoid pickling compiled TALES expressions

  * you can't pickle modules, so I changed zope.app.security.metaconfigure to
    pickle their names and resolve later.  This introduces a dependency on
    zope.dottedname, and probably breaks some eggs since I didn't think about
    updating setup.py

  * zope.formlib.form change is a very unfortunate: we can't rebind a name
    and then pickle the function object that had that name previously,  but
    decorators such as namedtemplate.implementation do exactly that.

  * we had to remove the ban on pickling ViewPageTemplateFile objects

  * interface names can't be unicode objects (menu directives define interfaces
    dynamically with the name in unicode).  I forgot the reason why it breaks
    pickling but not regular usage.

  * the C implementations of security checkers aren't picklable

  * pickling bound methods (?) requires us to stuff in 'instancemethod' into
    __builtin__

Outstanding problems:

  * the tests are currently broken

  * the C implementations of security checkers cannot be used

  * you can't use decorators that don't return the original function and then
    register the result as an adapter/utility

  * I have no confidence that these changes didn't break something we haven't tested

  * there's some debugging code left in there

  * there's some really ugly code in there


Index: src/BTrees/BTreeItemsTemplate.c
===================================================================
--- src/BTrees/BTreeItemsTemplate.c	(revision 75706)
+++ src/BTrees/BTreeItemsTemplate.c	(working copy)
@@ -661,7 +661,7 @@
 static PyTypeObject BTreeIter_Type = {
         PyObject_HEAD_INIT(NULL)
 	0,					/* ob_size */
-	MOD_NAME_PREFIX "-iterator",		/* tp_name */
+	MODULE_NAME MOD_NAME_PREFIX "TreeIterator",/* tp_name */
 	sizeof(BTreeIter),			/* tp_basicsize */
 	0,					/* tp_itemsize */
 	/* methods */
Index: src/BTrees/BTreeModuleTemplate.c
===================================================================
--- src/BTrees/BTreeModuleTemplate.c	(revision 75706)
+++ src/BTrees/BTreeModuleTemplate.c	(working copy)
@@ -491,6 +491,8 @@
 	return;
     if (!init_persist_type(&BTreeType))
 	return;
+    if (!init_persist_type(&BTreeIter_Type))
+	return;
     if (!init_persist_type(&SetType))
 	return;
     if (!init_persist_type(&TreeSetType))
Index: src/buddydemo/stubpostal.py
===================================================================
--- src/buddydemo/stubpostal.py	(revision 76404)
+++ src/buddydemo/stubpostal.py	(working copy)
@@ -14,7 +14,7 @@
     def __init__(self, city, state):
         self.city, self.state = city, state
 
-class Lookup:
+class Lookup(object):
 
     zope.interface.implements(IPostalLookup)
     
Index: src/zope/app/component/metaconfigure.py
===================================================================
--- src/zope/app/component/metaconfigure.py	(revision 79995)
+++ src/zope/app/component/metaconfigure.py	(working copy)
@@ -129,6 +129,17 @@
         args = (provides.__module__ + '.' + provides.__name__, type)
                )
 
+class ProxyView(object):
+    """Class to create simple proxy views."""
+
+    def __init__(self, factory, checker):
+        self.factory = factory
+        self.checker = checker
+
+    def __call__(self, *objects):
+        return proxify(self.factory(*objects), self.checker)
+
+
 def view(_context, factory, type, name, for_, layer=None,
          permission=None, allowed_interface=None, allowed_attributes=None,
          provides=Interface):
@@ -148,16 +159,7 @@
         checker = _checker(_context, permission,
                            allowed_interface, allowed_attributes)
 
-        class ProxyView(object):
-            """Class to create simple proxy views."""
 
-            def __init__(self, factory, checker):
-                self.factory = factory
-                self.checker = checker
-
-            def __call__(self, *objects):
-                return proxify(self.factory(*objects), self.checker)
-
         factory[-1] = ProxyView(factory[-1], checker)
 
 
Index: src/zope/app/form/browser/add.py
===================================================================
--- src/zope/app/form/browser/add.py	(revision 79995)
+++ src/zope/app/form/browser/add.py	(working copy)
@@ -153,7 +153,8 @@
 def AddViewFactory(name, schema, label, permission, layer,
                    template, default_template, bases, for_,
                    fields, content_factory, arguments,
-                   keyword_arguments, set_before_add, set_after_add):
+                   keyword_arguments, set_before_add, set_after_add,
+                   widgets=None):
 
     class_  = SimpleViewClass(
         template, used_for=schema, bases=bases, name=name)
@@ -167,6 +168,9 @@
     class_._keyword_arguments = keyword_arguments
     class_._set_before_add = set_before_add
     class_._set_after_add = set_after_add
+    if widgets:
+        for widget_name, widget in widgets.items():
+            setattr(class_, widget_name, widget)
 
     class_.generated_form = ViewPageTemplateFile(default_template)
 
Index: src/zope/app/form/browser/editview.py
===================================================================
--- src/zope/app/form/browser/editview.py	(revision 79995)
+++ src/zope/app/form/browser/editview.py	(working copy)
@@ -121,13 +121,17 @@
 
 def EditViewFactory(name, schema, label, permission, layer,
                     template, default_template, bases, for_, fields,
-                    fulledit_path=None, fulledit_label=None):
+                    fulledit_path=None, fulledit_label=None, widgets=None):
 
     class_ = SimpleViewClass(template, used_for=schema, bases=bases, name=name)
     class_.schema = schema
     class_.label = label
     class_.fieldNames = fields
 
+    if widgets:
+        for widget_name, widget in widgets.items():
+            setattr(class_, widget_name, widget)
+
     class_.fulledit_path = fulledit_path
     if fulledit_path and (fulledit_label is None):
         fulledit_label = "Full edit"
Index: src/zope/app/form/browser/metaconfigure.py
===================================================================
--- src/zope/app/form/browser/metaconfigure.py	(revision 79995)
+++ src/zope/app/form/browser/metaconfigure.py	(working copy)
@@ -94,12 +94,6 @@
 
         self._widgets[field+'_widget'] = factory
 
-    def _processWidgets(self):
-        if self._widgets:
-            customWidgetsObject = type('CustomWidgetsMixin', (object,),
-                                       self._widgets)
-            self.bases = self.bases + (customWidgetsObject,)
-
     def _normalize(self):
         if self.for_ is None:
             self.for_ = self.schema
@@ -229,7 +223,6 @@
             self.content_factory = self.content_factory_id
 
     def __call__(self):
-        self._processWidgets()
         self._handle_menu()
         self._handle_content_factory()
         self._handle_arguments()
@@ -239,7 +232,8 @@
             callable=AddViewFactory,
             args=self._args()+(self.content_factory, self.arguments,
                                  self.keyword_arguments,
-                                 self.set_before_add, self.set_after_add),
+                                 self.set_before_add, self.set_after_add,
+                                 self._widgets),
             )
 
 class EditFormDirectiveBase(BaseFormDirective):
@@ -268,12 +262,11 @@
                 layer=self.layer)
 
     def __call__(self):
-        self._processWidgets()
         self._handle_menu()
         self._context.action(
             discriminator=self._discriminator(),
             callable=EditViewFactory,
-            args=self._args(),
+            args=self._args() + (None, None, self._widgets),
         )
 
 class FormDirective(EditFormDirective):
@@ -299,11 +292,11 @@
     fulledit_label = None
 
     def __call__(self):
-        self._processWidgets()
         self._context.action(
             discriminator = self._discriminator(),
             callable = EditViewFactory,
-            args = self._args()+(self.fulledit_path, self.fulledit_label),
+            args = self._args()+(self.fulledit_path, self.fulledit_label,
+                                 self._widgets),
             )
 
 
@@ -313,10 +306,9 @@
     default_template = 'display.pt'
 
     def __call__(self):
-        self._processWidgets()
         self._handle_menu()
         self._context.action(
             discriminator = self._discriminator(),
             callable = DisplayViewFactory,
-            args = self._args()+(self.menu,)
+            args = self._args()+(None, None, self._widgets)
             )
Index: src/zope/app/form/browser/schemadisplay.py
===================================================================
--- src/zope/app/form/browser/schemadisplay.py	(revision 79995)
+++ src/zope/app/form/browser/schemadisplay.py	(working copy)
@@ -62,7 +62,7 @@
 
 def DisplayViewFactory(name, schema, label, permission, layer,
                        template, default_template, bases, for_, fields,
-                       fulledit_path=None, fulledit_label=None):
+                       fulledit_path=None, fulledit_label=None, widgets=None):
     class_ = SimpleViewClass(template, used_for=schema, bases=bases,
                              name=name)
     class_.schema = schema
@@ -73,6 +73,9 @@
         fulledit_label = "Full display"
     class_.fulledit_label = fulledit_label
     class_.generated_form = ViewPageTemplateFile(default_template)
+    if widgets:
+        for widgets_name, widget in widgets.items():
+            setattr(class_, widgets_name, widget)
     defineChecker(class_,
                   NamesChecker(("__call__", "__getitem__", "browserDefault"),
                                permission))
Index: src/zope/app/interpreter/python.py
===================================================================
--- src/zope/app/interpreter/python.py	(revision 79995)
+++ src/zope/app/interpreter/python.py	(working copy)
@@ -23,7 +23,7 @@
 from zope.interface import implements
 from zope.security.untrustedpython.interpreter import exec_src
 
-class PythonInterpreter(object):
+class _PythonInterpreter(object):
 
     implements(IInterpreter)
 
@@ -59,6 +59,9 @@
             code = 'if 1 == 1:\n' + code
         return self.evaluate(code, globals)
 
+    def __reduce__(self):
+        return 'PythonInterpreter'
 
+
 # It's a singelton for now.
-PythonInterpreter = PythonInterpreter()
+PythonInterpreter = _PythonInterpreter()
Index: src/zope/app/publication/requestpublicationregistry.py
===================================================================
--- src/zope/app/publication/requestpublicationregistry.py	(revision 79995)
+++ src/zope/app/publication/requestpublicationregistry.py	(working copy)
@@ -109,7 +109,10 @@
         # configuration (no default handler for method=* and mimetype=*)
         return None
 
+    def __reduce__(self):
+        return 'factoryRegistry'
 
+
 factoryRegistry = RequestPublicationRegistry()
 
 from zope.testing import cleanup
Index: src/zope/app/publisher/browser/menumeta.py
===================================================================
--- src/zope/app/publisher/browser/menumeta.py	(revision 79995)
+++ src/zope/app/publisher/browser/menumeta.py	(working copy)
@@ -36,11 +36,28 @@
 
 # Create special modules that contain all menu item types
 from types import ModuleType as module
+
+# XXXXXXX
+
+class MenusModule(module):
+
+    def __getattr__(self, name):
+        if name.startswith('__'): # ignore special names pls
+            return module.__getattr__(self, name)
+        interface = InterfaceClass(name, (),
+                                   __doc__='Menu Item Type: %s' %name,
+                                   __module__='zope.app.menus')
+        setattr(self, name, interface)
+        return interface
+
+
 import sys
-menus = module('menus')
+menus = MenusModule('zope.app.menus')
 sys.modules['zope.app.menus'] = menus
 
 
+
+
 _order_counter = {}
 
 
@@ -130,6 +147,9 @@
     def __call__(self, context, request):
         item = self.factory(context, request)
 
+        if isinstance(self.kwargs.get('filter'), basestring):
+            self.kwargs['filter'] = Engine.compile(self.kwargs['filter'])
+
         for key, value in self.kwargs.items():
             setattr(item, key, value)
 
@@ -156,9 +176,6 @@
     def menuItem(self, _context, action, title, description=u'',
                  icon=None, filter=None, permission=None, extra=None, order=0):
 
-        if filter is not None:
-            filter = Engine.compile(filter)
-
         if permission is None:
             permission = self.permission
 
@@ -178,9 +195,6 @@
                     action=u'', icon=None, filter=None, permission=None,
                     extra=None, order=0):
 
-        if filter is not None:
-            filter = Engine.compile(filter)
-
         if permission is None:
             permission = self.permission
 
Index: src/zope/app/publisher/browser/viewmeta.py
===================================================================
--- src/zope/app/publisher/browser/viewmeta.py	(revision 79995)
+++ src/zope/app/publisher/browser/viewmeta.py	(working copy)
@@ -84,6 +84,53 @@
 
 # page
 
+class PageFactory(object):
+
+    def __init__(self, class_, attribute, name, template, required):
+        self.class_ = class_
+        self.attribute = attribute
+        self.name = name
+        self.template = template
+        self.required = required
+
+    def __call__(self, context, request):
+        # TODO: new __name__ attribute must be tested
+        if self.class_:
+            if self.attribute != '__call__':
+                if not hasattr(self.class_, self.attribute):
+                    raise ConfigurationError(
+                        "The provided class doesn't have the specified attribute "
+                        )
+            if self.template:
+                # class and template
+                new_class = SimpleViewClass(self.template,
+                                            bases=(self.class_, ), name=self.name)
+            else:
+                if not hasattr(self.class_, 'browserDefault'):
+                    cdict = {
+                        'browserDefault':
+                        lambda slf, request: (getattr(slf, self.attribute), ())
+                        }
+                else:
+                    cdict = {}
+
+                cdict['__name__'] = self.name
+                cdict['__page_attribute__'] = self.attribute
+                new_class = type(self.class_.__name__, (self.class_, simple,), cdict)
+
+            if hasattr(self.class_, '__implements__'):
+                classImplements(new_class, IBrowserPublisher)
+
+        else:
+            # template
+            new_class = SimpleViewClass(self.template, name=self.name)
+
+        new_class.__Security_checker__ = Checker(self.required)
+
+        return new_class(context, request)
+
+
+
 def page(_context, name, permission, for_,
          layer=IDefaultBrowserLayer, template=None, class_=None,
          allowed_interface=None, allowed_attributes=None,
@@ -101,7 +148,6 @@
         if template:
             raise ConfigurationError(
                 "Attribute and template cannot be used together.")
-
         if not class_:
             raise ConfigurationError(
                 "A class must be provided if attribute is used")
@@ -112,53 +158,23 @@
             raise ConfigurationError("No such file", template)
         required['__getitem__'] = permission
 
-    # TODO: new __name__ attribute must be tested
-    if class_:
-        if attribute != '__call__':
-            if not hasattr(class_, attribute):
-                raise ConfigurationError(
-                    "The provided class doesn't have the specified attribute "
-                    )
-        if template:
-            # class and template
-            new_class = SimpleViewClass(template, bases=(class_, ), name=name)
-        else:
-            if not hasattr(class_, 'browserDefault'):
-                cdict = {
-                    'browserDefault':
-                    lambda self, request: (getattr(self, attribute), ())
-                    }
-            else:
-                cdict = {}
-
-            cdict['__name__'] = name
-            cdict['__page_attribute__'] = attribute
-            new_class = type(class_.__name__, (class_, simple,), cdict)
-
-        if hasattr(class_, '__implements__'):
-            classImplements(new_class, IBrowserPublisher)
-
-    else:
-        # template
-        new_class = SimpleViewClass(template, name=name)
-
-    for n in (attribute, 'browserDefault', '__call__', 'publishTraverse'):
-        required[n] = permission
-
     _handle_allowed_interface(_context, allowed_interface, permission,
                               required)
     _handle_allowed_attributes(_context, allowed_attributes, permission,
                                required)
 
     _handle_for(_context, for_)
+    
+    for n in (attribute, 'browserDefault', '__call__', 'publishTraverse'):
+        required[n] = permission
 
-    defineChecker(new_class, Checker(required))
+    factory = PageFactory(class_, attribute, name, template, required)
 
     _context.action(
         discriminator = ('view', for_, name, IBrowserRequest, layer),
         callable = handler,
         args = ('registerAdapter',
-                new_class, (for_, layer), Interface, name, _context.info),
+                factory, (for_, layer), Interface, name, _context.info),
         )
 
 
@@ -196,50 +212,17 @@
 # This is a different case. We actually build a class with attributes
 # for all of the given pages.
 
-class view(object):
+class ViewFactory(object):
+    
+    def __init__(self, class_, pages, required, default, name):
+        self.class_ = class_
+        self.pages = pages
+        self.required = required
+        self.default = default
+        self.name = name
 
-    default = None
+    def __call__(self, context, request):
 
-    def __init__(self, _context, for_, permission,
-                 name='', layer=IDefaultBrowserLayer, class_=None,
-                 allowed_interface=None, allowed_attributes=None,
-                 menu=None, title=None, provides=Interface,
-                 ):
-
-        _handle_menu(_context, menu, title, [for_], name, permission, layer)
-
-        permission = _handle_permission(_context, permission)
-
-        self.args = (_context, name, for_, permission, layer, class_,
-                     allowed_interface, allowed_attributes)
-
-        self.pages = []
-        self.menu = menu
-        self.provides = provides
-
-    def page(self, _context, name, attribute=None, template=None):
-        if template:
-            template = os.path.abspath(_context.path(template))
-            if not os.path.isfile(template):
-                raise ConfigurationError("No such file", template)
-        else:
-            if not attribute:
-                raise ConfigurationError(
-                    "Must specify either a template or an attribute name")
-
-        self.pages.append((name, attribute, template))
-        return ()
-
-    def defaultPage(self, _context, name):
-        self.default = name
-        return ()
-
-    def __call__(self):
-        (_context, name, for_, permission, layer, class_,
-         allowed_interface, allowed_attributes) = self.args
-
-        required = {}
-
         cdict = {}
         pages = {}
 
@@ -247,23 +230,20 @@
 
             if template:
                 cdict[pname] = ViewPageTemplateFile(template)
-                if attribute and attribute != name:
+                if attribute and attribute != self.name:
                     cdict[attribute] = cdict[pname]
             else:
-                if not hasattr(class_, attribute):
+                if not hasattr(self.class_, attribute):
                     raise ConfigurationError("Undefined attribute",
                                              attribute)
 
             attribute = attribute or pname
-            required[pname] = permission
-
             pages[pname] = attribute
 
         # This should go away, but noone seems to remember what to do. :-(
-        if hasattr(class_, 'publishTraverse'):
+        if hasattr(self.class_, 'publishTraverse'):
 
-            def publishTraverse(self, request, name,
-                                pages=pages, getattr=getattr):
+            def publishTraverse(self, request, name, pages=pages, class_=self.class_):
 
                 if name in pages:
                     return getattr(self, pages[name])
@@ -275,8 +255,7 @@
                 return m(request, name)
 
         else:
-            def publishTraverse(self, request, name,
-                                pages=pages, getattr=getattr):
+            def publishTraverse(self, request, name, pages=pages):
 
                 if name in pages:
                     return getattr(self, pages[name])
@@ -288,31 +267,83 @@
 
         cdict['publishTraverse'] = publishTraverse
 
-        if not hasattr(class_, 'browserDefault'):
+        if not hasattr(self.class_, 'browserDefault'):
             if self.default or self.pages:
                 default = self.default or self.pages[0][0]
                 cdict['browserDefault'] = (
                     lambda self, request, default=default:
                     (self, (default, ))
                     )
-            elif providesCallable(class_):
+            elif providesCallable(self.class_):
                 cdict['browserDefault'] = (
                     lambda self, request: (self, ())
                     )
 
-        if class_ is not None:
-            bases = (class_, simple)
+        if self.class_ is not None:
+            bases = (self.class_, simple)
         else:
             bases = (simple,)
 
         try:
-            cname = str(name)
-        except:
+            cname = str(self.name)
+        except UnicodeError:
             cname = "GeneratedClass"
 
-        cdict['__name__'] = name
+        cdict['__name__'] = self.name
         newclass = type(cname, bases, cdict)
 
+        newclass.__Security_checker__ = Checker(self.required)
+
+        return newclass(context, request)
+
+
+class view(object):
+
+    default = None
+
+    def __init__(self, _context, for_, permission,
+                 name='', layer=IDefaultBrowserLayer, class_=None,
+                 allowed_interface=None, allowed_attributes=None,
+                 menu=None, title=None, provides=Interface,
+                 ):
+
+        _handle_menu(_context, menu, title, [for_], name, permission, layer)
+
+        permission = _handle_permission(_context, permission)
+
+        self.args = (_context, name, for_, permission, layer, class_,
+                     allowed_interface, allowed_attributes)
+
+        self.pages = []
+        self.menu = menu
+        self.provides = provides
+
+    def page(self, _context, name, attribute=None, template=None):
+        if template:
+            template = os.path.abspath(_context.path(template))
+            if not os.path.isfile(template):
+                raise ConfigurationError("No such file", template)
+        else:
+            if not attribute:
+                raise ConfigurationError(
+                    "Must specify either a template or an attribute name")
+
+        self.pages.append((name, attribute, template))
+        return ()
+
+    def defaultPage(self, _context, name):
+        self.default = name
+        return ()
+
+    def __call__(self):
+        (_context, name, for_, permission, layer, class_,
+         allowed_interface, allowed_attributes) = self.args
+
+        required = {}
+
+        for pname, attribute, template in self.pages:
+            required[pname] = permission
+ 
         for n in ('publishTraverse', 'browserDefault', '__call__'):
             required[n] = permission
 
@@ -322,8 +353,10 @@
                                    required)
         _handle_for(_context, for_)
 
-        defineChecker(newclass, Checker(required))
 
+        factory = ViewFactory(class_, self.pages, required, self.default,
+                              name)
+
         if self.provides is not None:
             _context.action(
                 discriminator = None,
@@ -335,7 +368,7 @@
             discriminator = ('view', (for_, layer), name, self.provides),
             callable = handler,
             args = ('registerAdapter',
-                    newclass, (for_, layer), self.provides, name,
+                    factory, (for_, layer), self.provides, name,
                     _context.info),
             )
 
Index: src/zope/app/publisher/xmlrpc/metaconfigure.py
===================================================================
--- src/zope/app/publisher/xmlrpc/metaconfigure.py	(revision 79995)
+++ src/zope/app/publisher/xmlrpc/metaconfigure.py	(working copy)
@@ -49,6 +49,24 @@
                 args = ('', for_)
                 )
 
+    if name:
+        _context.action(
+            discriminator = ('view', for_, name, layer),
+            callable = registerSingleXMLRPCView,
+            args = (for_, methods, class_, permission, require, name, layer, _context.info)
+            )
+    else:
+        for name in require:
+            _context.action(
+                discriminator = ('view', for_, name, layer),
+                callable = registerOneOfMultipleXMLRPCViews,
+                args = (for_, methods, class_, permission, require, name, layer, _context.info)
+                )
+
+
+def registerSingleXMLRPCView(for_, methods, class_, permission, require, name,
+                             layer, info):
+
     # Make sure that the class inherits MethodPublisher, so that the views
     # have a location
     if class_ is None:
@@ -57,65 +75,63 @@
         original_class = class_
         class_ = type(class_.__name__, (class_, MethodPublisher), {})
 
-    if name:
-        # Register a single view
+    # Register a single view
 
-        if permission:
-            checker = Checker(require)
+    if permission:
+        checker = Checker(require)
 
-            def proxyView(context, request, class_=class_, checker=checker):
-                view = class_(context, request)
-                # We need this in case the resource gets unwrapped and
-                # needs to be rewrapped
-                view.__Security_checker__ = checker
-                return view
+        def proxyView(context, request, class_=class_, checker=checker):
+            view = class_(context, request)
+            # We need this in case the resource gets unwrapped and
+            # needs to be rewrapped
+            view.__Security_checker__ = checker
+            return view
 
-            class_ = proxyView
-            class_.factory = original_class
-        else:
-            # No permission was defined, so we defer to the checker
-            # of the original class
-            def proxyView(context, request, class_=class_):
-                view = class_(context, request)
-                view.__Security_checker__ = getCheckerForInstancesOf(
-                    original_class)
-                return view
-            class_ = proxyView
-            class_.factory = original_class
+        class_ = proxyView
+        class_.factory = original_class
+    else:
+        # No permission was defined, so we defer to the checker
+        # of the original class
+        def proxyView(context, request, class_=class_):
+            view = class_(context, request)
+            view.__Security_checker__ = getCheckerForInstancesOf(
+                original_class)
+            return view
+        class_ = proxyView
+        class_.factory = original_class
 
-        # Register the new view.
-        _context.action(
-            discriminator = ('view', for_, name, layer),
-            callable = handler,
-            args = ('registerAdapter',
-                    class_, (for_, layer), Interface, name,
-                    _context.info)
-            )
+    # Register the new view.
+    handler('registerAdapter', class_, (for_, layer), Interface, name, info)
+
+    # Register the used interfaces with the site manager
+    provideInterface('', for_)
+
+
+def registerOneOfMultipleXMLRPCViews(for_, methods, class_, permission,
+                                     require, name, layer, info):
+
+    # Make sure that the class inherits MethodPublisher, so that the views
+    # have a location
+    if class_ is None:
+        class_ = original_class = MethodPublisher
     else:
-        if permission:
-            checker = Checker({'__call__': permission})
-        else:
-            raise ConfigurationError(
-              "XML/RPC view has neither a name nor a permission. "
-              "You have to specify at least one of the two.")
+        original_class = class_
+        class_ = type(class_.__name__, (class_, MethodPublisher), {})
 
-        for name in require:
-            # create a new callable class with a security checker;
-            cdict = {'__Security_checker__': checker,
-                     '__call__': getattr(class_, name)}
-            new_class = type(class_.__name__, (class_,), cdict)
-            _context.action(
-                discriminator = ('view', for_, name, layer),
-                callable = handler,
-                args = ('registerAdapter',
-                        new_class, (for_, layer), Interface, name,
-                        _context.info)
-                )
+    if permission:
+        checker = Checker({'__call__': permission})
+    else:
+        raise ConfigurationError(
+          "XML/RPC view has neither a name nor a permission. "
+          "You have to specify at least one of the two.")
 
+    # create a new callable class with a security checker;
+    cdict = {'__Security_checker__': checker,
+             '__call__': getattr(class_, name)}
+    new_class = type(class_.__name__, (class_,), cdict)
+    handler('registerAdapter', new_class, (for_, layer), Interface,
+            name, info)
+
     # Register the used interfaces with the site manager
-    if for_ is not None:
-        _context.action(
-            discriminator = None,
-            callable = provideInterface,
-            args = ('', for_)
-            )
+    provideInterface('', for_)
+
Index: src/zope/app/security/metaconfigure.py
===================================================================
--- src/zope/app/security/metaconfigure.py	(revision 79995)
+++ src/zope/app/security/metaconfigure.py	(working copy)
@@ -19,17 +19,25 @@
 from zope.component.zcml import utility
 from zope.security.checker import moduleChecker, Checker, defineChecker
 from zope.security.checker import CheckerPublic
+from zope.dottedname.resolve import resolve
 
 from zope.app.security import principalregistry
 from zope.app.security import interfaces
 
 
+def dotted_name(module):
+    return module.__name__
+
+
 def protectModule(module, name, permission):
     """Set up a module checker to require a permission to access a name
 
     If there isn't a checker for the module, create one.
     """
 
+    if isinstance(module, basestring):
+        module = resolve(module)
+
     checker = moduleChecker(module)
     if checker is None:
         checker = Checker({}, {})
@@ -62,9 +70,9 @@
     for name in _names(attributes, interface):
         context.action(
             discriminator=('http://namespaces.zope.org/zope:module',
-                           context.module, name),
+                           dotted_name(context.module), name),
             callable = protectModule,
-            args = (context.module, name, 'zope.Public'),
+            args = (dotted_name(context.module), name, 'zope.Public'),
             )
 
 
@@ -72,9 +80,9 @@
     for name in _names(attributes, interface):
         context.action(
             discriminator=('http://namespaces.zope.org/zope:module',
-                           context.module, name),
+                           dotted_name(context.module), name),
             callable = protectModule,
-            args = (context.module, name, permission),
+            args = (dotted_name(context.module), name, permission),
             )
 
 def _principal():
Index: src/zope/app/security/principalregistry.py
===================================================================
--- src/zope/app/security/principalregistry.py	(revision 79995)
+++ src/zope/app/security/principalregistry.py	(working copy)
@@ -124,6 +124,9 @@
         self.__defaultid = None
         self.__defaultObject = None
 
+    def __reduce__(self):
+        return 'principalRegistry'
+
 principalRegistry = PrincipalRegistry()
 
 # Register our cleanup with Testing.CleanUp to make writing unit tests
Index: src/zope/app/securitypolicy/principalpermission.py
===================================================================
--- src/zope/app/securitypolicy/principalpermission.py	(revision 79995)
+++ src/zope/app/securitypolicy/principalpermission.py	(working copy)
@@ -107,7 +107,10 @@
         ''' See the interface IPrincipalPermissionManager '''
         return self.getAllCells()
 
+    def __reduce__(self):
+        return 'principalPermissionManager'
 
+
 # Permissions are our rows, and principals are our columns
 principalPermissionManager = PrincipalPermissionManager()
 
Index: src/zope/app/securitypolicy/principalrole.py
===================================================================
--- src/zope/app/securitypolicy/principalrole.py	(revision 79995)
+++ src/zope/app/securitypolicy/principalrole.py	(working copy)
@@ -99,6 +99,9 @@
         ''' See the interface IPrincipalRoleMap '''
         return self.getAllCells()
 
+    def __reduce__(self):
+        return 'principalRoleManager'
+
 # Roles are our rows, and principals are our columns
 principalRoleManager = PrincipalRoleManager()
 
Index: src/zope/app/securitypolicy/rolepermission.py
===================================================================
--- src/zope/app/securitypolicy/rolepermission.py	(revision 79995)
+++ src/zope/app/securitypolicy/rolepermission.py	(working copy)
@@ -102,6 +102,9 @@
         '''See interface IRolePermissionMap'''
         return self.getAllCells()
 
+    def __reduce__(self):
+        return 'rolePermissionManager'
+
 # Permissions are our rows, and roles are our columns
 rolePermissionManager = RolePermissionManager()
 
Index: src/zope/component/zcml.py
===================================================================
--- src/zope/component/zcml.py	(revision 79995)
+++ src/zope/component/zcml.py	(working copy)
@@ -144,20 +144,25 @@
     factory.factory = factories[0]
     return factory
 
-def _protectedFactory(original_factory, checker):
-    # This has to be named 'factory', aparently, so as not to confuse
-    # apidoc :(
-    def factory(*args):
-        ob = original_factory(*args)
+
+class _protectedFactory(object):
+
+    def __init__(self, original_factory, checker):
+        # This has to be named 'factory', aparently, so as not to confuse
+        # apidoc :(
+        self.factory = original_factory
+        self.checker = checker
+        self.__name__ = 'factory' # XXX not sure why zope.security.adapter needs this
+
+    def __call__(self, *args):
+        ob = self.factory(*args)
         try:
-            ob.__Security_checker__ = checker
+            ob.__Security_checker__ = self.checker
         except AttributeError:
-            ob = Proxy(ob, checker)
-
+            ob = Proxy(ob, self.checker)
         return ob
-    factory.factory = original_factory
-    return factory
 
+
 def adapter(_context, factory, provides=None, for_=None, permission=None,
             name='', trusted=False, locate=False):
 
Index: src/zope/configuration/xmlconfig.py
===================================================================
--- src/zope/configuration/xmlconfig.py	(revision 79995)
+++ src/zope/configuration/xmlconfig.py	(working copy)
@@ -129,6 +129,12 @@
     def end(self, line, column):
         self.eline, self.ecolumn = line, column
 
+    def __cmp__(self, other):
+        if not isinstance(other, ParserInfo):
+            return False
+        return cmp((self.file, self.line, self.column, self.eline, self.ecolumn),
+                   (other.file, other.line, other.column, other.eline, other.ecolumn))
+
     def __repr__(self):
         if (self.line, self.column) == (self.eline, self.ecolumn):
             return 'File "%s", line %s.%s' % (
@@ -607,9 +613,69 @@
         registerCommonDirectives(context)
         context.package = package
 
-    include(context, name, package)
+    import cPickle as pickle
+    import time
+    import __builtin__, types
+    __builtin__.instancemethod = types.MethodType # HAAAAAAAAAAACK for pickling
+    t0 = time.time()
+    try:
+        import zope.app.publisher.browser.menumeta  # register the fake menus module
+        context.actions = pickle.load(open('zcml.cache'))
+        t1 = time.time()
+        print "\n\nZCML unpickling: %.3f seconds" % (t1 - t0)
+    except (IOError, EOFError):
+        include(context, name, package)
+        t1 = time.time()
+        print "\n\nZCML processing: %.3f seconds" % (t1 - t0)
+####    pf = open('zcml.cache.sample', 'w')
+####    failures = 0
+####    fail_set = dict()
+####    for n, action in enumerate(context.actions):
+####        try:
+####            pickle.dump(action, pf)
+####        except Exception, e:
+####            failures += 1
+####            what = action
+####            while isinstance(what, tuple):
+####                what = what[0]
+####            fail_set[what] = fail_set.get(what, 0) + 1
+####            print n, 'of', len(context.actions)
+####            from pprint import pprint
+####            pprint(action)
+####            import traceback
+####            traceback.print_exc()
+####            if 0:
+####                import pdb; pdb.set_trace()
+##      print failures, 'of', len(context.actions), 'actions failed to be pickled'
+##      print 'discriminators: ', sorted(fail_set.items())
+####    pf.close()
+        t0 = time.time()
+        pickle.dump(context.actions, open('zcml.cache', 'w'))
+ ##     test = pickle.load(open('zcml.cache'))
+ ##     assert len(test) == len(context.actions)
+ ##     good = bad = 0
+ ##     bad_set = {}
+ ##     for a, t in zip(context.actions, test):
+ ##         if a != t and (not isinstance(a[0], tuple) or a[0][0] != 'ContentDirective'):
+ ##             what = a[0]
+ ##             while isinstance(what, tuple):
+ ##                 what = what[0]
+ ##             bad_set[what] = bad_set.get(what, 0) + 1
+###             import pdb; pdb.set_trace()
+ ##             bad += 1
+ ##         else:
+ ##             good += 1
+ ##     print "Got %d good and %d bad actions" % (good, bad)
+ ##     print "Bad ones:", sorted(bad_set.items())
+ ##     context.actions = test
+        t1 = time.time()
+        print "\n\nZCML pickling: %.3f seconds" % (t1 - t0)
     if execute:
+        t0 = time.time()
         context.execute_actions()
+        t1 = time.time()
+        print "ZCML execution: %.3f seconds" % (t1 - t0)
+    print
 
     return context
 
Index: src/zope/formlib/form.py
===================================================================
--- src/zope/formlib/form.py	(revision 79995)
+++ src/zope/formlib/form.py	(working copy)
@@ -609,8 +609,7 @@
 
     render = namedtemplate.NamedTemplate('render')
 
-@namedtemplate.implementation(interfaces.IAction)
-def render_submit_button(self):
+def _render_submit_button(self):
     if not self.available():
         return ''
     label = self.label
@@ -620,6 +619,8 @@
             ' class="button" />' %
             (self.__name__, self.__name__, label)
             )
+# it needs to be pickleable, so we can't rebind the name :(
+render_submit_button = namedtemplate.implementation(interfaces.IAction)(_render_submit_button)
 
 class action:
     def __init__(self, label, actions=None, **options):
Index: src/zope/i18n/zcml.py
===================================================================
--- src/zope/i18n/zcml.py	(revision 79995)
+++ src/zope/i18n/zcml.py	(working copy)
@@ -26,6 +26,8 @@
 from zope.i18n.translationdomain import TranslationDomain
 from zope.i18n.interfaces import ITranslationDomain
 from zope.component.zcml import utility
+from zope.component.zcml import handler
+from zope.component.interface import provideInterface
 
 class IRegisterTranslationsDirective(Interface):
     """Register translations with the global site manager."""
@@ -37,6 +39,15 @@
         )
 
 def registerTranslations(_context, directory):
+
+    _context.action(
+        discriminator = ('i18n:registerTranslations', directory),
+        callable = actuallyRegisterTranslations,
+        args = (directory, )
+        )
+
+
+def actuallyRegisterTranslations(directory):
     path = os.path.normpath(directory)
     domains = {}
 
@@ -64,4 +75,8 @@
         # make sure we have a TEST catalog for each domain:
         domain.addCatalog(TestMessageCatalog(name))
 
-        utility(_context, ITranslationDomain, domain, name=name)
+        handler('registerUtility', domain, ITranslationDomain, name=name)
+
+    provideInterface(ITranslationDomain.__module__ + '.' + ITranslationDomain.getName(),
+                     ITranslationDomain)
+
Index: src/zope/interface/interface.py
===================================================================
--- src/zope/interface/interface.py	(revision 79995)
+++ src/zope/interface/interface.py	(working copy)
@@ -62,7 +62,7 @@
             __doc__ = __name__
             __name__ = None
 
-        self.__name__=__name__
+        self.__name__=str(__name__)
         self.__doc__=__doc__
         self.__tagged_values = {}
 
Index: src/zope/pagetemplate/pagetemplatefile.py
===================================================================
--- src/zope/pagetemplate/pagetemplatefile.py	(revision 79995)
+++ src/zope/pagetemplate/pagetemplatefile.py	(working copy)
@@ -112,8 +112,8 @@
     def pt_source_file(self):
         return self.filename
 
-    def __getstate__(self):
-        raise TypeError("non-picklable object")
+##  def __getstate__(self):
+##      raise TypeError("non-picklable object")
 
 XML_PREFIXES = [
     "<?xml",                      # ascii, utf-8
Index: src/zope/security/checker.py
===================================================================
--- src/zope/security/checker.py	(revision 79995)
+++ src/zope/security/checker.py	(working copy)
@@ -417,7 +417,7 @@
 
 # Get optimized versions
 try:
-    import zope.security._zope_security_checker
+    import zope.security._zope_security_checker_XXX_disable_HACK_HACK_HACK
 except ImportError:
     pass
 else:
Index: src/zope/viewlet/metaconfigure.py
===================================================================
--- src/zope/viewlet/metaconfigure.py	(revision 79995)
+++ src/zope/viewlet/metaconfigure.py	(working copy)
@@ -29,6 +29,27 @@
 
 from zope.app.publisher.browser import viewmeta
 
+
+class ViewletManagerFactory(object):
+
+    def __init__(self, class_, name, provides, template, required):
+        self.class_ = class_
+        self.name = name
+        self.provides = provides
+        self.template = template
+        self.required = required
+
+    def __call__(self, context, request, view):
+        # Create a new class based on the template and class.
+        new_class = manager.ViewletManager(
+            self.name, self.provides, template=self.template, bases=(self.class_, ))
+
+        # Create a checker for the viewlet manager
+        new_class.__Secrity_checker__ = checker.Checker(self.required)
+
+        return new_class(context, request, view)
+
+
 def viewletManagerDirective(
     _context, name, permission,
     for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView,
@@ -53,13 +74,6 @@
             raise ConfigurationError("No such file", template)
         required['__getitem__'] = permission
 
-        # Create a new class based on the template and class.
-        new_class = manager.ViewletManager(
-            name, provides, template=template, bases=(class_, ))
-    else:
-        # Create a new class based on the class.
-        new_class = manager.ViewletManager(name, provides, bases=(class_, ))
-
     # Register some generic attributes with the security dictionary
     for attr_name in ('browserDefault', 'update', 'render', 'publishTraverse'):
         required[attr_name] = permission
@@ -81,18 +95,65 @@
     viewmeta._handle_for(_context, for_)
     zcml.interface(_context, view)
 
-    # Create a checker for the viewlet manager
-    checker.defineChecker(new_class, checker.Checker(required))
-
+    factory = ViewletManagerFactory(class_, name, provides, template, required)
+    
     # register a viewlet manager
     _context.action(
         discriminator = ('viewletManager', for_, layer, view, name),
         callable = zcml.handler,
         args = ('registerAdapter',
-                new_class, (for_, layer, view), provides, name,
+                factory, (for_, layer, view), provides, name,
                  _context.info),)
 
 
+class ViewletFactory(object):
+    def __init__(self, class_, attribute, template, kwargs, name):
+        self.class_ = class_
+        self.attribute = attribute
+        self.template = template
+        self.kwargs = kwargs
+        self.name = name
+
+    def __call__(self, context, request, view, manager):
+        # Make sure the has the right form, if specified.
+        if self.class_:
+            if self.attribute != 'render':
+                if not hasattr(self.class_, self.attribute):
+                    raise ConfigurationError(
+                        "The provided class doesn't have the specified attribute "
+                        )
+            if self.template:
+                # Create a new class for the viewlet template and class.
+                new_class = viewlet.SimpleViewletClass(
+                    self.template, bases=(self.class_, ), attributes=self.kwargs, name=self.name)
+            else:
+                if not hasattr(self.class_, 'browserDefault'):
+                    cdict = {'browserDefault':
+                             lambda slf, request: (getattr(slf, self.attribute), ())}
+                else:
+                    cdict = {}
+    
+                cdict['__name__'] = self.name
+                cdict['__page_attribute__'] = self.attribute
+                cdict.update(self.kwargs)
+                new_class = type(self.class_.__name__,
+                                 (self.class_, viewlet.SimpleAttributeViewlet), cdict)
+    
+            if hasattr(self.class_, '__implements__'):
+                classImplements(new_class, IBrowserPublisher)
+    
+        else:
+            # Create a new class for the viewlet template alone.
+            new_class = viewlet.SimpleViewletClass(self.template, name=self.name,
+                                                   attributes=self.kwargs)
+    
+        # Create the security checker for the new class
+
+        new_class.__Secrity_checker__ = checker.Checker(required)
+
+        return new_class(context, request, view, manager)
+
+
 def viewletDirective(
     _context, name, permission,
     for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView,
@@ -129,38 +190,8 @@
             raise ConfigurationError("No such file", template)
         required['__getitem__'] = permission
 
-    # Make sure the has the right form, if specified.
-    if class_:
-        if attribute != 'render':
-            if not hasattr(class_, attribute):
-                raise ConfigurationError(
-                    "The provided class doesn't have the specified attribute "
-                    )
-        if template:
-            # Create a new class for the viewlet template and class.
-            new_class = viewlet.SimpleViewletClass(
-                template, bases=(class_, ), attributes=kwargs, name=name)
-        else:
-            if not hasattr(class_, 'browserDefault'):
-                cdict = {'browserDefault':
-                         lambda self, request: (getattr(self, attribute), ())}
-            else:
-                cdict = {}
+    factory = ViewletFactory(class_, attribute, template, kwargs, name)
 
-            cdict['__name__'] = name
-            cdict['__page_attribute__'] = attribute
-            cdict.update(kwargs)
-            new_class = type(class_.__name__,
-                             (class_, viewlet.SimpleAttributeViewlet), cdict)
-
-        if hasattr(class_, '__implements__'):
-            classImplements(new_class, IBrowserPublisher)
-
-    else:
-        # Create a new class for the viewlet template alone.
-        new_class = viewlet.SimpleViewletClass(template, name=name,
-                                               attributes=kwargs)
-
     # Set up permission mapping for various accessible attributes
     viewmeta._handle_allowed_interface(
         _context, allowed_interface, permission, required)
@@ -177,13 +208,10 @@
     viewmeta._handle_for(_context, for_)
     zcml.interface(_context, view)
 
-    # Create the security checker for the new class
-    checker.defineChecker(new_class, checker.Checker(required))
-
     # register viewlet
     _context.action(
         discriminator = ('viewlet', for_, layer, view, manager, name),
         callable = zcml.handler,
         args = ('registerAdapter',
-                new_class, (for_, layer, view, manager), interfaces.IViewlet,
+                factory, (for_, layer, view, manager), interfaces.IViewlet,
                  name, _context.info),)
Index: src/zope/viewlet/viewlet.py
===================================================================
--- src/zope/viewlet/viewlet.py	(revision 79995)
+++ src/zope/viewlet/viewlet.py	(working copy)
@@ -107,18 +107,21 @@
         return self.index(*args, **kw)
 
 
-def JavaScriptViewlet(path):
-    """Create a viewlet that can simply insert a javascript link."""
-    src = os.path.join(os.path.dirname(__file__), 'javascript_viewlet.pt')
+class JavaScriptViewlet(object):
 
-    klass = type('JavaScriptViewlet',
-                 (ResourceViewletBase, ViewletBase),
-                  {'index': ViewPageTemplateFile(src),
-                   '_path': path})
+    def __init__(self, path):
+        self.path = path
 
-    return klass
+    def __call__(self, context, request, view, manager):
+        src = os.path.join(os.path.dirname(__file__), 'javascript_viewlet.pt')
 
+        klass = type('JavaScriptViewlet',
+                     (ResourceViewletBase, ViewletBase),
+                     {'index': ViewPageTemplateFile(src),
+                      '_path': self.path})
+        return klass(context, request, view, manager)
 
+
 class CSSResourceViewletBase(ResourceViewletBase):
 
     _media = 'all'
@@ -130,19 +133,23 @@
     def getRel(self):
         return self._rel
 
+class CSSViewlet(object):
 
-def CSSViewlet(path, media="all", rel="stylesheet"):
-    """Create a viewlet that can simply insert a javascript link."""
-    src = os.path.join(os.path.dirname(__file__), 'css_viewlet.pt')
+    def __init__(self, path, _media="all", _rel="stylesheet"):
+        self.path = path
+        self._media = _media
+        self._rel = _rel
 
-    klass = type('CSSViewlet',
-                 (CSSResourceViewletBase, ViewletBase),
-                  {'index': ViewPageTemplateFile(src),
-                   '_path': path,
-                   '_media':media,
-                   '_rel':rel})
+    def __call__(self, context, request, view, manager):
+        klass = type('CSSViewletWhatever',
+                     (CSSResourceViewletBase, ViewletBase),
+                     {'index': ViewPageTemplateFile(src),
+                      '_path': self.path,
+                      '_media':self._media,
+                      '_rel':self._rel,
+                      })
 
-    return klass
+        return klass(context, request, view, manager)
 
 
 class ResourceBundleViewletBase(object):

