class ShopItemVariations(object):
    """DataCollector adapter for Shop Item Variations.
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, obj):
        self.object = obj

    security.declarePrivate('getData')
    def getData(self):
        variations_config = queryAdapter(self.object, IVariationConfig)
        if variations_config:
            data = deepcopy(variations_config.getVariationDict())
        else:
            data = PersistentMapping()
        data = make_serializable(data)
        data = serialize_decimals(data)
        return data

    security.declarePrivate('setData')
    def setData(self, data, metadata):
        variations_config = queryAdapter(self.object, IVariationConfig)
        self.logger.info('Updating variations config (UID %s)' %
                         (self.object.UID()))

        data = make_persistent(data)
        data = deserialize_decimals(data)
        if data in ({}, PersistentMapping()):
            variations_config.purge_dict()
        else:
            variations_config.updateVariationConfig(data)
class GeoData(object):
    """returns geo data
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, obj):
        self.object = obj
        self.manager = queryAdapter(obj, IGeoManager)

    security.declarePrivate('getData')

    def getData(self):
        if self.manager:
            return self.manager.getCoordinates()
        else:
            return None

    security.declarePrivate('setData')

    def setData(self, geodata, metadata):
        self.logger.info('Updating geo data (UID %s)' % (self.object.UID()))

        if geodata == (None, None):
            self.manager.removeCoordinates()
        else:
            self.manager.setCoordinates(*geodata)
class ShopCategorizableReferences(object):
    """DataCollector adapter for the category references of the categorizable shop types.
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, obj):
        self.object = obj

    security.declarePrivate('getData')
    def getData(self):
        return [reference.UID() for reference in self.object.listCategories()]

    security.declarePrivate('setData')
    def setData(self, data, metadata):
        self.logger.info('Updating shop item category references (UID %s)' %
                         (self.object.UID()))

        uid_catalog = getToolByName(self.object, 'uid_catalog')

        # Add categories available on source.
        for category_uid in data:
            brains = uid_catalog(UID=category_uid)
            if brains:
                self.object.addToCategory(brains[0].getObject())

        # Remove categories no longer available on the source.
        existing_categories = [reference.UID() for reference in self.object.listCategories()]
        categories_to_be_removed = set(existing_categories) - set(data)
        for category in categories_to_be_removed:
            self.object.removeFromCategory(category)
Exemple #4
0
class InterfaceData(object):
    """returns all properties data
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, object):
        self.object = object
        self.adapted = IMarkerInterfaces(self.object)

    security.declarePrivate('getData')
    def getData(self):
        """returns all important data"""
        return self.getInterfaceData()

    security.declarePrivate('getInterfaceData')
    def getInterfaceData(self):
        """
        Returns a list of directlyProvided interfaces.
        Example Return: [
            'foo.bar.IDemoInterface',
            ....
            ]

        @return:    list
        @rtype:     list
        """
        return self.adapted.getDirectlyProvidedNames()

    security.declarePrivate('setData')
    def setData(self, interfacedata, metadata):
        """
        Sets a list of properties on a object.
        Warning: all currently set properties which are not in the
        properties-list wille be removed!

        @param object:      Plone-Object to set the properties on
        @type object:       Plone-Object
        @param properties:  list of propertes.
                            See ftw.publisher.sender.extractor
                            for format details.
        @param type:        list
        @return:            None
        """
        self.logger.info('Updating interface data (UID %s)' %
                (self.object.UID())
        )

        current_ifaces = set(self.adapted.getDirectlyProvidedNames())
        desired_ifaces = set(interfacedata)

        for iface_dotted in current_ifaces - desired_ifaces:
            iface = resolve(iface_dotted)
            noLongerProvides(self.object, iface)

        for iface_dotted in desired_ifaces - current_ifaces:
            iface = resolve(iface_dotted)
            alsoProvides(self.object, iface)
class AfterCreatedEvent(ObjectEvent):
    interface.implements(IAfterCreatedEvent)
    security = ClassSecurityInformation()

    def __init__(self, context):
        ObjectEvent.__init__(self, context)
        self.obj = context
class Realm(Persistent):
    """
    A Realm object provides information about a target plone instance
    (receiver) which should have installed ftw.publisher.receiver.
    It stores and provides information such as URL or credentials.
    URL+username should be unique!
    """

    interface.implements(IAttributeAnnotatable, IRealm)
    security = ClassSecurityInformation()

    active = 0
    url = ''
    username = ''
    password = ''

    def __init__(self, active, url, username, password):
        """
        Constructor: stores the given arguments in the object
        @param active:      Is this realm active?
        @type active:       boolean or int
        @param url:         URL to the plone site of the target realm
        @type url:          string
        @param username:    Username to login on target realm
        @type username:     string
        @param password:    Password of the User with **username**
        @type password:     string
        """
        self.active = active and 1 or 0
        self.url = url
        self.username = username
        self.password = password
Exemple #7
0
class set_statusmessage(object):
    security = ClassSecurityInformation()

    def __init__(self, context, event):
        IStatusMessage(event.request).addStatusMessage(
            u'Object with Title: %s has been %s on %s' %
            (event.title.decode('utf-8'),
             event.state.toString().decode('utf-8'),
             event.path.decode('utf-8')),
            type='info')
Exemple #8
0
class UpgradeInformationGatherer(object):
    implements(IUpgradeInformationGatherer)
    adapts(ISetupTool)

    security = ClassSecurityInformation()

    def __init__(self, portal_setup):
        self.portal_setup = portal_setup
        self.cyclic_dependencies = False

    security.declarePrivate('get_upgrades')

    def get_upgrades(self):
        return self._sort_profiles_by_dependencies(self._get_profiles())

    security.declarePrivate('_get_profiles')

    def _get_profiles(self):
        for profileid in self.portal_setup.listProfilesWithUpgrades():
            if not self._is_profile_installed(profileid):
                continue

            data = self._get_profile_data(profileid)
            if len(data['upgrades']) == 0:
                continue

            if profileid == 'Products.CMFPlone:plone':
                # Plone has its own migration mechanism.
                # We do not support upgrading plone.
                continue

            yield data

    security.declarePrivate('_get_profile_data')

    def _get_profile_data(self, profileid):
        db_version = self.portal_setup.getLastVersionForProfile(profileid)
        if isinstance(db_version, (tuple, list)):
            db_version = '.'.join(db_version)

        data = {
            'upgrades': self._get_profile_upgrades(profileid),
            'db_version': db_version
        }

        try:
            data.update(self.portal_setup.getProfileInfo(profileid))
        except KeyError, exc:
            if exc.args and exc.args[0] == profileid:
                # package was removed - profile is no longer available.
                return {'upgrades': []}
            else:
                raise

        return data
class PathBlacklist(object):
    """ The `PathBlacklist` adapter knows if the adapted context or any other context / path
    is blacklisted.
    """

    implements(IPathBlacklist)
    security = ClassSecurityInformation()

    def __init__(self, context):
        self.context = context
        self.portal = context.portal_url.getPortalObject()

    security.declarePrivate('is_blacklisted')

    def is_blacklisted(self, context=None, path=None):
        """ Checks if the adapter the context, the given
        `context` or the given `path` is blacklisted.
        """
        if context and path:
            raise ValueError(
                'Only one of `context` and `path` can be checked at once.')
        elif not context and not path:
            context = self.context
        elif not path and type(context) in (str, unicode):
            path = context
            context = None
        if not path and isinstance(context,
                                   CatalogBrains.AbstractCatalogBrain):
            # context is a brain
            path = context.getPath()
        if not path:
            path = '/'.join(context.getPhysicalPath())

        path = path.strip()
        if path.endswith('/'):
            path = path[:-1]

        # check the path
        config = IConfig(self.portal)

        for blocked_path in config.getPathBlacklist():
            blocked_path = blocked_path.strip()
            if path == blocked_path:
                return True
            if blocked_path.endswith('*') and \
                    path.startswith(blocked_path[:-1]):
                if path == blocked_path[:-1]:
                    return True
                elif blocked_path[-2] != '/' and \
                        path[len(blocked_path) - 1] == '/':
                    return False
                else:
                    return True
        return False
