in src/main/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImpl.java [107:203]
private Collection<String> getAllMappingsInternal(final String resourcePath, final RequestContext requestContext) {
// 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;
}
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(requestContext));
// 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);
}