Talks about how to modify assets by using an AEM workflow and the AEM APIs

Article summary

Summary

Discusses how to move digital assets by using Experience Manager workflows. This article also shows you how to build a custom workflow step that uses DS annotations for Experience Manager 6.4. 

This development article uses these APIs to achieve this use case: 

  • Workflow API - to handle the payload that represents the digital asset
  • JCR API - to get the node and read the archive property 
  •  com.adobe.granite.asset.api.AssetManagerAPI - to place the asset into another DAM folder.

To read AEM Workflow best practices, see https://helpx.adobe.com/experience-manager/6-3/sites/developing/using/workflows-best-practices.html.

A special thank you to Ratna Kumar Kotla, a top AEM community member, for testing this article to ensure it works.  

Digital Marketing Solution(s) Adobe Experience Manager 6.4
Audience Developer
Required Skills Java, HTML, JavaScript
Version 6.4
Ask the AEM Community Experts  

Download

Note:

Before following along with this article, create a new folder named myArchive under /content/dam in the Experience Manager JCR. Its recommended that you create this folder in CRXDE Lite. Also create a new property named archive on a digital asset of type Boolean and set to true if you want the workflow to move the asset. (This is shown in the video at the end of the article). Last, you need to create the MoveAsset workflow after you deploy the OSGi bundle (or install the package). 

Introduction

When working with Adobe Experience Manager 6.4 digital assets, you can use workflows to achieve business requirements. Not all business requirements can be achieved by using out of the box Experience Manager components. To achieve some requirements, you need to build custom workflows. In some cases, you need to build custom workflow steps by using the Experience Manager Workflow API.

For example, consider a business requirement where a digital asset has to be archived based on an asset property. That is, you want to place an asset into a archive folder if its archive property is true. Using out of the box workflow steps, you cannot achieve this use case. To achieve this requirement, you need to build a custom workflow step that moves the asset to a folder under /content/dam

The following illustration shows the workflow model that moves a digital asset to the archive folder and then deletes it. 

 

model
A Workflow Model that places an asset into another DAM folder based on an asset property

In this model, the step that moves the digital asset to the archive folder is a custom workflow step that uses these AEM APIs: 

  • Workflow API - to handle the payload that represents the digital asset
  • JCR API - to get the node and read the jcr:data property to obtain an InputStream 
  • AssetManager API - to place the asset into the archive folder.

Note:

To create a custom workflow step for Experience Manager 6.3, you should use Declarative Services annotations as opposed to Apache Felix SCR Annotations (this is shown in this article).

Create an AEM Maven 13 archetype project

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

M10
Files generated by Maven 11 Archetype

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

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 moveAsset 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
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 Java files to the com.adobe.community.core package. The Java class that you create in this section implements the com.adobe.granite.workflow.exec.WorkflowProcess interface. For information, see Interface WorkflowProcess.

Create a Java class named CustomStep and specify the following Declarative Services annotation:

@Component(service=WorkflowProcess.class, property = {"process.label=My Archieve Step"})


public class CustomStep implements WorkflowProcess

In this example, notice that process.label refers to My Archieve Step. This is the value that appears in the process step when you create a workflow model that uses this custom workflow step. 

Step
A Process Step that uses a custom workflow step

Because the CustomStep class extends WorkflowProccess, you have to create a method named excute. The Java application logic in this method is invoked when the custom workflow step is executed. The execute method has the following signature:

public void execute(WorkItem item, WorkflowSession wfsession,MetaDataMap args) throws WorkflowException

In this development article, the custom step obtains a path of the digital asset. For example, the path can be something like:

/content/dam/we-retail/en/people/womens/women_6.jpg

To retrieve the path of the digital asset, you can use this application logic like this: 

WorkflowData workflowData = item.getWorkflowData(); //gain access to the payload data
String path = workflowData.getPayload().toString();

Once you have the path to the digital asset, you can use the JCR API to get a node instance that represents the asset. 

