Skip to main content

Adobe

Sling Mappings++ Large Lists and Mapping Arrays

As all AEM Developers know, AEM, and more-so the underlying JCR, has limitations on how many children a single parent can have before we start to see performance issues.  First in the UI, and then more critically in the ability to look-up and manage content nodes themselves.  When designing for a net-new AEM website, this can be planned around, however, many times when dealing with a migration, there may be requirements to both keep existing links in-tact, which may put us in a situation where a large flat list, at least as exposed to the end-user, is unavoidable.

In this post, I will not go down the rabbit hole of whether 500, 1000, or 10000 children would start to have performance issues (although I do certainly have opinions here), and instead will focus on ways to allow for a single parent, to appear to have many more than the maximum number of children.

Enter Sling Mappings

Now, Sling mappings are not new for most, and commonly used for things like stripping the initial prefix (/content/<project>) away from outgoing URLs, but these mappings have additional untapped power which documentation, in my opinion, does not clearly emphasize.  Before we jump in, lets talk about a few key features of sling mappings as a whole.

Sling mappings are configured based on regular expressions, and often inherited by the node names themselves, within /etc/map.  Within each node, there are optional properties we can add to define how mappings should behave.  For example, if we are wanting to introduce a higher complexity regular expression, we cannot rely on the node name alone (characters required for regex mappings are restricted from being used as a node name).  This means that when we need to define a mapping which does NOT align with the name of the node, we can insert the following property (single value only):

sling:match <Regular Expression or Reverse Regular Expression>
sling:match content/wknd/us/en/magazine/(.*)

Now, the above is a relatively straight-forward example, but we’re here to learn about mappings, not regular expressions.  Do note, I am using a capture group here – multiple capture groups are certainly also supported, but for ease of this post, am keeping it quite simple.

Mappingonrootnode

The other item to note on the above is that I have added this node as a root node in the /etc/map/localhost tree.  As such, the mapping begins from the root element.  If we were to structure the /etc/map node differently, the start-point of that regular expression would change as well, for example:

Mappingonnonrootlevel

In the above scenario, we’d structure the sling:match accordingly, as the content and wknd nodes would have already been matched against:

sling:match us/en/magazine/(.*)

Well, that is half of the issue, is being able to capture the incoming request.  Next is to actually map it to a different internal resource.  This is where the “magic” comes in – on the same node, add an additional property, sling:internalRedirect, as follows:

sling:internalRedirect <path(s) to look for the matched content>
sling:internalRedirect /content/wknd/us/en/magazine/$1

In the above, we’re really just finding content in the same location it was requested.  The $1 in the internalRedirect is referencing the (.*) of the sling:match property, and saying that whatever was matched there, should first look at the location requested to find the resource, technically speaking, we have not really mapped anything at this point.   Now, what if the magazine section above is a long-standing blog, which has all urls published under the same /magazine root.  If organized 1:1 with the JCR structure, this would quickly cause reason for concern!  This is where the often overlooked ARRAY support of the internalRedirect comes to save the day.  From here, we can supply alternative locations to look for the same page.  For example, if we were to archive posts on a yearly basis, as in 2023 posts would live in the JCR structure /content/wknd/us/en/magazine/2023/<post>, while being publicly available still under /content/wknd/us/en/magazine/<post>, we could configure that using the “internalRedirect” array, as follows:

 

sling:internalRedirect /content/wknd/us/en/magazine/$1, /content/wknd/us/en/magazine/2023/$1

 

Now, when a request matches, it will first search under the magazine root folder, and if it does not find a resource there, will look to the 2023 folder, and so-on until all items of the Array have been searched against.  It does search from the top, down, so this means that order is important, especially if same-name nodes are expected.  If the entire list is exhausted and there is no resource found, then the normal 404 behavior is observed.

Now, lets assume we’ve got the following content (note the 2023 and 2024 folders added, with children):

Contentexample

With the forward mapping applied, we should be able to access the “san-diego-surf-2023” and “san-diego-surf-2024” under the /magazine folder directly.  Lets test it out!

Correctlymappedurl

Awesome.  We’re accessing the content without using the “2023” folder in the URL!

 

Great!  Well this solves the problem of accessing the content, however, if we look at a page which links to this content, we still are seeing an issue….

Incorrectlymappedlink

Looks like we still have one other piece of the puzzle to solve for.  One other important thing to know about regular expressions (with capture groups) is that when including in a sling mapping, they will only work in one direction.  Since we have configured it to capture on the request side (match) it will only handle the logic of handling the request.  In order to create a rewrite of outgoing URLs, we will need to also handle the reverse mapping.  For this, I use the suffix “-reverse” with the same node name as initial mapping for consistency.

 

So, next step is to copy the node we just created, and paste it as a sibling (under same parent).  We’ll then reverse the regular expression logic to have capture groups in the “sling:internalRedirect” property, and the references in the sling mapping property, as follows:

 

sling:match /content/wknd/us/en/magazine/$1
sling:internalRedirect /content/wknd/us/en/magazine/(.*),/content/wknd/us/en/magazine/2023/(.*)

 

Reversemappingonrootnode

 

This behaves in much the same way as the other mapping node, but because we have swapped the matching logic for the regular expression, it will now be applied in the url rewriting or output sling mapping as well.

 

Now, lets take a look back at our links to this content, and….

Correctlymappedlink

We’re golden!  Content is hosted under multiple JCR Nodes while being exposed under a single parent to the end-user.

 

I would like to add a disclaimer here: while this method is certainly a suitable work-around, a much more desirable implementation would have the JCR structure more closely matching the expected sitemap.

 

Hope this quick demonstration has been helpful!

 

 

Reference:

Thoughts on “Sling Mappings++ Large Lists and Mapping Arrays”

  1. Excellent writeup, Ryan. As someone who has spent countless hours debugging sling mappings, I thank you for pointing out this feature!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Ryan McCullough, Director, Adobe

Technical Director with over a decade of experience in the Adobe Digital Marketing and CMS platforms.

More from this Author

Follow Us