Article summary

Summary

Discusses how to create an AEM application that lets users select image files and upload them to a Java Sling Servlet. Once uploaded, the Servlet uses the AssetManager API to store the image file in the Experience Manager DAM.

This article discusses how to perform these tasks:

  • how to write a sling servlet to handle input streams
  • how to use Asset Manager API to handle assets
  • how to post files to a sling servlet from an HTL component
  • in AEM 6.x - how to handle CSRF token requirements when using AJAX

A Special thank you to Ratna Kumar for testing this Experience League Community Article to ensure it works. 

Note: To run this code on the Publisher server, you must create the System user on the Publisher instance. 

Digital Marketing Solution(s) Adobe Experience Manager
Audience
Developer (intermediate)
Required Skills
Java, JQuery
AEM Versions(s) Adobe Experience Manager 6.4
Video N/A

Introduction

You can create an Adobe Experience Manager application that lets a user select a file from their local desktop and upload it to AEM Digital Asset Manager (DAM).  For example, assume you want your customers to upload photos taken from a mobile device and upload to enter a local contest.

 

photo2
A web site that lets users upload images

Using an Experience Manager component that is developed in this article, an image can be uploaded from the web site to the Experience Manager DAM.

 

client
An Experience Manager component that uploads images to the DAM on a public facing site

The Experience Manager component posts the selected image to a Sling Servlet. The Sling Servlet uses the AssetManager API to place the uploaded file into the DAM, as shown in the following illustration.

 

dam
An uploaded Asset located in the AEM DAM

This development article walks you through how to create this Experience Manager component that lets a user select and upload an image file to the DAM.  

Create an Experience Manager System User

Create an Experience Manager System User that can access JCR data located at content/employees. 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).  

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:

Upload64.core:datawrite=data

where:

  • Upload64.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. 

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 QUeryJCR64.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. 

Maven
Default files created by the Maven archetype plugin

Note:

Before you can create a Maven archetype project, you need to setup Maven. For details, see Creating an Adobe Experience Manager 6.4 Project using Adobe Maven Archetype 13.

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 - Upload64
  • artifactId - Upload64
  • version - 1.0-SNAPSHOT
  • package - com.adobe.community
  • appsFolderName - Upload64
  • artifactName - Upload64
  • componentGroupName - Upload64
  • contentFolderName - Upload64
  • cssId - Upload64
  • packageGroup - Upload64
  • siteName - Upload64

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 Upload64 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.

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
The Eclipse Import Project dialog

The next step is to replace the default code in SimpleServlet in the com.adobe.community.core.servlets package. Within the doPost method, create Java Sling application logic that reads the file that is uploaded to the Sling servlet. The fully qualified names of the Java objects are used so you understand the data types used in this code fragment.

The uploaded file is placed into an InputStream instance named stream. Next, the uploaded file is written to the AEM DAM using the AssetManager API. The following Java code represents a method named writeToDam. This method uses the AssetManager API to place the file into the following DAM location:

/content/dam/travel

Replace all code in the Simple Servlet code with this code.

 

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.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.rmi.ServerException;
import java.util.Dictionary;
import java.util.Calendar;
import java.io.*;
  
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.ComponentContext;
import javax.jcr.Session;
import javax.jcr.Node;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import javax.jcr.ValueFactory;
import javax.jcr.Binary;
   
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.util.Iterator;
import java.util.List;
import java.io.OutputStream; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
    
import java.io.StringWriter;
  
import java.util.ArrayList;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;    
import javax.jcr.Repository;
import javax.jcr.SimpleCredentials;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
     
import org.apache.jackrabbit.commons.JcrUtils;
  
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.util.HashMap; 
import java.util.Map; 
  
    
import javax.jcr.Session;
import javax.jcr.Node;

import javax.servlet.Servlet;

import org.apache.sling.api.servlets.HttpConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

//Sling Imports
import org.apache.sling.api.resource.ResourceResolverFactory ;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.Resource;


@Component(service=Servlet.class,
        property={
                Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
                "sling.servlet.methods=" + HttpConstants.METHOD_POST,
                "sling.servlet.paths="+ "/bin/updamfile"
           })
public class SimpleServlet extends SlingAllMethodsServlet {
	
