From e4f94fbc986249c5420a894f9d4ababbb0258645 Mon Sep 17 00:00:00 2001 From: David Erni Date: Tue, 2 Jul 2019 15:19:46 +0200 Subject: [PATCH] Fix for acquired objects moved into their parents. Correct special case handling for objects that were moved into one of their parent objects and thus can still be found via acquisition when their former path is used as their id was not changed. We assumed that the redirector is involved, this is not correct. Thus drop redirector check and instead make sure that the acquisition chain of the wrapped and unwrapped object is different. --- ftw/catalogdoctor/surgery.py | 31 ++++++------- ftw/catalogdoctor/tests/test_surgery.py | 61 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/ftw/catalogdoctor/surgery.py b/ftw/catalogdoctor/surgery.py index 227d3e1..8609265 100644 --- a/ftw/catalogdoctor/surgery.py +++ b/ftw/catalogdoctor/surgery.py @@ -1,9 +1,10 @@ +from Acquisition import aq_chain +from Acquisition import aq_inner from ftw.catalogdoctor.compat import DateRecurringIndex from ftw.catalogdoctor.exceptions import CantPerformSurgery from ftw.catalogdoctor.utils import find_keys_pointing_to_rid from plone import api from plone.app.folder.nogopip import GopipIndex -from plone.app.redirector.interfaces import IRedirectionStorage from Products.ExtendedPathIndex.ExtendedPathIndex import ExtendedPathIndex from Products.PluginIndexes.BooleanIndex.BooleanIndex import BooleanIndex from Products.PluginIndexes.DateIndex.DateIndex import DateIndex @@ -12,7 +13,6 @@ from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex from Products.PluginIndexes.UUIDIndex.UUIDIndex import UUIDIndex from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex -from zope.component import queryUtility class IndexSurgery(object): @@ -312,12 +312,13 @@ def perform(self): class RemoveRidOrReindexObject(Surgery): """Reindex an object for all indexes or remove the stray rid. - This can have three causes: - - Either there are stray rids left behind in the catalogs `uid` and `path` - mappings. In such cases the object is no longer traversable as plone - content and we can remove the stray rid. - - The object has been moved, but somehow `uid` and `path` mappings have - not been updated correctly. We can remove the stray rid. + This can have two causes: + - Either there are orphaned rids left behind in the catalogs `uid` and + `path` mappings. In such cases the referenced object is is no longer + traversable as plone content and we can remove the orphaned rid. + - Special case of above when the object has been moved into its parents. In + such cases the object can still be traversed as object is found via + acquisition. We can remove the orphaned rid in such cases. - The object has not been indexed correctly, in such cases the object can be traversed and has to be reindexed in all indexes. @@ -359,18 +360,16 @@ def perform(self): return - # this happens when the object has been moved but the old path has not - # been correctly removed from the catalog. we expect a redirect in such - # cases, also the path of the object we traversed to will be different - # from the path we input to `unrestrictedTraverse` + # special case when the object has been moved into one of its parents. + # it can be traversed as it is found via acquisition. safeguard so we + # only unindex objects where this special case is true. obj_path = '/'.join(obj.getPhysicalPath()) if obj_path != path: - storage = queryUtility(IRedirectionStorage) - if storage.get(path) != obj_path: + if aq_chain(aq_inner(obj))[1:] == aq_chain(obj)[1:]: raise CantPerformSurgery( "Object path after traversing {} differs from path before " - "traversing and in catalog {}, but no redirect is " - "registered.".format(obj_path, path)) + "traversing and in catalog {}, but acquisition chain " + "is unexpectedly equal.".format(obj_path, path)) self.unindex_rid_from_all_catalog_indexes(rid) self.delete_rid_from_paths(rid) diff --git a/ftw/catalogdoctor/tests/test_surgery.py b/ftw/catalogdoctor/tests/test_surgery.py index 24e335c..cf3bad7 100644 --- a/ftw/catalogdoctor/tests/test_surgery.py +++ b/ftw/catalogdoctor/tests/test_surgery.py @@ -1,5 +1,6 @@ from ftw.builder import Builder from ftw.builder import create +from ftw.catalogdoctor.exceptions import CantPerformSurgery from ftw.catalogdoctor.surgery import ReindexMissingUUID from ftw.catalogdoctor.surgery import RemoveExtraRid from ftw.catalogdoctor.surgery import RemoveOrphanedRid @@ -133,6 +134,66 @@ def test_surgery_reindex_missing_uuid(self): result = self.run_healthcheck() self.assertTrue(result.is_healthy()) + def test_surgery_remove_object_moved_into_parent_and_found_via_acquisition_abort(self): + self.parent['qux'] = self.child + broken_path = '/'.join(self.child.getPhysicalPath()[:-1] + ('qux',)) + + rid = self.choose_next_rid() + self.catalog.uids[broken_path] = rid + self.catalog.paths[rid] = broken_path + self.catalog.data[rid] = {} + self.catalog._length.change(1) + + result = self.run_healthcheck() + self.assertFalse(result.is_healthy()) + unhealthy_rid = result.get_unhealthy_rids()[0] + self.assertEqual(rid, unhealthy_rid.rid) + self.assertEqual( + ( + 'in_catalog_not_in_uuid_index', + 'in_catalog_not_in_uuid_unindex', + ), + result.get_symptoms(unhealthy_rid.rid)) + + surgery = RemoveRidOrReindexObject(self.catalog, unhealthy_rid) + with self.assertRaises(CantPerformSurgery): + surgery.perform() + + def test_surgery_remove_object_moved_into_parent_and_found_via_acquisition(self): + grandchild = create(Builder('folder') + .within(self.child) + .titled(u'nastygrandchild')) + + old_grandchild_path = '/'.join(grandchild.getPhysicalPath()) + # move object into parent's parent + self.parent.manage_pasteObjects( + self.child.manage_cutObjects(grandchild.getId()), + ) + + # re-register old grandchild path with different rid + rid = self.choose_next_rid() + self.catalog.uids[old_grandchild_path] = rid + self.catalog.paths[rid] = old_grandchild_path + self.catalog.data[rid] = {} + self.catalog._length.change(1) + + result = self.run_healthcheck() + self.assertFalse(result.is_healthy()) + unhealthy_rid = result.get_unhealthy_rids()[0] + self.assertEqual(rid, unhealthy_rid.rid) + self.assertEqual( + ( + 'in_catalog_not_in_uuid_index', + 'in_catalog_not_in_uuid_unindex', + ), + result.get_symptoms(unhealthy_rid.rid)) + + surgery = RemoveRidOrReindexObject(self.catalog, unhealthy_rid) + surgery.perform() + + result = self.run_healthcheck() + self.assertTrue(result.is_healthy()) + def test_surgery_add_dropped_object_to_indices(self): self.drop_object_from_catalog_indexes(self.parent)