Ejemplo n.º 1
0
class ArchivistTool(UniqueObject, SimpleItem):
    """
    """

    id = 'portal_archivist'
    alternative_id = 'portal_standard_archivist'

    meta_type = 'CMFEditions Portal Archivist Tool'

    # make interfaces, exceptions and classes available through the tool
    interfaces = KwAsAttributes(
        IVersionData=IVersionData,
        IVersionAwareReference=IVersionAwareReference,
        IAttributeAdapter=IAttributeAdapter,
    )
    exceptions = KwAsAttributes(
        ArchivistError=ArchivistError,
    )
    classes = KwAsAttributes(
        VersionData=VersionData,
        VersionAwareReference=VersionAwareReference,
        AttributeAdapter=AttributeAdapter,
        ObjectManagerStorageAdapter=ObjectManagerStorageAdapter,
    )

    security = ClassSecurityInfo()


    # -------------------------------------------------------------------
    # private helper methods
    # -------------------------------------------------------------------
    def _cloneByPickle(self, obj):
        """Returns a deep copy of a ZODB object, loading ghosts as needed.
        """
        modifier = getToolByName(self, 'portal_modifier')
        callbacks = modifier.getOnCloneModifiers(obj)
        if callbacks is not None:
            pers_id, pers_load, inside_orefs, outside_orefs = callbacks[0:4]
        else:
            inside_orefs, outside_orefs = (), ()

        stream = StringIO()
        p = Pickler(stream, 1)
        if callbacks is not None:
            p.persistent_id = pers_id
        p.dump(aq_base(obj))
        approxSize = stream.tell()
        stream.seek(0)
        u = Unpickler(stream)
        if callbacks is not None:
            u.persistent_load = pers_load
        return approxSize, u.load(), inside_orefs, outside_orefs


    # -------------------------------------------------------------------
    # methods implementing IArchivist
    # -------------------------------------------------------------------

    security.declarePrivate('prepare')
    def prepare(self, obj, app_metadata=None, sys_metadata={}):
        """See IArchivist.
        """
        storage = getToolByName(self, 'portal_historiesstorage')
        modifier = getToolByName(self, 'portal_modifier')

        obj, history_id = dereference(obj, zodb_hook=self)
        if storage.isRegistered(history_id):
            # already registered
            version_id = len(self.queryHistory(obj))
            is_registered = True
        else:
            # object isn't under version control yet
            # A working copy being under version control needs to have
            # a history_id, version_id (starts with 0) and a location_id
            # (the current implementation isn't able yet to handle multiple
            # locations. Nevertheless lets set the location id to a well
            # known default value)
            uidhandler = getToolByName(self, 'portal_historyidhandler')
            history_id = uidhandler.register(obj)
            version_id = obj.version_id = 0
            alsoProvides(obj, IVersioned)
            obj.location_id = 0
            is_registered = False

        # the hard work done here is:
        # 1. ask for all attributes that have to be passed to the
        #    history storage by reference
        # 2. clone the object with some modifications
        # 3. modify the clone further
        referenced_data = modifier.getReferencedAttributes(obj)
        approxSize, clone, inside_orefs, outside_orefs = \
            self._cloneByPickle(obj)
        metadata, inside_crefs, outside_crefs = \
            modifier.beforeSaveModifier(obj, clone)

        # extend the ``sys_metadata`` by the metadata returned by the
        # ``beforeSaveModifier`` modifier
        sys_metadata.update(metadata)

        # set the version id of the clone to be saved to the repository
        # location_id and history_id are the same as on the working copy
        # and remain unchanged
        clone.version_id = version_id

        # return the prepared infos (clone, refs, etc.)
        clone_info = ObjectData(clone, inside_crefs, outside_crefs)
        obj_info = ObjectData(obj, inside_orefs, outside_orefs)
        return PreparedObject(history_id, obj_info, clone_info,
                              referenced_data, app_metadata,
                              sys_metadata, is_registered, approxSize)

    security.declarePrivate('register')
    def register(self, prepared_obj):
        """See IArchivist.
        """
        # only register at the storage layer if not yet registered
        if not prepared_obj.is_registered:
            storage = getToolByName(self, 'portal_historiesstorage')
            return storage.register(prepared_obj.history_id,
                                    prepared_obj.clone,
                                    prepared_obj.referenced_data,
                                    prepared_obj.metadata)

    security.declarePrivate('save')
    def save(self, prepared_obj, autoregister=None):
        """See IArchivist.
        """
        if not prepared_obj.is_registered:
            if autoregister:
                return self.register(prepared_obj)
            raise ArchivistSaveError(
                "Saving an unregistered object is not possible. Register "
                "the object '%r' first. "% prepared_obj.original.object)

        storage = getToolByName(self, 'portal_historiesstorage')
        return storage.save(prepared_obj.history_id,
                            prepared_obj.clone,
                            prepared_obj.referenced_data,
                            prepared_obj.metadata)

    # -------------------------------------------------------------------
    # methods implementing IPurgeSupport
    # -------------------------------------------------------------------

    security.declarePrivate('purge')
    def purge(self, obj=None, history_id=None, selector=None, metadata={},
              countPurged=True):
        """See IPurgeSupport.
        """
        storage = getToolByName(self, 'portal_historiesstorage')
        obj, history_id = dereference(obj, history_id, self)
        storage.purge(history_id, selector, metadata, countPurged)

    security.declarePrivate('retrieve')
    def retrieve(self, obj=None, history_id=None, selector=None, preserve=(),
                 countPurged=True):
        """See IPurgeSupport.
        """
        # retrieve the object by accessing the right history entry
        # (counting from the oldest version)
        # the histories storage called by LazyHistory knows what to do
        # with a None selector
        history = self.getHistory(obj, history_id, preserve, countPurged)
        try:
            return history[selector]
        except StorageRetrieveError:
            raise ArchivistRetrieveError(
                "Retrieving of '%r' failed. Version '%s' does not exist. "
                % (obj, selector))

    security.declarePrivate('getHistory')
    def getHistory(self, obj=None, history_id=None, preserve=(),
                   countPurged=True):
        """See IPurgeSupport.
        """
        try:
            return LazyHistory(self, obj, history_id, preserve, countPurged)
        except StorageUnregisteredError:
            raise ArchivistUnregisteredError(
                "Retrieving a version of an unregistered object is not "
                "possible. Register the object '%r' first. " % obj)

    security.declarePrivate('getHistoryMetadata')
    def getHistoryMetadata(self, obj=None, history_id=None):
        """ Return the metadata blob for presenting summary
            information, etc. If obj is not supplied, history is found
            by history_id, if history_id is not supplied, history is
            found by obj. If neither, return None.
        """
        obj, history_id = dereference(obj, history_id, self)
        storage = getToolByName(self, 'portal_historiesstorage')
        try:
            return storage.getHistoryMetadata(history_id)
        except StorageUnregisteredError:
            raise ArchivistUnregisteredError(
                "Retrieving a version of an unregistered object is not "
                "possible. Register the object '%r' first. " % obj)

    security.declarePrivate('queryHistory')
    def queryHistory(self, obj=None, history_id=None, preserve=(), default=None,
                     countPurged=True):
        """See IPurgeSupport.
        """
        if default is None:
            default = []
        try:
            return LazyHistory(self, obj, history_id, preserve, countPurged)
        except StorageUnregisteredError:
            return default

    security.declarePrivate('isUpToDate')
    def isUpToDate(self, obj=None, history_id=None, selector=None,
                   countPurged=True):
        """See IPurgeSupport.
        """
        storage = getToolByName(self, 'portal_historiesstorage')
        obj, history_id = dereference(obj, history_id, self)
        if not storage.isRegistered(history_id):
            raise ArchivistUnregisteredError(
                "The object %r is not registered" % obj)

        modified = storage.getModificationDate(history_id, selector,
                                               countPurged)
        return modified == obj.modified()
