Using Apache Sling adaptTo API

Datetime:2016-08-22 23:33:54          Topic: JSP           Share

Apache Sling provides a way to adpat Sling related classes to our domain classes. The Resource and ResourceResolver interface provides the adaptTo method, which adapts the objects to other classes.

Examples of the usage of this API would be when we want to get the JCR Node instance from the current Resource object. The API documentation can be found here .

In this post we will change our contact list application a bit to use the adaptTo API.

Creating Our First Adapter

The first thing we are going to do is to create our model class, Contact . It basically will contain the attributes of our contact model. It will be a simple POJO:

public class Contact {

    private String name;
    private String email;
    private String phone;
    private String address;
    private String contactImage;

    //..getters and setters
}

Now in order to be able to adapt Resource objects to our Contact class we need to create an AdapterFactory . The adapter factories are registered as OSGI services. For that we need to implement the AdpaterFactory interface and the method getAdapter . The AdapterFactory have two key properties that should be defined:

  • adaptables : An array of the classes that can adapted to other classes defined in the adapters property
  • adapters : An array of the classes that can be adapted from the classes defined in the adaptables property

In our example we will create an AdpaterFactory that will make us able to adapt an instance of Resource class to our Contact class.

We can see it below:

@Component
@Service
public class ContactAdapterFactory implements AdapterFactory{
	
	@Property(name="adapters")
	public static final String[] ADAPTER_CLASSES = {
			Contact.class.getName()
	};
	
	@Property(name="adaptables")
    public static final String[] ADAPTABLE_CLASSES = {
            Resource.class.getName()
	};
	

	@Override
	@SuppressWarnings("unchecked")
	public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
		if(adaptable instanceof Resource &&
				type.equals(Contact.class)) {
			Contact contact = new Contact();
			Resource resource = (Resource)adaptable;
			ValueMap valueMap = ResourceUtil.getValueMap((Resource)adaptable);
			contact.setName(valueMap.get("name", String.class));
			contact.setEmail(valueMap.get("email", String.class));
			contact.setPhone(valueMap.get("phone", String.class));
			contact.setAddress(valueMap.get("address", String.class));
			
			Resource contactImage = resource.getChild("contactImage");
			
			if(contactImage != null) {
				contact.setContactImage(contactImage.getPath());
			}
			System.out.println(contact);
			return (AdapterType)contact;
		} else {
			return null;
		}
	}
	
}

Let’s explain it a bit:

We can see the adapters and adaptables properties being set below:

@Property(name="adapters")
public static final String[] ADAPTER_CLASSES = {
        Contact.class.getName()
};

@Property(name="adaptables")
public static final String[] ADAPTABLE_CLASSES = {
        Resource.class.getName()
};

As we could see we’ve set the adaptables to Resource class and the adapters to Contact class.

Then the next step is the getAdapter method which is responsible to create an instance of the adapter class from the adaptable instance. We can see it below:

public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
    if(adaptable instanceof Resource &&
            type.equals(Contact.class)) {
        Contact contact = new Contact();
        Resource resource = (Resource)adaptable;
        ValueMap valueMap = ResourceUtil.getValueMap((Resource)adaptable);
        contact.setName(valueMap.get("name", String.class));
        contact.setEmail(valueMap.get("email", String.class));
        contact.setPhone(valueMap.get("phone", String.class));
        contact.setAddress(valueMap.get("address", String.class));

        Resource contactImage = resource.getChild("contactImage");

        if(contactImage != null) {
            contact.setContactImage(contactImage.getPath());
        }
        return (AdapterType)contact;
    } else {
        return null;
    }
}

It receives two arguments; adaptable, which is the instance of the Adaptable class, and type, the class we are going to adapt to. Basically we check if the adaptable is instance of our expected adaptable class and if the type matches our expected adapter class. If so, we create our Contact instance and set its properties with the resource properties.

Now if we deploy our OSGI bundle, we can see our adapter registered in the Apache Felix Web Console on Sling -> Sling Adapters section:

Now we can change our JSP to use our adapter, so we can adapt our contact nodes to the Contact class. We can see the changed part of the JSP below:

<c:forEach var="contact" items="${contacts}" >
  <sling:adaptTo adaptable="${contact}" adaptTo="com.xicojunior.contacts.models.Contact" var="contactProps" />

  <li class="list-group-item">
          <div class="col-xs-12 col-sm-3">
          <c:if test="${not empty contactProps.contactImage }">
              <img src="${contactProps.contactImage}" alt="${contactProps.name}" class="img-responsive img-circle">
          </c:if>
          </div>
          <div class="col-xs-12 col-sm-9">
              <span class="name">${contactProps.name}</span><br>
              <span class="glyphicon glyphicon-map-marker text-muted c-info" data-toggle="tooltip" title="" data-original-title="${contactProps.address}"></span>
              <span class="visible-xs"> <span class="text-muted">${contactProps.address}</span><br></span>
              <span class="glyphicon glyphicon-earphone text-muted c-info" data-toggle="tooltip" title="" data-original-title="${contactProps.phone}"></span>
              <span class="visible-xs"> <span class="text-muted">${contactProps.phone}</span><br></span>
              <span class="fa fa-comments text-muted c-info" data-toggle="tooltip" title="" data-original-title="${contactProps.email}"></span>
              <span class="visible-xs"> <span class="text-muted">${contactProps.email}</span><br></span>
          </div>
          <div class="clearfix"></div>
      </li>
  </c:forEach>

