Generating unique entity references that honor the page naming strategy

Hi devs,

I’m opening this brainstorming so that we have an agreed direction for whoever picks it up. I don’t plan to work on it myself.

Note that I’ve drafted it quickly and there’s probably more to specify: it should be considered as a direction, to decide if we want to go there or not.

The problem

XWiki#getUniquePageName(space, name) (and $xwiki.getUniquePageName()) always runs the name through clearName(), which predates the page naming strategies. On a wiki with a naming strategy, code that transforms a name with the strategy and then asks for a unique name gets back a name that doesn’t comply with it.

Concrete case on xwiki.org (kebab case strategy with dots kept between digits, see Allowing dots in URLs on xwik.org and in the kebab-case page naming strategy): creating a blog post titled “XWiki 18.4.6 Released” when Blog.xwiki-18.4.6-released already exists gives Blog.xwiki-1846-released (dots stripped by clearName()).

The bug is tracked in Loading....

Other issues with this API:

  • It’s in oldcore, on the XWiki class, String-based (space name + page name), and checks existence with space + "." + pageName, which is wrong for nested spaces or names containing dots.
  • We have at least 2 other copies of the “append an index until it doesn’t exist” logic (HeadingNameNamingCriterion and PageIndexNamingCriterion in refactoring).
  • It’s also misused to generate random ids (extension.vm, RatingsMacros).
  • There’s no generic way to check whether an entity exists: DocumentAccessBridge.exists(DocumentReference), XWiki.exists(...), WikiDescriptorManager.exists(wikiId), each in a different module.

Idea

  1. A generic existence checker in xwiki-platform-model-api:
    • @Role EntityReferenceExistenceChecker with boolean exists(EntityReference), one implementation per entity type (hint = entity type: document, page, space, attachment, wiki), each living in the module owning that entity (oldcore, wiki-default…).
    • A default (no hint) implementation dispatching on the reference type.
    • Possibly exposed as $services.model.exists(reference).
  2. A generic UniqueEntityReferenceGenerator in xwiki-platform-model-validation-api, e.g. <T extends EntityReference> T generate(T reference):
    • Transforms only the last segment’s name with the configured strategy (when transformation is enabled). Parents are kept as is, since EntityNameValidation#transform(EntityReference) would also rewrite them.
    • Appends a suffix until the reference doesn’t exist, the suffix also complying with the strategy (e.g. xwiki-18.4.6-released-1).
    • Rejects types for which a free-form unique name doesn’t make sense (objects, properties, blocks).
  3. Deprecate XWiki#getUniquePageName() and make it delegate to the generator. This way all existing callers get the fix without changes (Blog, Forum, Newsletter, Recruitment and about 10 other contrib extensions use it).

Open questions

  • Who decides the suffix format (-1, _1, 1)? Maybe a default getUniqueSuffix(int) method on EntityNameValidation that each strategy can override.
  • For pages, should the generator produce a terminal (Space.name) or a nested (Space.name.WebHome) reference? Maybe it just follows the type of the reference given.
  • Note that a space exists only if it contains at least one document, to be documented on the space checker.

Note that the naming strategy is meant to apply to attachment names too (see Loading...), so the generator should apply it to attachments as soon as that’s implemented. For wikis, I’d say it shouldn’t apply, since wiki ids have their own rules.

WDYT?

Thanks