Exemple #10
0
class ResponseLogger(object):

    security = ClassSecurityInformation()

    def __init__(self, response, annotate_result=False):
        self.response = response
        self.handler = None
        self.formatter = None
        self.annotate_result = annotate_result

    def __enter__(self):
        self.handler = logging.StreamHandler(self)
        self.formatter = logging.root.handlers[-1].formatter
        self.handler.setFormatter(self.formatter)
        logging.root.addHandler(self.handler)
        return self

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            LOG.error('FAILED')
            traceback.print_exception(exc_type, exc_value, tb, None, self)
            if self.annotate_result:
                self.write('Result: FAILURE\n')

        elif self.annotate_result:
            self.write('Result: SUCCESS\n')

        logging.root.removeHandler(self.handler)

        # Plone testing does not collect data written to the response stream
        # but only data set directly as body.
        # Since we want to test the response body, we need to re-set the
        # stream data as body for testing..
        if self.response.__class__.__name__ == 'TestResponse':
            self.response.setBody(self.response.stdout.getvalue())

    security.declarePrivate('write')

    def write(self, line):
        if isinstance(line, unicode):
            line = line.encode('utf8')

        line = line.replace('<', '&lt;').replace('>', '&gt;')

        self.response.write(line)
        self.response.flush()

    security.declarePrivate('writelines')

    def writelines(self, lines):
        for line in lines:
            self.write(line)
class SimplelayoutBlockAnnotations(object):
    implements(IDataCollector)
    security = ClassSecurityInformation()

    def __init__(self, context):
        self.context = context

    security.declarePrivate('getData')

    def getData(self):
        return unwrap_persistence(IBlockConfiguration(self.context).load())

    security.declarePrivate('setData')

    def setData(self, data, metadata):
        IBlockConfiguration(self.context).store(data)
class Decoder(object):
    """
    Decodes json data to dictionary and validates it.
    It also validates and decodes all schema field values.
    """

    security = ClassSecurityInformation()

    def __init__(self, context):
        """
        Constructor: stores context as object attribute
        @param context:         Plone object
        @type:                  Plone Object
        """
        self.context = context
        self.logger = getLogger()

    security.declarePrivate('__call__')
    def __call__(self, jsondata):
        """
        Decodes the jsondata to a dictionary, validates it,
        unserializes the field values and returns it.
        @return:        Data dictionary
        @rtype:         dict
        """
        self.data = self.decodeJson(jsondata)
        self.validate()
        return self.data

    security.declarePrivate('decodeJson')
    def decodeJson(self, jsondata):
        """
        Decodes the JSON data with the json module.
        If the json module cannot decode the string, a
        DecodeError is raised.
        @param jsondata:    JSON data
        @type jsondata:     string
        @return:            Decode Data dictionary
        @rtype:             dict
        @raise:             DecodeError
        """
        try:
            data = json.loads(jsondata)
        except Exception, e:
            raise states.DecodeError(str(e))
        data = encode_after_json(data)
        return data
class ServiceNavigationDataCollector(object):
    security = ClassSecurityInformation()

    def __init__(self, context):
        self.context = context

    security.declarePrivate('getData')

    def getData(self):
        annotations = IAnnotations(self.context)
        return annotations.get(ANNOTATION_KEY, {})

    security.declarePrivate('setData')

    def setData(self, data, metadata=None):
        annotations = IAnnotations(self.context)
        annotations[ANNOTATION_KEY] = data
Exemple #14
0
class CustomStyles(object):

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, obj):
        self.obj = obj

    security.declarePrivate('getData')

    def getData(self):
        return ICustomStyles(self.obj).get_styles()

    security.declarePrivate('setData')

    def setData(self, themedata, metadata):
        ICustomStyles(self.obj).set_styles(themedata)
class SimplelayoutBlockProperties(object):
    implements(IDataCollector)
    security = ClassSecurityInformation()

    def __init__(self, context):
        self.context = context

    security.declarePrivate('getData')

    def getData(self):
        properties = queryMultiAdapter((self.context, self.context.REQUEST),
                                       IBlockProperties)
        view_name = properties.get_current_view_name()
        return view_name

    security.declarePrivate('setData')

    def setData(self, data, metadata):
        properties = queryMultiAdapter((self.context, self.context.REQUEST),
                                       IBlockProperties)
        properties.set_view(data)