 private Session adminSession;
      
     
//Inject a Sling ResourceResolverFactory
 @Reference
 private ResourceResolverFactory resolverFactory;
     
     @Override
     protected void doPost(final SlingHttpServletRequest req,
             final SlingHttpServletResponse resp) throws ServletException, IOException {
	 try
     {
     final boolean isMultipart = org.apache.commons.fileupload.servlet.ServletFileUpload.isMultipartContent(req);
     PrintWriter out = null;
       
       out = resp.getWriter();
       if (isMultipart) {
         final java.util.Map<String, org.apache.sling.api.request.RequestParameter[]> params = req.getRequestParameterMap();
         for (final java.util.Map.Entry<String, org.apache.sling.api.request.RequestParameter[]> pairs : params.entrySet()) {
           final String k = pairs.getKey();
           final org.apache.sling.api.request.RequestParameter[] pArr = pairs.getValue();
           final org.apache.sling.api.request.RequestParameter param = pArr[0];
           final InputStream stream = param.getInputStream();
            
               //Save the uploaded file into the Adobe CQ DAM
            out.println("The Sling Servlet placed the uploaded file here: " + writeToDam(stream,param.getFileName()));
           
         }
       }
     }
       
     catch (Exception e) {
         e.printStackTrace();
     }
   
 }
   
 
//Save the uploaded file into the AEM DAM using AssetManager APIs
private String writeToDam(InputStream is, String fileName)
{
	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);
       
        //Use AssetManager to place the file into the AEM DAM
        com.day.cq.dam.api.AssetManager assetMgr = resolver.adaptTo(com.day.cq.dam.api.AssetManager.class);
        String newFile = "/content/dam/travel/"+fileName ; 
        assetMgr.createAsset(newFile, is,"image/jpeg", true);
     

        // Return the path to the file was stored
        return newFile;
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}

}

Notice that this code example uses R6 annotations. For information about these annotations, see Official OSGi Declarative Services Annotations in AEM.

Modify the Maven POM file

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

<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\Upload64\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>Upload64</groupId>
        <artifactId>Upload64</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>Upload64.core</artifactId>
    <packaging>bundle</packaging>
    <name>Upload64 - Core</name>
    <description>Core bundle for Upload64</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 -->
        <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\Upload64.
  2. Run the following maven command: mvn -PautoInstallPackage install.
  3. The OSGi component can be found in the following folder: C:\AdobeCQ\Upload64\core\target. The file name of the OSGi component is Upload64.core-1.0-SNAPSHOT.jar.

The command -PautoInstallPackage automatically deploys the OSGi bundle to Experience Manager.

