in src/main/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImpl.java [86:187]
public Collection<String> getAllMappings(String resourcePath, HttpServletRequest request) {
resolver.checkClosed();
// A note on the usage of the 'mappings' variable and the order of the results
//
// The API contract of the ResourceMapper does not specify the order in which the elements are returned
// As an implementation detail however the getMapping method picks the first element of the return value
// as the 'winner'.
//
// Therefore we take care to add the entries in a very particular order, which preserves backwards
// compatibility with the existing implementation. Previously the order was
//
// resource path → aliases → mapping (with aliases potentially being empty)
//
// To ensure we keep the same behaviour but expose all possible mappings, we now have the following
// flow
//
// resource path → mapping
// resource path → aliases
// aliases → mappings
//
// After all are processed we reverse the order to preserve the logic of the old ResourceResolver.map() method (last
// found wins) and also make sure that no duplicates are added.
//
// There is some room for improvement here by using a data structure that does not need reversing ( ArrayList
// .add moves the elements every time ) or reversal of duplicates but since we will have a small number of
// entries ( <= 4 in case of single aliases) the time spent here should be negligible.
List<String> mappings = new ArrayList<>();
// 1. parse parameters
// find a fragment or query
int fragmentQueryMark = resourcePath.indexOf('#');
if (fragmentQueryMark < 0) {
fragmentQueryMark = resourcePath.indexOf('?');
}
// cut fragment or query off the resource path
String mappedPath;
final String fragmentQuery;
if (fragmentQueryMark >= 0) {
fragmentQuery = resourcePath.substring(fragmentQueryMark);
mappedPath = resourcePath.substring(0, fragmentQueryMark);
logger.debug("map: Splitting resource path '{}' into '{}' and '{}'", resourcePath, mappedPath,
fragmentQuery);
} else {
fragmentQuery = null;
mappedPath = resourcePath;
}
final RequestContext requestContext = new RequestContext(request, resourcePath);
ParsedParameters parsed = new ParsedParameters(mappedPath);
// 2. load mappings from the resource path
populateMappingsFromMapEntries(mappings, Collections.singletonList(mappedPath), requestContext);
// 3. load aliases
final Resource nonDecoratedResource = resolver.resolveInternal(parsed.getRawPath(), parsed.getParameters());
if (nonDecoratedResource != null) {
List<String> aliases = loadAliasesIfApplicable(nonDecoratedResource);
// ensure that the first declared alias will be returned first
Collections.reverse(aliases);
// 4. load mappings for alias
mappings.addAll(aliases);
populateMappingsFromMapEntries(mappings, aliases, requestContext);
}
// 5. add the requested path itself, if not already populated
if ( !mappedPath.isEmpty() && !mappings.contains(mappedPath))
mappings.add(0, mappedPath);
// 6. add vanity paths
List<String> vanityPaths = mapEntries.getVanityPathMappings().getOrDefault(mappedPath, Collections.emptyList());
// vanity paths are prepended to make sure they get returned last
mappings.addAll(0, vanityPaths);
// 7. final effort to make sure we have at least one mapped path
if ( mappings.isEmpty() ) {
mappings.add(nonDecoratedResource != null ? nonDecoratedResource.getPath() : "/");
}
// 8. apply context path if needed
mappings.replaceAll(new ApplyContextPath(request));
// 9. set back the fragment query if needed
if ( fragmentQuery != null ) {
mappings.replaceAll(path -> path.concat(fragmentQuery));
}
if (logger.isDebugEnabled()) {
mappings.forEach(path ->
logger.debug("map: Returning URL {} as mapping for path {}", path, resourcePath)
);
}
Collections.reverse(mappings);
return new LinkedHashSet<>(mappings);
}