Article summary

Summary

Discusses how to create a custom AEM 6.4 Sling Servlet that uses the AEM QueryBuilder API to retrieve DAM assets. The Sling Servlet contains application logic that searches the AEM DAM for assets and places the collection of assets in a ZIP file. This article also discusses how to create a web page that invokes the Sling Servlet to download the ZIP file.

This article uses DS Annotations as opposed to Felix SRC annotations. It also registers the AEM Sling Servlet by using Resource Type, which reflects best practice. For information, see Sling Servlets. 

A special thank you to Ratna Kumar Kotla, a member of the AEM community, for testing this article and ensuring it works. 

Digital Marketing Solution(s) Adobe Experience Manager Forms 
Audience
Developer (intermediate)
Required Skills
Java, OSGi, Maven
Tested On  Adobe Experience Manager 6.4

Introduction

You can create an Adobe Experience Manager 6.4 Sling Servlet that uses the Query Builder API to search the Digital Asset Manager (DAM) and return a collection of assets within a ZIP file. The client web browser downloads the ZIP file that contains the DAM assets.

You can create a Sling Servlet that performs these tasks:

  • Uses the QueryBuilder API to search a specific DAM folder
  • Retrieves assets (for example, PNG files)
  • Places the assets in a ZIP file
  • Returns the ZIP file

The following illustration shows the use case described in this development article.

overview
An AEM Sling Servlet returns a collection of DAM Assets within a ZIP file

An Experience Manager Sling Servlet returns a collection of DAM Assets within a ZIP file.

In this development article, assume that the AEM DAM assets are located in the following DAM location: /content/dam/car.

DAM
Digital Assets located in the AEM DAM

Note:

This development article uses the QueryBuilder API to search for the Assets. For more information about the Experience Manager Query Builder API, see Query Builder API.

Create an Experience Manager System User

Create an Experience Manager System User that can access JCR data located at content/car. The user account must have privileges, as shown in the following illustration.  

Permissions
User permissions to the JCR

To successfully query JCR data, create an Experience Manager System user by performing these tasks.

1. Open http://localhost:4502/crx/explorer/index.jsp.

2. Login as admin.

3. Click User Administration.

4. Click Create System User. Name the user data (data is used in this article).

5. Set the UserId.

6. Click Save

7. Access the user page at http://localhost:4502/useradmin.

8. Select the data user.

9. From the right-hand pane, select the Permissions tab.

10. Expand the content tab and then select the employees row.

11. Click all the checkboxes that represent the permissions (click the top row in permissions for this example).

12. Click the Save button located in the top menu bar (located above the Path heading).

Watch the following video that shows you these steps. 

How to create an Adobe Experience Manager System User

How to create an Adobe Experience Manager System User
How to create an Adobe Experience Manager System User

Configure the Sling Mapper Service

The next step is to configure the Apache Sling Service User Mapper service by adding a new entry. Enter the following value:

downloadAsset.core:datawrite=data

where:

  • downloadAsset.core – is the Bundle-SymbolicName value of the OSGi bundle this is developed in the upcoming sections of this article.
  • datawrite – the name of the sub service (you reference this value in a Java Map object)
  • data – the system user account with data privileges for content/car

The following illustration shows an entry for this service. 

SlingMapping
An entry in the Apache Sling Service Mapping Service

To create an entry in the Apache Sling Mapper service, perform these tasks:

1. Go to the Apache Sling Mapper service at http://localhost:4502/system/console/configMgr.

2. Click OSGI, Configurations.

2. Scroll to an entry named Apache Sling Service User Mapper Service.

3. Enter the value downloadAsset.core:datawrite=data.

4. Click Save.

Create an Experience Manager Maven 13 archetype project

You can create an Experience Manager archetype project by using the Maven archetype plugin. In this example, assume that the working directory is C:\AdobeCQ. 

ProjectA
An Experience Manager Maven Archetype 12 project

To create an Experience Manager archetype project, perform these steps:

1. Open the command prompt and go to your working directory (for example, C:\AdobeCQ).

2. Run the following Maven command:

mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=com.adobe.granite.archetypes -DarchetypeArtifactId=aem-project-archetype -DarchetypeVersion=13 -DarchetypeCatalog=https://repo.adobe.com/nexus/content/groups/public/

