What is Sling Models

Sling Models is a light-weight dependency injection framework integrated with the Sling Adapter concept. It fills the blind spot of OSGi to support context-sensitive dependency injection (which OSGi doesn't support).

Context examples: Request, Resource, Page, ValueMap.

It can be used to build "domain objects" with automatic injection of data properties. Furthermore Sling Models supports a component architecture style based on Sling Models and HTL (Sightly).

Simple Example — Resource Data in POJO

// Resource properties: name = "T-Shirt", price = 19.50, available = true

@Model(adaptables = Resource.class)
public class MatrixProduct {

    @ValueMapValue
    private String name;

    @ValueMapValue
    private double price;

    @ValueMapValue
    private boolean available;
}

// Usage:
MatrixProduct item = resource.adaptTo(MatrixProduct.class);

Simple Example — Interface instead of POJO

// Same resource properties, but modeled as an interface

@Model(adaptables = Resource.class)
public interface MatrixProduct {

    @ValueMapValue
    String getName();

    @ValueMapValue
    double getPrice();

    @ValueMapValue
    boolean isAvailable();
}

// Usage:
MatrixProduct item = resource.adaptTo(MatrixProduct.class);

Complex Example — Business Class

@Model(adaptables = { SlingHttpServletRequest.class, Resource.class },
       adapters = MatrixNavigator.class)
public final class MatrixNavigatorImpl implements MatrixNavigator {

    @Self
    private OracleConfig config;

    @SlingObject
    private ResourceResolver resolver;

    @OSGiService
    private SlingSettingsService slingSettings;

    // optional injections (only available inside a request)
    @SlingObject(injectionStrategy = InjectionStrategy.OPTIONAL)
    private SlingHttpServletRequest request;

    // ...
}

// Usage:
MatrixNavigator navigator = request.adaptTo(MatrixNavigator.class);

@PostConstruct Method

@Model(adaptables = Resource.class)
public class OracleService {

    @ValueMapValue
    private String name;

    @ValueMapValue(name = "jcr:title",
        injectionStrategy = InjectionStrategy.OPTIONAL)
    private String title;

    @OSGiService
    private SlingSettingsService settings;

    // Called after creation and when all references are injected
    // Similar to OSGi DS @Activate method
    @PostConstruct
    private void activate() {
        // initialization logic
    }
}

Constructor Injection

@Model(adaptables = Resource.class)
public class SentinelService {

    // Constructor must be annotated with @Inject
    // Specifying a name is mandatory —
    // JDK cannot read constructor parameter names via reflection
    @Inject
    public SentinelService(
            @ValueMapValue(name = "name") String name,
            @ValueMapValue(name = "jcr:title",
                injectionStrategy = InjectionStrategy.OPTIONAL) String title,
            @OSGiService SlingSettingsService settings) {
        // initialization logic
    }
}

Modular Architecture

Sling Models has a very flexible and extensible architecture. By default it supports a set of "injectors" for the common use cases in context of Sling. Via an SPI it is possible to add custom injectors and support custom annotations from other OSGi bundles.

Preparing Your Application

To activate Sling Models for your bundle, set a bundle header to register packages containing model classes:

<Sling-Model-Packages>
  org.example.matrix.components
</Sling-Model-Packages>

Registers all classes annotated with @Model in this package and all sub-packages.

Since Sling Models Impl 1.3.4 this is no longer required when the appropriate bnd plugin is used (configured by default in the aem-global-parent POM).

Injectors (Order of Precedence)

  1. @ScriptVariable — Script Bindings injector
  2. @ValueMapValue — ValueMap injector
  3. @ResourcePath — Resource Path injector
  4. @ChildResource — Child Resources injector
  5. @RequestAttribute — Request Attributes injector
  6. @OSGiService — OSGi Service injector
  7. @SlingObject — Sling Object injector
  8. @Self — Self injector

Script Bindings Injector (@ScriptVariable)

// Context object is a request
// Property name is the variable name (or name specified via annotation)
// Should be rarely used.

@ScriptVariable
private Object myVariable;

ValueMap Injector (@ValueMapValue)

// Context object is a resource, or a value map
// Property name is the variable name (or name specified via annotation)

@ValueMapValue
private String name;

@ValueMapValue(name = "jcr:title")
private String title;

Resource Path Injector (@ResourcePath)

// Context object is a resource or request
// Injects one or multiple resources
// Paths given by @Path annotations, path/paths element,
// or by resource property referenced by name

// injects resource by path defined in resource property 'item1'
@ResourcePath
private Resource item1;

Child Resources Injector (@ChildResource)

// Context object is a resource
// Child resource name is the variable name (or name specified via annotation)

// injects a child resource
@ChildResource
private Resource subpath;

// injects a child resource and adapts it to a Sling Model
@ChildResource
private MatrixProduct subpath;

// injects a list of child resources being children of this path
@ChildResource
private List<Resource> subpath;

// injects a list of child resources and adapts each to a Sling Model
@ChildResource
private List<MatrixProduct> subpath;

Request Attributes Injector (@RequestAttribute)

// Context object is request
// Attribute name is the variable name (or name specified via annotation)
// Also used for passing properties from HTL use statement to Sling Model

@RequestAttribute
private String name;

OSGi Service Injector (@OSGiService)

// Any context object supported (because it's not used at all)
// Optionally a service filter expression can be defined

// injects an OSGi service that implements the given interface
@OSGiService
private SentinelService sentinelService;

// injects a list of OSGi services implementing the given interface
@OSGiService
private List<SentinelService> sentinelServices;

Sling Object Injector (@SlingObject)

// Context object is a resource, resource resolver or request
// Supported: ResourceResolver, Resource,
//   SlingHttpServletRequest, SlingHttpServletResponse, SlingScriptHelper

@SlingObject
private ResourceResolver resourceResolver;

@SlingObject
private Resource resource;

Self Injector (@Self)

// Injects an object adapted from the context object,
// or the adaptable itself.
// Useful to build "chains" of business classes adapting from request/resource.

@Model(adaptables = Resource.class)
public class MatrixController {

    // the adaptable itself
    @Self
    private Resource resource;

    // a Sling Model adapted from the Resource
    @Self
    private MatrixProduct product;

    // any object adapted from the Resource
    @Self
    private ValueMap properties;
}

Alternate Adapter Interfaces

Model implementations can be adapted to interfaces they implement, instead of their implementation class. Similar to OSGi services registering to a service interface.

  • Separates interface from implementation, making mocking in unit tests easier
  • If multiple models implement the same interface, "ImplementationPicker" services can choose the right one

Alternate Adapter Interfaces — Example

@Model(adaptables = SlingHttpServletRequest.class,
       adapters = MatrixNavigator.class)
public class MatrixNavigatorImpl implements MatrixNavigator {

    @SlingObject
    private ResourceResolver resolver;

    @Override
    public List<MatrixProduct> listProducts() {
        // business logic
    }
}

// Usage — adapts to the interface, not the implementation:
MatrixNavigator navigator = request.adaptTo(MatrixNavigator.class);

Sling Models Best Practices

  • Don't create a Sling Model if you only want to access the ValueMap — you can do this directly in HTL without a model
  • Don't use the generic @Inject on variables — use injector-specific annotations like @ValueMapValue, @RequestAttribute, @SlingObject etc. — more performant and more obvious what is injected
  • Don't confuse OSGi/SCR @Reference annotations with Sling Model annotations — although both inject dependencies they cannot be used interchangeably. @Reference can only be used in OSGi component classes; Sling annotations only in Sling Model classes.
  • Do not extend a Sling Model class from another Sling Model class. You can create a common abstract class to let two Sling Models extend from.
  • Create a Sling Model per concern, not per Sling component — Sling components can (re-)use multiple controllers.