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


No comments:

Post a Comment