Last modified by Vincent Massol on 2026/08/05 16:03

Show last authors
1 {{toc start="2"/}}
2
3 == Main idea ==
4
5 * **Drop** the notion of "**space**" in XWiki altogether and instead **use** the existing **parent-child** relationship ("parent" field") so that documents only store the relative hierarchy. This helps build a tree structure that is resistant to changes in the hierarchy.
6 * Instead of references (Space.Name) **use unique document names** that are initially deduced from the user filled document title and then tweaked by users to their liking (so that they get a nice URL) or in case the deduced document ID is already taken (so involve the user in the ID selection if need be). This allows us to get retrieve a document in O(1), using its ID. Useful in URL resolution and other operations.
7 * Update the **rights** system to **use** the **parent-child relationship** instead of the document reference. This allows the inheritance of rights based on the parent-child relationship.
8
9 == Uniquely identifying a document node in the document tree ==
10
11 === Absolute references (paths) ===
12
13 The problem with storing the document's absolute path in the "space" field in the database (e.g. "Space1.Space2.Space3" for a document located in Space3) is that you need to maintain this path up to date in all the levers of your document tree.
14
15 A>B>C
16
17 B: space = "A"
18
19 C: space = "A.B"
20
21 If I move document A under some other document, the I need to go through all the children of A (direct and indirect), recursively, so I can update the "space" field (path).
22
23 X>A => X>A>B>C
24
25 A: space = "X"
26
27 B: space = "X.A"
28
29 C: space = "X.A.B.C"
30
31 If instead we are to rename the document A, we would have to perform a similar operation, again, recursively.
32
33 When deleting document A, we have 2 options:
34
35 * delete just the document A and recursively update all its children to "upgrade" them one level in the hierarchy or
36 * delete the node A and, recursively, all its children
37
38 === Parent-child relationship instead of absolute reference/path ===
39
40 If we only store relative parent-child information in each document node, then the tasks above translate to the following.
41
42 A>B>C
43
44 B: parent = "A"
45
46 C: parent = "B"
47
48 Move document A:
49
50 X>A => X>A>B>C
51
52 A: parent = "X"
53
54
55 //B: parent = "A" (unmodified)//
56
57 //C: parent = "B" (unmodified)//
58
59 The problem that we face in this case is how to identify the parent of the document if we only store the immediate parent and not the entire path. In other words, if you only have "C" as parent, how do you know which "C" it is in the tree of documents you have?
60
61
62 When using absolute references you fix this problem because there is only one path "A>B>C" in your tree leading to your node. However, we have established above that storing such paths for each node of the document tree is inefficient and unmaintainable, so we need to add a restriction on what "C" actually means. We need to make "C" unique in the document tree, so that we have all the information we need to uniquely identify a document in the tree.
63
64
65 == Unique document ID ==
66
67 Current document ID (reference): //Space.Name//
68
69 Nested spaces proposal document ID: //Space1.Space2.SpaceN.Name//
70
71 Nested documents proposal document ID (based on nested spaces): //Space1.Space2.Name.WebHome//
72
73 UniqueID proposal document ID: //Name//
74
75 === How the unique document ID is created, at document creation time: ===
76
77 1. User enters document title (e.g. "My Document")
78 1. Document ID is extracted from title (e.g. "MyDocument")
79 1. The user is presented with the resulting URL of his document (e.g. "http:~/~/host/xwiki/bin/view/MyDocument")
80 11. If the document ID "MyDocument" already exists, the user is asked to provide an unique document URL (ID).
81 11. If the document ID "MyDocument" is available, the user clicks next and proceeds to editing the newly created document.
82
83 === Rationale: ===
84
85 We already have 2 cases where this creation flow is successfully applied: wiki creation and AWM app creation. Even for new documents, we are already involving the user in picking an unique document ID, just that we are hiding this unicity constraint by prefixing the document ID with the space (Space.Name) so that the unicity constraint is pushed at a space level (but only because the prefix).
86
87 **This idea is old:**
88
89 Even since from the beginning we have identified the need for an unique document ID:
90
91 |=Issue|=Title|=Creator
92 |[[http:~~/~~/jira.xwiki.org/browse/XWIKI-117>>url:http://jira.xwiki.org/browse/XWIKI-117]]|Switching to GUID for document and objects|(Ludovic 2005)
93 |[[http:~~/~~/jira.xwiki.org/browse/XWIKI-1021>>url:http://jira.xwiki.org/browse/XWIKI-1021]]|New ID model|(Catalin 2007)
94
95 Until now, we have considered this Unique ID problem from the perspective that the ID needs to be machine generated and not human readable. This proposal suggests that we instead involve the users in the selection of the document ID, presented from the perspective of the resulting URL, since that is the only place where users should care about the document's name (i.e. more than its title).
96
97 === Scripts ===
98
99 Another big advantage of not using random machine generated unique IDs is that they are also usable in scripts, so we no longer need to worry about how to grab the "XWiki.XWikiUsers" class when it is named "23EA233B" and instead we can still grab it by the ID "XWiki.XWikiUsers", or "MyDocument" or "MyDocument1", etc.
100
101
102 == Advantages of using Unique ID + Nested Documents based on parent-child ==
103
104 === URLs ===
105
106 ==== Shorter URLs ====
107
108 **/bin/view/MyDocument** uniquely identifies the document MyDocument inside the wiki, while preserving the document hierarchy.
109
110 I.e. **/bin/view/MyDocument** takes you directly to the "MyDocument" document which is currently inside the hierarcy "Parent1>Parent2>MyDocument>Child1>etc."
111
112 ==== More expressive URLs (optional / just an idea) ====
113
114 **/bin/view/MyDocument#The+Title+Of+My+Document**
115
116 Note: Using anchor instead of path elements to allow changes in the document title for the future while preserving existing bookmarks.
117
118 ==== URLs become resistant to hierarchy changes ====
119
120 **/bin/view/MyDocument** will remain the same even if the parent of the document changes since MyDocument uniquely identifies the document, regardless of its position in the document hierarchy.
121
122 On the other hand, in the Nested Spaces-based approach, in an URL of /bin/view/Space1/Space2/Space3/Page1 if you rename/move/remove Space2, all the child document URLs will be affected and all those bookmarks will be invalidated.
123
124 The same bookmark invalidation happens to Nested Documents based on Nested Spaces where you have an URL similar to /bin/view/Page1/Page2/Page3 and you alter Page2.
125
126 ==== Support for path in URLs while preserving resistance to hierarchy changes ====
127
128 **/bin/view/Parent1/Parent2/MyDocument** is supported because we only need the last path element to identify the document MyDocument. The parent information can be exposed in the URL if desired, for aesthetic reasons, but it is not really useful/needed for practical reasons. As an addition, the parent information can be validated such that wrong parent information in the URL **/bin/view/FakeParent**/Parent2/MyDocument returns a Document Not Found error.
129
130 This could be an optional / disabled by default feature, in order to encourage short URLs which are more user-friendly.
131
132 === Deleting/Renaming a document requires minimal local tree fixing ===
133
134 When a parent is renamed or deleted, it is only the immediate children that need to be fixed in order to restore consistency in the tree and that is it. No need for a recursive operation, unless it is a delete operation and the user instructed to delete the children together with the parent node.
135
136 In the path-based solution, we are always forced to update the path information of all the nodes.
137
138 === Fixing backlinks on a document rename ===
139
140 Since a wiki link will now be just **~[~[My Document>>MyDocument]]** you will basically have __removed__ the notion of __relative links__ and all the problems faced while processing/resolving/fixing them since the unique document ID is also absolute.
141
142
143 == Exporting XAR format ==
144
145 One advantage of using an unique document ID that is not purely technical and random generated is that it still makes sense outside the XWiki instance it was generated on and can be easily exported and reimported on a different XWiki instance.
146
147 However, the format of the exported XAR will need to be changed to support:
148
149 1. Child pages when exporting a subtree of documents (i.e. P1>P2>P3). The directory structure would be something like:
150 /P1/P1.xml
151 /P1/P2/P2.xml
152 /P1/P2/P3/P3.xml
153 1. The hierarchy of an exported document (i.e. exporting only P3 from P1>P2>P3). The directory structure would be something like:
154 /P1/P2/P3/P3.xml
155 such that we don`t touch the XML of the exported document and the parent field looks just like:
156 <parent>P2</parent>
157 Importing this xar would result in two blank documents (P1 with P2 as child) being created to preserve the original hierarchy.
158
159 == Migration to Parent-child + Unique ID + Nested Documents ==
160
161 === From a previous XWiki version ===
162
163 Previous XWiki document references are in the form "Space.Page" (space="Space", name="Page") and are accessible in the URL through "Space/Page".
164
165 A migrator can be written to use the previous serialized document reference as unique document ID (since the "<space>.<page>" combination is unique, as described earlier in this proposal). As a result of this migrator, the new URL of a migrated Space1.Page1 document would become:
166
167 * from **/bin/view/Space1/Page1**
168 * to **/bin/view/Space1.Page1**
169
170 With the intent of preserving bookmarks that were existing before the migration, an **extension** can be written and installed on a __wiki that was migrated__ from previous version which has exactly this purpose. To achieve that, such an extension can look at the incoming URL if it contains more than 1 path element. Example:
171
172 * Incoming **/bin/view/Space1/Page1** contains 2 path elements (Space1 and Page1). The wiki is set to use short URLs which require only 1 path element (the document ID). This means that this is a request for the old URL scheme coming from an existing bookmark.
173 * The URL gets resolved to **/bin/view/Space1.Page1** (i.e. the "/" is converted to a "." and the "Space1.Page1" document ID is checked and returned if it exists)
174
175 A new XWiki instance that is not migrating from any previous version should not need to install this extension and. Also, an XWiki instance that did migrate and has installed this extension can choose to uninstall it at whichever point its admins wish to stop supporting existing bookmarks on the old URLs that were up before the migration.
176
177 ==== Migrating relative links ====
178
179 To handle existing relative links that need to be migrated (e.g. "WebHome"), the migrator would first have to make existing links absolute before switching to the unique ID approach so that, after the migration is complete, we end up with a documentID named "Main.WebHome" and all the links that were previously relative ("WebHome") will now be absolute ("Main.WebHome").
180
181 === From a different wiki system that supports ND and wants to preserve hierarchy URLs ===
182
183 After transferring the content and hierarchy information to the XWiki instance, simply enable the [["hierarchy URLs" feature described above>>||anchor="HSupportforpathinURLswhilepreservingresistancetohierarchychanges"]].
184
185 == Other changes for backwards compatibility ==
186
187 === Add XWiki.getDocumentByID(String documentID) ===
188
189 In order to support existing scripts and code that gets documents by DocumentReference or (serialized) Document Reference String, we should add an **XWiki.getDocumentByID(String documentID)** method where "documentID" is the unique document ID. Any existing API methods of the XWiki class such as **XWiki.getDocument(...)** should serialize the DocumentReference to a String (for DocumentReference versions) and use that String as documentID in this new method.
190
191 == Other implementation notes ==
192
193 === Obtaining children and parents information for a node ===
194
195 An **extra hierarchy table** (so not just the "parent" field or rather, replacing the "parent" field) would most likely be needed in both this approach and in a full-fledged Nested Documents approach (where we only store document parents and not full paths), for the simple reason that we would be storing a tree in an SQL database. Some [[implementation details here>>url:http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back/68]] on tree structures stored in SQL databases.
196
197 This is needed to be able to efficiently obtain the children information for a node, including direct children (also requires depth information / column to be stored in the hierarchy table), and also the parent information (for breadcrumbs for example or just for loading the document's full path in 1 query, sorted by depth).
198
199 ==== Alternative without an extra hierarchy table ====
200
201 If we wish to avoid the extra table, we could avoid the breadcrums usecase by displaying only the direct parent of the current document in the breadcrumbs and the wiki of the current document, with the possiblity to expand the gap between and get a tree view of the hierarchy. [[UI Example>>url:http://design.xwiki.org/xwiki/bin/download/Proposal/ExtendedBreadcrumb/example_linear.png]] and [[UI example when expanding the gap>>http://design.xwiki.org/xwiki/bin/download/Proposal/ExtendedBreadcrumb/tree_expand.png]].
202
203 For the top-down navigation of the tree (e.g. in a tree widget), the "parent" field provides enough information to recursively get the list of immediate children of a node.
204
205 === Preserving URLs after a document rename ===
206
207 In order to preserve an URL after a document rename we can still apply the "Document alias" solution that would leave an "alias" of the document with the old ID in order to preserve existing bookmarks.
208
209 http://jira.xwiki.org/browse/XWIKI-3622 Add an automatic redirect when renaming a document
210
211 === Concerns about document unicity constraint ===
212
213 As mentioned near the beginning of this proposal, a "solution" for reusing a document name is to prefix it with a namespace. Example for reusing "Test":
214
215 * "My:Test"
216 * "My.Test"
217 * "My_Test"
218 * etc.
219
220 It is purely the user's choice what scheme he wishes to apply to obtain an unique document ID, whether it is to append to the name ("Test1", "Test2", etc.), to prepend ("ATest", "BTest", etc.) or to define a naming scheme as exemplified above.
221
222 === More details on storing trees in SQL databases ===
223
224 The following slides contain comparisons between the options and also presents details on Closure Tables and examples:
225
226 http://www.slideshare.net/billkarwin/models-for-hierarchical-data
227
228 http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back/48

Get Connected