How exactly does it work - 4.4

Injectors repository and how to fetch injector

Once the injector is created in your activator, it must be stored somewhere, so that it can be reused to provide models for you components. Thanks to this, there will be no need to recreate injector for every subsequent request. The place where created and configured injectors live is called Injectors Repository.

Basically, it's a simple OSGi service (com.cognifide.slice.api.injector.InjectorsRepository) which stores injectors under given names and then allows to fetch them using those names. You can have as many injectors as you want however usually there is only one injector per application, even if an application consists of number of bundles.

<slice:lookup> allows you to specify which injector to choose (using its appName property). However, if you leave it blank, the tag will try to detect the application name. It's done by going up in the content tree and looking property called injectorName. If no injectorName property has been found, the appName is computed basing on the current resource path - it is part of path directly after apps. For /apps/testApp/... it will return testApp. Therefore, it is suggested to call the injector after the name of the application in the content structure. Additionally, the name of the app (if not defined) is being found only once per request and then cached (to avoid content traversing). If an application uses various app names at the same time, the appName attribute must be explicitly defined so that a proper app name is selected

Execution stack

Execution stack is something very internal in Slice - you will never ever see it in action. However, it's really worth knowing that Slice is based on it and what exactly you can achieve thanks to it.

Every call of ModelProvider#get(class, resource) within a single request causes that a resource (or path) passed as a parameter is pushed to the stack of current execution, then the guice injector is called, and after the object has been created the resource is popped from the stack. This resource is available for all other classes as a so called current resource and can also be injected to any object. In the below example the resource injected through constructor is just the current resource - the resource from the top of the execution stack. It should be noted that at the beginning of execution (most usually in <slice:lookup>), the resource from sling request (request.getResource() is passed to ModelProvider (exactly as it is described in here).

public class SimpleModel {
    
    private final String text;

    @Inject
    public SimpleModel(final Resource resource) {
        ValueMap valueMap = resource.adaptTo(ValueMap.class);
        text = valueMap.get("text");
    }
    ...
}

The whole execution stack idea would have not been useful for such simple cases. Its main advantages shows up when we consider more advanced scenario. Let's look at the extended model which tries to read our simple model from different locations in CRX.

public class AdvancedModel {
    
    private String text;
	
    @Inject
    public AdvancedModel(final ModelProvider modelProvider, final Resource resource) {
        ValueMap valueMap = resource.adaptTo(ValueMap.class);
        text = valueMap.get("advText");

        SimpleModel simpleModel = modelProvider.get(SimpleModel.class, "/content/app/some-page/jcr:content/par/simple");
        text += "Read: " + simpleModel.getText();
        
        text += " and some property from my resource " + valueMap.get("otherText");
    }
}

To understand what's exactly happening here let's go line by line through the constructor:

  • ModelProvider along with the current resource are injected. The resource is this from the request (see: <slice:lookup>). For this case, let's assume it's a resource from /content/app/my-adv-page/jcr:content/par/adv.
  • In the first two lines, the advText property from /content/app/my-adv-page/jcr:content/par/adv node is read - it's current resource.
  • In fourth line, the SimpleModel is read from /content/app/some-page/jcr:content/par/simple:
    • ModelProvider reads the resource from the provided path and push it to the stack
    • ModelProvider calls injector to create the SimpleModel.
    • Injector creates the SimpleModel and injects as a constructor parameter the current resource which is now the one from under /content/app/some-page/jcr:content/par/simple
    • ModelProvider pops the /content/app/some-page/jcr:content/par/simple resource from the stack and return the object.
  • In seventh line, otherText property from /content/app/my-adv-page/jcr:content/par/adv resource is read.

To sum it up, the execution stack allows you to write your models completely independently from other models, and still you can have them injected from whatever location you want. What's more - you don't have to think about it at all, because it is done behind the scenes by ModelProvider. This approach strongly improves loose coupling between your objects and makes them more testable.

In above examples we injected resource and then adapted it to ValueMap in order to read its properties. Normally, you don't want to do this - you should use @SliceResource annotated classes and properties values will be injected in fields. Read more about Mapper.