Ejemplo n.º 2
0
class ModifierRegistryTool(UniqueObject, OrderedFolder):
    __doc__ = __doc__  # copy from module

    id = 'portal_modifier'
    alternative_id = 'portal_modifierregistry'

    meta_type = 'Version Data Modifier Registry'

    # make interfaces, exceptions and classes available through the tool
    interfaces = KwAsAttributes(
        IConditionalModifier=IConditionalModifier,
        IConditionalTalesModifier=IConditionalTalesModifier,
    )
    exceptions = KwAsAttributes()
    classes = KwAsAttributes(
        ConditionalModifier=ConditionalModifier,
        ConditionalTalesModifier=ConditionalTalesModifier,
    )
    modules = KwAsAttributes(StandardModifiers=StandardModifiers, )

    security = ClassSecurityInfo()

    def all_meta_types(self, interfaces=None):
        """Allow adding of objects implementing 'IConditionalModifier' only.
        """
        if interfaces is None:
            interfaces = (IConditionalModifier, )
        return OrderedFolder.all_meta_types(self, interfaces)

    # be aware that the tool implements also the OrderedObjectManager API

    orderedFolderSetObject = OrderedFolder._setObject

    def _setObject(self, id, object, roles=None, user=None, set_owner=1):
        """Wrap condition and modifier into one object if necessary.
        """

        # wrap the object by a conditional tales modifier if it isn't one yet
        if not IConditionalModifier.providedBy(object):
            object = ConditionalTalesModifier(id, object)

        return self.orderedFolderSetObject(id,
                                           object,
                                           roles=roles,
                                           user=user,
                                           set_owner=set_owner)

    def _collectModifiers(self, obj, interface, reversed=False):
        """ Returns a list of valid modifiers
        """
        modifier_list = []
        portal = getToolByName(self, 'portal_url').getPortalObject()
        for id, o in self.objectItems():
            # collect objects modifier only when appropriate
            if IConditionalModifier.providedBy(o) \
               and o.isApplicable(obj, portal):
                mod = o.getModifier()
                if interface.providedBy(mod):
                    modifier_list.append((id, mod))

        if reversed:
            modifier_list.reverse()

        return modifier_list

    # -------------------------------------------------------------------
    # methods implementing IModifier
    #
    #    From the viewpoint of a repository the ModifierRegistryTool is
    #    an IModifier.
    # -------------------------------------------------------------------

    security.declarePrivate('getReferencedAttributes')

    def getReferencedAttributes(self, obj):
        """See IModifier
        """
        # just loop over all objects implementing the IModifier interface.
        referenced_data = {}
        for id, mod in self._collectModifiers(obj, IAttributeModifier):
            # prepend the modifiers id to the attributes name
            template = '%s/%%s' % id
            for name, attrs in mod.getReferencedAttributes(obj).items():
                referenced_data[template % name] = attrs

        # the return value is of the format:
        #     {'<modifier_id>/<name>': <refrenced_data>, ...}
        return referenced_data

    security.declarePrivate('reattachReferencedAttributes')

    def reattachReferencedAttributes(self, obj, referenced_data):
        """
        """
        # the input of 'referenced_data' is of the format:
        #     {'<modifier_id>/<name>': <refrenced_data>, ...}
        # build a structure by modifier id
        #     {'<modifier_id>': {'<name>':< refrenced_data>, ...}, ...}
        data_by_modid = {}

        for id_name, data in referenced_data.items():
            id, name = id_name.split('/', 1)
            if not id in data_by_modid:
                data_by_modid[id] = {}
            data_by_modid[id][name] = data

        # loop over modifiers in reverse
        if data_by_modid:
            for id, mod in self._collectModifiers(obj,
                                                  IAttributeModifier,
                                                  reversed=True):
                if id in data_by_modid:
                    mod.reattachReferencedAttributes(obj, data_by_modid[id])

    security.declarePrivate('getOnCloneModifiers')

    def getOnCloneModifiers(self, obj):
        """See IModifier
        """
        # First check if there is at least one ICloneModifier to loop over.
        # The clone operation is much fater if there are no 'persistent_id'
        # hooks to call.
        modifiers = self._collectModifiers(obj, ICloneModifier)
        if not modifiers:
            return None

        # collect callbacks of all modifiers uplying one
        pers_id_list = []
        pers_id_nameByMeth = {}
        pers_load_byname = {}
        inside_orefs = []
        outside_orefs = []

        for id, m in modifiers:
            clone_mod = m.getOnCloneModifiers(obj)
            if clone_mod is not None:
                pers_id_list.append(clone_mod[0])
                pers_id_nameByMeth[clone_mod[0]] = id
                inside_orefs.extend(clone_mod[2])
                outside_orefs.extend(clone_mod[3])
                pers_load_byname[id] = clone_mod[1]

        def persistent_id(obj):
            # loop over modifiers having a persistent_id callback
            for pers_id in pers_id_list:
                pid = pers_id(obj)
                if pid is not None:
                    # found a modifier, add the modifiers name to its pid
                    return "%s/%s" % (pers_id_nameByMeth[pers_id], pid)

        def persistent_load(named_pid):
            # call the right modifiers persistent_load callback
            name, pid = named_pid.split('/', 1)
            return pers_load_byname[name](pid)

        return persistent_id, persistent_load, inside_orefs, outside_orefs, ''

    security.declarePrivate('beforeSaveModifier')

    def beforeSaveModifier(self, obj, obj_clone):
        """See IModifier
        """
        inside_crefs = []
        outside_crefs = []
        metadata = {}

        # just loop over all modifiers
        for ignored_id, mod in self._collectModifiers(obj,
                                                      ISaveRetrieveModifier):
            mdata, icrefs, ocrefs = mod.beforeSaveModifier(obj, obj_clone)
            inside_crefs.extend(icrefs)
            outside_crefs.extend(ocrefs)
            metadata.update(mdata)

        return metadata, inside_crefs, outside_crefs

    security.declarePrivate('afterRetrieveModifier')

    def afterRetrieveModifier(self, obj, repo_clone, preserve=None):
        """See IModifier
        """
        if preserve is None:
            preserve = []
        # before letting the after retrieve modifiers replace
        # attributes save those attributes away that may got
        # overwritten but have to be preserved.
        preserved = {}
        for key in preserve:
            v = getattr(repo_clone, key, MV)
            preserved[key] = v

        orig_preserved = preserved.copy()
        # just loop over all modifiers in reverse order
        refs_to_be_deleted = []
        attrs_handling_subobjects = []
        for ignored_id, mod in self._collectModifiers(obj,
                                                      ISaveRetrieveModifier,
                                                      reversed=True):
            to_be_del, attrs, preserve = mod.afterRetrieveModifier(
                obj, repo_clone)
            refs_to_be_deleted.extend(to_be_del)
            attrs_handling_subobjects.extend(attrs)
            preserved.update(preserve)

        # Make sure that the original preserved values override
        preserved.update(orig_preserved)

        return refs_to_be_deleted, attrs_handling_subobjects, preserved

    # -------------------------------------------------------------------
    # methods implementing IModifierRegistrySet and IModifierRegistryQuery
    #
    #    The ModifierRegistryTool is also a registry of IModifier objects.
    # -------------------------------------------------------------------

    security.declareProtected(ManagePortal, 'register')

    def register(self, id, modifier, pos=-1):
        """See IModifierRegistrySet
        """
        # add the modifier
        id = self._setObject(id, modifier)

        # move it to the specified position, unfortunately the
        # the ordered object manager isn't able to count the position from
        # the end using negative numbers.
        if pos < 0:
            pos += max(0, len(self.objectIds()) + 1)
        self.moveObjectToPosition(id, pos)

    security.declareProtected(ManagePortal, 'unregister')

    def unregister(self, id):
        """See IModifierRegistrySet
        """
        self.manage_delObjects(ids=[id])

    security.declareProtected(ManagePortal, 'edit')

    def edit(self, id, enabled=None, condition=None):
        """See IModifierRegistrySet
        """
        modifier = self.get(id)
        if IConditionalTalesModifier.providedBy(modifier):
            modifier.edit(enabled, condition)
        else:
            if condition:
                raise NotImplementedError('%s does not implement conditions.' %
                                          modifier)
            modifier.edit(enabled)

    security.declareProtected(ManagePortal, 'get')

    def get(self, id):
        """See IModifierRegistryQuery
        """
        # raises the correct exception for us
        getattr(aq_base(self), id)
        return getattr(self, id)

    security.declareProtected(ManagePortal, 'query')

    def query(self, id, default=None):
        """See IModifierRegistryQuery
        """
        try:
            return self.get(id)
        except AttributeError:
            return default