AEM: Inject child adapting from SlingHttpServletRequest

2020-09-04

Let's create a simple component that lists the names of users, the data would something like this in the crx:

It can be rendered in the following way:

1
2
3
4
5
6
7
8
<p>@ChildResourceFromRequest:</p>
<ul data-sly-list="${model.acsCommonsUsers}">
    <li>${item.name}</li>
</ul>
<p>ModelFactory:</p>
<ul data-sly-list="${model.modelFactoryUsers}">
    <li>${item.name}</li>
</ul>

The sling model for the User looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class User {
    @Self
    private SlingHttpServletRequest request;

    @SlingObject
    private Resource resource;

    @Inject
    private Page currentPage;

    @ValueMapValue
    private String name;

    public String getName() {
        return name;
    }
}

As you can see it's adapting from SlingHttpServletRequest.class, let's imagine that for some reason this model needs the request and maybe other data that's only available when adapting from a request. Normally when your class adapts from a resource you could easily Inject the user model in the component sling model, for example:

1
2
@Inject
private List<Users> users;

But we need the actual request in our User model so this approach cannot be used. So what can we do?

@ChildResourceFromRequest

The annotation can be found in the ACS AEM Commons package and provides exactly what we need.

This injector is similar to the standard @ChildResource injector provided by sling, but with a key difference in that it uses a mock request object pointed to the resource path as the adaptable, allowing the sling model to reference the request and other sling bindings not otherwise accessible when adapting a resource directly. This is particularly useful when injecting instances of WCM Core components, which are generally not adaptable from Resource and thus fail to inject via the standard @ChildResource injector.

- ACS AEM Commons

1
2
3
4
5
6
7
8
private static final String NODE_USERS = "users";

@ChildResourceFromRequest(name = NODE_USERS)
private List<User> acsCommonsUsers;

public List<User> getAcsCommonsUsers() {
    return acsCommonsUsers;
}

ModelFactory

If you don't want a dependency on ACS commons you can Apache Sling ModelFactory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private static final String NODE_USERS = "users";

@Inject
private ModelFactory modelFactory;

@Self
private SlingHttpServletRequest request;

@SlingObject
private Resource resource;

private List<User> modelFactoryUsers;

@PostConstruct
private void init() {
    modelFactoryUsers = new ArrayList<>();
    Resource users = resource.getChild(NODE_USERS);
    if (users != null) {
        users.getChildren().forEach(resource -> {
            User user = modelFactory.getModelFromWrappedRequest(request, resource, User.class);
            if (user != null) {
                modelFactoryUsers.add(user);
            }
        });
    }
}

public List<User> getModelFactoryUsers() {
    return modelFactoryUsers;
}

If you have any questions, do not hesitate to contact me or leave a comment below.

Created by Jeroen Druwé