class ShopCategorizableRanks(object):
    """DataCollector adapter for the (category) ranks of the categorizable shop types.
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, obj):
        self.object = obj

    security.declarePrivate('getData')
    def getData(self):
        return getattr(self.object, '_categoryRanks', PersistentMapping())

    security.declarePrivate('setData')
    def setData(self, data, metadata):
        self.logger.info('Updating shop item ranks (UID %s)' %
                         (self.object.UID()))

        self.object._categoryRanks = data
Exemple #17
0
class FixEventTimezones(object):
    """
    Because plone.app.event's IEventBasic has a separate timezone field,
    it does some strange things with FakeZone on start / end.
    This leads to problems when publishing with the regulare publisher
    field adapter.

    In order to fix to those issues we handle start / end dates in this
    adapter explicitly, so that we have full control.
    """
    security = ClassSecurityInformation()

    def __init__(self, context):
        self.context = context

    def getData(self):
        event = IDXEvent(self.context)
        return {'start': DateTime(event.start), 'end': DateTime(event.end)}

    def setData(self, data, metadata):
        event = IDXEvent(self.context)
        event.start = data['start'].asdatetime()
        event.end = data['end'].asdatetime()
class ObjectSyncerProgressLogger(ProgressLogger):
    """Provide a custom progress-logger that can log iterables without
    a predefined length.
    """

    security = ClassSecurityInformation()

    def __init__(self, message, iterable, logger=None, timeout=5):
        self.logger = logger or logging.getLogger('opengever.contact')
        self.message = message
        self.iterable = iterable

        self.timeout = timeout
        self._timestamp = None
        self._counter = 0

    security.declarePrivate('__call__')

    def __call__(self):
        self._counter += 1
        if not self.should_be_logged():
            return

        self.logger.info('%s: %s' % (self._counter, self.message))
Exemple #19
0
class ResponseLogger(object):

    security = ClassSecurityInformation()

    def __init__(self, response):
        self.response = response
        self.handler = None
        self.formatter = None

    def __enter__(self):
        self.handler = logging.StreamHandler(self)
        self.formatter = logging.root.handlers[-1].formatter
        self.handler.setFormatter(self.formatter)
        logging.root.addHandler(self.handler)

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            LOG.error('FAILED')
            traceback.print_exception(exc_type, exc_value, tb, None, self)

        logging.root.removeHandler(self.handler)

    security.declarePrivate('write')

    def write(self, line):
        if isinstance(line, unicode):
            line = line.encode('utf8')

        self.response.write(line)
        self.response.flush()

    security.declarePrivate('writelines')

    def writelines(self, lines):
        for line in lines:
            self.write(line)
Exemple #20
0
class Executioner(object):

    implements(IExecutioner)
    adapts(ISetupTool)
    security = ClassSecurityInformation()

    def __init__(self, portal_setup):
        self.portal_setup = portal_setup
        alsoProvides(portal_setup.REQUEST, IDuringUpgrade)

    security.declarePrivate('install')

    def install(self, data):
        self._register_after_commit_hook()
        for profileid, upgradeids in data:
            self._upgrade_profile(profileid, upgradeids)

        for adapter in self._get_sorted_post_upgrade_adapters():
            adapter()

        TransactionNote().set_transaction_note()
        recook_resources()
        self._process_indexing_queue()

    security.declarePrivate('install_upgrades_by_api_ids')

    def install_upgrades_by_api_ids(self, *upgrade_api_ids, **kwargs):
        gatherer = IUpgradeInformationGatherer(self.portal_setup)
        upgrades = gatherer.get_upgrades_by_api_ids(*upgrade_api_ids, **kwargs)
        data = [(upgrade['profile'], [upgrade['id']]) for upgrade in upgrades]
        return self.install(data)

    security.declarePrivate('install_profiles_by_profile_ids')

    def install_profiles_by_profile_ids(self, *profile_ids, **options):
        force_reinstall = options.get('force_reinstall', False)
        for profile_id in profile_ids:
            # Starting from GenericSetup 1.8.0 getLastVersionForProfile can
            # handle profile ids with or without 'profile-' prefix, but we need
            # to support older versions as well, which only support it without
            # the prefix.
            prefix = 'profile-'
            if profile_id.startswith(prefix):
                profile_id = profile_id[len(prefix):]
            installed = self.portal_setup.getLastVersionForProfile(profile_id)
            if installed != 'unknown' and not force_reinstall:
                logger.info('Ignoring already installed profile %s.',
                            profile_id)
                continue
            logger.info('Installing profile %s.', profile_id)
            # For runAllImportStepsFromProfile we still must have 'profile-' at
            # the start.
            self.portal_setup.runAllImportStepsFromProfile(prefix + profile_id)
            logger.info('Done installing profile %s.', profile_id)
            optimize_memory_usage()
        self._process_indexing_queue()

    security.declarePrivate('_register_after_commit_hook')

    def _register_after_commit_hook(self):
        def notification_hook(success, *args, **kwargs):
            result = success and 'committed' or 'aborted'
            logger.info('Transaction has been %s.' % result)

        txn = transaction.get()
        txn.addAfterCommitHook(notification_hook)

    def _process_indexing_queue(self):
        """Reindex all objects in the indexing queue.

        Process the indexing queue after installing upgrades to ensure that
        its progress is also logged to the ResponseLogger.
        """
        processQueue()

    security.declarePrivate('_upgrade_profile')

    def _upgrade_profile(self, profileid, upgradeids):
        last_dest_version = None

        for upgradeid in upgradeids:
            last_dest_version = self._do_upgrade(profileid, upgradeid) \
                or last_dest_version

        old_version = self.portal_setup.getLastVersionForProfile(profileid)
        compareable = lambda v: LooseVersion('.'.join(v))

        if old_version == 'unknown' or \
           compareable(last_dest_version) > compareable(old_version):
            self.portal_setup.setLastVersionForProfile(profileid,
                                                       last_dest_version)

        self._set_quickinstaller_version(profileid)

    security.declarePrivate('_set_quickinstaller_version')

    def _set_quickinstaller_version(self, profileid):
        try:
            profileinfo = self.portal_setup.getProfileInfo(profileid)
        except KeyError:
            return

        quickinstaller = getToolByName(self.portal_setup,
                                       'portal_quickinstaller')
        product = profileinfo['product']
        if not quickinstaller.isProductInstalled(product):
            return

        version = quickinstaller.getProductVersion(product)
        if version:
            quickinstaller.get(product).installedversion = version

    security.declarePrivate('_do_upgrade')

    def _do_upgrade(self, profileid, upgradeid):
        start = time.time()

        step = _upgrade_registry.getUpgradeStep(profileid, upgradeid)
        logger.log(logging.INFO, '_' * 70)
        logger.log(logging.INFO,
                   'UPGRADE STEP %s: %s' % (profileid, step.title))

        step.doStep(self.portal_setup)
        TransactionNote().add_upgrade(profileid, step.dest, step.title)

        msg = "Ran upgrade step %s for profile %s" % (step.title, profileid)
        logger.log(logging.INFO, msg)

        logger.log(
            logging.INFO,
            'Upgrade step duration: %s' % format_duration(time.time() - start))

        return step.dest

    security.declarePrivate('_get_sorted_post_upgrade_adapters')

    def _get_sorted_post_upgrade_adapters(self):
        """Returns a list of post upgrade adapters, sorted by
        profile dependencies.
        Assumes that the names of the adapters are profile names
        (e.g. "ftw.upgrade:default").
        """

        profile_order = get_sorted_profile_ids(self.portal_setup)

        portal_url = getToolByName(self.portal_setup, 'portal_url')
        portal = portal_url.getPortalObject()
        adapters = list(getAdapters((portal, portal.REQUEST), IPostUpgrade))

        def _sorter(a, b):
            name_a = a[0]
            name_b = b[0]

            if name_a not in profile_order and name_b not in profile_order:
                return 0

            elif name_a not in profile_order:
                return -1

            elif name_b not in profile_order:
                return 1

            else:
                return cmp(profile_order.index(name_a),
                           profile_order.index(name_b))

        adapters.sort(_sorter)
        return [adapter for name, adapter in adapters]
class ProgressLogger(object):
    """Loggs the proggress of a process to the passed
    logger.
    """

    security = ClassSecurityInformation()

    def __init__(self, message, iterable, logger=None, timeout=5):
        self.logger = logger or logging.getLogger('ftw.upgrade')
        self.message = message

        if isinstance(iterable, (int, long, float)):
            self.length = iterable
        else:
            self.length = len(iterable)

        self.timeout = timeout
        self._timestamp = None
        self._counter = 0

    security.declarePrivate('__enter__')

    def __enter__(self):
        self.logger.info('STARTING %s' % self.message)
        return self

    security.declarePrivate('__exit__')

    def __exit__(self, exc_type, exc_value, traceback):
        if not exc_type:
            self.logger.info('DONE %s' % self.message)

        else:
            self.logger.error(
                'FAILED %s (%s: %s)' %
                (self.message, str(exc_type.__name__), str(exc_value)))

    security.declarePrivate('__call__')

    def __call__(self):
        self._counter += 1
        if not self.should_be_logged():
            return

        percent = int(self._counter * 100.0 / self.length)
        self.logger.info('%s of %s (%s%%): %s' %
                         (self._counter, self.length, percent, self.message))

    security.declarePrivate('should_be_logged')

    def should_be_logged(self):
        now = float(time())

        if self._timestamp is None:
            self._timestamp = now
            return True

        next_stamp = self._timestamp + self.timeout
        if next_stamp <= now:
            self._timestamp = now
            return True

        else:
            return False
Exemple #22
0
class BookLayoutRequestLayerCollector(object):
    """IDataCollector for supporting publishing books (ftw.publisher) with
    custom layouts and schema extender fields bound on layout request layer.

    When a custom layout is activated in the book, the request provides the
    layout interface as soon as traversing the book.
    The layout may add schema extender fields when the request provides the
    layout interface.
    In order to publish those field values correctly (with the standard
    FieldData collector), we need to provide the request layer before
    extracting data (sender) or setting data (receiver).

    Schema extender fields may be applied to not only the book itself but also
    to any content within the book. Therfore this collector should be used
    for all objects.

    The collector may be triggered when the book is traversed but also when
    it wasn't, therefore we cannot rely on the default book traversal adapter
    applying the layer interfaces to the request.
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, context):
        self.context = context

    security.declarePrivate('getData')

    def getData(self):
        book = self.get_parent_book()
        if not book:
            return None

        layers = [IWithinBookLayer]
        layout_layer_name = getattr(book, 'latex_layout', '')
        if layout_layer_name:
            layers.append(resolve(layout_layer_name))

        self.provide_request_layers(layers)
        self.flush_schemaextender_cache()
        return map(dottedname, layers)

    security.declarePrivate('setData')

    def setData(self, layers_dottednames, metadata):
        if not layers_dottednames:
            return

        self.logger.info(
            'BookLayoutRequestLayerCollector: provide request layers {0}'.
            format(layers_dottednames))

        layers = map(resolve, layers_dottednames)
        self.provide_request_layers(layers)
        self.flush_schemaextender_cache()

    def get_parent_book(self):
        context = self.context

        while context and not ISiteRoot.providedBy(context):
            if IBook.providedBy(context):
                return context
            context = aq_parent(aq_inner(context))

        return None

    def provide_request_layers(self, layers):
        """ Add a layer interface on the request
        """
        request = self.context.REQUEST
        layers = [iface for iface in layers if not iface.providedBy(request)]
        ifaces = layers + list(directlyProvidedBy(request))

        # Since we allow multiple markers here, we can't use
        # zope.publisher.browser.applySkin() since this filters out
        # IBrowserSkinType interfaces, nor can we use alsoProvides(), since
        # this appends the interface (in which case we end up *after* the
        # default Plone/CMF skin)
        directlyProvides(request, *ifaces)

    def flush_schemaextender_cache(self):
        """Flushes the schemaextender cache after conditions for
        schema extenders may have changed.
        """
        try:
            delattr(self.context.REQUEST, CACHE_KEY)
        except AttributeError:
            pass