String newPath = path.replaceFirst("/", "");

//USE JCR API TO get the Asset Data so we can move it to another JCR location
Node root = session.getRootNode();
Node fileNode = root.getNode(newPath);

 

Note:

In this example, notice that path.replaceFirst method is used. This code modifies the path of the asset. The path returned from the Workflow API is /content/dam/we-retail/en/people/womens/women_6.jpg. You need to remove the 1st / character to use it with the JCR API; otherwise, an exception is thrown. 

The AssetManager API is used to place the asset under /content/dam/myArchive if the archive property is true. The following Java code represents the custom Workflow step. 

 

package com.adobe.community.core;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
    
 
import javax.jcr.Session;
import javax.jcr.Node; 
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
     
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
     
import javax.jcr.Repository; 
import javax.jcr.SimpleCredentials; 
import javax.jcr.Node; 
import javax.jcr.Binary;
import javax.jcr.Property;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import com.day.cq.dam.api.Asset; 
import java.util.Collections;
      
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 javax.jcr.Session;
import javax.jcr.Node; 
import org.osgi.framework.Constants;
   
import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowData;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
 
//AssetManager
import com.day.cq.dam.api.AssetManager; 
 
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream ; 
import java.io.OutputStream ; 
import java.io.ByteArrayInputStream ; 
import java.io.FileOutputStream ; 
   
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
   
    
//Sling Imports
import org.apache.sling.api.resource.ResourceResolverFactory ; 
import org.apache.sling.api.resource.ResourceResolver; 
import org.apache.sling.api.resource.Resource; 
import com.day.cq.wcm.api.Page; 
    
 
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
 
import com.adobe.granite.workflow.model.WorkflowNode;
import com.adobe.granite.workflow.exec.WorkflowData;
 
 
@Component(service=WorkflowProcess.class, property = {"process.label=My Archieve Step"})
 
public class CustomStep implements WorkflowProcess 
{
         
        
/** Default log. */
protected final Logger log = LoggerFactory.getLogger(this.getClass());
        
//Inject a MessageGatewayService 
//Inject a Sling ResourceResolverFactory
@Reference
private ResourceResolverFactory resolverFactory;
  
private Session session;
        
public void execute(WorkItem item, WorkflowSession wfsession,MetaDataMap args) throws WorkflowException {
            
try
{
    log.info("**** Here in execute method");    //ensure that the execute method is invoked    
            
    WorkflowNode myNode = item.getNode(); 
    String myTitle = myNode.getTitle(); //returns the title of the workflow step
    log.info("**** The title is "+myTitle);  
      
    WorkflowData workflowData = item.getWorkflowData(); //gain access to the payload data
    String path = workflowData.getPayload().toString();//Get the path of the asset
      
      
    //Get only the name of the asset - including the ext
    int index = path.lastIndexOf("/");
    String fileName = path.substring(index + 1);
        
   //Write the Asset to the archive folder in the DAM
    writeToDam(path,fileName,wfsession) ;
}
    
    catch (Exception e)
    {
    e.printStackTrace()  ; 
    }
 }
  
  
//Place the Asset into the AEM DAM using AssetManager API
private String writeToDam(String path, String fileName, WorkflowSession wfsession)
{
try
{
    //Inject a ResourceResolver - make sure to whitelist the bundle
    Session session = wfsession.adaptTo(Session.class);
    ResourceResolver resourceResolver = resolverFactory.getResourceResolver(Collections.singletonMap("user.jcr.session", (Object) session));  
         
      
   //Remove the first / char - JCR API does not like that
    String newPath = path.replaceFirst("/", "");   
      
    //USE JCR API TO read archive property 
    Node root = session.getRootNode(); 
    Node fileNode = root.getNode(newPath);
    
    //Append the path where the Asset data is stored
    String dataPath1 = newPath+"/jcr:content";
         
    //Check to make sure asset has archive property
    Node dataPathNode = root.getNode(dataPath1);
    Boolean propExist = dataPathNode.hasProperty("archive");
    
    if (propExist)
    {
     	Property property = dataPathNode.getProperty("archive");
    	Boolean bool = property.getBoolean();
       	log.info("**** THE VALUE OF BOOL IS "+bool);
   
       	//only move the asset if achieve is true
    	if (bool)
    	{
       		//Use AssetManager to place the file into the archive location
    		com.adobe.granite.asset.api.AssetManager assetMgr = resourceResolver.adaptTo(com.adobe.granite.asset.api.AssetManager.class);
    		String newFile = "/content/dam/myArchive/"+fileName ; 
    	    	
    		log.info("About to COPY "+newPath +" to here "+newFile); 
    		assetMgr.copyAsset(path,newFile); 
    	    	
    		log.info("About to REMOVE ASSET HERE" +newPath); 
    	
    		//Remove the original asset
    		assetMgr.removeAsset(path); 
      	}
    	else
    	{
    		log.info("**** THE archeve property is false - asset was not moved");	
    	}
       }
    else
    {
    	log.info("**** THE archive prop does not exist");
    }
  
    return fileName;
}
catch(Exception e)
{
    e.printStackTrace();
    log.info("**** Error: " +e.getMessage()) ;  
}
return null;
}
}

