December 28, 2020
Estimated Post Reading Time ~

AEM Sling Models Injectors Service Ranking

When working on an AEM project, Sling Models provides several custom Sling Models injectors to aid injection of Sling objects, Sling object values, OSGI services, etc…

While using the injectors within in Sling Models, how do injectors invoke in order? Injectors are invoked in order, of their service ranking, from lowest to highest. If you are writing a custom injector, it is good practice to include service ranking.

Examples of the common injectors and their service ranking from the Apache Sling Models available injectors, injector-specific annotations, list (since version 1.1.0):
  • @ScriptVariable, 1000
  • @ValueMapValue, 2000
  • @ChildResource, 3000
  • @RequestAttribute, 4000
  • @ResourcePath, 2500
  • @OSGiService, 5000
  • @Self, 2147483647 (Integer.MAX_VALUE)
  • @SlingObject, 2147483647 (Integer.MAX_VALUE)
Example of the @OSGIService, injector:
An example below illustrates the @OSGIService, injector specific annotation, which here we understand that the Service Ranking is set to 5000.

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information...
*/
package org.apache.sling.models.impl.injectors;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.models.annotations.Filter;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.spi.AcceptsNullName;
import org.apache.sling.models.spi.DisposalCallback;
import org.apache.sling.models.spi.DisposalCallbackRegistry;
import org.apache.sling.models.spi.Injector;
import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor2;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor2;
import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(property=Constants.SERVICE_RANKING+":Integer=5000", service={Injector.class, StaticInjectAnnotationProcessorFactory.class, AcceptsNullName.class})
public class OSGiServiceInjector implements Injector, StaticInjectAnnotationProcessorFactory, AcceptsNullName {

private static final Logger log = LoggerFactory.getLogger(OSGiServiceInjector.class);

private BundleContext bundleContext;

@Override
public @NotNull String getName() {
return "osgi-services";
}

@Activate
public void activate(BundleContext ctx) {
this.bundleContext = ctx;
}

@Override
public Object getValue(@NotNull Object adaptable, String name, @NotNull Type type, @NotNull AnnotatedElement element,
@NotNull DisposalCallbackRegistry callbackRegistry) {
return getValue(adaptable, name, type, element, callbackRegistry, bundleContext);
}

/**
*
* @param adaptable
* @param name
* @param type
* @param element
* @param callbackRegistry
* @param modelContext
* @return
*/
public Object getValue(@NotNull Object adaptable, String name, @NotNull Type type, @NotNull AnnotatedElement element,
@NotNull DisposalCallbackRegistry callbackRegistry, @Nullable BundleContext modelContext) {
OSGiService annotation = element.getAnnotation(OSGiService.class);
String filterString = null;
if (annotation != null) {
if (StringUtils.isNotBlank(annotation.filter())) {
filterString = annotation.filter();
}
} else {
Filter filter = element.getAnnotation(Filter.class);
if (filter != null) {
filterString = filter.value();
}
}
return getValue(adaptable, type, filterString, callbackRegistry, modelContext == null ? bundleContext : modelContext);
}

private <T> Object getService(Object adaptable, Class<T> type, String filter,
DisposalCallbackRegistry callbackRegistry, BundleContext modelContext) {
// cannot use SlingScriptHelper since it does not support ordering by service ranking due to https://issues.apache.org/jira/browse/SLING-5665
try {
ServiceReference<?>[] refs = modelContext.getServiceReferences(type.getName(), filter);
if (refs == null || refs.length == 0) {
return null;
} else {
// sort by service ranking (lowest first) (see ServiceReference.compareTo)
List<ServiceReference<?>> references = Arrays.asList(refs);
Collections.sort(references);
callbackRegistry.addDisposalCallback(new Callback(refs, modelContext));
return modelContext.getService(references.get(references.size() - 1));
}
} catch (InvalidSyntaxException e) {
log.error("invalid filter expression", e);
return null;
}
}

private <T> Object[] getServices(Object adaptable, Class<T> type, String filter,
DisposalCallbackRegistry callbackRegistry, BundleContext modelContext) {
// cannot use SlingScriptHelper since it does not support ordering by service ranking due to https://issues.apache.org/jira/browse/SLING-5665
try {
ServiceReference<?>[] refs = modelContext.getServiceReferences(type.getName(), filter);
if (refs == null || refs.length == 0) {
return null;
} else {
// sort by service ranking (lowest first) (see ServiceReference.compareTo)
List<ServiceReference<?>> references = Arrays.asList(refs);
Collections.sort(references);
// make highest service ranking being returned first
Collections.reverse(references);
callbackRegistry.addDisposalCallback(new Callback(refs, modelContext));
List<Object> services = new ArrayList<>();
for (ServiceReference<?> ref : references) {
Object service = modelContext.getService(ref);
if (service != null) {
services.add(service);
}
}
return services.toArray();
}
} catch (InvalidSyntaxException e) {
log.error("invalid filter expression", e);
return null;
}
}

private Object getValue(Object adaptable, Type type, String filterString, DisposalCallbackRegistry callbackRegistry,
BundleContext modelContext) {
if (type instanceof Class) {
Class<?> injectedClass = (Class<?>) type;
if (injectedClass.isArray()) {
Object[] services = getServices(adaptable, injectedClass.getComponentType(), filterString,
callbackRegistry, modelContext);
if (services == null) {
return null;
}
Object arr = Array.newInstance(injectedClass.getComponentType(), services.length);
for (int i = 0; i < services.length; i++) {
Array.set(arr, i, services[i]);
}
return arr;
} else {
return getService(adaptable, injectedClass, filterString, callbackRegistry, modelContext);
}
} else if (type instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType) type;
if (ptype.getActualTypeArguments().length != 1) {
return null;
}
Class<?> collectionType = (Class<?>) ptype.getRawType();
if (!(collectionType.equals(Collection.class) || collectionType.equals(List.class))) {
return null;
}

Class<?> serviceType = (Class<?>) ptype.getActualTypeArguments()[0];
Object[] services = getServices(adaptable, serviceType, filterString, callbackRegistry, modelContext);
if (services == null) {
return null;
}
return Arrays.asList(services);
} else {
log.warn("Cannot handle type {}", type);
return null;
}
}

private static class Callback implements DisposalCallback {
private final ServiceReference<?>[] refs;
private final BundleContext context;

public Callback(ServiceReference<?>[] refs, BundleContext context) {
this.refs = refs;
this.context = context;
}

@Override
public void onDisposed() {
if (refs != null) {
for (ServiceReference<?> ref : refs) {
context.ungetService(ref);
}
}
}
}

@Override
public InjectAnnotationProcessor2 createAnnotationProcessor(AnnotatedElement element) {
// check if the element has the expected annotation
OSGiService annotation = element.getAnnotation(OSGiService.class);
if (annotation != null) {
return new OSGiServiceAnnotationProcessor(annotation);
}
return null;
}

private static class OSGiServiceAnnotationProcessor extends AbstractInjectAnnotationProcessor2 {

private final OSGiService annotation;

public OSGiServiceAnnotationProcessor(OSGiService annotation) {
this.annotation = annotation;
}

@Override
public InjectionStrategy getInjectionStrategy() {
return annotation.injectionStrategy();
}

@Override
@SuppressWarnings("deprecation")
public Boolean isOptional() {
return annotation.optional();
}
}
}


NOTE:
  • The source code can be found from the Github repository, Apache Sling Models Implementation, here.
  • The official documentation for the Sling Models available injector’s service ranking status can be found here.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.