Exemple #23
0
class QueueCatalog(Implicit, SimpleItem):
    """Queued ZCatalog (Proxy)

    A QueueCatalog delegates most requests to a ZCatalog that is named
    as part of the QueueCatalog configuration.

    Requests to catalog or uncatalog objects are queued. They must be
    processed by a separate process (or thread). The queuing provides
    benefits:

    - Content-management operations, performed by humans, complete
      much faster, this making the content-management system more
      effiecient for it's users.

    - Catalog updates are batched, which makes indexing much more
      efficient.

    - Indexing is performed by a single thread, allowing more
      effecient catalog document generation and avoiding conflict
      errors from occuring during indexing.

    - When used with ZEO, indexing might e performed on the same
      machine as the storage server, making updates faster.

    """

    security = ClassSecurityInformation()

    _immediate_indexes = ()  # The names of indexes to update immediately
    _location = None
    _immediate_removal = 1  # Flag: don't queue removal
    title = ''

    # When set, _v_catalog_cache is a tuple containing the wrapped ZCatalog
    # and the REQUEST it is bound to.
    _v_catalog_cache = None

    def __init__(self, buckets=1009):
        self._buckets = buckets
        self._clearQueues()

    def _clearQueues(self):
        self._queues = [CatalogEventQueue() for i in range(self._buckets)]

    def getTitle(self):
        return self.title

    security.declareProtected(view_management_screens, 'setLocation')

    def setLocation(self, location):
        if self._location is not None:
            try:
                self.process()
            except QueueConfigurationError:
                self._clearQueues()
        self._location = location

    security.declareProtected(view_management_screens, 'getIndexInfo')

    def getIndexInfo(self):
        try:
            c = self.getZCatalog()
        except QueueConfigurationError:
            return None
        else:
            items = [(ob.id, ob.meta_type) for ob in c.getIndexObjects()]
            items.sort()
            res = []
            for id, meta_type in items:
                res.append({'id': id, 'meta_type': meta_type})
            return res

    security.declareProtected(view_management_screens, 'getImmediateIndexes')

    def getImmediateIndexes(self):
        return self._immediate_indexes

    security.declareProtected(view_management_screens, 'setImmediateIndexes')

    def setImmediateIndexes(self, indexes):
        self._immediate_indexes = tuple(map(str, indexes))

    security.declareProtected(view_management_screens, 'getImmediateRemoval')

    def getImmediateRemoval(self):
        return self._immediate_removal

    security.declareProtected(view_management_screens, 'setImmediateRemoval')

    def setImmediateRemoval(self, flag):
        self._immediate_removal = not not flag

    security.declareProtected(view_management_screens, 'getBucketCount')

    def getBucketCount(self):
        return self._buckets

    security.declareProtected(view_management_screens, 'setBucketCount')

    def setBucketCount(self, count):
        if self._location:
            self.process()
        self._buckets = int(count)
        self._clearQueues()

    security.declareProtected(manage_zcatalog_entries, 'getZCatalog')

    def getZCatalog(self, method=''):
        ZC = None
        REQUEST = getattr(self, 'REQUEST', None)
        cache = self._v_catalog_cache
        if cache is not None:
            # The cached catalog may be wrapped with an earlier
            # request.  Before using it, check the request.
            (ZC, req) = cache
            if req is not REQUEST:
                # It's an old wrapper.  Discard.
                ZC = None

        if ZC is None:
            if self._location is None:
                raise QueueConfigurationError(
                    "This QueueCatalog hasn't been "
                    "configured with a ZCatalog location.")
            parent = aq_parent(aq_inner(self))
            try:
                ZC = parent.unrestrictedTraverse(self._location)
            except (KeyError, AttributeError):
                raise QueueConfigurationError("ZCatalog not found at %s." %
                                              self._location)
            if not hasattr(ZC, 'getIndexObjects'):  # XXX need a better check
                raise QueueConfigurationError(
                    "The object at %s does not implement the "
                    "IZCatalog interface." % self._location)

            security_manager = getSecurityManager()
            if not security_manager.validateValue(ZC):
                raise Unauthorized(self._location, ZC)

            ZC = aq_base(ZC).__of__(parent)
            self._v_catalog_cache = (ZC, REQUEST)

        if method:
            if not _is_zcatalog_method(method):
                raise AttributeError(method)
            m = getattr(ZC, method)
            # Note that permission to access the method may be checked
            # later on.  This isn't the right place to check permission.
            return m
        else:
            return ZC

    def __getattr__(self, name):
        # The original object must be wrapped, but self isn't, so
        # we return a special object that will do the attribute access
        # on a wrapped object.
        if _is_zcatalog_method(name):
            return AttrWrapper(name)

        raise AttributeError(name)

    def _update(self, uid, etype):
        t = time()
        self._queues[hash(uid) % self._buckets].update(uid, etype)

    security.declareProtected(manage_zcatalog_entries, 'catalog_object')

    def catalog_object(self, obj, uid=None):

        # Make sure the current context is allowed to to this:
        catalog_object = self.getZCatalog('catalog_object')

        if uid is None:
            uid = '/'.join(obj.getPhysicalPath())
        elif not isinstance(uid, StringType):
            uid = '/'.join(uid)

        catalog = self.getZCatalog()
        cat_indexes = list(catalog.indexes())
        cat_indexes.sort()
        immediate_indexes = list(self._immediate_indexes)
        immediate_indexes.sort()

        # The ZCatalog API doesn't allow us to distinguish between
        # adds and updates, so we have to try to figure this out
        # ourselves.

        # There's a risk of a race here. What if there is a previously
        # unprocessed add event? If so, then this should be a changed
        # event. If we undo this transaction later, we'll generate a
        # remove event, when we should generate an add changed event.
        # To avoid this, we need to make sure we see consistent values
        # of the event queue. We also need to avoid resolving
        # (non-undo) conflicts of add events. This will slow things
        # down a bit, but adds should be relatively infrequent.

        # Now, try to decide if the catalog has the uid (path).

        if immediate_indexes != cat_indexes:
            if cataloged(catalog, uid):
                event = CHANGED
            else:
                # Looks like we should add, but maybe there's already a
                # pending add event. We'd better check the event queue:
                if (self._queues[hash(uid) % self._buckets].getEvent(uid)
                        in ADDED_EVENTS):
                    event = CHANGED
                else:
                    event = ADDED

            self._update(uid, event)

        if immediate_indexes:
            # Update some of the indexes immediately.
            catalog.catalog_object(obj, uid, immediate_indexes)

    security.declareProtected(manage_zcatalog_entries, 'uncatalog_object')

    def uncatalog_object(self, uid):
        if not isinstance(uid, StringType):
            uid = '/'.join(uid)

        self._update(uid, REMOVED)

        if self._immediate_removal:
            self.process()

    security.declareProtected(manage_zcatalog_entries, 'process')

    def process(self, max=None):
        """ Process pending events and return number of events processed. """
        if not self.manage_size():
            return 0

        count = 0
        catalog = self.getZCatalog()
        for queue in filter(None, self._queues):
            limit = None
            if max:
                # limit the number of events
                limit = max - count

            events = queue.process(limit)

            for uid, (t, event) in events.items():
                if event is REMOVED:
                    if cataloged(catalog, uid):
                        catalog.uncatalog_object(uid)
                else:
                    # add or change
                    if event is CHANGED and not cataloged(catalog, uid):
                        continue
                    # Note that the uid may be relative to the catalog.
                    obj = catalog.unrestrictedTraverse(uid, None)
                    if obj is not None:
                        catalog.catalog_object(obj, uid)

                count = count + 1

            if max and count >= max:
                # On reaching the maximum, return immediately
                # so the caller can commit the transaction,
                # sleep for a while, or do something else.
                break

        return count

    #
    # CMF catalog tool methods.
    #
    security.declarePrivate('indexObject')

    def indexObject(self, object):
        """Add to catalog.
        """
        self.catalog_object(object, self.uidForObject(object))

    security.declarePrivate('unindexObject')

    def unindexObject(self, object):
        """Remove from catalog.
        """
        self.uncatalog_object(self.uidForObject(object))

    security.declarePrivate('reindexObject')

    def reindexObject(self, object, idxs=[]):
        """Update catalog after object data has changed.

        The optional idxs argument is a list of specific indexes
        to update (all of them by default).
        """
        # Punt for now and ignore idxs.
        self.catalog_object(object, self.uidForObject(object))

    security.declarePrivate('uidForObject')

    def uidForObject(self, obj):
        """Get a catalog uid for the object. Allows the underlying catalog
        to determine the uids if it implements this method"""
        catalog = self.getZCatalog()
        try:
            uidForObject = aq_base(catalog).uidForObject
        except AttributeError:
            return '/'.join(obj.getPhysicalPath())
        else:
            return uidForObject(obj)

    # Provide web pages. It would be nice to use views, but Zope 2.6
    # just isn't ready for views. :( In particular, we'd have to fake
    # out the PageTemplateFiles in some brittle way to make them do
    # the right thing. :(

    security.declareProtected(view_management_screens, 'manage_editForm')
    manage_editForm = PageTemplateFile('www/edit', globals())

    security.declareProtected(view_management_screens, 'manage_getLocation')

    def manage_getLocation(self):
        return self._location or ''

    security.declareProtected(view_management_screens, 'manage_edit')

    def manage_edit(self,
                    title='',
                    location='',
                    immediate_indexes=(),
                    immediate_removal=0,
                    bucket_count=0,
                    RESPONSE=None):
        """ Edit the instance """
        self.title = title
        self.setLocation(location or None)
        self.setImmediateIndexes(immediate_indexes)
        self.setImmediateRemoval(immediate_removal)
        if bucket_count:
            bucket_count = int(bucket_count)
            if bucket_count != self.getBucketCount():
                self.setBucketCount(bucket_count)

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_editForm?manage_tabs_message='
                              'Properties+changed' % self.absolute_url())

    security.declareProtected(manage_zcatalog_entries, 'list_queue_items')

    def list_queue_items(self, limit=100):
        """Return a list of items in the queue."""
        items = []
        count = 0
        for queue in filter(None, self._queues):
            qitems = queue._data.keys()
            count += len(qitems)
            items += qitems
        if limit is not None:
            if count > limit:
                items = items[:limit]
        return items

    security.declareProtected(manage_zcatalog_entries, 'manage_queue')
    manage_queue = DTMLFile('dtml/queue', globals())

    security.declareProtected(manage_zcatalog_entries, 'manage_size')

    def manage_size(self):
        size = 0
        for q in self._queues:
            size += len(q)

        return size

    security.declareProtected(manage_zcatalog_entries, 'manage_process')

    def manage_process(self, count=100, REQUEST=None):
        "Web UI to manually process queues"
        count = int(count)
        processed = self.process(max=count)
        if REQUEST is not None:
            msg = '%i Queue item(s) processed' % processed
            return self.manage_queue(manage_tabs_message=msg)
        else:
            return processed

    # Provide Zope 2 offerings

    index_html = None

    meta_type = 'ZCatalog Queue'

    manage_options = ((
        {
            'label': 'Configure',
            'action': 'manage_editForm',
            'help': ('QueueCatalog', 'QueueCatalog-Configure.stx')
        },
        {
            'label': 'Queue',
            'action': 'manage_queue',
            'help': ('QueueCatalog', 'QueueCatalog-Queue.stx')
        },
    ) + SimpleItem.manage_options)

    security.declareObjectPublic()
    # Disallow access to subobjects with no security assertions.
    security.setDefaultAccess('deny')

    security.declarePublic('getTitle', 'title_or_id')

    security.declareProtected(manage_zcatalog_entries, 'catalog_object',
                              'uncatalog_object', 'refreshCatalog')
