aemsling-models

How to adapt a child node in sling model of aem6


I am learning to use one of the new features of AEM6 - Sling Models. I have already fetched the properties of a node following the steps described here

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

  @Inject @Named("jcr:title")
  private String title;

  @Inject @Default(values = "xyz")
  private String firstName;

  @Inject @Default(values = "xyz")
  private String lastName;

  @Inject @Default(values = "xyz")
  private String city;

  @Inject @Default(values = "aem")
  private String technology;

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public String getTechnology() {
    return technology;
  }

  public String getTitle() {
    return title;
  }
}

and adapted it from a resource

UserInfo userInfo = resource.adaptTo(UserInfo.class);

Now i have the hierarchy as -

+ UserInfo (firstName, lastName, technology)
  |
  + UserAddress (houseNo, locality, city, state)

Now I want to fetch the properties of the UserAddress.

I had got some hints from the documentation page, such as -

If the injected object does not match the desired type and the object implements the Adaptable interface, Sling Models will try to adapt it. This provides the ability to create rich object graphs. For example:

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

  @Inject
  ImageModel getImage();
}

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

  @Inject
  String getPath();
}

When a resource is adapted to MyModel, a child resource named image is automatically adapted to an instance of ImageModel.

but I don't know how to implement it in my own classes. Please help me out with this.


Solution

  • It sounds like you need a separate class for the UserAddress to wrap the houseNo, city, state and locality properties.

    + UserInfo (firstName, lastName, technology)
      |
      + UserAddress (houseNo, locality, city, state)
    

    Just mirror the structure you outlined in your Sling Models.

    Create the UserAddress model:

    @Model(adaptables = Resource.class)
    public class UserAddress {
    
        @Inject
        private String houseNo;
    
        @Inject
        private String locality;
    
        @Inject
        private String city;
    
        @Inject
        private String state;
    
        //getters
    }
    

    This model can then be used in your UserInfo class:

    @Model(adaptables = Resource.class)
    public class UserInfo {
    
        /*
         * This assumes the hierarchy you described is 
         * mirrored in the content structure.
         * The resource you're adapting to UserInfo
         * is expected to have a child resource named
         * userAddress. The @Named annotation should
         * also work here if you need it for some reason.
         */
        @Inject
        @Optional
        private UserAddress userAddress;
    
        public UserAddress getUserAddress() {
            return this.userAddress;
        }
    
        //simple properties (Strings and built-in types) omitted for brevity
    }
    

    You can tweak the behaviour with additional annotations for default values and optional fields but this is the general idea.

    In general, Sling Models should be able to handle an injection of another model as long as it finds an appropriate adaptable. In this case, it's another Sling Model but I've done it with legacy classes based on adapter factories as well.