After you deploy the OSGi bundle, you will be able to see it in the Apache Felix Web Console (http://localhost:4502/system/console/configMgr).

OSGi
Apache Felix Web Console Bundles view

Create the HTL Front End Component

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

files
Default components

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

/apps/Upload64/components/content/helloworld

Add the following HTML code that creates a basic form.

<div>
<h2>Upload files to the Adobe Experience Manager DAM</h2>
<p>Assets are placed at <b>/content/dam/travel</b></p>
<p id="support-notice">Your browser does not support Ajax uploads :-(The form will be submitted as normal.</p>


        <!-- The form starts -->
        <form action="/" method="POST" enctype="multipart/form-data" id="form-id">
  
            <!-- The file to upload -->
            <p><input id="file-id" type="file" name="our-file" />
  
                <!--
                  Also by default, we disable the upload button.
                  If Ajax uploads are supported we'll enable it.
                -->
                <input type="button" value="Upload" id="upload-button-id" disabled="disabled" /></p>
             

  
            <!-- Placeholders for messages set by event handlers -->
            <p id="upload-status"></p>
            <p id="progress"></p>
            <pre id="result"></pre>
  
        </form>
  
</div>
  
  

Add files to the ClientLibs folder

To perform an AJEX operation to work with a HTL component, you need to add a script file to the clientlibs folder located here:

/apps/Upload64/clientlibs/clientlib-base

In this folder, peform these tasks:


1 . In the js.txt file, add this line: script.js

2. Create a new file named script.js. This file contains JavaScript logic that uploads a file to the Sling Servlet. Add the following code represents the script.js file

 

jQuery(function ($) {




  // Function that will allow us to know if Ajax uploads are supported
                function supportAjaxUploadWithProgress() {
                    return supportFileAPI() && supportAjaxUploadProgressEvents() && supportFormData();
  
                    // Is the File API supported?
                    function supportFileAPI() {
                        var fi = document.createElement('INPUT');
                        fi.type = 'file';
                        return 'files' in fi;
                    };
  
                    // Are progress events supported?
                    function supportAjaxUploadProgressEvents() {
                        var xhr = new XMLHttpRequest();
                        return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
                    };
  
                    // Is FormData supported?
                    function supportFormData() {
                        return !! window.FormData;
                    }
                }
  
                // Actually confirm support
                if (supportAjaxUploadWithProgress()) {
                    // Ajax uploads are supported!
                    // Change the support message and enable the upload button
                    var notice = document.getElementById('support-notice');
                    var uploadBtn = document.getElementById('upload-button-id');
                    notice.innerHTML = "Your browser supports HTML uploads to AEM.";
                    uploadBtn.removeAttribute('disabled');
  
                    // Init the Ajax form submission
                    initFullFormAjaxUpload();
  
                    // Init the single-field file upload
                    initFileOnlyAjaxUpload();
                }
  
                function initFullFormAjaxUpload() {
                    var form = document.getElementById('form-id');
                    form.onsubmit = function() {
                        // FormData receives the whole form
                        var formData = new FormData(form);
  
                        // We send the data where the form wanted
                        var action = form.getAttribute('action');
  
                        // Code common to both variants
                        sendXHRequest(formData, action);
  
                        // Avoid normal form submission
                        return false;
                    }
                }
  
                function initFileOnlyAjaxUpload() {
                    var uploadBtn = document.getElementById('upload-button-id');
                    uploadBtn.onclick = function (evt) {
                        var formData = new FormData();
  
                        // Since this is the file only, we send it to a specific location
                        //   var action = '/upload';
  
                        // FormData only has the file
                        var fileInput = document.getElementById('file-id');
                        var file = fileInput.files[0];
                        formData.append('our-file', file);
  
                        // Code common to both variants
                        sendXHRequest(formData);
                    }
                }
  
                // Once the FormData instance is ready and we know
                // where to send the data, the code is the same
                // for both variants of this technique
                function sendXHRequest(formData) {
  
                    var test = 0; 
  
                    $.ajax({
                            type: 'POST',    
                            url:'/bin/updamfile',
                            processData: false,  
                            contentType: false,  
                            data:formData,
                            success: function(msg){
                              alert(msg); //display the data returned by the servlet
                        }
                    });
                      
                }
  
                // Handle the start of the transmission
                function onloadstartHandler(evt) {
                    var div = document.getElementById('upload-status');
                    div.innerHTML = 'Upload started!';
                }
  
                // Handle the end of the transmission
                function onloadHandler(event) {
                    //Refresh the URL for Form Preview
                    var msg = event.target.responseText;
  
                   alert(msg);
                }
  
                // Handle the progress
                function onprogressHandler(evt) {
                    var div = document.getElementById('progress');
                    var percent = evt.loaded/evt.total*100;
                    div.innerHTML = 'Progress: ' + percent + '%';
                }
  
                // Handle the response from the server
                function onreadystatechangeHandler(evt) {
                    var status = null;
  
                    try {
                        status = evt.target.status;
                    }
                    catch(e) {
                        return;
                    }
  
                    if (status == '200' && evt.target.responseText) {
                        var result = document.getElementById('result');
                        result.innerHTML = '<p>The server saw it as:</p><pre>' + evt.target.responseText + '</pre>';
                    }
                }


      
      });


In addition, add the following property to this clientlib folder. This is required to enable the AJAX operation to be successful.

clientlib

Note:

You can develop an easy and fast way to upload files to client lib folders. See the following article for more information: Creating Java Swing applications that posts files to AEM ClientLibs folders

View the output of the HTL component

To access the component, enter the following URL: 

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

The following illustration shows the HTL component.

 

client
A file is uploaded to the Experience Manager DAM

See also

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