Exemple #24
0
class UpgradeInformationGatherer(object):
    implements(IUpgradeInformationGatherer)
    adapts(ISetupTool)

    security = ClassSecurityInformation()

    def __init__(self, portal_setup):
        self.portal_setup = portal_setup
        self.portal = getToolByName(
            portal_setup, 'portal_url').getPortalObject()
        self.cyclic_dependencies = False

    security.declarePrivate('get_profiles')
    def get_profiles(self, proposed_only=False):
        profiles = self._sort_profiles_by_dependencies(
            self._get_profiles(proposed_only=proposed_only))
        profiles = flag_profiles_with_outdated_fs_version(profiles)
        profiles = extend_auto_upgrades_with_human_formatted_date_version(
            profiles)
        return profiles

    security.declarePrivate('get_upgrades')
    get_upgrades = deprecated(get_profiles,
                              'get_upgrades was renamed to get_profiles')

    security.declarePrivate('get_upgrades_by_api_ids')
    def get_upgrades_by_api_ids(self, *api_ids):
        upgrades = filter(lambda upgrade: upgrade['api_id'] in api_ids,
                          reduce(list.__add__,
                                 map(itemgetter('upgrades'),
                                     self.get_profiles())))
        missing_api_ids = (set(api_ids)
                           - set(map(itemgetter('api_id'), upgrades)))
        if missing_api_ids:
            raise UpgradeNotFound(tuple(missing_api_ids)[0])
        return upgrades

    security.declarePrivate('_get_profiles')
    def _get_profiles(self, proposed_only=False):
        for profileid in self.portal_setup.listProfilesWithUpgrades():
            if not self._is_profile_installed(profileid):
                continue

            data = self._get_profile_data(
                profileid, proposed_only=proposed_only)
            if len(data['upgrades']) == 0:
                continue

            if profileid == 'Products.CMFPlone:plone':
                # Plone has its own migration mechanism.
                # We do not support upgrading plone.
                continue

            yield data

    security.declarePrivate('_get_profile_data')
    def _get_profile_data(self, profileid, proposed_only=False):
        db_version = self.portal_setup.getLastVersionForProfile(profileid)
        if isinstance(db_version, (tuple, list)):
            db_version = '.'.join(db_version)

        data = {
            'upgrades': self._get_profile_upgrades(
                profileid, proposed_only=proposed_only),
            'db_version': db_version}

        try:
            profile_info = self.portal_setup.getProfileInfo(profileid).copy()
            if 'for' in profile_info:
                del profile_info['for']
            data.update(profile_info)

        except KeyError, exc:
            if exc.args and exc.args[0] == profileid:
                # package was removed - profile is no longer available.
                return {'upgrades': []}

            else:
                raise

        return data
