Sling selectors best practices header

Sling: More About Selector Best Practices

It’s written quite a lot about Sling selectors, best practices and how we should use them. However, misuse is as common as many articles about selectors. So I would like to add my 5 cents to this topic.

How to (not) use Sling Selectors

Let’s imagine a situation when you have a component which generates some reports. And it’s requested from some other part of the site via Ajax. There can be 2 representation of them – full and short. We know that it’s a good approach to map our servlet, which will generate a report, to a resource type. Also, you know about selectors, so you decide to map it to “/apps/mysite/mycomponent” as a resource type and selectors “fullreport” and “shortreport”. It may look like this:

@Service
@Component(metatype = false)
@Properties(value = {
        @Property(name = "sling.servlet.resourceTypes", value = "apps/mysite/mycomponent"),
        @Property(name = "sling.servlet.selectors", value = { "fullreport", "shortreport" }),
        @Property(name = "sling.servlet.extensions", value = { "html" }),
        @Property(name = "sling.servlet.methods", value = { "GET" }) 
    })
public class ReportServlet extends SlingSafeMethodsServlet {

    public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
      throws ServletException, IOException {
        //do smth
    }
}

Now, whenever you call /content/mysite/mypage/jcr:content/mycomponent.(fullreport|shortreport).html our new servlet’s method doGet is called. We still need to print different format of the report on each of selectors. We may decide to do this with simple switch-case in our doGet method:

final String selectorString = request.getRequestPathInfo().getSelectorString();

switch (selectorString) {
    case "fullreport":
        printFullReport();
        break;
    case "shortreport":
        printShortReport();
        break;
}

This code will perfectly work. At least for some time.

One day, in our imagined world, requirement changes and now we need to add one more report representation – “printablereport”. What should we do? It’s easy – we add one more case parameter, like this (+ adding selector to the servlet’s annotations):

final String selectorString = request.getRequestPathInfo().getSelectorString();

switch (selectorString) {
    case "fullreport":
        printFullReport();
        break;
    case "shortreport":
        printShortReport();
        break;
    case "printablereport":
        printPrintableReport();
        break;
}

Now we are asked to provide the ability to generate the report with either vertical, either horizontal histograms. And we would like to have this as a selector as well (why not? we can cache our reports on a dispatcher and it complies with a rule to use selectors for defining a format and not a content). But now it’s gonna be tricky, because if we use switch-case, then we would have 6 cases now. Or we can go with if statement and String.contains. But all these approaches look messy – if/switch-cases constructions getting bigger, readability decreases, chances to fail somewhere – increase.

Our code violates the open-closed principle. To extend functionality with new report type, with our approach, we need to make quite a couple of modifications in our servlet. Also, such approach does not tend to be robust.

When reality strikes

Here is an example from my experience.

We had a form, which was used to generate such reports. We had just 2 selectors + switch-case in servlet. Ajax communication between page and backend was quite sophisticated and included different pre- and postprocessors in Javascript + JQuery code. One of such preprocessors was checking URLs before making Ajax requests to the backend. It was a globally used one. One day, one team needed to add extra selector (e.g. “selector1”) in all Ajax requests in popups. However, they missed to add a check if this preprocessor is used in a popup, so it was adding the selector to all the URLs.

As a result, our form was not able to provide reports anymore. Yes, we got one more extra selector, but we still had the one, which servlet was mapped to and our component still had to work properly. BUT, we had that switch-case. So breaking several best practices led to us having a headache while trying to understand why form does not work anymore. So a human mistake in another team and fragile code in our servlet resulted in lost time.

So, how could we make our code more robust? It’s simple:

  1. Never ever extract selectors in your servlets. You just not need it, remember this! Sling provides enough functionality to map code to specific selectors without a need to check them directly.
  2. As a result, in our case we just needed 2 servlets each of them mapped to same resource type but to different selectors:
@Service
@Component(metatype = false)
@Properties(value = { 
        @Property(name = "sling.servlet.resourceTypes", value = "apps/mysite/mycomponent"),
        @Property(name = "sling.servlet.selectors", value = "fullreport"),
        @Property(name = "sling.servlet.extensions", value = { "html" }), 
        @Property(name = "sling.servlet.methods", value = { "GET" }) 
    })
public class FullReportServlet extends SlingSafeMethodsServlet {

  public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
    throws ServletException, IOException {
      //do full report
  }
}
@Service
@Component(metatype = false)
@Properties(value = { 
        @Property(name = "sling.servlet.resourceTypes", value = "apps/mysite/mycomponent"),
        @Property(name = "sling.servlet.selectors", value = "shortreport"),
        @Property(name = "sling.servlet.extensions", value = { "html" }),
        @Property(name = "sling.servlet.methods", value = { "GET" })
    })
public class ShortReportServlet extends SlingSafeMethodsServlet {

  public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
    throws ServletException, IOException {
      //do short report
  }
}

With the code above, our component would still work (taking into account that new selector added after our’s).

Conclusion

Don’t be afraid of too many servlets – it’s not the excuse for violating Sling best practices and bringing problems into your code.

2 thoughts on “Sling: More About Selector Best Practices

    1. Hi Shain. Didn’t really get your point. URLs for which resources? There is only one resource mentioned with URL path “/content/mysite/mypage/jcr:content/mycomponent.(fullreport|shortreport).html”. Or am I missing something?

Your thoughts are welcome