3. When prompted, specify the following information:

  • groupId - downloadAsset
  • artifactId - downloadAsset
  • version - 1.0-SNAPSHOT
  • package - com.adobe.community
  • appsFolderName - downloadAsset
  • artifactName - downloadAsset
  • componentGroupName - downloadAsset
  • contentFolderName - downloadAsset
  • cssId - downloadAsset
  • packageGroup - downloadAsset
  • siteName -downloadAsset

4. When prompted, specify Y.

5. Once done, you will see a message like:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:42 min
[INFO] Finished at: 2016-04-25T14:34:19-04:00
[INFO] Final Memory: 16M/463M
[INFO] ------------------------------------------------------------------------

6. Change the working directory to downloadAsset and then enter the following command.

mvn eclipse:eclipse

After you run this command, you can import the project into Eclipse as discussed in the next section.

Note:

If you have not setup Maven, see this article Creating an Adobe Experience Manager 6.4 Project using Adobe Maven Archetype 13.

Add Java files to the Maven project using Eclipse 

To make it easier to work with the Maven generated project, import it into the Eclipse development environment, as shown in the following illustration.

project
Eclipse Import Project Dialog

Note:

Do not worry about the errors reported in Eclipse. It does not read the POM file where the APIs are resolved. You build the bundle with Maven. Eclipse is used to edit the Java files and the POM file.

The next step is to add a Java file to the com.adobe.community.core.servlets package named DownloadAssets

DownloadAssets

The DownloadAssets class uses the following DS annotations:

  • @Component- defines the class as a Sling Servlet
  • @Reference - injects ResourceResolverFactory and QueryBuilder instances into the Sling Servlet

Note:

For more information about DS Annotations, see DS Annotations

In this development article, a QueryBuilder instance is injected into the doGet method. This instance is used to retrieve DAM Assets located in /content/dam/car.

Within the doGet method, a ResourceResolverFactory instance is injected. This instance is required to create a Session instance that lets you create a Query instance. To inject a ResourceResolverFactory instance, you use the @Reference annotation to define a class member, as shown in the following example.

@Reference
private ResourceResolverFactory resolverFactory;

The goGet method creates a ZIP file (that contains the digital assets) and uses a ServletOutputStream instance to write out the ZIP file in the HTTP Response stream.

The Java class uses a Component annotation to define the Servlet:

@Component(service=Servlet.class,
property={
Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.resourceTypes="+ "downloadAsset/components/structure/page",
"sling.servlet.selectors=" + "assets"
})
public class DownloadAssets extends SlingAllMethodsServlet{

The resourceTypes property registeres the Servlet by using resource type. Later in this article, this is invoked using an href tag.

The following Java code represents the entire DownloadAssets class.

package com.adobe.community.core.servlets;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedInputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.rmi.ServerException;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.Iterator;
import javax.servlet.ServletOutputStream ;


import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import javax.jcr.Session;

//Sling Imports
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceResolver; 
import org.apache.sling.api.resource.Resource; 
//QueryBuilder APIs
import com.day.cq.search.QueryBuilder; 
import com.day.cq.search.Query; 
import com.day.cq.search.PredicateGroup;
import com.day.cq.search.result.SearchResult;
import com.day.cq.search.result.Hit; 
 
//DAM API
import com.day.cq.dam.api.Asset ; 



/**
 * Servlet that writes some sample content into the response. It is mounted for
 * all resources of a specific Sling resource type. The
 * {@link SlingSafeMethodsServlet} shall be used for HTTP methods that are
 * idempotent. For write operations use the {@link SlingAllMethodsServlet}.
 */
@Component(service=Servlet.class,
        property={
                Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.resourceTypes="+ "downloadAsset/components/structure/page",
                "sling.servlet.selectors=" + "assets"
        })
public class DownloadAssets  extends SlingAllMethodsServlet{
	
	//Set up References
    /** Default log. */
    protected final Logger log = LoggerFactory.getLogger(this.getClass());
               
    private Session session;
                   
    //Inject a Sling ResourceResolverFactory
    @Reference
    private ResourceResolverFactory resolverFactory;
               
    @Reference
    private QueryBuilder builder;
	    
