Last modified by superadmin on 2026/10/22 17:32

Show last authors
1 == Quick start with examples ==
2
3 1. Write a mapper for each family of links you want to convert. They can be components, or simple classes. Mappers extend the ##AbstractURLMapper## class which lets you:
4 1*. provide one or several regular expressions that match the links. One should be enough for most cases. We advise using named groups in regular expressions: notice how clear it makes the conversion code below
5 1*. implement a ##convert## method that returns a resource reference to the target of the link.
6 1*. optionally implement a ##getSuggestions## method that returns suggestions in case ##convert## returns ##null##.
7 1. Write a prefix handler: this is a **named** component that extends ##AbstractURLMappingPrefixHandler##. The name must be unique and will be used for configuration management.
8
9 For the prefix handler:
10
11 * Implement ##getMappers##. It must return an array of mappers. If your mappers are components, inject them and put them in the array. If they are regular classes, put instances of these classes.
12 * Optionally implement ##initializeConfigurationDefaults(DefaultURLMappingConfiguration configuration)##. This is for if you want to provide more specific defaults for this handler, while still allowing administrators to override your choices. It can be used to provide more specific messages, or a whole custom screen template.
13
14 Here are two simplified examples of mappers (where error handling has been cut down):
15
16 {{code language="java"}}
17 import java.net.URLDecoder;
18 import java.nio.charset.StandardCharsets;
19 import java.util.regex.Matcher;
20
21 import javax.inject.Inject;
22 import javax.inject.Singleton;
23
24 import org.xwiki.component.annotation.Component;
25 import org.xwiki.contrib.urlmapping.AbstractURLMapper;
26 import org.xwiki.contrib.urlmapping.DefaultURLMappingMatch;
27 import org.xwiki.model.reference.AttachmentReference;
28 import org.xwiki.model.reference.DocumentReference;
29 import org.xwiki.model.reference.EntityReference;
30 import org.xwiki.resource.ResourceReference;
31 import org.xwiki.resource.entity.EntityResourceAction;
32 import org.xwiki.resource.entity.EntityResourceReference;
33
34 @Component (roles = ConfluenceAttachmentURLMapper.class)
35 @Singleton
36 public class ConfluenceAttachmentURLMapper extends AbstractURLMapper
37 {
38 @Inject
39 private ConfluencePageIdResolver confluenceIdResolver;
40
41 public ConfluenceAttachmentURLMapper()
42 {
43 super("download/attachments/(?<pageId>\\d+)/(?<filename>[^?#]+)(?<params>\\?.*)?$");
44 }
45
46 @Override
47 public ResourceReference convert(DefaultURLMappingMatch match)
48 {
49 try {
50 Matcher matcher = match.getMatcher();
51 String pageIdStr = matcher.group("pageId");
52 String filename = URLDecoder.decode(matcher.group("filename"), StandardCharsets.UTF_8);
53 long pageId = Long.parseLong(pageIdStr);
54 EntityReference docRef = confluenceIdResolver.getDocumentById(pageId);
55 AttachmentReference attachmentRef = new AttachmentReference(filename, new DocumentReference(docRef));
56 return new EntityResourceReference(attachmentRef, EntityResourceAction.VIEW);
57 } finally {
58 return null;
59 }
60 }
61 }
62 {{/code}}
63
64 {{code language="java"}}
65 import java.net.URLDecoder;
66 import java.nio.charset.StandardCharsets;
67 import java.util.regex.Matcher;
68
69 import javax.inject.Inject;
70 import javax.inject.Singleton;
71
72 import org.xwiki.component.annotation.Component;
73 import org.xwiki.contrib.urlmapping.AbstractURLMapper;
74 import org.xwiki.contrib.urlmapping.DefaultURLMappingMatch;
75 import org.xwiki.contrib.urlmapping.suggestions.URLMappingSuggestionUtils;
76 import org.xwiki.model.reference.EntityReference;
77 import org.xwiki.model.reference.LocalDocumentReference;
78 import org.xwiki.rendering.block.Block;
79 import org.xwiki.resource.ResourceReference;
80 import org.xwiki.resource.entity.EntityResourceAction;
81 import org.xwiki.resource.entity.EntityResourceReference;
82 import org.xwiki.contrib.confluence.resolvers.ConfluencePageTitleResolver;
83
84 @Component (roles = ConfluenceDisplayURLMapper.class)
85 @Singleton
86 public class ConfluenceDisplayURLMapper extends AbstractURLMapper
87 {
88 @Inject
89 private ConfluencePageTitleResolver confluencePageTitleResolver;
90
91 @Inject
92 private URLMappingSuggestionUtils suggestionUtils;
93
94 public ConfluenceDisplayURLMapper()
95 {
96 super("display/(?<spaceKey>[^/]+)/(?<pageTitle>[^?/#]+)(?<params>\\?.*)?$");
97 }
98
99 @Override
100 public ResourceReference convert(DefaultURLMappingMatch match)
101 {
102 try {
103 Matcher matcher = match.getMatcher();
104 String spaceKey = matcher.group("spaceKey");
105 String pageTitle = URLDecoder.decode(matcher.group("pageTitle"), StandardCharsets.UTF_8);
106 EntityReference docRef = confluencePageTitleResolver.getDocumentByTitle(spaceKey, pageTitle);
107 return new EntityResourceReference(docRef, EntityResourceAction.VIEW);
108 } finally {
109 return null;
110 }
111 }
112
113 @Override
114 protected Block getSuggestions(DefaultURLMappingMatch match)
115 {
116 Matcher matcher = match.getMatcher();
117 String spaceKey = matcher.group("spaceKey");
118 String pageTitle = URLDecoder.decode(matcher.group("pageTitle"), StandardCharsets.UTF_8);
119 return suggestionUtils.getSuggestionsFromDocumentReference(new LocalDocumentReference(spaceKey, pageTitle));
120 }
121 }
122 {{/code}}
123
124 This second mapper uses ##URLMappingSuggestionUtils## from the ##url-mapping-suggestions## module to return suggestions from a fictive entity reference.
125
126 Here is an example of a prefix handler:
127
128 {{code language="java"}}
129 import javax.inject.Inject;
130 import javax.inject.Named;
131 import javax.inject.Singleton;
132
133 import org.xwiki.component.annotation.Component;
134 import org.xwiki.contrib.urlmapping.DefaultURLMappingConfiguration;
135 import org.xwiki.contrib.urlmapping.URLMapper;
136 import org.xwiki.contrib.urlmapping.AbstractURLMappingPrefixHandler;
137
138 import static org.xwiki.contrib.urlmapping.DefaultURLMappingConfiguration.Key;
139
140 @Component
141 @Singleton
142 @Named("confluence")
143 public class ConfluenceURLMappingPrefixHandler extends AbstractURLMappingPrefixHandler
144 {
145 @Inject
146 private ConfluenceAttachmentURLMapper confluenceAttachmentURLMapper;
147
148 @Inject
149 private ConfluenceDisplayURLMapper confluenceDisplayURLMapper;
150
151 @Inject
152 private ConfluenceViewPageURLMapper confluenceViewPageURLMapper;
153
154 @Override
155 protected URLMapper[] getMappers()
156 {
157 return new URLMapper[] {
158 confluenceAttachmentURLMapper,
159 confluenceDisplayURLMapper,
160 confluenceViewPageURLMapper
161 };
162 }
163
164 @Override
165 protected void initializeConfigurationDefaults(DefaultURLMappingConfiguration configuration)
166 {
167 // Of course, use a localization manager, don't hardcode messages like this.
168 configuration.setDefault(Key.INTRO_MESSAGE, "Hello! It seems you used an old Confluence link. Update your bookmark :-)");
169 configuration.setDefault(Key.NOT_FOUND_INTRO_MESSAGE, "Sorry, we could not find the document corresponding to this old Confluence link");
170 }
171 }
172 {{/code}}
173
174 The full, working examples can be found in the [[##confluence-url-mapping##>>Extension.Confluence.URL Mapping]] module of the ##confluence## contrib package at {{scm user="xwiki-contrib" project="confluence" path="confluence-url-mapping/src/main/java/org/xwiki/contrib/confluence/urlmapping/internal"/}}.
175
176 == Documentation ==
177
178 The URL Mapping framework is centered around the following interfaces.
179
180 === ##URLMappingPrefixHandler## ===
181
182 This represents a prefix handler that handles all the URLs under a given prefix.
183
184 * its ##String getPrefix()## method provides the prefix it handles
185 * its ##URLMappingResult convert(String path, String method, HttpServletRequest request)## method converts an incoming request. Returning ##null## means that the handler didn't handle the request in the end, although it's generally better to handle the situation explicitly. See the next section.
186
187 === ##URLMappingResult## ===
188
189 This represents the result of a conversion:
190
191 * its ##ResourceReference getResourceReference()## or its ##String getURL()## method contains the target of the conversion. If no conversion could be performed, both should return null or empty. Resource references will be converted using ##Wiki.getURL()## and the specified action should be honored.
192 * its ##Block getSuggestions()## method optionally provides some suggestion to show in case the conversion failed.
193 * its ##int getHTTPStatus()## method provides the HTTP status to use in case a redirect is not issued and a redirection / not found screen is shown instead. 0 can be used to let the URL Mapping extension return a sensible status (404 is no target is provided, 200 if one is provided).
194 * its ##URLMappingConfiguration getConfiguration()## method returns the configuration to use for the redirection.
195
196 === ##URLMappingConfiguration## ===
197
198 This represents the configuration to use for a conversion. It can be static, or it can change every time a conversion is computed. It can be sourced from the wiki configuration, or totally generated at runtime. See the Configuration section for an intuition on what this interface provides (the configuration of an handler is a direct reflection of this interface), and the code for the exact details.
199
200 === ##AbstractURLMappingPrefixHandler## ===
201
202 This is an opinionated / "managed" take on how to handle conversions. We think that you should implement your handlers by extending this class unless you have very specific needs.
203
204 It provides:
205
206 * automatic configuration and prefix management;
207 * a conversion mechanism centered around the concept of URL mappers.
208
209 See the example above for basic usage. You can read the code to discover the protected and public methods you can override but there's not much here. You can go wild and override ##getConfiguration()## if you want to customize the configuration management from your handler but this should not be usually necessary.
210
211 === ##URLMapper## and ##AbstractURLMapper## ===
212
213 URL mappers are available to handlers extending ##AbstractURLMappingPrefixHandler##.
214
215 They let you convert separate families of links separately instead of doing everything in the ##convert## method. For instance, you can have a mapper handling links to spaces, a mapper handling links to attachments, and a mapper handlings links to documents.
216
217 Here's how things work:
218
219 1. Each mapper specifies the family of links it manages through a ##URLMappingSpecification## object returned by its ##getSpecification()## method. Note: ##AbstractURLMapper## conveniently lets you pass the specification to use in its constructors, either as an ##URLMappingSpecification## object, or as a list of regex provided as ##String## or ##Pattern## so you don't have to implement ##getSpecification()## yourself.
220 1. When a request is to be converted by the handler, it gets matched with its mappers' specifications, in order.
221 1. As soon as a specification matches, the result of this match (including the captured groups of regular expressions in a [[##Matcher##>>https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/regex/Matcher.html]] object, which also includes named groups) is passed to the corresponding mapper using its ##convert(URLMappingMatch)## method
222 1. If the mapper can convert the request, it returns a non-null result. The conversion ends: ##AbstractURLMappingPrefixHandler## will return this result. If it can't, it returns null and the next specifications / mappers are tried.
223
224 Mappers can also provide a ##getSuggestions()## method that will be called if the ##convert## method return null, to provide suggestions to the user.
225
226 Tips:
227
228 * Usually, if request matches a specification, no other mapper will handle the request. As a result, if the mapper doesn't find the target, it can still meaningfully return a non-null URLMappingResult that contains suggestions.
229 * You can manage fallbacks using a catch-all mapper at the end of the list of mappers your handler provides. Catch-all mappers have a specification that match any request. This is the case when no regular expression have been provided. This can be useful for more general suggestions or a more generic link handling.
230
231 {{info}}
232 With ##AbstractURLMapper##, conversion is usually provided by implementing ##ResourceReference convert(DefaultURLMappingMatch match)##. Alternatively, if you need more control, you can override ##public URLMappingResult convert(URLMappingMatch match)## instead.
233 {{/info}}
234
235 === ##URLMappingSpecification## ===
236
237 * Its ##Collection<String> getHandledHTTPMethods()## method lets you list the HTTP methods (like ##"get"## or ##"post"##) your mapper should be limited to. null or empty matches any HTTP method.
238
239 * Its ##Pattern[] getRegexes()## method lets you provide zero, one or more regular expressions to match the requests' URLs. If you don't provide any regular expression, any path will be matched.
240
241 {{info}}
242 Paths don't include the configured prefix and don't start with a slash.
243 {{/info}}
244
245 ##DefaultURLMappingSpecification## additionally provides a convenient way to initialize a specification.

Get Connected