Exemple #25
0
class Backreferences(object):
    """ Resets backreferences (pointing from another already released
    object to "me").
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, obj):
        self.context = obj

    security.declarePrivate('getData')
    def getData(self):
        """ Returns backreferences:
        {
            'uid-obj-a': {
                'the-field': [
                    'uid-of-another-unpublished-object',
                    'my-uid',
                    'uid-obj-b',
                ],
            },
            'uid-obj-b': {
                'ref-field': 'my-uid',
            },
        }
        """

        data = {}

        if hasattr(aq_base(self.context), 'getBackReferenceImpl'):
            referenceable = self.context

        else:
            try:
                referenceable = IReferenceable(self.context)

            except TypeError:
                # could not adapt
                # this means we have a dexterity object without
                # plone.app.referenceablebehavior activated.
                return data

        old_security_manager = getSecurityManager()
        newSecurityManager(self.context.REQUEST, SpecialUsers.system)
        try:
            references = referenceable.getBackReferenceImpl()
        finally:
            setSecurityManager(old_security_manager)

        for ref in references:
            # get source object
            src = ref.getSourceObject()
            if src is None:
                continue

            suid = src.UID()

            if suid not in data.keys():
                data[suid] = {}
            if getattr(ref, 'field', None) is None:
                continue

            if ref.field in data[suid]:
                # we already added this field
                continue
            else:
                # add the field value
                field = src.getField(ref.field)
                if field:
                    data[suid][ref.field] = field.getRaw(src)

        return data

    security.declarePrivate('setData')
    def setData(self, data, metadata):
        self.logger.info('Updating backreferences (UID %s)' %
                         metadata['UID'])
        cuid = self.context.UID()

        for suid, mapping in data.items():
            sobj = uuidToObject(suid)
            if not sobj:
                # source object is not published
                continue

            self.logger.info('... source-obj: %s' % '/'.join(
                    sobj.getPhysicalPath()))
            for fieldname, value in mapping.items():
                # value maybe uid (str) or list of uids (list)
                if isinstance(value, str):
                    value = [value]
                    single = True
                else:
                    single = False
                new_value = []

                # only set the targets that exist (=may be published)
                for tuid in value:
                    if tuid == cuid:
                        new_value.append(self.context)
                    else:
                        tobj = reference_catalog.lookupObject(tuid)
                        if tuid:
                            new_value.append(tobj)

                # set the new value on the field
                field = sobj.getField(fieldname)
                if single and new_value:
                    field.set(sobj, new_value[0])
                elif single:
                    field.set(sobj, None)
                else:
                    field.set(sobj, new_value)
class Extractor(object):
    """
    The Extractor module is used for extracting the data from a Object and
    pack it with json.
    """

    security = ClassSecurityInformation()

    security.declarePrivate('__call__')

    def __call__(self, object, action, additional_data):
        """
        Extracts the required data (action dependent) from a object for
        creating a Job.
        @param object:      Plone Object to export data from
        @param action:      Action to perform [push|delete]
        @type action:       string
        @param additional_data: Additional infos.
        @type additional_data: dict
        @return:            data (json "encoded")
        @rtype:             string
        """
        self.object = object
        self.is_root = IPloneSiteRoot.providedBy(self.object)
        data = {}

        if action not in ['delete', 'move']:
            adapters = sorted(getAdapters((self.object, ), IDataCollector))
            for name, adapter in adapters:
                data[name] = adapter.getData()
        # gets the metadata, we dont use an adapter in this case,
        # cause metdata is the most important data-set we need
        data['metadata'] = self.getMetadata(action)

        # remove ignored fields
        portal = self.object.portal_url.getPortalObject()
        config = IConfig(portal)
        ignore = config.get_ignored_fields()
        for field_to_ignore in ignore.get(data['metadata']['portal_type'], ()):
            # AT:
            data.get('field_data_adapter', {}).pop(field_to_ignore, None)
            # DX:
            for schemata in data.get('dx_field_data_adapter', {}).values():
                schemata.pop(field_to_ignore, None)

        if action == 'move':
            data['move'] = additional_data['move_data']

        # convert to json
        jsondata = self.convertToJson(data)
        return jsondata

    security.declarePrivate('getMetadata')

    def getMetadata(self, action):
        """
        Returns a dictionary with metadata about this object. It contains also
        the action.
        @param action:  publishing action [push|delete]
        @type action:   string
        @return:        metadata dictionary
        @rtype:         dict
        """
        # get object positions
        positions = {}
        if not self.is_root:
            parent = self.object.aq_inner.aq_parent
            for obj_id in parent.objectIds():
                try:
                    positions[obj_id] = parent.getObjectPosition(obj_id)
                except NotFound:
                    # catch notfound, some zope objects
                    # (ex. syndication_information of a topic)
                    # has no getObjectPosition
                    pass

        # create metadata dict
        uid = self.is_root and self.getRelativePath() or self.object.UID()
        try:
            wf_info = self.object.portal_workflow.getInfoFor(
                self.object, 'review_state')
        except (ConflictError, Retry):
            raise
        except Exception:
            wf_info = ''
        try:
            modifiedDate = str(self.object.modified())
        except AttributeError:
            modifiedDate = None
        data = {
            'UID': uid,
            'id': self.object.id,
            'portal_type': self.object.portal_type,
            'action': action,
            'physicalPath': self.getRelativePath(),
            'sibling_positions': positions,
            'review_state': wf_info,
            'modified': modifiedDate,
        }
        return data

    security.declarePrivate('getRelativePath')

    def getRelativePath(self):
        """
        Returns the relative path (relative to plone site) to the current
        context object.
        @return:    relative path
        @rtype:     string
        """
        path = '/'.join(self.object.getPhysicalPath())
        portalPath = '/'.join(
            self.object.portal_url.getPortalObject().getPhysicalPath())
        if not path.startswith(portalPath):
            raise TypeError('Expected object to be in a portal object -.-')
        relative_path = path[len(portalPath):]
        if relative_path == '':
            return '/'
        return relative_path

    security.declarePrivate('convertToJson')

    def convertToJson(self, data):
        """
        Converts a dictionary to a JSON-string
        @param data:    data dictionary
        @type data:     dict
        @return:        JSON
        @rtype:         string
        """
        data = decode_for_json(data)

        return json.dumps(data, sort_keys=True)
Exemple #27
0
class ManageUpgrades(BrowserView):

    security = ClassSecurityInformation()

    def __init__(self, *args, **kwargs):
        super(ManageUpgrades, self).__init__(*args, **kwargs)
        self.cyclic_dependencies = False

    def __call__(self):
        if self.request.get('submitted', False):
            assert not self.plone_needs_upgrading(), \
                'Plone is outdated. Upgrading add-ons is disabled.'

            if self.request.get('ajax', False):
                return self.install_with_ajax_stream()

            else:
                self.install()

        return super(ManageUpgrades, self).__call__(self)

    security.declarePrivate('install')

    def install(self):
        """Installs the selected upgrades.
        """
        start = time.time()

        gstool = getToolByName(self.context, 'portal_setup')
        executioner = getAdapter(gstool, IExecutioner)
        data = self._get_upgrades_to_install()
        executioner.install(data)

        logging.getLogger('ftw.upgrade').info('FINISHED')

        logging.getLogger('ftw.upgrade').info(
            'Duration for all selected upgrade steps: %s' %
            (format_duration(time.time() - start)))

    security.declarePrivate('install_with_ajax_stream')

    def install_with_ajax_stream(self):
        """Installs the selected upgrades and streams the log into
        the HTTP response.
        """
        response = self.request.RESPONSE
        response.setHeader('Content-Type', 'text/html')
        response.setHeader('Transfer-Encoding', 'chunked')
        response.write('<html>')
        response.write('<body>')
        response.write('  ' * getattr(response, 'http_chunk_size', 100))
        response.write('<pre>')

        with ResponseLogger(self.request.RESPONSE):
            self.install()

        response.write('</pre>')
        response.write('</body>')
        response.write('</html>')

    security.declarePrivate('get_data')

    def get_data(self):
        gstool = getToolByName(self.context, 'portal_setup')
        gatherer = getAdapter(gstool, IUpgradeInformationGatherer)
        try:
            return gatherer.get_upgrades()
        except CyclicDependencies, exc:
            self.cyclic_dependencies = exc.cyclic_dependencies
            return []
class TopicCriteraData(object):
    """returns all properties data
    """

    implements(IDataCollector)
    logger = getLogger()
    security = ClassSecurityInformation()

    def __init__(self, object):
        self.object = object

    security.declarePrivate('getData')

    def getData(self):
        """returns all important data"""
        return self.getTopicCriterias()

    security.declarePrivate('getTopicCriterias')

    def getTopicCriterias(self):
        """
        extract data from topic criterions, like regular field adapter.
        but in a special way, because the topic criteras are no accessible
        by the catalog

        data = {'criteria_type':{field_data_adapter result}}

        """
        criterias = {}
        # Use contentValues for implicit ftw.trash compatibility.
        for criteria in self.object.contentValues():
            field_data_adapter = queryAdapter(criteria,
                                              IDataCollector,
                                              name="field_data_adapter")
            # this adapter must be available, otherwise we cannot go ahead
            if field_data_adapter is None:
                continue

            # dont add subcollections
            if isinstance(criteria, self.object.__class__):
                continue

            id = criteria.id
            data = field_data_adapter.getData()
            data['meta_type'] = criteria.meta_type
            criterias[id] = data

        return criterias

    security.declarePrivate('setData')

    def setData(self, topic_criteria_data, metadata):
        """
        creates criterias fro a topic from
        {'criteria_type':{field_data_adapter result}}
        """
        self.logger.info('Updating criterias for topic (UID %s)' %
                         (self.object.UID()))

        # easiest way - first delete all criterias
        self.object.manage_delObjects([
            i for i in self.object.objectIds()
            if i != 'syndication_information'
        ])

        for criteria_id, data in topic_criteria_data.items():
            # create criteria

            # sortCriteria behave a bit special
            if 'ATSortCriterion' not in criteria_id:
                criteria = self.object.addCriterion(data['field'],
                                                    data['meta_type'])

            # add/change sort criteria
            if 'ATSortCriterion' in criteria_id:
                self.object.setSortCriterion(data['field'], data['reversed'])

            # we don't have to update data for for ATSortCriterion
            # check topic.py line 293
            if 'ATSortCriterion' not in criteria_id:
                field_data_adapter = queryAdapter(criteria,
                                                  IDataCollector,
                                                  name="field_data_adapter")
                field_data_adapter.setData(data, metadata)
Exemple #29
0
class UpgradeInformationGatherer(object):
    adapts(ISetupTool)

    security = ClassSecurityInformation()

    def __init__(self, portal_setup):
        self.portal_setup = portal_setup
        self.portal = getToolByName(portal_setup,
                                    'portal_url').getPortalObject()
        self.cyclic_dependencies = False

    security.declarePrivate('get_profiles')

    def get_profiles(self, proposed_only=False, propose_deferrable=True):
        profiles = self._sort_profiles_by_dependencies(
            self._get_profiles(proposed_only=proposed_only,
                               propose_deferrable=propose_deferrable))
        profiles = flag_profiles_with_outdated_fs_version(profiles)
        profiles = extend_auto_upgrades_with_human_formatted_date_version(
            profiles)
        return profiles

    security.declarePrivate('get_upgrades')
    get_upgrades = deprecated(get_profiles,
                              'get_upgrades was renamed to get_profiles')

    security.declarePrivate('get_upgrades_by_api_ids')

    def get_upgrades_by_api_ids(self, *api_ids, **kwargs):
        propose_deferrable = kwargs.pop('propose_deferrable', True)
        upgrades = [
            upgrade for upgrade in reduce(
                list.__add__,
                map(itemgetter('upgrades'),
                    self.get_profiles(propose_deferrable=propose_deferrable)))
            if upgrade['api_id'] in api_ids
        ]

        missing_api_ids = (set(api_ids) -
                           set(map(itemgetter('api_id'), upgrades)))
        if missing_api_ids:
            raise UpgradeNotFound(tuple(missing_api_ids)[0])
        return upgrades

    security.declarePrivate('_get_profiles')

    def _get_profiles(self, proposed_only=False, propose_deferrable=True):
        for profileid in self.portal_setup.listProfilesWithUpgrades():
            if not self._is_profile_installed(profileid):
                continue

            data = self._get_profile_data(
                profileid,
                proposed_only=proposed_only,
                propose_deferrable=propose_deferrable)
            if len(data['upgrades']) == 0:
                continue

            if profileid == 'Products.CMFPlone:plone':
                # Plone has its own migration mechanism.
                # We do not support upgrading plone.
                continue

            yield data

    security.declarePrivate('_get_profile_data')

    def _get_profile_data(self,
                          profileid,
                          proposed_only=False,
                          propose_deferrable=True):
        db_version = self.portal_setup.getLastVersionForProfile(profileid)
        if isinstance(db_version, (tuple, list)):
            db_version = '.'.join(db_version)

        data = {
            'upgrades':
            self._get_profile_upgrades(profileid,
                                       proposed_only=proposed_only,
                                       propose_deferrable=propose_deferrable),
            'db_version':
            db_version
        }

        try:
            profile_info = self.portal_setup.getProfileInfo(profileid).copy()
            if 'for' in profile_info:
                del profile_info['for']
            data.update(profile_info)

        except KeyError as exc:
            if exc.args and exc.args[0] == profileid:
                # package was removed - profile is no longer available.
                return {'upgrades': []}

            else:
                raise

        return data

    security.declarePrivate('_get_profile_upgrades')

    def _get_profile_upgrades(self,
                              profileid,
                              proposed_only=False,
                              propose_deferrable=True):
        proposed_ids = set()
        upgrades = []

        proposed_upgrades = list(
            flatten_upgrades(self.portal_setup.listUpgrades(profileid)))
        all_upgrades = list(
            flatten_upgrades(
                self.portal_setup.listUpgrades(profileid, show_old=True)))

        for upgrade in proposed_upgrades:
            proposed_ids.add(upgrade['id'])

        for upgrade in all_upgrades:
            upgrade = upgrade.copy()
            if upgrade['id'] not in proposed_ids:
                upgrade['proposed'] = False
                upgrade['done'] = True

            if self._was_upgrade_executed(profileid, upgrade):
                upgrade['proposed'] = False
                upgrade['done'] = True

            upgrade['orphan'] = self._is_orphan(profileid, upgrade)
            if upgrade['orphan']:
                upgrade['proposed'] = True
                upgrade['done'] = False

            upgrade['deferrable'] = self._is_deferrable(upgrade)
            if upgrade['deferrable'] and upgrade['proposed']:
                upgrade['proposed'] = propose_deferrable

            if 'step' in upgrade:
                del upgrade['step']

            upgrade['profile'] = profileid
            upgrade['api_id'] = '@'.join((upgrade['sdest'], profileid))

            if proposed_only and not upgrade['proposed']:
                continue

            upgrades.append(upgrade)

        return upgrades

    security.declarePrivate('_is_profile_installed')

    def _is_profile_installed(self, profileid):
        try:
            profileinfo = self.portal_setup.getProfileInfo(profileid)
        except KeyError:
            return False
        product = profileinfo['product']

        if get_installer is not None:
            quickinstaller = get_installer(self.portal, self.portal.REQUEST)

            if (quickinstaller.is_product_installable(product)
                    and not quickinstaller.is_product_installed(product)):
                return False
        else:
            quickinstaller = getToolByName(self.portal_setup,
                                           'portal_quickinstaller')
            if (quickinstaller.isProductInstallable(product)
                    and not quickinstaller.isProductInstalled(product)):
                return False

        version = self.portal_setup.getLastVersionForProfile(profileid)
        return version != 'unknown'

    security.declarePrivate('_sort_profiles_by_dependencies')

    def _sort_profiles_by_dependencies(self, profiles):
        """Sort the profiles so that the profiles are listed after its
        dependencies since it is safer to first install dependencies.
        """

        sorted_profile_ids = get_sorted_profile_ids(self.portal_setup)
        return sorted(profiles,
                      key=lambda p: sorted_profile_ids.index(p.get('id')))

    security.declarePrivate('_is_orphan')

    def _is_orphan(self, profile, upgrade_step_info):
        if upgrade_step_info['proposed']:
            return False
        if not self._is_recordeable(upgrade_step_info):
            return False
        return not self._was_upgrade_executed(profile, upgrade_step_info)

    security.declarePrivate('_is_deferrable')

    def _is_deferrable(self, upgrade_step_info):
        step = upgrade_step_info.get('step')
        if not step:
            return False

        maybe_ftw_upgrade_step_wrapper = getattr(step, 'handler', None)
        if not maybe_ftw_upgrade_step_wrapper:
            return False

        upgrade_step_class = getattr(maybe_ftw_upgrade_step_wrapper, 'handler',
                                     None)
        if not upgrade_step_class:
            return False

        return bool(getattr(upgrade_step_class, 'deferrable', False))

    security.declarePrivate('_is_recordeable')

    def _is_recordeable(self, upgrade_step_info):
        if not isinstance(upgrade_step_info['step'], UpgradeStep):
            return False
        handler = upgrade_step_info['step'].handler
        return IRecordableHandler.providedBy(handler)

    security.declarePrivate('_was_upgrade_executed')

    def _was_upgrade_executed(self, profile, upgrade_step_info):
        """Check for whether we know if an upgrade step was executed.

        Returns True when the recorder is sure that this upgrade was executed.
        Returns False when the recorder is sure that it was not executed.
        Returns None when the recorder does not know whether this upgrade was executed.
        """
        if not self._is_recordeable(upgrade_step_info):
            return None
        else:
            recorder = getMultiAdapter((self.portal, profile),
                                       IUpgradeStepRecorder)
            return recorder.is_installed(upgrade_step_info['sdest'])
Exemple #30
0
class Executioner(object):

    implements(IExecutioner)
    adapts(ISetupTool)
    security = ClassSecurityInformation()

    def __init__(self, portal_setup):
        self.portal_setup = portal_setup

    security.declarePrivate('install')

    def install(self, data):
        for profileid, upgradeids in data:
            self._upgrade_profile(profileid, upgradeids)

        for adapter in self._get_sorted_post_upgrade_adapters():
            adapter()

    security.declarePrivate('_upgrade_profile')

    def _upgrade_profile(self, profileid, upgradeids):
        last_dest_version = None

        for upgradeid in upgradeids:
            last_dest_version = self._do_upgrade(profileid, upgradeid) \
                or last_dest_version

        self.portal_setup.setLastVersionForProfile(profileid,
                                                   last_dest_version)

    security.declarePrivate('_do_upgrade')

    def _do_upgrade(self, profileid, upgradeid):
        step = _upgrade_registry.getUpgradeStep(profileid, upgradeid)
        step.doStep(self.portal_setup)

        msg = "Ran upgrade step %s for profile %s" % (step.title, profileid)
        logger.log(logging.INFO, msg)

        transaction_note = '%s -> %s (%s)' % (step.profile, '.'.join(
            step.dest), step.title)
        transaction.get().note(transaction_note)

        return step.dest

    security.declarePrivate('_get_sorted_post_upgrade_adapters')

    def _get_sorted_post_upgrade_adapters(self):
        """Returns a list of post upgrade adapters, sorted by
        profile dependencies.
        Assumes that the names of the adapters are profile names
        (e.g. "ftw.upgrade:default").
        """

        profile_order = get_sorted_profile_ids(self.portal_setup)

        portal_url = getToolByName(self.portal_setup, 'portal_url')
        portal = portal_url.getPortalObject()
        adapters = list(getAdapters((portal, portal.REQUEST), IPostUpgrade))

        def _sorter(a, b):
            name_a = a[0]
            name_b = b[0]

            if name_a not in profile_order and name_b not in profile_order:
                return 0

            elif name_a not in profile_order:
                return -1

            elif name_b not in profile_order:
                return 1

            else:
                return cmp(profile_order.index(name_a),
                           profile_order.index(name_b))

        adapters.sort(_sorter)
        return [adapter for name, adapter in adapters]