	    @Override
	    protected void doGet(final SlingHttpServletRequest req,
	            final SlingHttpServletResponse resp) throws ServletException, IOException {
	        
	    	           
	    	Map<String, Object> param = new HashMap<String, Object>();
	    	param.put(ResourceResolverFactory.SUBSERVICE, "datawrite");
	    	ResourceResolver resolver = null;

	    	try {
	    	           
	    	    //Invoke the adaptTo method to create a Session used to create a QueryManager
	    	 resolver = resolverFactory.getServiceResourceResolver(param);
	    	    session = resolver.adaptTo(Session.class);

	             
	           // create query description as hash map (simplest way, same as form post)
	           Map<String, String> map = new HashMap<String, String>();
	         
	           //set QueryBuilder search criteria                   
	           map.put("type", "dam:Asset");
	           map.put("path", "/content/dam/car"); 
	         
	           
	           builder= resolver.adaptTo(QueryBuilder.class);
	            
	           //INvoke the Search query
	           Query query = builder.createQuery(PredicateGroup.create(map), session);
	            
	           SearchResult sr= query.getResult();
	            
	           //write out to the AEM Log file
	           log.info("Search Results: " +sr.getTotalMatches() ) ;
	            
	           //Create a MAP to store results
	           Map<String, InputStream> dataMap = new HashMap<String, InputStream>();
	        
	           // iterating over the results
	           for (Hit hit : sr.getHits()) {
	                
	               //Convert the HIT to an asset - each asset will be placed into a ZIP for downloading
	               String path = hit.getPath();
	               Resource rs = resolver.getResource(path);
	               Asset asset = rs.adaptTo(Asset.class);   
	                  
	               //We have the File Name and the inputstream
	               InputStream data = asset.getOriginal().getStream();
	               String name =asset.getName(); 
	                            
	              //Add to map
	               dataMap.put(name, data); // key is fileName and value is inputStream - this will all be placed in ZIP file
	          }
	                       
	           //ZIP up the AEM DAM Assets
	           byte[] zip = zipFiles(dataMap);
	            
	           //
	           // Sends the response back to the user / browser. The
	           // content for zip file type is "application/zip". We
	           // also set the content disposition as attachment for
	           // the browser to show a dialog that will let user 
	           // choose what action will he do to the sent content.
	           //
	            
	           ServletOutputStream sos = resp.getOutputStream();
	            
	           resp.setContentType("application/zip");
	           resp.setHeader("Content-Disposition", "attachment;filename=dam.zip");
	            
	            
	           // Write bytes to tmp file.
	           sos.write(zip);
	           sos.flush();    
	           log.info("The ZIP is sent" ) ;    
	        }
	        catch(Exception e)
	        {
	            log.info("OH NO-- AN EXCEPTION: " +e.getMessage() );
	        }
	      }
	        
	        
	        
	       /**
	        * Create the ZIP with AEM DAM Assets.
	        */
	       private byte[] zipFiles(Map data) throws IOException {
	           
	            
	            
	            
	           ByteArrayOutputStream baos = new ByteArrayOutputStream();
	           ZipOutputStream zos = new ZipOutputStream(baos);
	           byte bytes[] = new byte[2048];
	           Iterator<Map.Entry<String, InputStream>> entries = data.entrySet().iterator();
	            
	           while (entries.hasNext()) {
	               Map.Entry<String, InputStream> entry = entries.next();
	                
	               String fileName =(String) entry.getKey(); 
	               InputStream is1 =(InputStream) entry.getValue(); 
	                
	               BufferedInputStream bis = new BufferedInputStream(is1);
	    
	               //populate the next entry of the ZIP with the AEM DAM asset
	               zos.putNextEntry(new ZipEntry(fileName));
	    
	               int bytesRead;
	               while ((bytesRead = bis.read(bytes)) != -1) {
	                   zos.write(bytes, 0, bytesRead);
	                   
	               }
	               zos.closeEntry();
	               bis.close();
	               is1.close();
	                
	                
	           }
	            
	          zos.flush();
	           baos.flush();
	           zos.close();
	           baos.close();
	    
	           return baos.toByteArray();
	       }
	    
	   }

Modify the Maven POM file

Add the following POM dependency to the POM file located at C:\AdobeCQ\downloadAsset.

<dependency>
    <groupId>com.adobe.aem</groupId>
    <artifactId>uber-jar</artifactId>
    <version>6.4.0</version>
    <classifier>apis</classifier>
    <scope>provided</scope>
</dependency>
                
