URL Mapping
![]() | URL Mapping is a framework to handle requests from configured prefixes. |
Type | JAR |
Category | Other |
Developed by | Unknown |
Rating | |
License | GNU Lesser General Public License 2.1 |
Table of contents
Description
URL Mapping is a framework to handle requests from configured prefixes. The initial motivation behind this extension is that some people migrate to XWiki from another wiki solution (for example Confluence, for which we have a extension) or from a CMS and want their old links to still work (because Cool URIs don’t change). Visitors could have mails linking to the old website, or could have bookmark.
The URL Mapping extension provides a convenient way for developers to implement such redirections, and provides a convenient way for administrators to configure such implementations.
For instance, figure a wiki freshly migrated from Confluence. Many visitors of this wiki still have Confluence links to documents in their history, bookmarks or mails, but after the migration, the documents have of course moved and are accessed through URLs shaped by XWiki instead of URL shaped by Confluence. The URL Mapping extension makes it possible to:
- for a developer to write a set of URL mappers which will recognize URLs that look like /display/SPACE/Page title, /spaces/SPACE/pages/NNN/Page title and download/attachments/NNN/filename.ext and redirect to the right document or attachment;
- for an administrator to configure a prefix where these URLs will be expected, so a reverse proxy can direct these old URLs to this prefix without risking to clash with XWiki URLs.
- for an administrator to configure a redirection screen that automatically redirects after a specified delay and that tells users to update their bookmarks… or, on the contrary, to let the redirects be transparent and immediate
Targets of this framework
It targets two groups of people:
- Administrators, who want to configure and provide redirections to their users. Usually, they will be installing a particular URL Mapping implementation and will find information here on how to configure this implementation.
- Extension developers, who want to provide such a particular implementation.
In this documentation, we will first see how to configure the URL Mapping extension (for administrators), and then how to provide an implementation (for developers).
Configuration
URL Mapping provides a configuration mechanism. We describe how it works in this section.
Each implementation has a unique name, which you should find in its documentation. In the reminder of this section, we will consider a fictive implementation named myhandler. Replace myhandler with the actual documented name.
Here is a comprehensive example of a snippet to put in xwiki.properties:
# Without a prefix, myhandler will not be active
urlmapping.prefixhandlers.myhandler.prefix=myprefix
# Display a redirection screen for the following number of seconds.
# Special values:
# 0 (the default) skips the redirection screen altogether
# -1 disables automatic redirections, users will have to click the target link themselves
urlmapping.prefixhandlers.myhandler.delay=0
# In the redirection screen, show this message. If left unset or empty, a generic, default message will be shown. This message will be HTML-escaped and displayed as is. There is no need to escape anything and there is no way to use any formatting.
urlmapping.prefixhandlers.myhandler.introMessage=Redirecting. Please update your bookmarks.
# If a URL is understood but the target is not found, the user will be shown the following error message. If left unset or empty, a generic, default error message will be shown. There is no need to escape anything and there is no way to use any formatting.
urlmapping.prefixhandlers.myhandler.notFoundIntroMessage=Sorry, we could not find what you are looking for.
# If set and non-empty, the following will be used as a title in the redirection screen instead of the default, generic one.
urlmapping.prefixhandlers.myhandler.title=We found your doc!
# If set and non-empty, the following will be used as a title in the redirection screen instead of the default, generic one.
# Note: it is not used in the not found screen.
urlmapping.prefixhandlers.myhandler.title=We found your doc!
# The template to use for the redirect screen. If empty, the one provided with the URL Mapping extension will be used.
urlmapping.prefixhandlers.myhandler.redirectScreenTemplate=mycustomredirectscreen.vm
# The template to use for the screen displayed when a target is not found. If empty, the one provided with the URL Mapping extension will be used.
urlmapping.prefixhandlers.myhandler.notFoundScreenTemplate=
# The redirect HTTP status to use. By default, a 302 temporary redirect status will be used.
urlmapping.prefixhandlers.myhandler.redirectHttpStatus=307
In addition to the handler-specific configuration, it is possible to provide a default configuration which will apply to all handlers. It is possible to use any key that is documented earlier under urlmapping.default (except prefix: no default prefix can be configured as each handler needs its own, unique prefix).
In addition to the handler-specific configuration and the default configuration, each implementation (handler) can suggest or force configuration values. When fetching the configuration, the URL Mapping will look for a value in this order:
- any configuration forced by the implementation (likely unusual);
- the handler-specific configuration;
- the default configuration;
- the configuration suggested by the implementation.
This lets developers provide good defaults, administrator to have a fine-grained control over the configuration and developers to force values when anything else would not make sense.
Implementing a URL Mapping handler
Quick start with examples
- 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:
- 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
- implement a convert method that returns a resource reference to the target of the link.
- optionally implement a getSuggestions method that returns suggestions in case convert returns null.
- Write a prefix handler: this is a named component that extends AbstractURLMappingPrefixHandler. The name must be unique and will be used for configuration management.
For the prefix handler:
- 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.
- 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.
Here are two simplified examples of mappers (where error handling has been cut down):
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.contrib.urlmapping.AbstractURLMapper;
import org.xwiki.contrib.urlmapping.DefaultURLMappingMatch;
import org.xwiki.model.reference.AttachmentReference;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.resource.ResourceReference;
import org.xwiki.resource.entity.EntityResourceAction;
import org.xwiki.resource.entity.EntityResourceReference;
@Component (roles = ConfluenceAttachmentURLMapper.class)
@Singleton
public class ConfluenceAttachmentURLMapper extends AbstractURLMapper
{
@Inject
private ConfluencePageIdResolver confluenceIdResolver;
public ConfluenceAttachmentURLMapper()
{
super("download/attachments/(?<pageId>\\d+)/(?<filename>[^?#]+)(?<params>\\?.*)?$");
}
@Override
public ResourceReference convert(DefaultURLMappingMatch match)
{
try {
Matcher matcher = match.getMatcher();
String pageIdStr = matcher.group("pageId");
String filename = URLDecoder.decode(matcher.group("filename"), StandardCharsets.UTF_8);
long pageId = Long.parseLong(pageIdStr);
EntityReference docRef = confluenceIdResolver.getDocumentById(pageId);
AttachmentReference attachmentRef = new AttachmentReference(filename, new DocumentReference(docRef));
return new EntityResourceReference(attachmentRef, EntityResourceAction.VIEW);
} finally {
return null;
}
}
}
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.contrib.urlmapping.AbstractURLMapper;
import org.xwiki.contrib.urlmapping.DefaultURLMappingMatch;
import org.xwiki.contrib.urlmapping.suggestions.URLMappingSuggestionUtils;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.LocalDocumentReference;
import org.xwiki.rendering.block.Block;
import org.xwiki.resource.ResourceReference;
import org.xwiki.resource.entity.EntityResourceAction;
import org.xwiki.resource.entity.EntityResourceReference;
import org.xwiki.contrib.confluence.resolvers.ConfluencePageTitleResolver;
@Component (roles = ConfluenceDisplayURLMapper.class)
@Singleton
public class ConfluenceDisplayURLMapper extends AbstractURLMapper
{
@Inject
private ConfluencePageTitleResolver confluencePageTitleResolver;
@Inject
private URLMappingSuggestionUtils suggestionUtils;
public ConfluenceDisplayURLMapper()
{
super("display/(?<spaceKey>[^/]+)/(?<pageTitle>[^?/#]+)(?<params>\\?.*)?$");
}
@Override
public ResourceReference convert(DefaultURLMappingMatch match)
{
try {
Matcher matcher = match.getMatcher();
String spaceKey = matcher.group("spaceKey");
String pageTitle = URLDecoder.decode(matcher.group("pageTitle"), StandardCharsets.UTF_8);
EntityReference docRef = confluencePageTitleResolver.getDocumentByTitle(spaceKey, pageTitle);
return new EntityResourceReference(docRef, EntityResourceAction.VIEW);
} finally {
return null;
}
}
@Override
protected Block getSuggestions(DefaultURLMappingMatch match)
{
Matcher matcher = match.getMatcher();
String spaceKey = matcher.group("spaceKey");
String pageTitle = URLDecoder.decode(matcher.group("pageTitle"), StandardCharsets.UTF_8);
return suggestionUtils.getSuggestionsFromDocumentReference(new LocalDocumentReference(spaceKey, pageTitle));
}
}
This second mapper uses URLMappingSuggestionUtils from the url-mapping-suggestions module to return suggestions from a fictive entity reference.
Here is an example of a prefix handler:
import javax.inject.Named;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.contrib.urlmapping.DefaultURLMappingConfiguration;
import org.xwiki.contrib.urlmapping.URLMapper;
import org.xwiki.contrib.urlmapping.AbstractURLMappingPrefixHandler;
import static org.xwiki.contrib.urlmapping.DefaultURLMappingConfiguration.Key;
@Component
@Singleton
@Named("confluence")
public class ConfluenceURLMappingPrefixHandler extends AbstractURLMappingPrefixHandler
{
@Inject
private ConfluenceAttachmentURLMapper confluenceAttachmentURLMapper;
@Inject
private ConfluenceDisplayURLMapper confluenceDisplayURLMapper;
@Inject
private ConfluenceViewPageURLMapper confluenceViewPageURLMapper;
@Override
protected URLMapper[] getMappers()
{
return new URLMapper[] {
confluenceAttachmentURLMapper,
confluenceDisplayURLMapper,
confluenceViewPageURLMapper
};
}
@Override
protected void initializeConfigurationDefaults(DefaultURLMappingConfiguration configuration)
{
// Of course, use a localization manager, don't hardcode messages like this.
configuration.setDefault(Key.INTRO_MESSAGE, "Hello! It seems you used an old Confluence link. Update your bookmark :-)");
configuration.setDefault(Key.NOT_FOUND_INTRO_MESSAGE, "Sorry, we could not find the document corresponding to this old Confluence link");
}
}
The full, working examples can be found in the confluence-url-mapping module of the confluence contrib package at https://github.com/xwiki-contrib/confluence/tree/master/confluence-url-mapping/src/main/java/org/xwiki/contrib/confluence/urlmapping/internal.
Documentation
The URL Mapping framework is centered around the following interfaces.
URLMappingPrefixHandler
This represents a prefix handler that handles all the URLs under a given prefix.
- its String getPrefix() method provides the prefix it handles
- 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.
URLMappingResult
This represents the result of a conversion:
- 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.
- its Block getSuggestions() method optionally provides some suggestion to show in case the conversion failed.
- 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).
- its URLMappingConfiguration getConfiguration() method returns the configuration to use for the redirection.
URLMappingConfiguration
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.
AbstractURLMappingPrefixHandler
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.
It provides:
- automatic configuration and prefix management;
- a conversion mechanism centered around the concept of URL mappers.
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.
URLMapper and AbstractURLMapper
URL mappers are available to handlers extending AbstractURLMappingPrefixHandler.
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.
Here's how things work:
- 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.
- When a request is to be converted by the handler, it gets matched with its mappers' specifications, in order.
- As soon as a specification matches, the result of this match (including the captured groups of regular expressions in a Matcher object, which also includes named groups) is passed to the corresponding mapper using its convert(URLMappingMatch) method
- 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.
Mappers can also provide a getSuggestions() method that will be called if the convert method return null, to provide suggestions to the user.
Tips:
- 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.
- 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.
URLMappingSpecification
- 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.
- 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.
DefaultURLMappingSpecification additionally provides a convenient way to initialize a specification.
Prerequisites & Installation Instructions
We recommend using the Extension Manager to install this extension (Make sure that the text "Installable with the Extension Manager" is displayed at the top right location on this page to know if this extension can be installed with the Extension Manager).
You can also use the manual method which involves dropping the JAR file and all its dependencies into the WEB-INF/lib folder and restarting XWiki.
Release Notes
v0.0.5
v0.0.4
v0.0.3
v0.0.2
v0.0.1
Initial release