Note:

When you need to use Sessions in a custom workflow step, it's best practice to use the Session available with the Workflow API, as shown in the above Java code. 

Modify the Maven POM file

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

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

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

View the Active OSGi bundle

After you deploy the OSGi bundle by using the Maven command, you can see it in an active state in the Adobe Apache Felix Web Console.

 

OSGi
The OSGi bundle in an Active State

View your OSGi bundle by performing these steps:

  1. Login to Adobe Apache Felix Web Console at http://server:port/system/console/bundles (default admin user = admin with password= admin).
  2. Click the Bundles tab, sort the bundle list by Id, and note the Id of the last bundle.

Create an AEM Workflow that uses the custom workflow step

In this step, create an AEM workflow in the Touch UI environment that moves the asset if it contains a property named archive that is set to true, as shown in this illustration. (If this property does not exist or is set to false, then the asset is not moved).

acrprop
An archive property on a digital asset

To create a workflow that moves a digital asset using the custom step, perform the following tasks:

1. Click the Hammer icon in the main AEM view at http://localhost:4502

2. Click Workflow on the side menu. 

3. Click Models

4. Click the Create button then Create Model

5. Enter MoveAsset as the workflow title.

6. Open the MoveAsset workflow by selecting it and clicking Edit. 

Edit
Click the Edit button

7. As the 1st step, add a Participant Step.

8. Add a Process Step. In this step, select My Archieve Step, which corresponds to the label property in the CustomStep class.

Step

9. Click the Sync button.

Invoke the MoveAsset Workflow

The final task to perform is to invoke the MoveAsset workflow from the Experience Manager Touch UI Digital Asset view located at:

http://localhost:4502/assets.html/content/dam/we-retail/en/people/womens (for this article, the Touch UI view is used)

Select an Asset, as shown in this illustration. 

woman
Select a digital asset in the Touch UI

From the top menu, select Create, Workflow as shown in this illustration. 

WF_TouchUI2
Select Timeline

The Workflow dialog appears. Select the MoveAsset workflow, as shown in this illustration. 

movewf
Select the MoveAsset Workflow

From the Asset admin view at:

http://localhost:4502/assets.html/content/dam

click on the messages icon, as shown here. 

Step1TouchUI
The admin message generated by the first step in the MoveAsset workflow

This bring you to the Workflow confirmation view. Click the Complete button and then the OK button. 

Complete
Click the Complete button to complete the MoveAsset workflow

If the digital asset has an archive property set to true, then it's moved to the /content/dam/myArchive folder. The following video shows you this use case. 


See also

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