  <dependency>
       <groupId>org.apache.geronimo.specs</groupId>
       <artifactId>geronimo-atinject_1.0_spec</artifactId>
       <version>1.0</version>
       <scope>provided</scope>
   </dependency>

When you add new Java classes under core, you need to modify a POM file to successfully build the OSGi bundle. You modify the POM file located at C:\AdobeCQ\downloadAsset\core. The following code represents this POM file.

<?xml version="1.0" encoding="UTF-8"?>
<!--
 |  Copyright 2017 Adobe Systems Incorporated
 |
 |  Licensed under the Apache License, Version 2.0 (the "License");
 |  you may not use this file except in compliance with the License.
 |  You may obtain a copy of the License at
 |
 |      http://www.apache.org/licenses/LICENSE-2.0
 |
 |  Unless required by applicable law or agreed to in writing, software
 |  distributed under the License is distributed on an "AS IS" BASIS,
 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 |  See the License for the specific language governing permissions and
 |  limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>downloadAsset</groupId>
        <artifactId>downloadAsset</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>downloadAsset.core</artifactId>
    <packaging>bundle</packaging>
    <name>downloadAsset - Core</name>
    <description>Core bundle for downloadAsset</description>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.sling</groupId>
                <artifactId>maven-sling-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <!-- Import any version of javax.inject, to allow running on multiple versions of AEM -->
                        <Import-Package>javax.inject;version=0.0.0,*</Import-Package>
                        <Sling-Model-Packages>
                            com.adobe.community.core
                        </Sling-Model-Packages>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- OSGi Dependencies -->
        <!-- OSGi Dependencies -->
         <dependency>
            <groupId>com.adobe.aem</groupId>
            <artifactId>uber-jar</artifactId>
            <classifier>apis</classifier>
        </dependency>
      
        <dependency>
            <groupId>org.apache.geronimo.specs</groupId>
            <artifactId>geronimo-atinject_1.0_spec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.cmpn</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi.annotation</artifactId>
        </dependency>
        <!-- Other Dependencies -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.jcr</groupId>
            <artifactId>jcr</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.adobe.aem</groupId>
            <artifactId>uber-jar</artifactId>
            <classifier>apis</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.models.api</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
        </dependency>
        <dependency>
            <groupId>junit-addons</groupId>
            <artifactId>junit-addons</artifactId>
        </dependency>
    </dependencies>
</project>

Build the OSGi bundle using Maven

To build the OSGi bundle by using Maven, perform these steps:

  1. Open the command prompt and go to the C:\AdobeCQ\downloadAsset.
  2. Run the following maven command: mvn -PautoInstallPackage install.
  3. The OSGi component can be found in the following folder: C:\AdobeCQ\downloadAsset\core\target. The file name of the OSGi component is downloadAsset.core-1.0-SNAPSHOT.jar.

The command -PautoInstallPackage automatically deploys the OSGi bundle to AEM.

Create the HTL Front End Component

When you use the Maven Archetype 13 archetype to create an Experience Manager project, a default front end project is created, as shown in the following illustration.

CRXDE

Add HTL code

For the purpose of this article, the HTL code is written within a Maven Archetype 13 default component located here:

/apps/downloadAsset/components/content/helloworld

Add the following HTML code to the helloworld.html file.

<h4>Adobe Experience Manager DAM Download Example</h4>
            
<a href="http://localhost:4502/content/downloadAsset/en.assets.html" style="font-size: 30px; text-decoration: none">Download AEM CAR DAM Assets</a>

View the output of the HTL component

To access the component, enter the following URL:

http://localhost:4502/editor.html/content/downloadAsset/en.html

The following video shows the DAM Assets being returned by the Sling Servlet created in this article. 

Download AEM DAM Assets

Download AEM DAM Assets
Download AEM DAM Assets

See also

Congratulations, you have just created an AEM workflow that approves or rejects an asset. 

You can view additional AEM Community generated content:

Join the AEM community at: Adobe Experience Manager Community

 

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy