2008-06-06 04:44:08 +02:00
|
|
|
import new
|
2009-03-30 06:55:53 +02:00
|
|
|
|
2011-01-18 09:30:04 +01:00
|
|
|
from threading import RLock
|
2009-03-30 06:55:53 +02:00
|
|
|
from plone.synchronize import synchronized
|
2008-06-06 04:44:08 +02:00
|
|
|
|
2008-06-24 01:26:32 +02:00
|
|
|
from zope.interface import implements, alsoProvides
|
2008-06-06 04:44:08 +02:00
|
|
|
from zope.interface.interface import InterfaceClass
|
|
|
|
|
2009-01-13 00:30:12 +01:00
|
|
|
from zope.component import adapter
|
2008-06-13 00:33:11 +02:00
|
|
|
from zope.component import queryUtility
|
|
|
|
|
2009-03-24 17:15:14 +01:00
|
|
|
from plone.behavior.interfaces import IBehavior
|
2008-06-06 04:44:08 +02:00
|
|
|
|
2009-03-24 17:15:14 +01:00
|
|
|
from plone.supermodel.parser import ISchemaPolicy
|
2009-07-12 10:00:05 +02:00
|
|
|
from plone.supermodel.utils import syncSchema
|
2008-06-25 00:46:42 +02:00
|
|
|
|
2008-06-06 04:44:08 +02:00
|
|
|
from plone.alterego.interfaces import IDynamicObjectFactory
|
|
|
|
|
2012-05-20 20:24:33 +02:00
|
|
|
from plone.dexterity.interfaces import IContentType
|
2008-06-06 04:44:08 +02:00
|
|
|
from plone.dexterity.interfaces import IDexteritySchema
|
2008-06-13 00:33:11 +02:00
|
|
|
from plone.dexterity.interfaces import IDexterityFTI
|
2009-01-13 00:30:12 +01:00
|
|
|
from plone.dexterity.interfaces import ISchemaInvalidatedEvent
|
|
|
|
|
2008-06-06 04:44:08 +02:00
|
|
|
from plone.dexterity import utils
|
|
|
|
from plone.alterego import dynamic
|
|
|
|
|
|
|
|
# Dynamic modules
|
|
|
|
generated = dynamic.create('plone.dexterity.schema.generated')
|
|
|
|
transient = new.module("transient")
|
|
|
|
|
2009-01-13 00:30:12 +01:00
|
|
|
# Schema cache
|
|
|
|
|
|
|
|
class SchemaCache(object):
|
2009-01-13 01:47:48 +01:00
|
|
|
"""Simple schema cache.
|
|
|
|
|
|
|
|
This cache will store a Python object reference to the schema, as returned
|
2009-07-12 10:00:05 +02:00
|
|
|
by fti.lookupSchema(), for any number of portal types. The value will
|
2009-01-13 01:47:48 +01:00
|
|
|
be cached until the server is restarted or the cache is invalidated or
|
|
|
|
cleared.
|
|
|
|
|
|
|
|
You should only use this if you require bare-metal speed. For almost all
|
|
|
|
operations, it's safer and easier to do:
|
|
|
|
|
|
|
|
>>> fti = getUtility(IDexterityFTI, name=portal_type)
|
2009-07-12 10:00:05 +02:00
|
|
|
>>> schema = fti.lookupSchema()
|
2009-01-13 01:47:48 +01:00
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
The lookupSchema() call is probably as fast as this cache. However, if
|
2009-01-13 01:47:48 +01:00
|
|
|
you need to avoid the utility lookup, you can use the cache like so:
|
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
>>> from plone.dexterity.schema import SCHEMA_CACHE
|
|
|
|
>>> my_schema = SCHEMA_CACHE.get(portal_type)
|
2009-01-13 01:47:48 +01:00
|
|
|
|
|
|
|
Invalidate the cache by calling invalidate() (for one portal_type) or
|
|
|
|
clear() (for all cached values), or simply raise a SchemaInvalidatedEvent.
|
2009-01-13 00:30:12 +01:00
|
|
|
"""
|
|
|
|
|
2011-01-18 09:30:04 +01:00
|
|
|
lock = RLock()
|
2009-01-13 00:30:12 +01:00
|
|
|
cache = {}
|
2009-03-24 17:15:14 +01:00
|
|
|
subtypes_cache = {}
|
2009-02-09 16:29:34 +01:00
|
|
|
counter_values = {}
|
2009-01-13 00:30:12 +01:00
|
|
|
|
|
|
|
@synchronized(lock)
|
|
|
|
def get(self, portal_type):
|
|
|
|
cached = self.cache.get(portal_type, None)
|
|
|
|
if cached is None:
|
|
|
|
fti = queryUtility(IDexterityFTI, name=portal_type)
|
|
|
|
if fti is not None:
|
2009-01-13 01:49:31 +01:00
|
|
|
try:
|
2009-07-12 10:00:05 +02:00
|
|
|
cached = self.cache[portal_type] = fti.lookupSchema()
|
2010-04-02 15:18:57 +02:00
|
|
|
except (AttributeError, ValueError):
|
2009-01-13 01:49:31 +01:00
|
|
|
pass
|
2009-01-13 00:30:12 +01:00
|
|
|
return cached
|
2009-03-24 17:15:14 +01:00
|
|
|
|
|
|
|
@synchronized(lock)
|
|
|
|
def subtypes(self, portal_type):
|
|
|
|
cached = self.subtypes_cache.get(portal_type, None)
|
|
|
|
if cached is None:
|
|
|
|
subtypes = []
|
|
|
|
fti = queryUtility(IDexterityFTI, name=portal_type)
|
2012-10-18 10:25:18 +02:00
|
|
|
if fti is None:
|
|
|
|
return ()
|
|
|
|
for behavior_name in fti.behaviors:
|
|
|
|
behavior = queryUtility(IBehavior, name=behavior_name)
|
|
|
|
if behavior is not None and behavior.marker is not None:
|
|
|
|
subtypes.append(behavior.marker)
|
|
|
|
cached = self.subtypes_cache[portal_type] = tuple(subtypes)
|
2009-03-24 17:15:14 +01:00
|
|
|
return cached
|
2009-02-09 16:29:34 +01:00
|
|
|
|
|
|
|
@synchronized(lock)
|
|
|
|
def counter(self, portal_type):
|
|
|
|
counter = self.counter_values.get(portal_type, None)
|
|
|
|
if counter is None:
|
|
|
|
counter = self.counter_values[portal_type] = 0
|
|
|
|
return counter
|
2009-01-13 00:30:12 +01:00
|
|
|
|
|
|
|
@synchronized(lock)
|
|
|
|
def invalidate(self, portal_type):
|
|
|
|
self.cache[portal_type] = None
|
2009-06-03 16:08:48 +02:00
|
|
|
self.subtypes_cache[portal_type] = None
|
2009-02-09 16:29:34 +01:00
|
|
|
if portal_type in self.counter_values:
|
|
|
|
self.counter_values[portal_type] += 1
|
|
|
|
else:
|
|
|
|
self.counter_values[portal_type] = 0
|
2009-01-13 00:30:12 +01:00
|
|
|
|
|
|
|
@synchronized(lock)
|
|
|
|
def clear(self):
|
|
|
|
self.cache.clear()
|
2009-03-24 17:15:14 +01:00
|
|
|
self.subtypes_cache.clear()
|
2009-01-13 00:30:12 +01:00
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
SCHEMA_CACHE = SchemaCache()
|
2009-01-13 00:30:12 +01:00
|
|
|
|
|
|
|
class SchemaInvalidatedEvent(object):
|
|
|
|
implements(ISchemaInvalidatedEvent)
|
|
|
|
|
|
|
|
def __init__(self, portal_type):
|
|
|
|
self.portal_type = portal_type
|
|
|
|
|
|
|
|
@adapter(ISchemaInvalidatedEvent)
|
|
|
|
def invalidate_schema(event):
|
|
|
|
if event.portal_type:
|
2009-07-12 10:00:05 +02:00
|
|
|
SCHEMA_CACHE.invalidate(event.portal_type)
|
2009-01-13 00:30:12 +01:00
|
|
|
else:
|
2009-07-12 10:00:05 +02:00
|
|
|
SCHEMA_CACHE.clear()
|
2009-01-13 00:30:12 +01:00
|
|
|
|
|
|
|
# Dynamic module factory
|
|
|
|
|
2008-06-06 04:44:08 +02:00
|
|
|
class SchemaModuleFactory(object):
|
|
|
|
"""Create dynamic schema interfaces on the fly
|
|
|
|
"""
|
|
|
|
|
|
|
|
implements(IDynamicObjectFactory)
|
|
|
|
|
2011-01-18 09:30:04 +01:00
|
|
|
lock = RLock()
|
2009-07-12 10:00:05 +02:00
|
|
|
_transient_SCHEMA_CACHE = {}
|
2008-06-13 00:33:11 +02:00
|
|
|
|
2009-01-13 00:30:12 +01:00
|
|
|
@synchronized(lock)
|
2008-06-06 04:44:08 +02:00
|
|
|
def __call__(self, name, module):
|
|
|
|
"""Someone tried to load a dynamic interface that has not yet been
|
|
|
|
created yet. We will attempt to load it from the FTI if we can. If
|
|
|
|
the FTI doesn't exist, create a temporary marker interface that we
|
|
|
|
can fill later.
|
|
|
|
|
2008-06-13 00:33:11 +02:00
|
|
|
The goal here is to ensure that we create exactly one interface
|
|
|
|
instance for each name. If we can't find an FTI, we'll cache the
|
|
|
|
interface so that we don't get a new one with a different id later.
|
|
|
|
This cache is global, so we synchronise the method with a thread
|
|
|
|
lock.
|
|
|
|
|
|
|
|
Once we have a properly populated interface, we set it onto the
|
|
|
|
module using setattr(). This means that the factory will not be
|
|
|
|
invoked again.
|
|
|
|
"""
|
2008-06-14 12:21:04 +02:00
|
|
|
|
2008-06-06 04:44:08 +02:00
|
|
|
try:
|
2009-07-12 10:00:05 +02:00
|
|
|
prefix, portal_type, schemaName = utils.splitSchemaName(name)
|
2008-06-06 04:44:08 +02:00
|
|
|
except ValueError:
|
|
|
|
return None
|
|
|
|
|
2011-04-22 08:43:54 +02:00
|
|
|
if name in self._transient_SCHEMA_CACHE:
|
2009-07-12 10:00:05 +02:00
|
|
|
schema = self._transient_SCHEMA_CACHE[name]
|
2008-06-13 00:33:11 +02:00
|
|
|
else:
|
|
|
|
bases = ()
|
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
is_default_schema = not schemaName
|
2008-06-13 00:33:11 +02:00
|
|
|
if is_default_schema:
|
|
|
|
bases += (IDexteritySchema,)
|
|
|
|
|
|
|
|
schema = InterfaceClass(name, bases, __module__=module.__name__)
|
|
|
|
|
2008-06-24 01:26:32 +02:00
|
|
|
if is_default_schema:
|
|
|
|
alsoProvides(schema, IContentType)
|
|
|
|
|
2011-04-22 08:43:54 +02:00
|
|
|
fti = queryUtility(IDexterityFTI, name=portal_type)
|
2009-07-12 10:00:05 +02:00
|
|
|
if fti is None and name not in self._transient_SCHEMA_CACHE:
|
|
|
|
self._transient_SCHEMA_CACHE[name] = schema
|
2008-06-14 12:21:04 +02:00
|
|
|
elif fti is not None:
|
2011-04-22 08:43:54 +02:00
|
|
|
model = fti.lookupModel()
|
|
|
|
syncSchema(model.schemata[schemaName], schema, sync_bases=True)
|
2008-07-20 22:04:16 +02:00
|
|
|
|
2008-06-13 00:33:11 +02:00
|
|
|
# Save this schema in the module - this factory will not be
|
|
|
|
# called again for this name
|
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
if name in self._transient_SCHEMA_CACHE:
|
|
|
|
del self._transient_SCHEMA_CACHE[name]
|
2008-06-14 12:21:04 +02:00
|
|
|
|
|
|
|
setattr(module, name, schema)
|
2009-01-13 00:30:12 +01:00
|
|
|
|
2008-06-13 00:33:11 +02:00
|
|
|
return schema
|
|
|
|
|
2008-06-06 04:44:08 +02:00
|
|
|
class DexteritySchemaPolicy(object):
|
|
|
|
"""Determines how and where imported dynamic interfaces are created.
|
|
|
|
Note that these schemata are never used directly. Rather, they are merged
|
|
|
|
into a schema with a proper name and module, either dynamically or
|
|
|
|
in code.
|
|
|
|
"""
|
|
|
|
implements(ISchemaPolicy)
|
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
def module(self, schemaName, tree):
|
2008-06-06 04:44:08 +02:00
|
|
|
return 'plone.dexterity.schema.transient'
|
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
def bases(self, schemaName, tree):
|
2008-10-11 22:40:54 +02:00
|
|
|
return ()
|
2008-06-06 04:44:08 +02:00
|
|
|
|
2009-07-12 10:00:05 +02:00
|
|
|
def name(self, schemaName, tree):
|
2008-06-06 04:44:08 +02:00
|
|
|
# We use a temporary name whilst the interface is being generated;
|
|
|
|
# when it's first used, we know the portal_type and site, and can
|
|
|
|
# thus update it
|
2009-07-12 10:00:05 +02:00
|
|
|
return '__tmp__' + schemaName
|
2009-01-13 00:30:12 +01:00
|
|
|
|