Saturday, July 30, 2016

Sling Model, the easier way to map data model in AEM

In the previous post, we see  how to adapt the JCR data to POJO model using Sling Adaptable interface. There are easier way to map jcr resource to domain models. Here comes the Sling Model for our help from AEM 6.0 onwards.

In the below example i'm trying to illustrate with same VehicleData model using Sling Model.

Only thing we need to annotate the VehicleData model class as follows.

import javax.inject.Inject;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;

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

@Inject
private String vin;
@Inject
private String manufacturer;

/**
* @return the vin
*/
public String getVin() {
return vin;
}

/**
* @param vin the vin to set
*/
public void setVin(String vin) {
this.vin = vin;
}

/**
* @return the manufacturer
*/
public String getManufacturer() {
return manufacturer;
}

/**
* @param manufacturer the manufacturer to set
*/
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}

}

ie all, now we easier to adapt the resource to vehicledata domain model.


VehicleData vehicleData = resource.adaptTo(VehicleData.class);

Wednesday, July 27, 2016

Custom node type and Adaptable mapping in AEM

Scenario: If we comes up with a requirement of  handling custom set of data  in AEM, and sometimes its better to create custom node types than using nt:unstructured node type. The reason is its easier to map and search the data.

In the following example, i'll try to explain how to create custom node type, how to map with model class using Sling Adaptable interface and how to display the model data in UI.

We can take an example of handling a custom vehicle data structure. The following are the sequence of steps to create, search and display custom data.

1: Register custom node type.

The following is the Node Definition for vehicle data.

<’tv’=‘http://mynamespace.tv/tv’>  
[tv:Vehicle] 
orderable
- vin (string) mandatory
- manufacturer (string)
- type (string)
- color (string)
- hp (long)
- capacity (long)

You can import the above definition via CRX explorer. Also we can add programmatically add using NodeTypeManager and it may depends on your project requirements.

2: Create vehicle data content

Next is to create list of vehicle nodes in crx of type tv:VehicleData. For this example, nodes are created under /content/TestBP/test

3: Create data model

This is just a data model representing the object of above vehicle data structure.

public class VehicleData {
private String vin;
private String manufacturer;
public String getVin() {
return vin;}
public void setVin(String vin) {
this.vin = vin;
}
public String getManufacturer() {
return manufacturer;}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;}
}

4: Create Custom Adapter class

The custom adapter class converts the sling resource to vehicledata model.  We have to write the mapping rule here.

public class VehicleAdapter {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
public VehicleData adaptToVehicleData(Resource resource){
VehicleData vehicleData = null;
Node node = null;
if(null != resource){
vehicleData = new VehicleData();
node = resource.adaptTo(Node.class);
try {
vehicleData.setVin(node.getProperty("vin").getString());
// TODO: we can add remaining mapping here 
} catch (ValueFormatException e) {
log.error(e.getMessage());
} catch (PathNotFoundException e) {
log.error(e.getMessage());
} catch (RepositoryException e) {
log.error(e.getMessage());
}
}
return vehicleData;
}
}

5: Create Custom AdapterFactory

This is  Osgi service registers the adaptables and adapters.

@Component
@Service(value=org.apache.sling.api.adapter.AdapterFactory.class)
@Properties({
   @Property(name = "adaptables", value = { "org.apache.sling.api.resource.Resource" }),
   @Property(name = "adapters", value = { "org.tv.aem.vehiclemgt.models.VehicleData" })
})
public class VehicleAdapterFactory implements AdapterFactory {

public <AdapterType> AdapterType getAdapter(final Object adaptable,
Class<AdapterType> type) {
return (AdapterType) getAdapter((Resource)adaptable, type);
}

private <AdapterType> AdapterType getAdapter(Resource resource,
Class<AdapterType> type) {
return (AdapterType) new VehicleAdapter().adaptToVehicleData(resource);
}

}


6:  Create domain service

This is a Osgi domain service with method to search the vehicle content and return the vehicle list.

public interface VehicleManagement {

List<VehicleData> getVehicles();

}

@Component
@Service(value=VehicleManagement.class)
public class VehicelManagementImpl implements VehicleManagement{

protected final Logger log = LoggerFactory.getLogger(this.getClass());
final static String VEHICLE_LIST_PATH = "/content/TestBP/test";
@Reference
private ResourceResolverFactory resolverFactory;
public List<VehicleData> getVehicles() {
ResourceResolver resourceResolver;
Session session;
//QueryManager queryManager;
QueryBuilder queryBuilder;
Query query;
SearchResult results;
List<VehicleData> vehicleList = null;
try {
resourceResolver = resolverFactory.getAdministrativeResourceResolver(null);
session = resourceResolver.adaptTo(Session.class);
queryBuilder = resourceResolver.adaptTo(QueryBuilder.class);
query = queryBuilder.createQuery(PredicateGroup.create(getSearchMap()), session);
results = query.getResult();
if(results.getHits().size() > 0){
vehicleList = new ArrayList<VehicleData>();
for (Hit hit : results.getHits()) {
Resource resource = hit.getResource();
VehicleData vehicleData = resource.adaptTo(VehicleData.class);
vehicleList.add(vehicleData);
}
}
} catch (LoginException e) {
log.error(e.getMessage());
}
catch (RepositoryException e) {
log.error(e.getMessage());
}
return vehicleList;
}
private Map<String, String> getSearchMap(){
Map<String, String> map = new HashMap<String, String>();
map.put("path", VEHICLE_LIST_PATH);
map.put("type", "tv:VehicleData");
return map;
}


}

7: Create vehicle list component

This is a custom aem component used to call vehicle list service and display.

<%@include file="/libs/foundation/global.jsp"%>
<%@page session="false" import="org.tv.aem.vehiclemgt.VehicleManagement,org.tv.aem.vehiclemgt.models.VehicleData,java.util.List" %><%
%><%
%>

<% VehicleManagement vms = sling.getService(VehicleManagement.class); %>
<p>Vehicle list</p>
<div id="container">
<% 
// TODO: create table structure.  
List<VehicleData> vehicles = vms.getVehicles();
if(vehicles != null ){
for(VehicleData vehicle:vehicles){
out.println(vehicle.getVin());
// we can add more 
}
}
%>


</div>

References
https://docs.adobe.com/docs/en/aem/6-1/develop/platform/custom-nodetypes.html