The main change is that instead of adapting to ValueMap class we are adapting to our Contact class:

<sling:adaptTo adaptable="${contact}" adaptTo="com.xicojunior.contacts.models.Contact" var="contactProps" />

Then we can acces our POJO attributes using the JSP Expression Language.

The JSP became a little bit cleaner but we can make it a bit better.

As we can see, we still need to get the the contacts node using the taglib and to adapt each contact node to our contact class. Let’s make it a bit better. Let’s create another AdapterFactory that will return us the complete Contact list so we do not need to use all those taglibs.

For this, we will create another POJO, ContactList :

public class ContactList {
    private List<Contact> contacts = new ArrayList<Contact>();

    //... getters and setters

    public void addContact(Contact contact) {
        this.contacts.add(contact);
    }

}

Now our AdapterFactory will have as adaptable SlingHttpServletRequest and as adapter ContactList. We can see it below:

@Component
@Service
public class ContactListAdapterFactory implements AdapterFactory{

	@Property(name="adapters")
	public static final String[] ADAPTER_CLASSES = {
			ContactList.class.getName()
	};
	
	@Property(name="adaptables")
    public static final String[] ADAPTABLE_CLASSES = {
            SlingHttpServletRequest.class.getName()
	};
	
	public static String CONTACTS_NODE = "contacts";
	
	@Override
	@SuppressWarnings("unchecked")
	public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
		if(adaptable instanceof SlingHttpServletRequest
				&& type.equals(ContactList.class)) {
			
			ContactList contactList = new ContactList();
			SlingHttpServletRequest request = (SlingHttpServletRequest)adaptable;
			
			Resource appResource = request.getResource();
			Resource contactsNode = appResource.getChild(CONTACTS_NODE);
			Iterable<Resource> contacts = contactsNode.getChildren();
			Iterator<Resource> contactsIterator = contacts.iterator();
			while(contactsIterator.hasNext()) {
				contactList.addContact(contactsIterator.next().adaptTo(Contact.class));
			}
			
			return (AdapterType)contactList;
		}
		return null;
	}
	
}

This is very similar to our ContactAdapterFactory, but now adapting from SlingHttpServletRequest class. Basically we get the contacts node than its children. Also we can see our first AdapterFactory in action when we make:

contactsIterator.next().adaptTo(Contact.class)

After we deploy our OSGI bundle with our new adapter factory we can see it registered in the Web Console:

Now we can rewrite our JSP to be like below:

<sling:adaptTo adaptable="${slingRequest}" adaptTo="com.xicojunior.contacts.models.ContactList" var="contactList" />
<ul class="list-group" id="contact-list">
<c:set var="contacts" value="${contactList.contacts}" />
<c:forEach var="contact" items="${contacts}" >
<li class="list-group-item">
        <div class="col-xs-12 col-sm-3">
        <c:if test="${not empty contact.contactImage }">
            <img src="${contact.contactImage}" alt="${contact.name}" class="img-responsive img-circle">
        </c:if>
        </div>
        <div class="col-xs-12 col-sm-9">
            <span class="name">${contact.name}</span><br>
            <span class="glyphicon glyphicon-map-marker text-muted c-info" data-toggle="tooltip" title="" data-original-title="${contact.address}"></span>
            <span class="visible-xs"> <span class="text-muted">${contact.address}</span><br></span>
            <span class="glyphicon glyphicon-earphone text-muted c-info" data-toggle="tooltip" title="" data-original-title="${contact.phone}"></span>
            <span class="visible-xs"> <span class="text-muted">${contact.phone}</span><br></span>
            <span class="fa fa-comments text-muted c-info" data-toggle="tooltip" title="" data-original-title="${contact.email}"></span>
            <span class="visible-xs"> <span class="text-muted">${contact.email}</span><br></span>
        </div>
        <div class="clearfix"></div>
    </li>
</c:forEach>
</ul>

Now we just need to adapt the slingRequet to our ContactList and use the JSTL core tags to iterate over the contacts and print display their properties using EL.

Conclusion

With the adaptTo API we can convert the Sling related objects to our Model objects by using the AdapterFactory classes. By checking the Sling Adapters console we can see that is used a lot in the Apache Sling framework.

The complete source code is my github repo in a new branch adapt_to.

In order to deploy the code you just need:

git clone https://github.com/fjunior87/sling-contact-list.git
cd sling-contact-list
git checkout adapt_to
mvn clean install -P autoInstallBundle

And access the application.

That’s it for today, I hope you enjoyed it.

Thanks and see you in the next post.





About List