All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.dam.commons.util.DamLanguageUtil Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2015 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
/**
 * AdobePatentID="P6809-US"
 */
package com.day.cq.dam.commons.util;

import com.adobe.cq.dam.cfm.FragmentData;
import com.adobe.cq.dam.cfm.SemanticDataType;
import java.io.IOException;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.AccessDeniedException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Workspace;
import javax.jcr.lock.Lock;
import javax.jcr.lock.LockManager;
import javax.jcr.lock.LockException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import com.adobe.cq.dam.cfm.ContentElement;
import com.adobe.cq.dam.cfm.ContentFragment;
import com.adobe.cq.dam.cfm.ContentFragmentException;
import com.adobe.cq.dam.cfm.ContentVariation;
import com.adobe.granite.asset.api.AssetRelation;
import com.adobe.granite.confmgr.Conf;
import com.adobe.granite.security.authorization.AuthorizationService;
import com.adobe.granite.translation.api.TranslationConstants;
import com.adobe.granite.translation.api.TranslationException;
import com.adobe.granite.translation.core.MachineTranslationCloudConfig;
import com.day.cq.dam.api.Rendition;
import com.day.cq.wcm.api.WCMException;

import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.api.JcrResourceConstants;
import org.apache.sling.resource.collection.ResourceCollection;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.commons.Language;
import com.day.cq.commons.LanguageUtil;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.DamConstants;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.PageManagerFactory;
import com.day.text.Text;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT;
import static com.day.cq.commons.jcr.JcrConstants.JCR_LASTMODIFIED;
import static com.day.cq.commons.jcr.JcrConstants.JCR_TITLE;
import static com.day.cq.commons.jcr.JcrConstants.NT_FOLDER;
import static com.day.cq.commons.jcr.JcrConstants.JCR_UUID;

/**
 * This class provides utility method for Language Copy used by sites
 */
public class DamLanguageUtil {

    private static final Logger log = LoggerFactory.getLogger(DamLanguageUtil.class);

    private static final String ATTRIBUTE_DESTINATION_LANGUAGE_COPY_PATH = "dam:destinationLanguageCopy";
    private static final String ATTRIBUTE_EXTRACT_METADATA = "dam:extractMetadata";
    private static final String ATTRIBUTE_SMART_ASSET_UPDATE_SOURCE = "dam:smartAssetUpdateSource";
    private static final String ATTRIBUTE_SMART_ASSET_UPDATE_REQUIRED = "dam:smartAssetUpdateRequired";
    private static final String ATTRIBUTE_CLOUD_CONFIG_PROPERTY = "cq:cloudserviceconfigs";
    private static final String ATTRIBUTE_CA_CONFIG_PROPERTY = "cq:conf";
    private static final String ATTRIBUTE_CQ_TRANSLATION_SOURCE_PATH = "cq:translationSourcePath";
    private static final String CACONFIG_ROOT = "/conf";
    private static final String CACONFIG_GLOBAL = CACONFIG_ROOT + "/global";
    private static final String CACONFIG_TRANSLATIONCFG_PATH = "cloudconfigs/translation/translationcfg";
    private static final String DEFAULT_LANGUAGES_HOME = "wcm/core/resources/languages";
    private static final String ASSET_PERFORMANCE_NODE_RELATIVE_PATH = "/" + JCR_CONTENT + "/" + "performance";
    private static final String ASSET_USAGE_NODE_RELATIVE_PATH = "/" + JCR_CONTENT + "/" + DamConstants.USAGES_FOLDER;
    private static final String ASSET_VERSION_MESSAGE = "Created by Asset Update Translation";
    private static final String ASSOCIATED_CONTENT_RELATIVE_PATH = JcrConstants.JCR_CONTENT + "/associated";
    private static final String ASSOCIATED_CONTENT_CHILD = "associated";
    private static final String CONTENT_FRAGMENT = "contentFragment";
    private static final String ATTRIBUTE_ASSET_DERIVED_RELATION = "derived";
    private static final String ATTRIBUTE_ASSET_OTHERS_RELATION = "others";
    private static final String MIME_TYPE_HTML = "text/html";
    private static final int MAX_DIFF_MILLISECOND_CHANGED = 2000;   // max diff allowed in difference is 2sec
    private static final String RESOURCE_TYPE_CFM_CONTENT_REFERENCE = "dam/cfm/models/editor/components/contentreference";
    private static final String ATTRIBUTE_CQ_TRANSLATION_CREATED= "cq:isTransCreated";
    private static final String ATTRIBUTE_CQ_TRANSLATION_STATUS = "cq:translationStatus";

    public static final String ATTRIBUTE_ASSET_LINKS_RELATION = "links";
    public static final String ATTRIBUTE_ASSET_SOURCE_RELATION = "sources";
    public static final String ATTRIBUTE_ASSET_UPDATE_REQUIRED = "dam:assetUpdateRequired";
    public static final String ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY = "dam:collectionSourceLanguageCopy";
    public static final String CQ_LASTMODIFIED = "cq:lastModified";
    public static final String ATTRIBUTE_CQ_TRANSLATION_LAST_UPDATE = "cq:lastTranslationUpdate";
    private static final String LIBS_RULES_FILE_PATH = "/libs/settings/translation/rules/translation_rules.xml";
    private static final String APPS_RULES_FILE_PATH = "/apps/settings/translation/rules/translation_rules.xml";
    private static final String LEGACY_ETC_RULES_FILE_PATH = "/etc/workflow/models/translation/translation_rules.xml";
    private static final String CONF_RULES_FILE_PATH = "/conf/global/settings/translation/rules/translation_rules.xml";
    private static final String[] RULE_FILES_PRIORITY_ARRAY = {CONF_RULES_FILE_PATH, APPS_RULES_FILE_PATH,
            LEGACY_ETC_RULES_FILE_PATH, LIBS_RULES_FILE_PATH};
    private static final String ELEMENT_CONTENT_FILTER_NODE = "contentFilterNode";
    private static final String ATTRIBUTE_CONTENT_FILTER_VALUE = "value";
    private static final String ATTRIBUTE_CONTENT_CREATE_LANG_COPY = "createLanguageCopy";
    private static final String CQ_TAG_NAME = "cq:tags";
    private static final String METADATA = "metadata";
    private static final String CHANGE_BY_TRANSLATION_WORKFLOW = "changeByTranslationWorkflow";
    private static final String CQ_TRANSLATION_SOURCE_JCR_UUID = "cq:translationSourceUUID";
    private static final String CQ_LANGUAGE_ROOT = "cq:languageRoot";

    /**
     * Category for translation while creating and updating language copies. This is a list of all possible language
     * copy locations.
     */
    private enum TranslationCategory {
        /**
         * Language copy translation is not required.
         */
        NONE,

        /**
         * Destination Language copy needs to be created or updated.
         */
        DESTINATION,

        /**
         * Temporary Language copies are required.
         */
        TEMPORARY
    }

    /**
     * possible states of content.
     */
    private enum ContentState {
        /**
         * content is updated.
         */
        IS_UPDATE_STATE,

        /**
         * content is translated.
         */
        IS_TRANSLATED,

        /**
         * content is for creation.
         */
        IS_CREATE_STATE,

        /**
         * Unknown stage of Content.
         */
        IS_UNKNOWN
    }


    /**
     * This method returns true if language copy of an asset exists, for the
     * asked locale
     * 
     * @param assetPath The path of an asset for which language copy is asked
     * @param languageCode Language for which language copy is asked
     * @param resolver ResourceResolver
     * @return
     */
    public static boolean hasLanguageCopy(final String assetPath,
            final String languageCode, final ResourceResolver resolver) {

        Asset asset = findLanguageCopy(assetPath, languageCode, resolver);
        if (asset != null) return true;
        return false;
    }
    /**
     * This method returns the Language copy asset if language copy exists, for
     * the asked locale
     * 
     * @param assetPath The path of an asset for which language copy is asked
     * @param languageCode Language for which language copy is asked
     * @param resolver ResourceResolver
     * @return
     */
    public static Asset getLanguageCopy(final String assetPath,
            final String languageCode, final ResourceResolver resolver) {

        return findLanguageCopy(assetPath, languageCode, resolver);

    }

    /**
     * This method creates language copy of an asset/folder
     * Adds Smart translation properties when language copy is present
     *
     * @param resourceResolver
     * @param pageManagerFactory
     * @param sourcePath - source for creating language copy
     * @param targetLanguageCodes - array of language codes
     * @return
     */
    public static List createLanguageCopy(final ResourceResolver resourceResolver,
        final PageManagerFactory pageManagerFactory, final String sourcePath, final String[] targetLanguageCodes) {
        Session session = resourceResolver.adaptTo(Session.class);
        PageManager pageManager = pageManagerFactory.getPageManager(resourceResolver);
        Resource sourceResource = resourceResolver.getResource(sourcePath);
        List createdCopies = new ArrayList();
        String contentPath = "";

        if (targetLanguageCodes == null || targetLanguageCodes.length == 0 || targetLanguageCodes[0].trim().length() == 0) {
            log.error("Failed to load destination language from payload.");
            return createdCopies;
        }

        String root = LanguageUtil.getLanguageRoot(sourcePath);
        String parentOfRoot = null;
        boolean createNewLanguageRoot = false;
        Node sourceLanguageRootNode = null;
        Node sourceLRContentNode = null;
        if (root == null) {
            log.debug("Language root does not exist for asset at path: {} and would be created. ", sourcePath);

            //CQ-61450 User creates a language copy of a page from sites and language root for the asset (referenced in site) does not exist
            if (Text.getRelativeParent(sourcePath, 1).equals(DamConstants.MOUNTPOINT_ASSETS)) {
                //Asset is directly under DamConstants.MOUNTPOINT_ASSETS
                parentOfRoot = DamConstants.MOUNTPOINT_ASSETS;
                root = DamConstants.MOUNTPOINT_ASSETS;
            } else if (sourcePath.startsWith(DamConstants.MOUNTPOINT_ASSETS + "/")) {
                //Asset follows structure DamConstants.MOUNTPOINT_ASSETS//
                int parentOfRootPathLength = sourcePath.indexOf('/', DamConstants.MOUNTPOINT_ASSETS.length() + 1);
                int oldRootPathLength = sourcePath.indexOf('/', parentOfRootPathLength);
                if (parentOfRootPathLength < 0 || sourcePath.length() <= parentOfRootPathLength) {
                    return createdCopies;
                }
                parentOfRoot = sourcePath.substring(0, parentOfRootPathLength);

                if (oldRootPathLength > 0 && sourcePath.length() > oldRootPathLength) {
                    root = sourcePath.substring(0, oldRootPathLength);
                }
            }
            createNewLanguageRoot = true;
            log.info("Parent of New Language root at path {} added for asset at path {}", parentOfRoot, sourcePath);
            if (parentOfRoot != null) {
                contentPath = sourcePath.replaceFirst(parentOfRoot, "");
            } else {
                parentOfRoot = "";
            }
        } else {
            contentPath = sourcePath.replaceFirst(root, "");
            parentOfRoot = Text.getRelativeParent(root, 1);
        }
        //for every indivisual asset, for every target langauge code
        for (int i = 0; i < targetLanguageCodes.length; i++) {
            String targetPath = "";
            String languageRootPath = "";
            String strDestinationLanguage = getDestinationLanguageWithAllowedDelimiters(parentOfRoot,
                    targetLanguageCodes[i], resourceResolver);

            languageRootPath = getDestinationLanguageRoot(parentOfRoot, strDestinationLanguage, resourceResolver);
            targetPath = languageRootPath + contentPath;

            String transCreatedPath = "" ;
            String pathToCheck = "";
            try {
                if (contentPath.trim().length() > 0) {
                    String pathToCreate = Text.getRelativeParent(
                            targetPath, 1);
                    pathToCheck = pathToCreate.replaceFirst(DamConstants.MOUNTPOINT_ASSETS, "") + "/";
                    transCreatedPath = getTransCreatedPath(pathToCheck, session);
                    String nodeType = "sling:Folder";
                    sourceLanguageRootNode = session.getNode(root);
                    sourceLRContentNode = sourceLanguageRootNode.getNode(JcrConstants.JCR_CONTENT);
                    if (sourceLanguageRootNode.isNodeType(
                            "sling:OrderedFolder")) {
                        nodeType = "sling:OrderedFolder";
                    }
                    JcrUtil.createPath(pathToCreate, nodeType,
                            nodeType, session, false);
                }
                if (!session.nodeExists(targetPath)) {
                    Resource destinationResource = pageManager.copy(sourceResource, targetPath, null, false,
                        false, false);
                    // Remove Derived, Others Relations from this resource, if any. Ensures that if this resource,
                    // is a source Asset then the derived, others relations don't point to old language copies
                    if (destinationResource != null) {
                        com.adobe.granite.asset.api.Asset destinationGraniteAsset = destinationResource.adaptTo(com.adobe.granite.asset.api.Asset.class);
                        if (destinationGraniteAsset != null) {
                            removeAssetRelation(destinationGraniteAsset, ATTRIBUTE_ASSET_DERIVED_RELATION);
                            removeAssetRelation(destinationGraniteAsset, ATTRIBUTE_ASSET_OTHERS_RELATION);
                        }
                        associateSourceJcrUuidToAssetLanguageCopyAndSetLanguageRoot(destinationResource, sourceResource, session, strDestinationLanguage);
                        addTranslationSourcePath(destinationResource, sourceResource.getPath());
                        setLastTranslationUpdate(destinationResource);
                        setIsTranslationCreated(destinationResource);
                        deleteInsightData(destinationResource.getPath(), resourceResolver);
                    }
                    //changing the title and set the Cloud Configs (CQ-61476) on the language root folder
                    Node targetNode = session.getNode(languageRootPath);
                    if (targetNode != null) {
                        if (!targetNode.hasNode(JcrConstants.JCR_CONTENT)) {
                            targetNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
                        }
                        Node content = targetNode.getNode(JcrConstants.JCR_CONTENT);
                        if (!content.hasProperty(JcrConstants.JCR_TITLE)) {
                            String displayLanguage = getLanguageDisplayName(resourceResolver, strDestinationLanguage);
                            content.setProperty(JcrConstants.JCR_TITLE, displayLanguage);
                        }

                        if (sourceLRContentNode != null) {

                            // Set the cq:conf property on target LR folder if the source language root folder has and target doesn't have it
                            if (!content.hasProperty(ATTRIBUTE_CA_CONFIG_PROPERTY) &&
                                    sourceLRContentNode.hasProperty(ATTRIBUTE_CA_CONFIG_PROPERTY)) {
                                content.setProperty(ATTRIBUTE_CA_CONFIG_PROPERTY,
                                    sourceLRContentNode.getProperty(ATTRIBUTE_CA_CONFIG_PROPERTY).getString());
                            }

                            // Set the cq:cloudserviceconfigs property on target LR folder if the source language root folder has and target doesn't have it
                            if (!content.hasProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY) &&
                                    sourceLRContentNode.hasProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY)) {
                                content.setProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY,
                                    sourceLRContentNode.getProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY).getValues());
                            }
                        }

                        //set JCR_TITLE and Cloud Configs (CQ-61476) for all folders in the created contentPath and language root
                        String tempAddTitlePath = contentPath.substring(0, contentPath.lastIndexOf('/'));
                        while (tempAddTitlePath.length() > 0) {
                            Node tempNode = session.getNode(languageRootPath + tempAddTitlePath);
                            if (tempNode != null) {
                                if (!tempNode.hasNode(JcrConstants.JCR_CONTENT)) {
                                    tempNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
                                }
                                Node tempNodeContent = tempNode.getNode(JcrConstants.JCR_CONTENT);
                                String sourceNodeForTitle = null;
                                if (createNewLanguageRoot) {
                                    sourceNodeForTitle = parentOfRoot + tempAddTitlePath;
                                } else {
                                    sourceNodeForTitle = root + tempAddTitlePath;
                                }
                                if (!tempNodeContent.hasProperty(JcrConstants.JCR_TITLE)) {
                                    String tempNodeTitle = UIHelper.getTitle(resourceResolver.getResource(sourceNodeForTitle));
                                    tempNodeContent.setProperty(JcrConstants.JCR_TITLE, tempNodeTitle);
                                    Node sourceNode = session.getNode(sourceNodeForTitle);
                                    if (sourceNode != null) {
                                        Node sourceContentNode = sourceNode.getNode(JcrConstants.JCR_CONTENT);
                                        if (sourceContentNode != null) {
                                            // Set the cq:conf property on this folder if relative folder in source path has and target doesn't have it
                                            if (!tempNodeContent.hasProperty(ATTRIBUTE_CA_CONFIG_PROPERTY) &&
                                                    sourceContentNode.hasProperty(ATTRIBUTE_CA_CONFIG_PROPERTY)) {
                                                tempNodeContent.setProperty(ATTRIBUTE_CA_CONFIG_PROPERTY,
                                                    sourceContentNode.getProperty(ATTRIBUTE_CA_CONFIG_PROPERTY).getString());
                                            }

                                            // Set the cq:cloudserviceconfigs property on this folder if relative folder in source path has and target doesn't have it
                                            if (!tempNodeContent.hasProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY) &&
                                                    sourceContentNode.hasProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY)) {
                                                tempNodeContent.setProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY,
                                                    sourceContentNode.getProperty(ATTRIBUTE_CLOUD_CONFIG_PROPERTY).getValues());
                                            }
                                        }
                                    }
                                }
                                tempAddTitlePath = tempAddTitlePath.substring(0, tempAddTitlePath.lastIndexOf('/'));
                            }
                        }

                        //set IsTransCreated on Translation Created Folders
                        String pathAlreadyPresent = DamConstants.MOUNTPOINT_ASSETS + pathToCheck.substring(0, pathToCheck.indexOf(transCreatedPath));
                        setTransCreatedFlagToPathResource(transCreatedPath, pathAlreadyPresent, resourceResolver);
                    }
                } else {
                    log.info(
                        "Could not create language copy for assets at path: " + targetPath +
                            ", resource already exists. Updating language copy.");
                    Asset sourceAsset =null, targetAsset = null;
                    if (resourceResolver.getResource(sourcePath) != null) {
                        sourceAsset = resourceResolver.getResource(sourcePath).adaptTo(Asset.class);
                    }
                    if (resourceResolver.getResource(targetPath) != null) {
                        targetAsset = resourceResolver.getResource(targetPath).adaptTo(Asset.class);
                    }
                    if (sourceAsset != null && targetAsset != null) {
                        ContentState contentState = getContentStateForSmartTranslation(sourceAsset, targetAsset, resourceResolver);
                        if (contentState != ContentState.IS_TRANSLATED && contentState != ContentState.IS_UNKNOWN) {
                            addSmartAssetUpdateSource(targetAsset, sourcePath);
                            if (contentState == ContentState.IS_UPDATE_STATE) {
                                addSmartAssetUpdateProperty(targetAsset, true);
                            }
                        } else {
                            addSmartAssetUpdateProperty(targetAsset, false);
                        }
                    }
                }
                if (session.hasPendingChanges()) {
                    session.save();
                }
                createdCopies.add(targetPath);
            } catch (Exception e) {
                // if failed before saving the sesion then refresh it (skip the current asset)
                if (session != null && session.isLive()) {
                    try {
                        session.refresh(false); //keepChanges = false (discard all changes to skip the current asset)
                    } catch (RepositoryException re) {
                        log.error("error while refreshing the session: ", re);
                    }
                }
                log.error( "error while creating language copy for assets at path: " + targetPath + "{}", e);
            }
        }
        return createdCopies;

    }

    private static void associateSourceJcrUuidToAssetLanguageCopyAndSetLanguageRoot(Resource destinationResource, Resource srcResource, Session session, String strDestinationLanguage) throws RepositoryException {
        if(destinationResource != null && srcResource != null){
            Node destinationNode = destinationResource.adaptTo(Node.class);
            Node srcNode = srcResource.adaptTo(Node.class);
            if (srcNode != null && destinationNode != null) {
                if (!srcNode.hasProperty(JCR_UUID)) {
                    LockProperties lockProperties = new LockProperties.PropertyBuilder().build();
                    try {
                        // Lock the node to avoid concurrent modification on source node
                        addMixinTypeInNodeWithLock(srcNode, JcrConstants.MIX_REFERENCEABLE, lockProperties);
                    } catch (AccessDeniedException e) {
                        String errorMessage = String.format("Taking lock on node [%s] failed. Access denied to lock node", srcNode.getPath());
                        log.error(errorMessage, e);
                        // To do. Add notification to user when Sync feature is enabled. https://jira.corp.adobe.com/browse/CQ-4357718
                    } catch (LockException e) {
                        String errorMessage = String.format("Taking lock on node [%s] failed. Node is already locked by another session",
                            srcNode.getPath());
                        log.error(errorMessage, e);
                        // To do. Add notification to user when Sync feature is enabled. https://jira.corp.adobe.com/browse/CQ-4357718
                    } catch (TranslationException e) {
                        String errorMessage = String.format("Taking lock on node [%s] failed.", srcNode.getPath());
                        log.error(errorMessage, e);
                        // To do. Add notification to user when Sync feature is enabled. https://jira.corp.adobe.com/browse/CQ-4357718
                    }
                }
                if (srcNode.hasProperty(JCR_UUID)) {
                    String uuid = srcNode.getProperty(JCR_UUID).getValue().toString();
                    if (!StringUtils.isEmpty(uuid)) {
                        if (destinationNode.hasNode(JCR_CONTENT)) {
                            Node jcrContent = destinationNode.getNode(JCR_CONTENT);
                            jcrContent.setProperty(CQ_TRANSLATION_SOURCE_JCR_UUID, uuid, PropertyType.WEAKREFERENCE);
                            log.debug("cq:TranslationSourceUUID set for resource {} from source {}", destinationResource.getPath(), srcResource.getPath());
                            jcrContent.setProperty(CQ_LANGUAGE_ROOT, strDestinationLanguage);
                            log.debug( CQ_LANGUAGE_ROOT + " set for resource {} to value {}", destinationResource.getPath(), strDestinationLanguage);
                        } else {
                            log.error("Error while Associating Source JCR UUID to Asset Language copy, destination's jcr:content node doesn't exist for {}", destinationResource.getPath());
                        }
                    } else {
                        log.error("Error while Associating Source JCR UUID to Asset Language copy, Source JCR UUID for Node is empty: {}", srcNode.getPath());
                    }
                } else  {
                    log.error("Error while Associating Source JCR UUID to Asset Language copy, source node: {} uuid doesn't exist", srcResource.getPath());
                }
            } else {
                log.error("Error while Associating Source JCR UUID to Asset Language copy, source node : {}  or destination node is null", srcResource.getPath(), destinationNode.getPath());
            }
        }
    }
    
    /**
     * This method is used to add mixin type to a node after locking it and unlocing it afterwards.
     * @param node
     * @param mixinType
     * @param lockProperties
     * @throws RepositoryException
     * @throws TranslationException
     */
    private static void addMixinTypeInNodeWithLock(Node node, String mixinType, LockProperties lockProperties)
        throws RepositoryException, TranslationException {
        Session session = node.getSession();
        Lock lock;
        try {
            lock = LockUtil.lockNode(node, lockProperties);
        } catch (LockException | AccessDeniedException | TranslationException e) {
            throw e;
        }
        if (lock != null) {
            if (node.canAddMixin(mixinType)) {
                node.addMixin(mixinType);
                session.save();
            }
            // Unlock the node
            try {
                if (LockUtil.canUnlock(node)) {
                    LockUtil.unlockNode(node);
                } else {
                    String errorMessage = String.format("User %s does not have permission to unlock resource %s." +
                            " This node is locked by user %s. Please contact the administrator.", session.getUserID(), node.getPath(),
                        lock.getLockOwner());
                    log.error(errorMessage);
                    throw new AccessDeniedException(errorMessage);
                }
            } catch (LockException | AccessDeniedException | TranslationException e) {
                log.error("Error while unlocking the node", e);
            }
        }
    }

    private static void addTranslationSourcePath(Resource destinationResource, String translationSourcePath) throws
        RepositoryException {
        if (destinationResource != null) {
            Node destNode = destinationResource.adaptTo(Node.class);
            if (destNode != null) {
                Node contentNode = destNode.getNode(JCR_CONTENT);
                if (contentNode != null) {
                    contentNode.setProperty(ATTRIBUTE_CQ_TRANSLATION_SOURCE_PATH, translationSourcePath);
                }
            } else {
                log.warn("Resource not able to adaptTo Node object while adding translation source path:" + destinationResource.getPath());
            }
        }
    }

    private static Asset findLanguageCopy(final String assetPath,
            final String languageCode, final ResourceResolver resolver) {

        Resource assetResource = resolver.getResource(assetPath);
        if (assetResource == null || assetResource.adaptTo(Asset.class) == null) {
            return null;
        }

        Asset asset = null;
        String languageRootPath = LanguageUtil.getLanguageRoot(assetPath);

        if(languageRootPath == null){
            return null;
        }

        String contentPath = assetPath.replaceFirst(languageRootPath, "");
        String languageRootParentPath = Text.getRelativeParent(
            languageRootPath, 1);
        String destinationLanguageRootPath = getDestinationLanguageRoot(languageRootParentPath , languageCode,
            resolver);

        String assetPathLC = destinationLanguageRootPath + contentPath;
        Resource assetResourceLC = resolver.getResource(assetPathLC);
        if (assetResourceLC != null) {
            asset = assetResourceLC.adaptTo(Asset.class);
        }
        return asset;
    }

    /**
     * Returns the language root for the given asset path by only analyzing the
     * path names starting at the root. For this implementation, language root
     * for dam was suffix of folder name joined by "-" ex. geometrixx-en.
     *
     * @param path path
     * @return the language root or null if not found
     *
     * @deprecated since 6.2, use {@link com.day.cq.commons.LanguageUtil} instead
     */
    @Deprecated
    public static String getLanguageRoot(String path) {
        throw new UnsupportedOperationException("This API has been deprecated.Please use com.day.cq.commons.LanguageUtil instead.");
    }

    /**
     * Returns the language for the given asset path by only analyzing the
     * path names starting at the root. For this implementation, language
     * root for dam was suffix of folder name joined by "-" ex. geometrixx-en.
     *
     * @param path path
     * @return the language or null if not found
     *
     * @deprecated since 6.2, use {@link com.day.cq.commons.LanguageUtil} instead
     */
    @Deprecated
    public static Language getLanguage(String path) {
        throw new UnsupportedOperationException("This API has been deprecated.Please use com.day.cq.commons.LanguageUtil instead.");
    }

    /**
     * Modified version of com.day.cq.wcm.core.impl.LanguageManagerImpl for Resources
     *
     * Returns a collection of language root pages for the given asset. Note that
     * only the path names are respected for the determination of the language.
     *
     * @param resolver resource resolver
     * @param path path of the current page
     * @return collection of language root paths
     */
    public static Collection getLanguageRoots(ResourceResolver resolver, String path) {
        Iterator resources = getLanguageRootSiblings(resolver, path);
        if (resources == null) {
            return Collections.emptySet();
        }
        List roots = new ArrayList();
        while(resources.hasNext()) {
            Resource res = resources.next();
            Locale locale = getLocaleFromResource(res);
            if (locale != null) {
                roots.add(res);
            }
        }

        Resource currentResource = resolver.getResource(path);
        if (currentResource != null) {
            Resource langRoot = getLanguageRootResource(currentResource);
            if (null != langRoot) {
                Resource langRootParent = langRoot.getParent();
        
                boolean additionalLanguageRootsFound = false;
                //go one level down
                Iterator iter = langRootParent.listChildren();
                while (iter.hasNext()) {
                    Resource sibling = iter.next();
                    Locale locale = getLocaleFromResource(sibling);
                    if (locale == null) {
                        additionalLanguageRootsFound = addLanguageRootsFromChildren(roots,
                            additionalLanguageRootsFound, sibling);
                    }
                }
        
                if (additionalLanguageRootsFound) {
                    //found roots one level down, no need to go up. Therefore, return
                    return roots;
                }
        
                //one level up
                //find language roots which are not language countries
                Resource langRootGrandParent = langRootParent.getParent();
                ArrayList nonLangRootUncles = new ArrayList();
                if (langRootGrandParent != null) {
                    Iterator langRootUncles = langRootGrandParent.listChildren();
                    while (langRootUncles.hasNext()) {
                        Resource langRootUncle = langRootUncles.next();
                        if (langRootUncle.getName().equals(langRootParent.getName())) { //node comparison not working :(
                            //already added
                            continue;
                        }
                
                        Locale gcLocale = getLocaleFromResource(langRootUncle);
                        //todo check that this is not a country node
                        if (gcLocale != null && !isCountryNode(langRootUncle)) {
                            roots.add(langRootUncle);
                            additionalLanguageRootsFound = true;
                        } else {
                            nonLangRootUncles.add(langRootUncle);
                        }
                    }
                }
        
                if (!additionalLanguageRootsFound) {
                    //didn't found roots one level up, no need to go down on nonLangRootUncles. Therefore, return
                    return roots;
                }
        
                for (Resource nonLangRootUncle : nonLangRootUncles) {
                    additionalLanguageRootsFound = addLanguageRootsFromChildren(roots,
                        additionalLanguageRootsFound, nonLangRootUncle);
                }
            }
        }
        return roots;
    }

    private static boolean addLanguageRootsFromChildren(List roots, boolean additionalLanguageRootsFound,
        Resource resource) {
        Iterator children = resource.listChildren();
        while (children.hasNext()) {
            Resource child = children.next();
            Locale childLocale = getLocaleFromResource(child);
            if (childLocale != null) {
                roots.add(child);
                additionalLanguageRootsFound = true;
            }
        }
        return additionalLanguageRootsFound;
    }

    private static Iterator getLanguageRootSiblings(ResourceResolver resolver, @Nonnull String path) {
        String root = LanguageUtil.getLanguageRoot(path);
        if (root == null) {
            return null;
        }
        String parent = Text.getRelativeParent(root, 1);
        Resource parentResource = resolver.getResource(parent);
        if (parentResource == null) {
            return null;
        }
        return resolver.listChildren(parentResource);
    }

    private static Resource getLanguageRootResource(@Nonnull Resource res) {
        String rootPath = LanguageUtil.getLanguageRoot(res.getPath());
        if (rootPath == null) {
            return null;
        }
        return res.getResourceResolver().getResource(rootPath);
    }

    /**
     * This method creates update language copy of an asset/folder
     *
     * @param resourceResolver
     * @param pageManagerFactory
     * @param sourcePath          - source for creating language copy
     * @param targetLanguageCode - destination language code
     * @param prefixPath - Root path where language copies are created
     * @return
     */
    public static String createUpdateLanguageCopy(final ResourceResolver resourceResolver, final PageManagerFactory pageManagerFactory, final String sourcePath,
                                                  final String targetLanguageCode, String prefixPath) {

        Session session = resourceResolver.adaptTo(Session.class);
        PageManager pageManager = pageManagerFactory.getPageManager(resourceResolver);
        Resource sourceResource = resourceResolver.getResource(sourcePath);
        String createdCopy = "";
        String contentPath = "";

        if (targetLanguageCode == null || targetLanguageCode.trim().length() == 0) {
            log.error("Failed to load destination language from payload.");
            return null;
        }

        String root = LanguageUtil.getLanguageRoot(sourcePath);
        String parentOfRoot = null;
        boolean createNewLanguageRoot = false;
        if (root == null) {
            log.debug("Language root does not exist for asset at path: {} and would be created. ", sourcePath);

            //CQ-61450 User creates a language copy of a page from sites and language root for the asset (referenced in site) does not exist
            if (Text.getRelativeParent(sourcePath, 1).equals(DamConstants.MOUNTPOINT_ASSETS)) {
                //Asset is directly under DamConstants.MOUNTPOINT_ASSETS
                parentOfRoot = DamConstants.MOUNTPOINT_ASSETS;
                root = DamConstants.MOUNTPOINT_ASSETS;
            } else if (sourcePath.startsWith(DamConstants.MOUNTPOINT_ASSETS + "/")) {
                //Asset follows structure DamConstants.MOUNTPOINT_ASSETS//
                int parentOfRootPathLength = sourcePath.indexOf('/', DamConstants.MOUNTPOINT_ASSETS.length() + 1);
                int oldRootPathLength = sourcePath.indexOf('/', parentOfRootPathLength);
                if (parentOfRootPathLength < 0 || sourcePath.length() <= parentOfRootPathLength) {
                    return createdCopy;
                }
                parentOfRoot = sourcePath.substring(0, parentOfRootPathLength);

                if (oldRootPathLength > 0 && sourcePath.length() > oldRootPathLength) {
                    root = sourcePath.substring(0, oldRootPathLength);
                }
            }
            createNewLanguageRoot = true;
            log.info("Parent of New Language root at path {} added for asset at path {}", parentOfRoot, sourcePath);
            if (parentOfRoot != null) {
                contentPath = sourcePath.replaceFirst(parentOfRoot, "");
            } else {
                parentOfRoot = "";
            }
        } else {
            contentPath = sourcePath.replaceFirst(root, "");
            parentOfRoot = Text.getRelativeParent(root, 1);
        }

        String targetPath = "";
        String languageRootPath = "";

        String strDestinationLanguage = getDestinationLanguageWithAllowedDelimiters(prefixPath + parentOfRoot,
                targetLanguageCode, resourceResolver);

        languageRootPath = prefixPath + getDestinationLanguageRoot(parentOfRoot, strDestinationLanguage,
                resourceResolver);
        targetPath = languageRootPath + contentPath;
        String destAssetPath = getDestinationLanguageRoot(parentOfRoot, strDestinationLanguage, resourceResolver) + contentPath;;
        try {
//            if (contentPath.trim().length() > 0) {
                String pathToCreate = Text.getRelativeParent(
                        targetPath, 1);
                String nodeType = "sling:Folder";

                if (session.getNode(root).isNodeType(
                        "sling:OrderedFolder")) {
                    nodeType = "sling:OrderedFolder";
                }
                JcrUtil.createPath(pathToCreate, nodeType,
                        nodeType, session, false);
//            }

            if (null == resourceResolver.getResource(targetPath)) {
                if (DamUtil.isAsset(sourceResource)) {
                    Resource destinationResource = pageManager.copy(sourceResource, targetPath, null, false, true, false);
                    // Remove Derived, Others Relations from this resource, if any. Ensures that if this resource,
                    // is a source Asset then the derived, others relations don't point to old language copies
                    if (destinationResource != null) {
                        com.adobe.granite.asset.api.Asset destinationGraniteAsset = destinationResource.adaptTo(com.adobe.granite.asset.api.Asset.class);
                        if (destinationGraniteAsset != null) {
                            removeAssetRelation(destinationGraniteAsset, ATTRIBUTE_ASSET_DERIVED_RELATION);
                            removeAssetRelation(destinationGraniteAsset, ATTRIBUTE_ASSET_OTHERS_RELATION);
                        }
    
                        Resource destAssetResource = resourceResolver.getResource(destAssetPath);
                        associateSourceJcrUuidToAssetLanguageCopyAndSetLanguageRoot(destAssetResource, sourceResource, session, strDestinationLanguage);
                        addTranslationSourcePath(destinationResource, sourceResource.getPath());
                        setLastTranslationUpdate(destinationResource);
                        setIsTranslationCreated(destinationResource);
                        deleteInsightData(destinationResource.getPath(), resourceResolver);
                        removeAllRenditionsInsideResource(destinationResource);
                        String destinationForTemporaryAsset = parentOfRoot + "/" + strDestinationLanguage + contentPath;
                        setDestinationLanguageCopyPath(destinationResource, destinationForTemporaryAsset);
                        createdCopy = destinationResource.getPath();
                    }
                }
                //changing the title of the folder
                Node targetNode = session.getNode(languageRootPath);
                if (targetNode != null) {
                    if (!targetNode.hasNode(JcrConstants.JCR_CONTENT)) {
                        targetNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
                    }
                    Node content = targetNode.getNode(JcrConstants.JCR_CONTENT);

                    if (!content.hasProperty(JcrConstants.JCR_TITLE)) {
                        String displayLanguage = getLanguageDisplayName(resourceResolver, strDestinationLanguage);
                        content.setProperty(JcrConstants.JCR_TITLE, displayLanguage);
                    }

                    //set JCR_TITLE for all folders in the created contentPath and language root
                    String tempAddTitlePath = contentPath.substring(0, contentPath.lastIndexOf('/'));
                    while (tempAddTitlePath.length() > 0) {
                        Node tempNode = session.getNode(languageRootPath + tempAddTitlePath);
                        if (tempNode != null) {
                            if (!tempNode.hasNode(JcrConstants.JCR_CONTENT)) {
                                tempNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
                            }
                            Node tempNodeContent = tempNode.getNode(JcrConstants.JCR_CONTENT);
                            String sourceNodeForTitle = null;
                            if (createNewLanguageRoot) {
                                sourceNodeForTitle = parentOfRoot + tempAddTitlePath;
                            } else {
                                sourceNodeForTitle = root + tempAddTitlePath;
                            }
                            if (!tempNodeContent.hasProperty(JcrConstants.JCR_TITLE)) {
                                String tempNodeTitle = UIHelper.getTitle(resourceResolver.
                                    getResource(sourceNodeForTitle));
                                tempNodeContent.setProperty(JcrConstants.JCR_TITLE, tempNodeTitle);
                            }
                            tempAddTitlePath = tempAddTitlePath.substring(0, tempAddTitlePath.lastIndexOf('/'));
                        }
                    }
                }
            } else {
                createdCopy = targetPath;
            }
            if (session.hasPendingChanges()) {
                session.save();
            }
        } catch (Exception e) {
            // if failed before saving the sesion then refresh it, and ignore all the pending changes for this asset
            if (session != null && session.isLive()) {
                try {
                    session.refresh(false); //keepChanges = false (discard all changes to skip the current asset)
                }
                catch (RepositoryException re) {
                    log.error("error while refreshing the session: ", re);
                }
            }
            log.error("error while creating language copy for assets at path: " + targetPath + "{}", e);
        }

        return createdCopy;
    }

    private static String getDestinationLanguageWithAllowedDelimiters(String contentPath, String strDestinationLanguage,
        ResourceResolver resourceResolver) {
        String langWithHyphen = strDestinationLanguage.replace("_", "-");
        String langWithUnderscore = strDestinationLanguage.replace("-", "_");
        boolean langWithHyphenExist = (null != resourceResolver.getResource(contentPath + "/" + langWithHyphen));
        boolean langWithUnderscoreExist = (null != resourceResolver.getResource(contentPath + "/" +
            langWithUnderscore));

        if (langWithHyphenExist && langWithUnderscoreExist) {
            return strDestinationLanguage;
        } else if (langWithHyphenExist) {
            return langWithHyphen;
        } else if (langWithUnderscoreExist) {
            return langWithUnderscore;
        } else {
            return strDestinationLanguage;
        }
    }

    private static void deleteInsightData(String resourcePath, ResourceResolver resourceResolver) {
        Resource resource = resourceResolver.getResource(resourcePath);
        if (resource != null) {
            if (DamUtil.isAsset(resource)) {
                deleteResource(resourcePath + ASSET_PERFORMANCE_NODE_RELATIVE_PATH, resourceResolver);
                deleteResource(resourcePath + ASSET_USAGE_NODE_RELATIVE_PATH, resourceResolver);
            } else if (isFolder(resource)) {
                Iterator assetIterator = DamUtil.getAssets(resource);
                while (assetIterator.hasNext()) {
                    String assetPath = assetIterator.next().getPath();
                    deleteResource(assetPath + ASSET_PERFORMANCE_NODE_RELATIVE_PATH, resourceResolver);
                    deleteResource(assetPath + ASSET_USAGE_NODE_RELATIVE_PATH, resourceResolver);
                }
            }
        }
    }

    private static void deleteResource(String path, ResourceResolver resourceResolver) {
        Resource resource = resourceResolver.getResource(path);
        if (resource != null) {
            try {
                resourceResolver.delete(resource);
            } catch (PersistenceException e) {
                log.error("Unable to delete resource from {} : {}", resource, e.getMessage());
            }
        }
    }


    private static void setDestinationLanguageCopyPath(Resource temporaryResource, String destinationPath)
            throws RepositoryException {
        Node temporaryNode = temporaryResource.adaptTo(Node.class);
        if(temporaryNode != null) {
            if (!temporaryNode.hasNode(JcrConstants.JCR_CONTENT)) {
                temporaryNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
            }
            Node destinationContentNode = temporaryNode.getNode(JcrConstants.JCR_CONTENT);
            destinationContentNode.setProperty(ATTRIBUTE_DESTINATION_LANGUAGE_COPY_PATH, destinationPath);
        } else {
            log.warn("Resource not able to adaptTo Node object while setting destination language copy path:" + temporaryResource.getPath());
        }
    }

    private static boolean isFolder(Resource resource){
        Node n = resource.adaptTo(Node.class);
        try {
            if (n != null) {
                return n.isNodeType(NT_FOLDER);
            } else {
                log.debug("Resource not able to adaptTo Node object while checking if resource is folder:" + resource.getPath());
            }
            return false;
        } catch (RepositoryException e) {
            return false;
        }
    }

    /**
     *
     * @param sourcePath
     * @param destinationPath
     * @param userSession
     * @param pageManagerFactory
     * @param resolverFactory
     *
     * @deprecated since 6.2, use
     * {@link #moveUpdatedAsset(String, String, Session, PageManagerFactory, ResourceResolver)}  instead
     */
    @Deprecated
    public static void moveUpdatedAsset(String sourcePath, String destinationPath, Session userSession,
                                        PageManagerFactory pageManagerFactory, ResourceResolverFactory resolverFactory){
        throw new UnsupportedOperationException("This API has been deprecated.Please use moveUpdatedAsset(String, String, Session, PageManagerFactory, "
                + "ResourceResolver) instead.");
    }

    public static void moveUpdatedAsset(String sourcePath, String destinationPath, Session userSession,
                                        PageManagerFactory pageManagerFactory, ResourceResolver resourceResolver){
        Map authInfo = new HashMap();
        authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, userSession);
        try {
            Resource sourceResource = resourceResolver.getResource(sourcePath);
            Resource destinationResource = resourceResolver.getResource(destinationPath);
            Asset sourceAsset = DamUtil.resolveToAsset(sourceResource);
            if(sourceAsset != null) {
                PageManager pageManager = pageManagerFactory.getPageManager(resourceResolver);
                if(destinationResource == null){
                    String pathToCreate = Text.getRelativeParent(
                            destinationPath, 1);
                    String nodeType = "sling:Folder";
//                if (userSession.getNode(destinationPath).isNodeType(
//                        "sling:OrderedFolder")) {
//                    nodeType = "sling:OrderedFolder";
//                }
                    JcrUtil.createPath(pathToCreate, nodeType,
                            nodeType, userSession, false);
                    pageManager.copy(sourceResource, destinationPath, null, false,
                            false, true);
                } else {
                    Asset destinationAsset = DamUtil.resolveToAsset(destinationResource);
                    if(destinationAsset != null) {
                    	destinationAsset.addRendition(DamConstants.ORIGINAL_FILE, sourceAsset.getOriginal().getBinary(), sourceAsset.getMimeType());
                    } else {
                        log.error("Unable to move updated asset : Destination Asset not found");
                    }
                    //todo copy metadata
                }
                userSession.save();
            } else {
                log.error("Unable to move updated asset : Source Asset not found");
            }
        } catch (Exception e) {
            log.error("Unable to move updated asset {}", e.getMessage());
        }
    }

    /**
     * Update the destination asset with all updates done on source asset.
     * It will not trigger Asset Processor or Update Asset workflow for the updated asset.
     * Caller of the method should trigger Asset Processor or Update Workflow for the
     * updated asset to create the renditions of the updated asset.
     *
     * @param sourcePath         Source Path of Asset
     * @param destinationPath    Destination Path of Asset
     * @param userSession        Will be used to save the session.
     * @param pageManagerFactory Will be use to get pageManager
     * @param resourceResolver   Will be used to get the resource
     */
    public static void replaceUpdatedAsset(String sourcePath, String destinationPath, Session userSession,
        PageManagerFactory pageManagerFactory, ResourceResolver resourceResolver){
        try {
            Resource sourceResource = resourceResolver.getResource(sourcePath);
            Resource destinationResource = resourceResolver.getResource(destinationPath);

            Asset sourceAsset = sourceResource.adaptTo(Asset.class);
            if (sourceAsset!= null && !isContentFragment(sourceAsset)) {
                addExtractMetadataPropertyForAsset(sourceAsset, false);
            }
            //See the discussion on CQ-4235761 and CQ-39476. Copy and create are two separate functionalities and we will
            //not trigger update_asset workflow on PageManager.copy(). Hence it needs to be triggered explicitly after this method execution
            //to create the renditions of the translated asset.
            if(sourceResource != null) {
                PageManager pageManager = pageManagerFactory.getPageManager(resourceResolver);
                if(destinationResource == null){
                    String pathToCreate = Text.getRelativeParent(
                            destinationPath, 1);
                    String nodeType = "sling:Folder";
                    JcrUtil.createPath(pathToCreate, nodeType,
                            nodeType, userSession, false);
                    pageManager.copy(sourceResource, destinationPath, null, false,
                        false, true);
                } else {
                    //create version
                    Asset destinationAsset = resourceResolver.getResource(destinationPath).adaptTo(Asset.class);
                    String languageRoot = null;
                    String destJcrUuid = null;
                    if (destinationAsset != null) {
                        Node destinationAssetNode = destinationAsset.adaptTo(Node.class);
                        if (destinationAssetNode != null) {
                            if (destinationAssetNode.hasNode(JCR_CONTENT)) {
                                Node destContentNode = destinationAssetNode.getNode(JCR_CONTENT);
                                if (destContentNode.hasProperty(CQ_TRANSLATION_SOURCE_JCR_UUID)) {
                                    destJcrUuid = destContentNode.getProperty(CQ_TRANSLATION_SOURCE_JCR_UUID).getValue().getString();
                                }
                                if (destContentNode.hasProperty(CQ_LANGUAGE_ROOT)) {
                                    languageRoot = destContentNode.getProperty(CQ_LANGUAGE_ROOT).getValue().getString();
                                }
                            }
                        }
                        destinationAsset.createRevision("", ASSET_VERSION_MESSAGE);
                        copyInsightData(destinationResource.getPath(), sourceResource.getPath(), resourceResolver,
                            pageManager);
                        deleteAllChildren(destinationResource, userSession);
                        copyAllChildren(sourceResource, destinationResource, pageManager, userSession);
                        Node updatedDestNode = destinationResource.adaptTo(Node.class);
                        if (updatedDestNode != null) {
                            if (updatedDestNode.hasNode(JCR_CONTENT)) {
                                Node updatedDestNodeJcrContent = updatedDestNode.getNode(JCR_CONTENT);
                                if (!StringUtils.isEmpty(destJcrUuid)) {
                                    updatedDestNodeJcrContent.setProperty(CQ_TRANSLATION_SOURCE_JCR_UUID, destJcrUuid, PropertyType.WEAKREFERENCE);
                                }
                                
                                if (!StringUtils.isEmpty(languageRoot)) {
                                    updatedDestNodeJcrContent.setProperty(CQ_LANGUAGE_ROOT, languageRoot);
                                }
                            }
                        }
                    } else {
                        log.error("Unable to move updated asset : Destination is not an Asset");
                    }
                }
                userSession.save();
            } else {
                log.error("Unable to move updated asset : Source Resource not found");
            }
        } catch (Exception e) {
            log.error("Unable to move updated asset {}", e.getMessage());
        }
    }

    private static void copyInsightData(String sourcePath, String destinationPath, ResourceResolver resourceResolver,
        PageManager pageManager) {
        copyResource(sourcePath + ASSET_PERFORMANCE_NODE_RELATIVE_PATH,
            destinationPath + ASSET_PERFORMANCE_NODE_RELATIVE_PATH, resourceResolver, pageManager);
        copyResource(sourcePath + ASSET_USAGE_NODE_RELATIVE_PATH, destinationPath + ASSET_USAGE_NODE_RELATIVE_PATH,
            resourceResolver, pageManager);
    }

    private static void copyResource(String sourcePath, String destinationPath, ResourceResolver resourceResolver,
        PageManager pageManager) {
        Resource sourceResource = resourceResolver.getResource(sourcePath);
        if (sourceResource != null) {
            try {
                pageManager.copy(sourceResource, destinationPath, null, false, false, false);
            } catch (WCMException e) {
                log.error("Unable to copy resource from " + sourcePath + " to " + destinationPath + " : {}",
                    e.getMessage());
            }
        }
    }

    private static void copyAllChildren(Resource sourceResource, Resource destinationResource, PageManager pageManager,
        Session session) throws RepositoryException, WCMException {
        if (sourceResource != null && destinationResource != null) {
            Iterable childList = sourceResource.getChildren();
            String destinationResourcePath = destinationResource.getPath();
            for (Resource child : childList) {
                String destinationChildPath = destinationResourcePath + "/" + child.getName();
                pageManager.copy(child, destinationChildPath, null, false, false, false);
            }
            session.save();
        }
    }

    private static void deleteAllChildren(Resource resource, Session session) throws RepositoryException {
        if (resource != null) {
            Iterable childList = resource.getChildren();
            for (Resource child : childList) {
                Node childNode = child.adaptTo(Node.class);
                if (childNode != null) {
                    childNode.remove();
                } else {
                    log.warn("Resource not able to adaptTo Node object while deleting the children:" + resource.getPath());
                }
            }
        }
        session.save();
    }

    /**
     * Removes all asset renditions contained in the resource.
     * If resource is a folder, then all its folders are navigated recursively to delete asset renditions.
     * @param resource resource
     */
    private static void removeAllRenditionsInsideResource(Resource resource) throws RepositoryException {
        Iterator assets = DamUtil.getAssets(resource);
        while (assets.hasNext()) {
            Asset asset = assets.next();
            if (!isContentFragment(asset)) {
                List renditions = asset.getRenditions();
                for (Rendition rendition : renditions) {
                    String name = rendition.getName();
                    if (!name.equals(DamConstants.ORIGINAL_FILE)) {
                        asset.removeRendition(name);
                    }
                }
            }
        }
    }

    private static boolean isContentFragment(Asset asset) throws RepositoryException {
        try {
            Node assetNode = asset.adaptTo(Node.class);
            Node jcrNode = assetNode;
            if (assetNode != null && assetNode.hasNode(JCR_CONTENT)){
                jcrNode = assetNode.getNode(JCR_CONTENT);
            }
            if (jcrNode != null && jcrNode.hasProperty(CONTENT_FRAGMENT)) {
                return jcrNode.getProperty(CONTENT_FRAGMENT).getBoolean();
            }
            return false;
        } catch (RepositoryException e) {
            log.error("Error while checking if asset is a content fragment", e);
            return false;
        }
    }

    public static boolean isSmartAssetUpdateRequired(Asset sourceAsset, Asset destinationAsset) {
        if (sourceAsset == null || destinationAsset == null) {
            return false;
        }

        Resource sourceResource = sourceAsset.adaptTo(Resource.class);
        Resource destResource = destinationAsset.adaptTo(Resource.class);

        boolean bRetVal = false;
        if (sourceResource != null && destResource != null) {
            // now check the modified time
            Resource sourceContentResource = sourceResource.getChild(JcrConstants.JCR_CONTENT);
            Resource destContentResource = destResource.getChild(JcrConstants.JCR_CONTENT);
            if (sourceContentResource == null) {
                sourceContentResource = sourceResource;
            }
            if (destContentResource == null) {
                destContentResource = destResource;
            }
            Calendar sourceLastModified = getNodeLastModifiedTime(sourceContentResource);
            Calendar lastTranslationUpdate =
                getCalendarAttribute(destContentResource, ATTRIBUTE_CQ_TRANSLATION_LAST_UPDATE);
            if (lastTranslationUpdate != null) {
                if (sourceLastModified != null) {
                    bRetVal =
                        (sourceLastModified.getTimeInMillis() - lastTranslationUpdate.getTimeInMillis()) > MAX_DIFF_MILLISECOND_CHANGED;
                }
            } else {
                bRetVal = true; // no translation done till now
            }
        }
        return bRetVal;
    }

    @Deprecated
    /**
     *@deprecated since 6.2, use
     *{@link #addSmartAssetUpdateSource(Asset, String)}  instead
     */
    public static void addSmartAssetUpdateFlag(Asset destinationAsset) throws RepositoryException {
        throw new UnsupportedOperationException("This API has been deprecated.Please use addSmartAssetUpdateSource(Asset, String) instead.");
    }

    public static void addSmartAssetUpdateSource(Asset destinationAsset, String sourcePath) throws RepositoryException {
        if (destinationAsset == null || sourcePath == null) {
            return;
        }
        Node assetNode = destinationAsset.adaptTo(Node.class);
        if (assetNode != null) {
            if (!assetNode.hasNode(JcrConstants.JCR_CONTENT)) {
                assetNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
            }

            Node assetContentNode = assetNode.getNode(JcrConstants.JCR_CONTENT);
            assetContentNode.setProperty(ATTRIBUTE_SMART_ASSET_UPDATE_SOURCE, sourcePath);
        } else {
            log.warn("Asset not able to adaptTo Node object while adding smart assets update source:" +
                    destinationAsset.getPath());
        }
    }

    private static void addSmartAssetUpdateProperty(Asset destinationAsset, boolean value) throws RepositoryException {
        if (destinationAsset == null) {
            return;
        }

        Node assetNode = destinationAsset.adaptTo(Node.class);
        if (assetNode != null) {
            if (!assetNode.hasNode(JcrConstants.JCR_CONTENT)) {
                assetNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
            }
            Node assetContentNode = assetNode.getNode(JcrConstants.JCR_CONTENT);
            assetContentNode.setProperty(ATTRIBUTE_SMART_ASSET_UPDATE_REQUIRED, value);
        } else {
            log.warn("Asset not able to adaptTo Node object while adding smart assets update property:" +
                    destinationAsset.getPath());
        }
    }

    private static void addExtractMetadataPropertyForAsset(Asset asset, boolean bExtractMetadata) throws RepositoryException {
        Node assetNode = asset.adaptTo(Node.class);
        if (assetNode != null) {
            if (!assetNode.hasNode(JcrConstants.JCR_CONTENT)) {
                assetNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
            }
            Node assetContentNode = assetNode.getNode(JcrConstants.JCR_CONTENT);
            assetContentNode.setProperty(ATTRIBUTE_EXTRACT_METADATA, bExtractMetadata);
        } else {
            log.warn("Asset not able to adaptTo Node object while adding smart assets update property:" +
                    asset.getPath());
        }
    }

    /**
     * Creates language copy of an asset
     *
     * @param resourceResolver
     * @param pageManagerFactory
     * @param sourcePath
     * @param targetLanguageCode
     * @return path of created language copy or null if language copy is already exists and does not require translation.
     */
    private static String createLanguageCopy(final ResourceResolver resourceResolver,
        final PageManagerFactory pageManagerFactory, final String sourcePath, final String targetLanguageCode) {
        String[] targetLanguageCodeArray = {targetLanguageCode};
        List languageCopyPathArray = createLanguageCopy(resourceResolver, pageManagerFactory, sourcePath,
            targetLanguageCodeArray);
        if (null != languageCopyPathArray && languageCopyPathArray.size() == 1) {
            return languageCopyPathArray.get(0);
        }
        return null;
    }

    /**
     * This method creates language copy of an asset/folder and its source(for example psd for jpeg). Information about
     * language copy which triggered translation is added in case temporary asset translation is required. Finally,
     * relations are changed with their language copies.
     *
     * @param resourceResolver
     * @param pageManagerFactory
     * @param sourcePath          - source for creating language copy
     * @param targetLanguageCodes - array of language codes
     * @return list of created language copies
     */
    public static List createLanguageCopyWithAssetRelations(final ResourceResolver resourceResolver,
        final PageManagerFactory pageManagerFactory, final String sourcePath, final String[] targetLanguageCodes)
        throws RepositoryException {

        Resource sourceResource = resourceResolver.getResource(sourcePath);

        if (null == sourceResource) {
            //returning empty array list for backward compatibility
            return new ArrayList();
        }

        //check for asset
        if (null != sourceResource.adaptTo(Asset.class) && isCreateLangCopyForRFLResource(sourceResource, resourceResolver)) {
            return createLanguageCopyWithAssetRelationsForAsset(resourceResolver, pageManagerFactory,
                sourcePath, targetLanguageCodes);
        }

        //check for asset folder
        return checkAndCreateLanguageCopyWithAssetRelationsForFolder(resourceResolver, pageManagerFactory,
            sourceResource, sourcePath, targetLanguageCodes);

    }

    private static List createLanguageCopyWithAssetRelations(final ResourceResolver resourceResolver, final PageManagerFactory pageManagerFactory,
        final String sourcePath, final String[] targetLanguageCodes, HashMap> processedAssets)
        throws RepositoryException {
        Resource sourceResource = resourceResolver.getResource(sourcePath);
        if (null == sourceResource) {
            return new ArrayList();
        }
        if (null != sourceResource.adaptTo(Asset.class) && isCreateLangCopyForRFLResource(sourceResource, resourceResolver)) {
            return createLanguageCopyWithAssetRelationsForAsset(resourceResolver, pageManagerFactory,
                sourcePath, targetLanguageCodes, processedAssets);
        }
        return checkAndCreateLanguageCopyWithAssetRelationsForFolder(resourceResolver, pageManagerFactory,
            sourceResource, sourcePath, targetLanguageCodes);
    }

    private static List checkAndCreateLanguageCopyWithAssetRelationsForFolder(
        final ResourceResolver resourceResolver, final PageManagerFactory pageManagerFactory, final Resource sourceResource,
        final String sourcePath, final String[] targetLanguageCodes)
        throws RepositoryException {
        Node sourceNode = sourceResource.adaptTo(Node.class);
        if (null != sourceNode && sourceNode.isNodeType(JcrConstants.NT_FOLDER)) {
            return createLanguageCopyWithAssetRelationsForNTFolder(resourceResolver, pageManagerFactory,
                sourcePath, targetLanguageCodes);
        }

        //return empty array list for backward compatibility
        return new ArrayList();
    }

    private static String createLanguageCopyWithAssetRelations(final String sourcePath, final String targetLanguageCode,
        final ResourceResolver resourceResolver, final PageManagerFactory pageManagerFactory, HashMap> processedAssets)
        throws RepositoryException {
        String[] languageArray = {targetLanguageCode};
        List languageCopyList = createLanguageCopyWithAssetRelations(resourceResolver, pageManagerFactory,
            sourcePath, languageArray, processedAssets);
        if (languageCopyList != null && languageCopyList.size() == 1) {
            return languageCopyList.get(0);
        }
        return null;
    }

    private static void adjustDerivedAndRemoveOthersRelationsForSourceAssetsLC (List relatedSourceAssets, String newDerivedAssetPath,
        String oldDerivedAssetPath, ResourceResolver resourceResolver) {
        String oldLanguageRoot = getLanguageRootLocale(oldDerivedAssetPath);
        for(Asset sourceDamAsset: relatedSourceAssets) {
            String targetLanguageCode = getLanguageRootLocale(newDerivedAssetPath);
            String sourceAssetLCPath = findLanguageCopyPathWithAutoCreatedRoots(sourceDamAsset.getPath(),
                    targetLanguageCode, resourceResolver);
            if (sourceAssetLCPath != null) {
                Resource sourceAssetLCResource = resourceResolver.getResource(sourceAssetLCPath);
                if (sourceAssetLCResource != null) {
                    com.adobe.granite.asset.api.Asset sourceAssetLC = sourceAssetLCResource.adaptTo(com.adobe.granite.asset.api.Asset.class);
                    if (sourceAssetLC != null) {
                        // First remove derived relations that point to old language copies
                        removeAssetRelationWithLanguageCode(sourceAssetLC, ATTRIBUTE_ASSET_DERIVED_RELATION, oldLanguageRoot);
                        // Add new derived relation for the source asset
                        adjustAssetRelations(sourceAssetLC, ATTRIBUTE_ASSET_DERIVED_RELATION, oldDerivedAssetPath, newDerivedAssetPath);
                        // Delete the others relation for the source asset
                        removeAssetRelation(sourceAssetLC, ATTRIBUTE_ASSET_OTHERS_RELATION);
                    }
                }
            } else {
                log.debug("No source assets language copy path found for asset: " + sourceDamAsset.getPath());
            }
        }
    }

    public static List createLanguageCopyWithAssetRelationsForAsset(final ResourceResolver resourceResolver,
    final PageManagerFactory pageManagerFactory, final String sourcePath, final String[] targetLanguageCodes)
        throws RepositoryException {

        Resource sourceResource = resourceResolver.getResource(sourcePath);

        List createdVariantCopies = new ArrayList();
        if (null != sourceResource) {
            createdVariantCopies = createLanguageCopyWithRelationsForAsset(resourceResolver,
                pageManagerFactory, sourcePath, sourceResource, targetLanguageCodes);
            Asset sourceAsset = sourceResource.adaptTo(Asset.class);
            if (sourceAsset != null && isContentFragment(sourceAsset)) {
                try {
                    //commit changes to get variations by query. todo: remove once this is fixed
                    resourceResolver.commit();
                    HashMap> processedAssets = new HashMap<>();
                    for (String targetLanguageCode : targetLanguageCodes) {
                        HashSet processedAssetList = new HashSet<>();
                        processedAssetList.add(sourceAsset.getPath());
                        processedAssets.put(targetLanguageCode, processedAssetList);
                    }
                    createAndReplaceLanguageCopyForEmbeddedAssets(resourceResolver, pageManagerFactory, sourcePath,
                        targetLanguageCodes, processedAssets);
                } catch (Exception e) {
                    log.error("Could not create language copy for embedded assets. {}", e);
                }
            }
        }
        return createdVariantCopies;
    }

    private static List createLanguageCopyWithAssetRelationsForAsset(final ResourceResolver resourceResolver, final PageManagerFactory pageManagerFactory, final String sourcePath,
        final String[] targetLanguageCodes, HashMap> processedAssets)
        throws RepositoryException {

        Resource sourceResource = resourceResolver.getResource(sourcePath);
        List createdVariantCopies = new ArrayList();
        if (null != sourceResource) {
            createdVariantCopies = createLanguageCopyWithRelationsForAsset(resourceResolver,
                pageManagerFactory, sourcePath, sourceResource, targetLanguageCodes);
            Asset sourceAsset = sourceResource.adaptTo(Asset.class);
            if (sourceAsset != null && isContentFragment(sourceAsset)) {
                try {
                    resourceResolver.commit();
                    createAndReplaceLanguageCopyForEmbeddedAssets(resourceResolver, pageManagerFactory, sourcePath,
                        targetLanguageCodes, processedAssets);
                } catch (Exception e) {
                    log.error("Could not create language copy for embedded assets. {}", e);
                }
            }
        }
        return createdVariantCopies;
    }

    private static List createLanguageCopyWithRelationsForAsset(final ResourceResolver resourceResolver,
        final PageManagerFactory pageManagerFactory, final String sourcePath, final Resource sourceResource, final String[] targetLanguageCodes)
        throws RepositoryException {

        List createdVariantCopies = new ArrayList();
        if (null != sourceResource) {
            String sourceResourcePath = sourceResource.getPath();
            List relatedSourceAssets = getRelatedAssets(sourceResource, ATTRIBUTE_ASSET_SOURCE_RELATION);
            List relatedLinkedAssets = getRelatedAssets(sourceResource, ATTRIBUTE_ASSET_LINKS_RELATION);
            if (!relatedSourceAssets.isEmpty() || !relatedLinkedAssets.isEmpty()) {
                for (int languageIndex = 0; languageIndex < targetLanguageCodes.length; languageIndex++) {
                    TranslationCategory translationCategory = getTranslationCategoryForAssetAndItsRelations(sourcePath,
                        targetLanguageCodes[languageIndex], resourceResolver);
                    switch (translationCategory) {
                        case TEMPORARY:
                            //Temporary Language copies are required
                            createMissingDestinationLanguageCopies(resourceResolver, pageManagerFactory, sourcePath,
                                targetLanguageCodes[languageIndex]);
                            String languageCopyPath = findLanguageCopyPathWithAutoCreatedRoots(sourcePath,
                                targetLanguageCodes[languageIndex], resourceResolver);
                            if (languageCopyPath != null) {
                                Resource languageCopyPathResource = resourceResolver.getResource(languageCopyPath);
                                if (languageCopyPathResource != null) {
                                    Asset destinationAsset = languageCopyPathResource.adaptTo(Asset.class);
                                    addSmartAssetUpdateSource(destinationAsset, sourcePath);
                                    addSmartAssetUpdateProperty(destinationAsset, true);
                                    createdVariantCopies.add(languageCopyPath);
                                }
                            } else {
                                log.warn("Language copy path not found for source path: " + sourcePath +
                                        " and target language:" + targetLanguageCodes[languageIndex]);
                            }
                            break;
                        case DESTINATION:
                            //Destination Language copy needs to be created or updated.
                            String variantLanguageCopyPath = createOrGetLanguageCopy(resourceResolver,
                                pageManagerFactory, sourcePath, targetLanguageCodes[languageIndex]);
                            Resource variantLanguageCopyResource =
                                resourceResolver.getResource(variantLanguageCopyPath);
                            if (variantLanguageCopyResource != null) {
                                com.adobe.granite.asset.api.Asset graniteDestinationAsset = variantLanguageCopyResource
                                        .adaptTo(com.adobe.granite.asset.api.Asset.class);
                                if (graniteDestinationAsset != null) {
                                    createRelationLanguageCopy(ATTRIBUTE_ASSET_SOURCE_RELATION, graniteDestinationAsset,
                                            relatedSourceAssets, targetLanguageCodes[languageIndex], resourceResolver,
                                            pageManagerFactory);
                                    // Adjust the derived, others relations for the source assets
                                    adjustDerivedAndRemoveOthersRelationsForSourceAssetsLC(relatedSourceAssets, variantLanguageCopyPath,
                                            sourceResourcePath, resourceResolver);
                                    // Delete the others relation for destination asset
                                    removeAssetRelation(graniteDestinationAsset, ATTRIBUTE_ASSET_OTHERS_RELATION);
                                    createRelationLanguageCopy(ATTRIBUTE_ASSET_LINKS_RELATION, graniteDestinationAsset,
                                            relatedLinkedAssets, targetLanguageCodes[languageIndex], resourceResolver,
                                            pageManagerFactory);
                                    createRelationLanguageCopyForSourceRelations(ATTRIBUTE_ASSET_LINKS_RELATION,
                                            relatedSourceAssets, targetLanguageCodes[languageIndex],
                                            resourceResolver, pageManagerFactory);
                                    createdVariantCopies.add(variantLanguageCopyPath);
                                }
                            } else {
                                log.warn("Variant language copy resource not found for path: " + variantLanguageCopyPath);
                            }
                            break;
                        case NONE:
                            String tempLanguageCopyPath = findLanguageCopyPathWithAutoCreatedRoots(sourcePath,
                                targetLanguageCodes[languageIndex], resourceResolver);
                            if (tempLanguageCopyPath != null) {
                                Resource tempLanguageCopyResource = resourceResolver.getResource(tempLanguageCopyPath);
                                if (tempLanguageCopyResource != null) {
                                    Asset tempDestinationAsset = tempLanguageCopyResource.adaptTo(
                                            Asset.class);
                                    addSmartAssetUpdateProperty(tempDestinationAsset, false);
                                    createdVariantCopies.add(tempLanguageCopyPath);
                                }
                            } else {
                                log.warn("Language copy path not found for source path: " + sourcePath +
                                        " and target language:" + targetLanguageCodes[languageIndex]);
                            }
                            break;
                    }
                }
            } else {
                createdVariantCopies = createLanguageCopy(resourceResolver, pageManagerFactory, sourcePath,
                    targetLanguageCodes);
            }
            //todo add destination if required
            Asset sourceAsset = sourceResource.adaptTo(Asset.class);
            if(sourceAsset != null && isContentFragment(sourceAsset)) {
                try {
                    createOrUpdateLanguageCopyForAssociatedContent(resourceResolver, pageManagerFactory, sourcePath,
                        targetLanguageCodes);
                } catch (Exception e) {
                    log.error("Could not create language copy for associated content. {}", e);
                }
            }
        }
        return createdVariantCopies;
    }

    private static void createAndReplaceLanguageCopyForEmbeddedAssets(ResourceResolver resourceResolver, PageManagerFactory pageManagerFactory,
        String sourcePath, String[] targetLanguageCodes, HashMap> processedAssets)
        throws RepositoryException, IOException, WCMException, ContentFragmentException {
        String sourceLanguage = getLanguageRootLocale(sourcePath);
        for (String targetLanguageCode : targetLanguageCodes) {
            String languageCopyPath = findLanguageCopyPathWithAutoCreatedRoots(sourcePath, targetLanguageCode, resourceResolver);
            if (null != languageCopyPath && isTranslateInlineMediaAssets(languageCopyPath,
                resourceResolver)) {
                if (!temporaryAssetSourcePropertyExist(languageCopyPath, resourceResolver)) {
                    boolean bReplacementRequired = createLanguageCopyForEmbeddedAssets(languageCopyPath, sourceLanguage, targetLanguageCode,
                        resourceResolver, pageManagerFactory, processedAssets);
                    if (bReplacementRequired) {
                        Resource res = resourceResolver.getResource(languageCopyPath);
                        if (res != null) {
                            Node languageNode = res.adaptTo(Node.class);
                            if(languageNode != null) {
                                CFEmbeddedAssetUtil.updateEmbeddedAssetReferences(languageNode, targetLanguageCode, resourceResolver, true);
                            }
                        } else {
                            log.warn("Resource not found for the path: " + languageCopyPath);
                        }
                    }
                } else {
                    //create language copies or mark for updates for source asset
                    createLanguageCopyForEmbeddedAssets(sourcePath, sourceLanguage, targetLanguageCode, resourceResolver,
                        pageManagerFactory, processedAssets);
                }
            }
        }
    }

    private static boolean temporaryAssetSourcePropertyExist(String assetpath, ResourceResolver resourceResolver) throws
        RepositoryException {
        boolean retVal = false;
        Resource resource = resourceResolver.getResource(assetpath);
        if (null != resource) {
            Node node = resource.adaptTo(Node.class);
            if (node != null && getUpdateAssetNodeSourcePath(node) != null) {
                retVal = true;
            }
        }
        return retVal;
    }

    private static boolean createLanguageCopyForEmbeddedAssets(String contentFragmentPath, String sourceLanguageCode, String targetLanguageCode,
        ResourceResolver resourceResolver, PageManagerFactory pageManagerFactory, HashMap> processedAssets)
        throws RepositoryException, IOException {
        Set embeddedAssets = CFEmbeddedAssetUtil.getEmbeddedAssets(contentFragmentPath, resourceResolver, true);
        boolean bReplacementRequired = false;

        getLanguageRootLocale(contentFragmentPath);

        for (Asset embeddedAsset : embeddedAssets) {
            if (processedAssets.get(targetLanguageCode).contains(embeddedAsset.getPath())) {
                if (!targetLanguageCode.equals(getLanguageRootLocale(embeddedAsset.getPath()))) {
                    bReplacementRequired = true;
                }
                continue;
            }
            processedAssets.get(targetLanguageCode).add(embeddedAsset.getPath());
            String embeddedAssetPath = embeddedAsset.getPath();
            String sourceEmbeddedAssetPath;

            String languageRootLocale = getLanguageRootLocale(embeddedAssetPath);
            if (languageRootLocale != null) {
                if (!targetLanguageCode.equals(languageRootLocale)) {
                    sourceEmbeddedAssetPath = embeddedAssetPath;
                    bReplacementRequired = true;
                } else {
                    sourceEmbeddedAssetPath = findLanguageCopyPathWithAutoCreatedRoots(embeddedAssetPath, sourceLanguageCode,
                            resourceResolver);
                }
                if (sourceEmbeddedAssetPath != null) {
                    createLanguageCopyWithAssetRelations(sourceEmbeddedAssetPath, targetLanguageCode, resourceResolver,
                        pageManagerFactory, processedAssets);
                }
            } else {
                log.info("Not Creating Language copy because Language root does not exist for Embedded Asset {} ", embeddedAsset.getPath());
            }
        }
        return bReplacementRequired;
    }

    private static void createOrUpdateLanguageCopyForAssociatedContent(ResourceResolver resourceResolver,
        PageManagerFactory pageManagerFactory, String sourcePath, String[] targetLanguageCodes)
        throws RepositoryException, PersistenceException, WCMException {
        Resource sourceResource = resourceResolver.getResource(sourcePath);
        if (null != sourceResource) {
            Node sourceNode = sourceResource.adaptTo(Node.class);
            if (sourceNode != null) {
                targetLanguageCodes = getAssociatedContentTranslationLanguages(sourceResource, targetLanguageCodes,
                        resourceResolver);
                ArrayList associatedAssets = getContentFragmentAssociatedAssets(sourceNode, resourceResolver);
                for (Asset asset : associatedAssets) {
                    createLanguageCopyWithAssetRelations(resourceResolver, pageManagerFactory, asset.getPath(),
                            targetLanguageCodes);
                }
                createOrUpdateLanguageCopyForAssociatedContentNode(resourceResolver, pageManagerFactory, sourcePath,
                        targetLanguageCodes);
            } else {
                log.warn("Resource not able to adaptTo Node object: ", sourceResource.getPath());
            }
        } else {
            log.error("Could not create language copy for associated content. Resource nt found at path {}",
                sourcePath);
        }
    }

    private static String[] getAssociatedContentTranslationLanguages(Resource sourceResource,
        String[] targetLanguageCodes, ResourceResolver resourceResolver) {
        ArrayList applicableLanguages = new ArrayList();
        String sourcePath = sourceResource.getPath();
        for (String targetLanguageCode : targetLanguageCodes) {
            String lcPath = findLanguageCopyPathWithAutoCreatedRoots(sourcePath, targetLanguageCode, resourceResolver);
            if (null != lcPath) {
                Resource lcResource = resourceResolver.getResource(lcPath);
                if (isTranslateAssociatedContent(lcResource, resourceResolver)) {
                    applicableLanguages.add(targetLanguageCode);
                }
            }
        }
        return applicableLanguages.toArray(new String[applicableLanguages.size()]);
    }

    private static void createOrUpdateLanguageCopyForAssociatedContentNode(ResourceResolver resourceResolver,
        PageManagerFactory pageManagerFactory, String sourcePath, String[] targetLanguageCodes)
        throws PersistenceException, WCMException, RepositoryException {
        createOrUpdateLanguageCopyForAssociatedContentNode(resourceResolver, pageManagerFactory, sourcePath,
            targetLanguageCodes, "");
    }

    private static void createOrUpdateLanguageCopyForAssociatedContentNode(ResourceResolver resourceResolver,
        PageManagerFactory pageManagerFactory, String sourcePath, String[] targetLanguageCodes, String prefixPath)
        throws PersistenceException, WCMException, RepositoryException {

        Resource sourceAssociatedContentResource = getAssociatedContentResource(sourcePath, resourceResolver);
        if (sourceAssociatedContentResource == null) {
            return;
        }

        PageManager pageManager = pageManagerFactory.getPageManager(resourceResolver);
        for (String targetLanguageCode : targetLanguageCodes) {
            ResourceCollection destinationAssociatedContents = findAssociatedContentLanguageCopy(sourcePath, prefixPath,
                targetLanguageCode, resourceResolver);
            if (null == destinationAssociatedContents) {
                continue;
            }

            Iterator collectionResources = getResourcesFromCollection(sourceAssociatedContentResource);
            if (null == collectionResources) {
                continue;
            }

            while (collectionResources.hasNext()) {
                Resource sourceCollectionResource = collectionResources.next();
                createOrUpdateCollectionLanguageCopy(sourceCollectionResource, targetLanguageCode,
                    destinationAssociatedContents, resourceResolver, pageManager);
                //nested collections
                createLCForNestedCollections(sourceCollectionResource, targetLanguageCode, resourceResolver,
                    pageManager);
            }
        }
    }

    private static void createLCForNestedCollections(Resource sourceCollectionResource, String targetLanguageCode,
        ResourceResolver resourceResolver, PageManager pageManager) throws RepositoryException, WCMException,
        PersistenceException {
        HashSet nestedCollections = new HashSet();
        createLCForNestedCollections(sourceCollectionResource, targetLanguageCode, resourceResolver, pageManager,
            nestedCollections);
    }

    private static void createLCForNestedCollections(Resource sourceCollectionResource, String targetLanguageCode,
        ResourceResolver resourceResolver, PageManager pageManager, HashSet updatedCollections)
        throws RepositoryException, WCMException, PersistenceException {
        ResourceCollection sourceCollection = sourceCollectionResource.adaptTo(ResourceCollection.class);
        if (sourceCollection != null) {
            Iterator sourceResources = sourceCollection.getResources();
            while (sourceResources.hasNext()) {
                Resource sourceResource = sourceResources.next();
                if (isDamCollection(sourceResource) && !updatedCollections.contains(sourceResource)) {
                    ResourceCollection destinationCollection = getDestinationCollection(sourceCollection.getPath(),
                            targetLanguageCode, resourceResolver);
                    createOrUpdateCollectionLanguageCopy(sourceResource, targetLanguageCode, destinationCollection,
                            resourceResolver, pageManager);
                    updatedCollections.add(sourceResource);
                    createLCForNestedCollections(sourceResource, targetLanguageCode, resourceResolver, pageManager,
                            updatedCollections);

                }
            }
        } else {
            log.warn("Resource not able to adaptTo Resource collection object: " + sourceCollectionResource.getPath());
        }
    }

    private static boolean isDamCollection(Resource sourceResource) {
        return sourceResource.isResourceType("dam/collection");
    }

    private static ResourceCollection getDestinationCollection(String sourceCollectionPath, String targetLanguageCode,
        ResourceResolver resourceResolver) {
        String destinationCollectionPath = getAssociatedContentLanguageCopyPathOrName(sourceCollectionPath,
            targetLanguageCode);
        Resource resource = resourceResolver.getResource(destinationCollectionPath);
        if (null == resource) {
            return null;
        }
        return resource.adaptTo(ResourceCollection.class);
    }

    private static ResourceCollection findAssociatedContentLanguageCopy(String sourceContentFragmentPath, String
        prefixPath, String targetLanguageCode, ResourceResolver resourceResolver) throws RepositoryException {
        String destinationContentFragmentPath = findLanguageCopyPathWithAutoCreatedRoots(sourceContentFragmentPath,
            targetLanguageCode, resourceResolver);
        Resource targetAssociatedContentResource = null;
        if (null != destinationContentFragmentPath) {
            destinationContentFragmentPath = prefixPath + destinationContentFragmentPath;
            targetAssociatedContentResource = getAssociatedContentResource(destinationContentFragmentPath,
                resourceResolver);
        }
        if (null != targetAssociatedContentResource) {
            return targetAssociatedContentResource.adaptTo(ResourceCollection.class);
        }

        return null;
    }

    private static Iterator getResourcesFromCollection(Resource collectionResource) {
        ResourceCollection associatedContentCollection = collectionResource.adaptTo(ResourceCollection.class);
        if(associatedContentCollection!=null) {
            return associatedContentCollection.getResources();
        }
        return null;
    }

    private static void createOrUpdateCollectionLanguageCopy(Resource sourceCollectionResource, String
        targetLanguageCode, ResourceCollection destinationAssociatedContents, ResourceResolver resourceResolver,
        PageManager pageManager) throws WCMException, PersistenceException, RepositoryException {
        String sourceCollectionPath = sourceCollectionResource.getPath();
        String targetPath = getAssociatedContentLanguageCopyPathOrName(sourceCollectionPath, targetLanguageCode);
        Resource targetCollectionResource = resourceResolver.getResource(targetPath);
        if (null == targetCollectionResource) {
            targetCollectionResource = pageManager.copy(sourceCollectionResource, targetPath, null, false, false, true);
            setProperty(targetCollectionResource, ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY, sourceCollectionPath);
            Node sourceCollectionNode = sourceCollectionResource.adaptTo(Node.class);
            if (null != sourceCollectionNode && sourceCollectionNode.hasProperty(JCR_TITLE)) {
                String sourceCollectionName = sourceCollectionNode.getProperty(JCR_TITLE)
                    .getString();
                String destinationCollectionName = getAssociatedContentLanguageCopyPathOrName(
                    sourceCollectionName, targetLanguageCode);
                setProperty(targetCollectionResource, JCR_TITLE, destinationCollectionName);
            }
        } else {
            updateExistingCollectionResource(sourceCollectionResource, targetCollectionResource, targetLanguageCode,
                resourceResolver);
        }
        
        ResourceCollection collectionLanguageCopy = targetCollectionResource.adaptTo(ResourceCollection.class);
        replaceCollectionResource(destinationAssociatedContents, sourceCollectionResource, targetCollectionResource,
            resourceResolver);
        if (collectionLanguageCopy != null) {
            replaceCollectionAssetLanguageCopies(collectionLanguageCopy, targetLanguageCode, resourceResolver);
        } else {
            log.warn("Resource not able to adaptTo Resource collection object: " + collectionLanguageCopy.getPath());
        }
    }

    private static void setProperty(Resource resource, String property, String value) throws RepositoryException {
        Node node = resource.adaptTo(Node.class);
        if (node != null) {
            node.setProperty(property, value);
        } else {
            log.error("Resource :" + resource.getPath() +
                    " not able to adaptTo Node object thus skipping the update of property: " + property
                    + " to value: " + value);
        }
    }

    private static String getAssociatedContentLanguageCopyPathOrName(String source, String targetLanguageCode) {
        String initial = source;
        if (initial.contains("-")) {
            //check if language code exists
            String languageCode = initial.substring(initial.lastIndexOf("-") + 1);
            Locale locale = LanguageUtil.getLocale(languageCode);
            if (locale != null) {
                //remove language code
                initial = initial.substring(0, initial.lastIndexOf("-"));
            }
        }
        String languageCopyPostfix = "-" + targetLanguageCode;
        return initial + languageCopyPostfix;
    }

    private static void updateExistingCollectionResource(Resource sourceCollectionResource, Resource
        targetCollectionResource, String targetLanguageCode, ResourceResolver resourceResolver) throws
        PersistenceException, RepositoryException {
        if (null == sourceCollectionResource || null == targetCollectionResource) {
            return;
        }

        ResourceCollection sourceCollection = sourceCollectionResource.adaptTo(ResourceCollection.class);
        ResourceCollection targetCollection = targetCollectionResource.adaptTo(ResourceCollection.class);

        if(null == sourceCollection  || null == targetCollection) {
            return;
        }

        Node targetNode = targetCollectionResource.adaptTo(Node.class);
        if (targetNode != null) {
            targetNode.setProperty(ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY, sourceCollection.getPath());
        } else {
            log.error("Resource :" + targetCollectionResource.getPath() +
                    " not able to adaptTo Node object thus skipping the update of property: " +
                    ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY);
        }

        Iterator sourceResources = sourceCollection.getResources();
        while (sourceResources.hasNext()) {
            Resource sourceResource = sourceResources.next();
            if (null != sourceResource.adaptTo(Asset.class) || isFolder(sourceResource)) {
                Resource destinationResource = findLanguageCopyWithAutoCreatedRootsForAssetOrNTFolder(sourceResource,
                    targetLanguageCode, resourceResolver);
                replaceCollectionResource(targetCollection, sourceResource, destinationResource, resourceResolver);
            }
        }
    }

    private static void replaceCollectionAssetLanguageCopies(ResourceCollection collectionLanguageCopy, String
        targetLanguageCode, ResourceResolver resourceResolver) throws PersistenceException, RepositoryException {
        Iterator resourceIterator = collectionLanguageCopy.getResources();
        while (resourceIterator.hasNext()) {
            Resource resource = resourceIterator.next();
            if (null != resource.adaptTo(Asset.class) || isFolder(resource)) {
                String currentLanguage = getLanguageRootLocale(resource.getPath());
                if (null == currentLanguage || !currentLanguage.equals(targetLanguageCode)) {
                    Resource languageCopyResource = findLanguageCopyWithAutoCreatedRootsForAssetOrNTFolder(
                        resource, targetLanguageCode, resourceResolver);
                    if (null != languageCopyResource) {
                        replaceCollectionResource(collectionLanguageCopy, resource, languageCopyResource,
                            resourceResolver);
                    }
                }
            }
        }
    }

    private static void replaceCollectionResource(ResourceCollection collectionLanguageCopy, Resource
        sourceLanguageResource, Resource destinationLanguageResource, ResourceResolver resourceResolver)
        throws PersistenceException {
        collectionLanguageCopy.remove(sourceLanguageResource);
        //commit changes to before adding language copy. todo: remove once this is fixed
        resourceResolver.commit();
        collectionLanguageCopy.add(destinationLanguageResource);
        resourceResolver.commit();
    }

    public static List createLanguageCopyWithAssetRelationsForNTFolder(final ResourceResolver resourceResolver,
        final PageManagerFactory pageManagerFactory, final String sourcePath, final String[] targetLanguageCodes)
        throws RepositoryException {
        Resource sourceResource = resourceResolver.getResource(sourcePath);
        if (null != sourceResource) {
            Iterator assetIterator = DamUtil.getAssets(sourceResource);
            while (assetIterator.hasNext()) {
                Asset nextAsset = assetIterator.next();
                createLanguageCopyWithAssetRelations(resourceResolver, pageManagerFactory, nextAsset.getPath(),
                    targetLanguageCodes);
            }
        }

        List createdLanguageCopies = new ArrayList();
        for (String targetLanguageCode : targetLanguageCodes) {
            Node targetLanguageCopy =
                findLanguageCopyForNTFolderWithAutoCreatedRoots(sourcePath, targetLanguageCode, resourceResolver);
            if (null != targetLanguageCopy) {
                createdLanguageCopies.add(targetLanguageCopy.getPath());
            }
        }
        return createdLanguageCopies;
    }

    private static void createRelationLanguageCopyForSourceRelations(String relationName,
        List relatedSourceAssets, String targetLanguageCode, ResourceResolver resourceResolver,
        PageManagerFactory pageManagerFactory) {

        for (Asset relatedSourceAsset : relatedSourceAssets) {
            Resource relatedSourceResource = relatedSourceAsset.adaptTo(Resource.class);
            com.adobe.granite.asset.api.Asset graniteSourceAsset = relatedSourceAsset
                .adaptTo(com.adobe.granite.asset.api.Asset.class);
            if (graniteSourceAsset != null) {
                List relatedLinkedAssets = getRelatedAssets(relatedSourceResource, relationName);
                Asset destinationAsset = findLanguageCopyWithAutoCreatedRoots(graniteSourceAsset.getPath(),
                        targetLanguageCode, resourceResolver);
                if (destinationAsset != null) {
                    com.adobe.granite.asset.api.Asset graniteDestinationAsset = destinationAsset
                            .adaptTo(com.adobe.granite.asset.api.Asset.class);
                    createRelationLanguageCopy(relationName, graniteDestinationAsset, relatedLinkedAssets,
                            targetLanguageCode, resourceResolver, pageManagerFactory);
                }
            } else {
                log.error("Asset :" + relatedSourceAsset.getPath() +
                        " not able to adaptTo com.adobe.granite.asset.api.Asset object.");
            }
        }
    }

    private static void createRelationLanguageCopy(String relationName, com.adobe.granite.asset.api.Asset
            graniteDestinationAsset, List relatedAssets, String targetLanguageCode, ResourceResolver
            resourceResolver, PageManagerFactory pageManagerFactory) {
        ArrayList resetRelationPaths = new ArrayList();
        for (Asset relatedSourceAsset : relatedAssets) {
            String sourceRelationPath = relatedSourceAsset.getPath();
            String sourceRelationLanguageCopyPath = createOrGetLanguageCopy(resourceResolver, pageManagerFactory,
                sourceRelationPath, targetLanguageCode);
            resetRelationPaths.add(sourceRelationLanguageCopyPath);
        }
        // reset the source relations for this asset
        resetAssetRelations(graniteDestinationAsset, relationName, resetRelationPaths);
    }

    private static String createOrGetLanguageCopy(ResourceResolver resourceResolver, PageManagerFactory pageManagerFactory, String sourcePath, String targetLanguageCode) {
        String languageCopyPath = createLanguageCopy(resourceResolver, pageManagerFactory, sourcePath,
            targetLanguageCode);
        if (languageCopyPath == null) {
            languageCopyPath = findLanguageCopyPathWithAutoCreatedRoots(sourcePath, targetLanguageCode, resourceResolver);
        }

        return languageCopyPath;
    }

    private static void createMissingDestinationLanguageCopies(ResourceResolver resourceResolver, PageManagerFactory pageManagerFactory, String assetPath, String targetLanguageCode) {
        if (null == findLanguageCopyPathWithAutoCreatedRoots(assetPath, targetLanguageCode, resourceResolver)) {
            createLanguageCopy(resourceResolver, pageManagerFactory, assetPath, targetLanguageCode);
        }

        Resource assetResource = resourceResolver.getResource(assetPath);
        createMissingDestinationLanguageCopiesForRelations(ATTRIBUTE_ASSET_SOURCE_RELATION, assetResource,
            targetLanguageCode, resourceResolver, pageManagerFactory);
        createMissingDestinationLanguageCopiesForRelations(ATTRIBUTE_ASSET_LINKS_RELATION, assetResource,
            targetLanguageCode, resourceResolver, pageManagerFactory);
        createMissingDestinationLanguageCopiesForRelationsOfSources(ATTRIBUTE_ASSET_LINKS_RELATION, assetResource,
                targetLanguageCode, resourceResolver, pageManagerFactory);
    }

    private static void createMissingDestinationLanguageCopiesForRelationsOfSources(String relationName, Resource
            assetResource, String targetLanguageCode, ResourceResolver resourceResolver, PageManagerFactory
            pageManagerFactory) {
        List relatedSourceAssets = getRelatedAssets(assetResource, ATTRIBUTE_ASSET_SOURCE_RELATION);
        for (Asset relatedSourceAsset : relatedSourceAssets) {
            Resource relatedSourceResource = relatedSourceAsset.adaptTo(Resource.class);
            createMissingDestinationLanguageCopiesForRelations(relationName, relatedSourceResource,
                targetLanguageCode, resourceResolver, pageManagerFactory);
        }

    }

    private static void createMissingDestinationLanguageCopiesForRelations(String relationName, Resource assetResource,
        String targetLanguageCode, ResourceResolver resourceResolver, PageManagerFactory pageManagerFactory) {
        List relatedAssets = getRelatedAssets(assetResource, relationName);
        if (!relatedAssets.isEmpty()) {
            for (Asset relatedAsset : relatedAssets) {
                String relatedAssetPath = relatedAsset.getPath();
                if (null == findLanguageCopyPathWithAutoCreatedRoots(relatedAssetPath, targetLanguageCode,
                    resourceResolver)) {
                    createLanguageCopy(resourceResolver, pageManagerFactory, relatedAssetPath, targetLanguageCode);
                }
            }
        }
    }

    public static List getRelatedAssets(Resource resource, String relationName) {
        ArrayList relatedAssets = new ArrayList();
        if (null != resource) {
            com.adobe.granite.asset.api.Asset graniteSourceAsset = resource
                .adaptTo(com.adobe.granite.asset.api.Asset.class);
            if (null != graniteSourceAsset) {
                Iterator assetRelationIterator = (Iterator) graniteSourceAsset
                    .listRelations(relationName);
                while (assetRelationIterator.hasNext()) {
                    AssetRelation assetRelation = assetRelationIterator.next();
                    com.adobe.granite.asset.api.Asset graniteAsset = assetRelation.getAsset();
                    relatedAssets.add(graniteAsset.adaptTo(Asset.class));
                }
            }
        }

        return relatedAssets;
    }

    private static TranslationCategory getTranslationCategoryForAssetAndItsRelations(String assetPath, String
        targetLanguageCode, ResourceResolver resourceResolver) throws RepositoryException {
        String languageRootLocale = getLanguageRootLocale(assetPath);
        if(languageRootLocale != null && languageRootLocale.equals(targetLanguageCode)) {
            return TranslationCategory.NONE;
        }

        TranslationCategory groupCategory = TranslationCategory.NONE;
        TranslationCategory currentCategory = null;

        Resource sourceResource = resourceResolver.getResource(assetPath);
        if (null != sourceResource) {
            //check current asset
            Asset assetInSourceLanguage = sourceResource.adaptTo(Asset.class);
            Asset assetInDestinationLanguage = findLanguageCopyWithAutoCreatedRoots(assetPath, targetLanguageCode,
                resourceResolver);
            if (null != assetInDestinationLanguage) {
                currentCategory = isSmartAssetUpdateRequired(assetInSourceLanguage, assetInDestinationLanguage) || isTranslationOnUpdateOnlyDisabledForAssets(assetInDestinationLanguage.getPath(), resourceResolver)
                    || isTargetPathInDraftState(assetInDestinationLanguage) ?
                    TranslationCategory.TEMPORARY : TranslationCategory.NONE;
            } else {
                currentCategory = TranslationCategory.DESTINATION;
            }
            groupCategory = combineCategory(groupCategory, currentCategory);


            if(groupCategory!=TranslationCategory.TEMPORARY){
                currentCategory = getTranslationCategoryForRelation(ATTRIBUTE_ASSET_SOURCE_RELATION,
                    sourceResource, targetLanguageCode, resourceResolver);
                groupCategory = combineCategory(groupCategory, currentCategory);
            }

            if(groupCategory!=TranslationCategory.TEMPORARY){
                currentCategory = getTranslationCategoryForRelation(ATTRIBUTE_ASSET_LINKS_RELATION,
                    sourceResource, targetLanguageCode, resourceResolver);
                groupCategory = combineCategory(groupCategory, currentCategory);
            }

            if (groupCategory!=TranslationCategory.TEMPORARY) {
                currentCategory = getTranslationCategoryForRelationOfSources(ATTRIBUTE_ASSET_LINKS_RELATION,
                    sourceResource, targetLanguageCode, resourceResolver);
                groupCategory = combineCategory(groupCategory, currentCategory);
            }
        }

        return groupCategory;
    }

    private static TranslationCategory combineCategory(TranslationCategory c1, TranslationCategory c2) {
        if (c1 == TranslationCategory.TEMPORARY || c2 == TranslationCategory.TEMPORARY) {
            //any one temporary --> make the entire group temporary
            return TranslationCategory.TEMPORARY;
        }

        if (c1 == TranslationCategory.NONE && c2 == TranslationCategory.NONE) {
            //both doesn't need translation
            return TranslationCategory.NONE;
        }

        //at least one DESTINATION and other NONE/DESTINATION
        return TranslationCategory.DESTINATION;
    }

    private static TranslationCategory getTranslationCategoryForRelationOfSources(String relationName, Resource
        relaterResource, String targetLanguageCode, ResourceResolver resourceResolver) throws RepositoryException {
        TranslationCategory groupCategory = TranslationCategory.NONE;
        List relatedSourceAssets = getRelatedAssets(relaterResource, ATTRIBUTE_ASSET_SOURCE_RELATION);
        for (Asset relatedSourceAsset : relatedSourceAssets) {
            Resource relatedSourceResource = relatedSourceAsset.adaptTo(Resource.class);
            TranslationCategory currentCategory = getTranslationCategoryForRelation(relationName, relatedSourceResource,
                targetLanguageCode, resourceResolver);
            groupCategory = combineCategory(groupCategory, currentCategory);
        }
        return groupCategory;
    }

    private static TranslationCategory getTranslationCategoryForRelation(String relationName, Resource sourceResource,
        String targetLanguageCode, ResourceResolver resourceResolver) throws RepositoryException {
        TranslationCategory translationCategory = TranslationCategory.NONE;
        List relatedAssetsInSourceLanguage = getRelatedAssets(sourceResource, relationName);
        for (Asset relatedAssetInSourceLanguage : relatedAssetsInSourceLanguage) {
            String relatedAssetInSourceLanguagePath = relatedAssetInSourceLanguage.getPath();
            Asset relatedAssetInDestinationLanguage = findLanguageCopyWithAutoCreatedRoots(
                relatedAssetInSourceLanguagePath, targetLanguageCode, resourceResolver);
            if (null != relatedAssetInDestinationLanguage) {
                if (isSmartAssetUpdateRequired(relatedAssetInSourceLanguage, relatedAssetInDestinationLanguage) ||
                    isTranslationOnUpdateOnlyDisabledForAssets(relatedAssetInDestinationLanguage.getPath(), resourceResolver) ||
                    isTargetPathInDraftState(relatedAssetInDestinationLanguage)) {
                    translationCategory = TranslationCategory.TEMPORARY;
                    break;
                }
            } else {
                translationCategory = TranslationCategory.DESTINATION;
            }
        }
        return translationCategory;
    }

    /**
     * Removes asset relations with given language code
     * Useful for removing relations of an {@link com.adobe.granite.asset.api.Asset}
     *
     * @param graniteAsset   Asset whose relations have to ve removed/added.
     * @param relationName   name of the relation.
     * @param languageRoot   the language root of the relations to remove
     */
    private static void removeAssetRelationWithLanguageCode(com.adobe.granite.asset.api.Asset graniteAsset,
        String relationName, String languageRoot) {
        List relations = getRelationsPathList(graniteAsset, relationName);
        String relationLanguageRoot;
        for (String relation: relations) {
            relationLanguageRoot = getLanguageRootLocale(relation);
            if (relationLanguageRoot != null && relationLanguageRoot.equals(languageRoot)) {
                graniteAsset.removeRelation(relationName, relation);
            }
        }
    }

    /**
     * Removes relation passed as unlinkRelation followed by adding relation passed as linkRelation.
     * Useful for replacing relations of an {@link com.adobe.granite.asset.api.Asset}.
     *
     * @param graniteAsset   Asset whose relations have to ve removed/added.
     * @param relationName   name of the relation.
     * @param unlinkRelation relations to be removed.
     * @param linkRelation   relations to be added.
     */
    private static void adjustAssetRelations(com.adobe.granite.asset.api.Asset graniteAsset, String relationName,
        String unlinkRelation, String linkRelation) {

        List relations = getRelationsPathList(graniteAsset, relationName);

        if (relations.contains(unlinkRelation)) {
            graniteAsset.removeRelation(relationName, unlinkRelation);
        }

        if (!relations.contains(linkRelation)) {
            graniteAsset.addRelation(relationName, linkRelation);
        }
    }

    /**
     * Removes relations of any type of a {@link com.adobe.granite.asset.api.Asset}.
     * @param graniteAsset   Asset whose relations have to ve removed/added.
     * @param relationName   name of the relation to remove
     */
    private static void removeAssetRelation (com.adobe.granite.asset.api.Asset graniteAsset, String relationName) {
        if (graniteAsset != null && graniteAsset.listRelations(relationName).hasNext()) {
            graniteAsset.removeRelation(relationName);
        }
    }
    
    private static void resetAssetRelations(com.adobe.granite.asset.api.Asset graniteAsset, String relationName,
        ArrayList linkRelations) {
        if (graniteAsset.listRelations(relationName).hasNext()) {
            graniteAsset.removeRelation(relationName);
        }

        for (String linkRelation : linkRelations) {
            graniteAsset.addRelation(relationName, linkRelation);
        }
    }

    private static List getRelationsPathList(com.adobe.granite.asset.api.Asset graniteAsset, String relation) {
        Iterator relationIterator = graniteAsset.listRelations(relation);
        List relationsList = new ArrayList();

        while (relationIterator.hasNext()) {
            AssetRelation assetRelation = (AssetRelation) relationIterator.next();
            com.adobe.granite.asset.api.Asset asset = assetRelation.getAsset();
            String relationPath = asset.getPath();
            relationsList.add(relationPath);
        }

        return relationsList;
    }

    private static String getUpdateAssetNodeSourcePath(Node node) throws RepositoryException {
        if (node != null && node.isNodeType(DamConstants.NT_DAM_ASSET)) {
            if (node.hasNode(JcrConstants.JCR_CONTENT)) {
                Node assetContentNode = node.getNode(JcrConstants.JCR_CONTENT);
                if (assetContentNode.hasProperty(ATTRIBUTE_SMART_ASSET_UPDATE_SOURCE)) {
                    return assetContentNode.getProperty(ATTRIBUTE_SMART_ASSET_UPDATE_SOURCE).getValue().getString();
                }
            }
        }
        return null;
    }

    /*
     * Return the temporary language copy of asset as a resource, where the temporary language copy
     * resides inside the prefix path
     */
    private static Resource findTemporaryLanguageCopy (Asset asset, String prefixPath, ResourceResolver resourceResolver) {
        String tempLCPath = prefixPath + '/' + asset.getPath();
        Resource tempLCResource = resourceResolver.getResource(tempLCPath);
        if (tempLCResource != null) {
            return tempLCResource;
        }
        return null;
    }

    /*
     * For each temporary copy of source asset, add the new derived relation and remove the others relations
     */
    private static void adjustDerivedAndRemoveOthersForSourceAssetsTempLC(List relatedSourceAssets, String destinationAssetPath,
        String prefixPath, String sourceLanguageRoot, ResourceResolver resourceResolver) {
        for (Asset sourceDamAsset: relatedSourceAssets) {
            Resource sourceDamAssetTempLC = findTemporaryLanguageCopy(sourceDamAsset, prefixPath, resourceResolver);
            if (sourceDamAssetTempLC != null) {
                com.adobe.granite.asset.api.Asset sourceGraniteAssetTempLC = sourceDamAssetTempLC.adaptTo(com.adobe.granite.asset.api.Asset.class);
                if (sourceGraniteAssetTempLC != null) {
                    // First remove relations that point to old language copies
                    removeAssetRelationWithLanguageCode(sourceGraniteAssetTempLC, ATTRIBUTE_ASSET_DERIVED_RELATION, sourceLanguageRoot);
                    // Add this new derived relation
                    sourceGraniteAssetTempLC.addRelation(ATTRIBUTE_ASSET_DERIVED_RELATION, destinationAssetPath);
                    // Remove the others relation
                    removeAssetRelation(sourceGraniteAssetTempLC, ATTRIBUTE_ASSET_OTHERS_RELATION);
                }
            }
        }
    }

    private static boolean createUpdateLanguageCopyWithAssetRelationsRequired(final ResourceResolver resourceResolver,
        final String destinationPath) throws RepositoryException {
        Resource destinationResource = resourceResolver.resolve(destinationPath);
        if (null != destinationResource) {
            Node destinationNode = destinationResource.adaptTo(Node.class);
            String updateAssetSourcePath = getUpdateAssetNodeSourcePath(destinationNode);
            if (null != updateAssetSourcePath) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method creates temporary language copy of an asset/folder and its source(for example psd for jpeg) in case
     * temporary asset translation is required. Finally, relations are changed with their temporary copies.
     *
     * @param resourceResolver
     * @param pageManagerFactory
     * @param destinationPath    - source for creating language copy
     * @param targetLanguageCode - destination language code
     * @param prefixPath         - Root path where language copies are created
     * @return created variant temporary language copy path if created, otherwise returns destinationPath
     */
    public static String createUpdateLanguageCopyWithAssetRelations(final ResourceResolver resourceResolver,
        final PageManagerFactory pageManagerFactory, final String destinationPath, final String targetLanguageCode,
        String prefixPath) throws RepositoryException {
        boolean updateRequired = createUpdateLanguageCopyWithAssetRelationsRequired(resourceResolver, destinationPath);
        String variantPath = destinationPath;
        if (updateRequired) {
            Resource destinationResource = resourceResolver.resolve(destinationPath);
            List relatedSourceAssets = getRelatedAssets(destinationResource, ATTRIBUTE_ASSET_SOURCE_RELATION);
            if (null != destinationResource) {
                Node destinationNode = destinationResource.adaptTo(Node.class);
                String updateAssetSourcePath = getUpdateAssetNodeSourcePath(destinationNode);
                String updateAssetLanguageLocale = getLanguageRootLocale(updateAssetSourcePath);
                variantPath = createUpdateLanguageCopy(resourceResolver, pageManagerFactory, updateAssetSourcePath,
                    targetLanguageCode, prefixPath);
                createUpdateLanguageCopyForRelation(ATTRIBUTE_ASSET_SOURCE_RELATION, updateAssetSourcePath, variantPath,
                    targetLanguageCode, prefixPath, resourceResolver, pageManagerFactory);
                //Adjust the derived, others relations for the source assets
                adjustDerivedAndRemoveOthersForSourceAssetsTempLC(relatedSourceAssets, destinationPath, prefixPath,
                        updateAssetLanguageLocale, resourceResolver);
                createUpdateLanguageCopyForRelation(ATTRIBUTE_ASSET_LINKS_RELATION, updateAssetSourcePath, variantPath,
                    targetLanguageCode, prefixPath, resourceResolver, pageManagerFactory);
                createUpdateLanguageCopyForRelationOfSources(ATTRIBUTE_ASSET_LINKS_RELATION, updateAssetSourcePath,
                    variantPath, targetLanguageCode, prefixPath, resourceResolver, pageManagerFactory);
                //update Content Fragment Associated Collection
                Asset destinationAsset = destinationResource.adaptTo(Asset.class);
                if (destinationAsset != null && isContentFragment(destinationAsset)) {
                    String[] targetLanguageCodeArray = {targetLanguageCode};
                    try {
                        createOrUpdateLanguageCopyForAssociatedContentNode(resourceResolver, pageManagerFactory,
                            updateAssetSourcePath, targetLanguageCodeArray, prefixPath);
                    } catch (Exception e) {
                        log.error("could not create or update langauge copy for associated content node for {}",
                            prefixPath + destinationPath, e);
                    }
                }
            }
        }
        return variantPath;
    }

    private static void createUpdateLanguageCopyForRelationOfSources(String relationName, String
        updateAssetSourcePath, String variantPath, String targetLanguageCode, String prefixPath, ResourceResolver
        resourceResolver, PageManagerFactory pageManagerFactory) {
        Resource updateAssetSourceResource = resourceResolver.getResource(updateAssetSourcePath);
        List relatedSources = getRelatedAssets(updateAssetSourceResource, ATTRIBUTE_ASSET_SOURCE_RELATION);
        for (Asset relatedSource : relatedSources) {
            String relatedSourcePath = relatedSource.getPath();
            String LCofRelatedSource = findLanguageCopyPathWithAutoCreatedRoots(relatedSourcePath,
                targetLanguageCode, resourceResolver);
            String tempLCofRelatedSource = prefixPath + LCofRelatedSource;
            createUpdateLanguageCopyForRelation(relationName, relatedSourcePath, tempLCofRelatedSource,
                targetLanguageCode, prefixPath, resourceResolver, pageManagerFactory);
        }
    }

    private static void createUpdateLanguageCopyForRelation(String relationName, String updateAssetSourcePath, String
        variantPath, String targetLanguageCode, String prefixPath, ResourceResolver resourceResolver,
        PageManagerFactory pageManagerFactory) {
        //variantPath will have relations which should point to source language copies
        adjustRelationsForAssetLanguageCopy(variantPath, relationName, targetLanguageCode,
            resourceResolver);

        Resource variantResource = resourceResolver.getResource(variantPath);
        Resource updateAssetSourceResource = resourceResolver.getResource(updateAssetSourcePath);
        List relatedAssets = getRelatedAssets(updateAssetSourceResource, relationName);
        for (Asset relatedAsset : relatedAssets) {
            String relatedAssetPath = relatedAsset.getPath();
            if (relatedAssetPath.startsWith(DamConstants.MOUNTPOINT_ASSETS)) {
                String tempLCOfRelatedAsset = createUpdateLanguageCopy(resourceResolver, pageManagerFactory,
                    relatedAssetPath, targetLanguageCode, prefixPath);
                String LCofRelatedAsset = findLanguageCopyPathWithAutoCreatedRoots(relatedAssetPath,
                    targetLanguageCode, resourceResolver);
                if (variantResource != null) {
                    com.adobe.granite.asset.api.Asset graniteVariantAsset = variantResource.adaptTo(com.adobe.granite
                            .asset.api.Asset.class);
                    if (graniteVariantAsset != null) {
                        adjustAssetRelations(graniteVariantAsset, relationName, LCofRelatedAsset, tempLCOfRelatedAsset);
                        // Remove the others relation for the variant language copy
                        removeAssetRelation(graniteVariantAsset, ATTRIBUTE_ASSET_OTHERS_RELATION);
                    } else {
                        log.warn("Resource not able to adaptTo com.adobe.granite.asset.api.Asset: "
                                + variantResource.getPath());
                    }
                } else {
                    log.warn("Resource not found in path: " + variantPath);
                }
            }
        }
    }

    private static void adjustRelationsForAssetLanguageCopy(String assetPath, String relationName, String
        targetLanguageCode, ResourceResolver resourceResolver) {
        Resource assetResource = resourceResolver.resolve(assetPath);
        if (null != assetResource) {
            List relatedAssets = getRelatedAssets(assetResource, relationName);
            for (Asset relatedAsset : relatedAssets) {
                String relatedAssetPath = relatedAsset.getPath();
                String sourceLanguageCode = getLanguageRootLocale(relatedAssetPath);
                if (null == sourceLanguageCode || !sourceLanguageCode.equals(targetLanguageCode)) {
                    String languageCopyPath = findLanguageCopyPathWithAutoCreatedRoots(relatedAssetPath,
                        targetLanguageCode, resourceResolver);
                    com.adobe.granite.asset.api.Asset graniteAsset = assetResource.adaptTo(com.adobe.granite.asset
                        .api.Asset.class);
                    adjustAssetRelations(graniteAsset, relationName, relatedAssetPath, languageCopyPath);
                }
            }
        }
    }

    public static String findLanguageCopyPathWithAutoCreatedRoots(final String assetPath,
        final String languageCode, final ResourceResolver resolver) {
        Asset languageCopy = findLanguageCopyWithAutoCreatedRoots(assetPath, languageCode, resolver);
        return null != languageCopy ? languageCopy.getPath() : null;
    }

    public static Resource findLanguageCopyWithAutoCreatedRootsForAssetOrNTFolder(final Resource resource, final String
        languageCode, final ResourceResolver resolver) throws RepositoryException {
        if (null != resource.adaptTo(Asset.class)) {
            Asset languageCopy = findLanguageCopyWithAutoCreatedRoots(resource.getPath(), languageCode, resolver);
            if (null != languageCopy) {
                return languageCopy.adaptTo(Resource.class);
            }
        } else if (isFolder(resource)) {
            Node folderNode = findLanguageCopyForNTFolderWithAutoCreatedRoots(resource.getPath(), languageCode,
                resolver);
            if (null != folderNode) {
                return resolver.getResource(folderNode.getPath());
            }
        }
        return null;
    }

    public static Asset findLanguageCopyWithAutoCreatedRoots(final String assetPath, final String languageCode,
        final ResourceResolver resolver) {

        Resource assetResource = resolver.getResource(assetPath);
        if (assetResource == null || assetResource.adaptTo(Asset.class) == null) {
            return null;
        }

        Asset asset = null;
        String languageRootPath = LanguageUtil.getLanguageRoot(assetPath);

        String languageRootParentPath = null;
        String contentPath = null;
        if (languageRootPath != null) {
            contentPath = assetPath.replaceFirst(languageRootPath, "");
            languageRootParentPath = Text.getRelativeParent(
                languageRootPath, 1);
        } else {
            //CQ-61450 User creates a language copy of a page from sites and language root for the asset (referenced in site) does not exist
            if (Text.getRelativeParent(assetPath, 1).equals(DamConstants.MOUNTPOINT_ASSETS)) {
                //Asset is directly under DamConstants.MOUNTPOINT_ASSETS
                languageRootParentPath = DamConstants.MOUNTPOINT_ASSETS;
            } else if (assetPath.startsWith(DamConstants.MOUNTPOINT_ASSETS + "/")) {
                //Asset follows structure DamConstants.MOUNTPOINT_ASSETS//
                int parentOfRootPathLength = assetPath.indexOf('/', DamConstants.MOUNTPOINT_ASSETS.length() + 1);
                if (parentOfRootPathLength < 0 || assetPath.length() <= parentOfRootPathLength) {
                    return null;
                }
                languageRootParentPath = assetPath.substring(0, parentOfRootPathLength);
            }
            if (languageRootParentPath != null) {
                contentPath = assetPath.replaceFirst(languageRootParentPath, "");
            } else {
                languageRootParentPath = "";
            }
        }
        String destLanguageRootPath = getDestinationLanguageRoot(languageRootParentPath, languageCode, resolver);
        String assetPathLC = destLanguageRootPath + contentPath;
        Resource assetResourceLC = resolver.getResource(assetPathLC);
        if (assetResourceLC != null) {
            asset = assetResourceLC.adaptTo(Asset.class);
        }
        return asset;
    }

    private static Node findLanguageCopyForNTFolderWithAutoCreatedRoots(final String folderPath, final String
        languageCode, final ResourceResolver resolver) throws RepositoryException {

        Resource folderResource = resolver.getResource(folderPath);
        if (folderResource == null) {
            return null;
        }

        Node folderNode = folderResource.adaptTo(Node.class);
        if (folderNode != null && !folderNode.isNodeType(JcrConstants.NT_FOLDER)) {
            return null;
        }

        Node folderNodeLanguageCopy = null;
        String languageRootPath = LanguageUtil.getLanguageRoot(folderPath);

        String languageRootParentPath = null;
        String contentPath = null;
        if (languageRootPath != null) {
            contentPath = folderPath.replaceFirst(languageRootPath, "");
            languageRootParentPath = Text.getRelativeParent(
                languageRootPath, 1);
        } else {
            //CQ-61450 User creates a language copy of a page from sites and language root for the asset (referenced in site) does not exist
            if (Text.getRelativeParent(folderPath, 1).equals(DamConstants.MOUNTPOINT_ASSETS)) {
                //Asset is directly under DamConstants.MOUNTPOINT_ASSETS
                languageRootParentPath = DamConstants.MOUNTPOINT_ASSETS;
            } else if (folderPath.startsWith(DamConstants.MOUNTPOINT_ASSETS + "/")) {
                //Asset follows structure DamConstants.MOUNTPOINT_ASSETS//
                int parentOfRootPathLength = folderPath.indexOf('/', DamConstants.MOUNTPOINT_ASSETS.length() + 1);
                if (parentOfRootPathLength < 0 || folderPath.length() <= parentOfRootPathLength) {
                    return null;
                }
                languageRootParentPath = folderPath.substring(0, parentOfRootPathLength);
            }
            if (languageRootParentPath != null) {
                contentPath = folderPath.replaceFirst(languageRootParentPath, "");
            } else {
                languageRootParentPath = "";
            }
        }

        String destLanguageRootPath = getDestinationLanguageRoot(languageRootParentPath, languageCode, resolver);
        String assetPathLC = destLanguageRootPath + contentPath;
        Resource folderResourceLC = resolver.getResource(assetPathLC);
        if (folderResourceLC != null) {
            folderNodeLanguageCopy = folderResourceLC.adaptTo(Node.class);
        }
        return folderNodeLanguageCopy;
    }


    public static void afterReplacingUpdatedAsset(String destinationPath, Session userSession, String prefixPath,
        ResourceResolver resourceResolver) throws RepositoryException {
        Resource resource = resourceResolver.getResource(destinationPath);
        if (null != resource) {
            com.adobe.granite.asset.api.Asset graniteAsset = resource.adaptTo(com.adobe.granite.asset.api.Asset.class);
            if (graniteAsset != null) {
                afterReplacingUpdatedAssetWithRelation(ATTRIBUTE_ASSET_SOURCE_RELATION, graniteAsset, prefixPath);
                afterReplacingUpdatedAssetWithRelation(ATTRIBUTE_ASSET_LINKS_RELATION, graniteAsset, prefixPath);
                afterReplacingUpdatedAssetForSources(ATTRIBUTE_ASSET_LINKS_RELATION, graniteAsset, prefixPath);
            }
        }
        userSession.save();
    }

    private static void afterReplacingUpdatedAssetForSources(String relationName,
            com.adobe.granite.asset.api.Asset relaterAsset, String prefixPath) {
        Resource relaterResource = relaterAsset.adaptTo(Resource.class);
        List relatedSources = getRelatedAssets(relaterResource, ATTRIBUTE_ASSET_SOURCE_RELATION);
        for (Asset relatedSource : relatedSources) {
            com.adobe.granite.asset.api.Asset graniteSource = relatedSource
                    .adaptTo(com.adobe.granite.asset.api.Asset.class);
            afterReplacingUpdatedAssetWithRelation(relationName, graniteSource, prefixPath);
        }
    }

    private static void afterReplacingUpdatedAssetWithRelation(String relationName,
        com.adobe.granite.asset.api.Asset graniteAsset, String prefixPath) {
        List relations = getRelationsPathList(graniteAsset, relationName);
        for (String relation : relations) {
            String replacedRelationPath = removePrefix(relation, prefixPath);
            if (null != replacedRelationPath) {
                adjustAssetRelations(graniteAsset, relationName, relation, replacedRelationPath);
            }
        }
    }

    private static String removePrefix(String string, String prefix) {
        return string.startsWith(prefix) ? string.replaceFirst(prefix, "") : null;
    }

    /**
     * Returns the locale for the given path. Supports language root structures
     * /<root>/\<path>lt;Language>_\<Country> and /\<root>lt;path>lt;Language>
     * @param path
     * @return locale or null in case any supported locale does not exist in the given path
     */
    public static String getLanguageRootLocale(String path) {
        String languageRootPath = LanguageUtil.getLanguageRoot(path);
        if (null != languageRootPath) {
            return languageRootPath.substring(languageRootPath.lastIndexOf("/") + 1);
        }
        return null;
    }

    public static String getLanguageDisplayName(ResourceResolver resolver, String langCode) {
        String langDisplayName = "";
        Resource languagesHome = resolver.getResource(DEFAULT_LANGUAGES_HOME);
        Resource language = getLanguage(languagesHome, langCode);
        if (language != null) {
            ValueMap langProperties = language.adaptTo(ValueMap.class);
            if (langProperties != null) {
                langDisplayName = langProperties.get("language", String.class);
            }
        }
        return (langDisplayName != null && !langDisplayName.isEmpty()) ? langDisplayName : langCode;
    }

    private static Resource getLanguage(Resource languagesHome, String languageCode) {
        if (languagesHome != null) {
            languageCode = languageCode.toLowerCase();
            String langWithUnderscore = languageCode.replace("-", "_");
            String langWithHyphen = languageCode.replace("_", "-");

            Resource languageRes = languagesHome.getChild(langWithUnderscore);
            if (languageRes == null) {
                languageRes = languagesHome.getChild(langWithHyphen);
            }
            return languageRes;
        }
        return null;
    }

    /**
     * Returns a list after replacing paths with their existing language copies. If a language copy of a path does
     * not exist, the path is retained in the returned list.
     *
     * @param paths The list of paths whose language copy has to be replaced
     * @param destinationLanguageCode language code of destination language copy
     * @param resourceResolver
     * @return a list after replacing paths with their existing language copies.
     */
    public static List replaceWithExistingLanguageCopiesIfPossible(List paths,
        String destinationLanguageCode, ResourceResolver resourceResolver) {
        ArrayList resolveLanguageCopies = new ArrayList();

        if (null == destinationLanguageCode) {
            return paths;
        }

        for (String path : paths) {
            String languageCopyPath = DamLanguageUtil.findLanguageCopyPathWithAutoCreatedRoots(path,
                destinationLanguageCode, resourceResolver);
            if (null != languageCopyPath) {
                path = languageCopyPath;
            }
            resolveLanguageCopies.add(path);
        }

        return resolveLanguageCopies;
    }

    private static ArrayList getContentFragmentAssociatedAssets(Node currentNode, ResourceResolver
        resourceResolver) throws RepositoryException {
        Resource associatedContentResource = getAssociatedContentResource(currentNode.getPath(), resourceResolver);
        if (associatedContentResource != null) {
            ResourceCollection associatedContent = associatedContentResource.adaptTo(ResourceCollection.class);
            return getAssetsFromAssociatedContentNode(associatedContent);
        }
        return new ArrayList();
    }

    public static ArrayList getAssetsFromAssociatedContent(Node contentFragmentNode, String destinationLanguage,
        ResourceResolver resourceResolver) throws RepositoryException {
        ArrayList returnList = new ArrayList();

        Node associatedContentNode = getAssociatedContentNode(contentFragmentNode);
        if (null != associatedContentNode) {
            Resource associatedContentResource = resourceResolver.getResource(associatedContentNode.getPath());
            if (associatedContentResource != null) {
                Iterator collectionResourceIterator = getResourcesFromCollection(associatedContentResource);
                if (collectionResourceIterator != null) {
                    //iterate over all collections
                    while (collectionResourceIterator.hasNext()) {
                        Resource collectionResource = collectionResourceIterator.next();
                        //check for collection
                        ResourceCollection damCollection = collectionResource.adaptTo(ResourceCollection.class);
                        if (damCollection != null) {
                            //get destination asset list
                            ArrayList collectionAssets = new ArrayList();
                            addAssetsFromCollection(damCollection, collectionAssets);
                            addAssetsFromNestedCollections(collectionResource, collectionAssets);

                            ArrayList sourceCollectionAssetLanguageCopies = getAssetLanguageCopiesFromSourceOfCollection(
                                    collectionResource, destinationLanguage, resourceResolver);
                            if (null != sourceCollectionAssetLanguageCopies) {
                                //remove asset not present in source language copy of collection
                                removeLanguageCopiesWithoutSource(collectionAssets, sourceCollectionAssetLanguageCopies);
                            }
                            returnList.addAll(collectionAssets);
                        }
                    }
                }
            } else {
                log.warn("Resource not found in path: " + associatedContentNode.getPath());
            }
        }
        return returnList;
    }

    private static void addAssetsFromNestedCollections(Resource collectionResource, ArrayList collectionAssets)
        throws RepositoryException {
        //nested collection
        HashSet nestedCollections = new HashSet();
        getNestedCollections(collectionResource, nestedCollections);
        for (Resource resource : nestedCollections) {
            ResourceCollection rc = resource.adaptTo(ResourceCollection.class);
            if (rc != null) {
                addAssetsFromCollection(rc, collectionAssets);
            } else {
                log.warn("Resource is not adaptable to ResourceCollection object: " + resource.getPath());
            }
        }
    }

    private static Node getAssociatedContentNode(Node currentNode) throws RepositoryException {
        if(currentNode.hasNode(ASSOCIATED_CONTENT_RELATIVE_PATH)) {
            return currentNode.getNode(ASSOCIATED_CONTENT_RELATIVE_PATH);
        }
        if(currentNode.hasNode(ASSOCIATED_CONTENT_CHILD)) {
            return currentNode.getNode(ASSOCIATED_CONTENT_CHILD);
        }
        return null;
    }

    private static Resource getAssociatedContentResource(String cfPath, ResourceResolver resourceResolver) throws
        RepositoryException {
        Resource resource = resourceResolver.getResource(cfPath + "/" + ASSOCIATED_CONTENT_RELATIVE_PATH);
        if (null == resource) {
            resource = resourceResolver.getResource(cfPath + "/" + ASSOCIATED_CONTENT_CHILD);
        }
        return resource;
    }

    private static void removeLanguageCopiesWithoutSource(ArrayList collectionAssets, ArrayList
        sourceCollectionLanguageCopyPaths) {
        if (null != sourceCollectionLanguageCopyPaths) {
            for (int ndx = collectionAssets.size() - 1; ndx >= 0; ndx--) {
                Asset collectionAsset = collectionAssets.get(ndx);
                if (!sourceCollectionLanguageCopyPaths.contains(collectionAsset.getPath())) {
                    collectionAssets.remove(ndx);
                }
            }
        }
    }

    private static ArrayList getAssetLanguageCopiesFromSourceOfCollection(Resource collectionResource, String
        destinationLanguage, ResourceResolver resourceResolver) throws RepositoryException {
        ArrayList sourceAssetList = getAssetsFromSourceOfCollection(collectionResource, resourceResolver);
        if (null != sourceAssetList) {
            ArrayList destinationCollectionAssets = new ArrayList();
            for (Asset asset : sourceAssetList) {
                Asset languageCopy = DamLanguageUtil.findLanguageCopyWithAutoCreatedRoots(asset.getPath(),
                    destinationLanguage, resourceResolver);
                if (null != languageCopy) {
                    destinationCollectionAssets.add(languageCopy.getPath());
                }
            }
            return destinationCollectionAssets;
        }
        return null;
    }

    private static ArrayList getAssetsFromSourceOfCollection(Resource collectionResource,
        ResourceResolver resourceResolver) throws RepositoryException {
        Resource sourceCollection = getSourceCollection(collectionResource, resourceResolver);
        if (null != sourceCollection) {
            ArrayList sourceAssetList = getAssetsFromCollection(sourceCollection);
            deleteProperty(collectionResource, ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY);
            return sourceAssetList;
        }
        return null;
    }

    private static void deleteProperty(Resource collectionResource, String property) throws RepositoryException {
        Node collectionNode = collectionResource.adaptTo(Node.class);
        if (collectionNode != null && collectionNode.hasProperty(property)) {
            javax.jcr.Property sourceCollectionProperty = collectionNode.getProperty(property);
            sourceCollectionProperty.remove();
        }
    }

    private static Resource getSourceCollection(Resource collectionResource, ResourceResolver resourceResolver)
        throws RepositoryException {
        Node collectionNode = collectionResource.adaptTo(Node.class);
        if (collectionNode != null && collectionNode.hasProperty(ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY)) {
            javax.jcr.Property sourceCollectionProperty = collectionNode.getProperty(
                ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY);
            String sourceCollectionPath = sourceCollectionProperty.getString();
            return resourceResolver.getResource(sourceCollectionPath);
        }
        return null;
    }

    private static ArrayList getAssetsFromCollection(Resource collectionResource)
        throws RepositoryException {
        ArrayList assetList = new ArrayList();
        return addAssetsFromCollectionResource(collectionResource, assetList);
    }

    private static ArrayList getAssetsFromAssociatedContentNode(ResourceCollection resourceCollection) throws
        RepositoryException {
        ArrayList assetList = new ArrayList();
        if (resourceCollection != null) {
            Iterator resourceIterator = resourceCollection.getResources();
            while (resourceIterator.hasNext()) {
                Resource collectionResource = resourceIterator.next();
                addAssetsFromCollectionResource(collectionResource, assetList);
            }
        }

        return assetList;
    }

    private static ArrayList addAssetsFromCollectionResource(Resource collectionResource,
        ArrayList assetList) throws RepositoryException {
        if (null == collectionResource) {
            return assetList;
        }
        ResourceCollection resourceCollection = collectionResource.adaptTo(ResourceCollection.class);
        if (null == resourceCollection) {
            return assetList;
        }

        addAssetsFromCollection(resourceCollection, assetList);
        addAssetsFromNestedCollections(collectionResource, assetList);
        return assetList;
    }

    private static void getNestedCollections(Resource sourceCollectionResource, HashSet updatedCollections)
        throws RepositoryException {
        ResourceCollection sourceCollection = sourceCollectionResource.adaptTo(ResourceCollection.class);
        Iterator sourceResources = sourceCollection.getResources();
        while (sourceResources.hasNext()) {
            Resource sourceResource = sourceResources.next();
            if (isDamCollection(sourceResource) && !updatedCollections.contains(sourceResource)) {
                updatedCollections.add(sourceResource);
                getNestedCollections(sourceResource, updatedCollections);
            }
        }
    }

    private static void addAssetsFromCollection(ResourceCollection resourceCollection, ArrayList assetList)
        throws RepositoryException {
        Iterator resourceIterator = resourceCollection.getResources();
        while (resourceIterator.hasNext()) {
            Resource resource = resourceIterator.next();
            Asset asset = resource.adaptTo(Asset.class);
            if (asset != null && !isContentFragment(asset)) {
                assetList.add(asset);
            } else if (isFolder(resource)) {
                Iterator assetIterator = DamUtil.getAssets(resource);
                while (assetIterator.hasNext()) {
                    Asset folderAsset = assetIterator.next();
                    if(!isContentFragment(folderAsset)) {
                        assetList.add(folderAsset);
                    }
                }
            }
        }
    }

    private static boolean isTranslateAssociatedContent(Resource contentFragmentResource, ResourceResolver
        resourceResolver) {
        boolean retVal = false;
        //get cloud config resource
        Resource cloudConfigResource = getCloudConfigResourceAppliedOnResource(contentFragmentResource,
            MachineTranslationCloudConfig.class, null, resourceResolver);
        //check if translation is required
        if (null != cloudConfigResource) {
            MachineTranslationCloudConfig cloudConfig = cloudConfigResource.adaptTo(
                MachineTranslationCloudConfig.class);
            if (null != cloudConfig) {
                retVal = cloudConfig.isTranslateAssociatedContentForAssets();
            }
        }
        return retVal;
    }

    private static boolean isTranslateInlineMediaAssets(String contentFragmentPath, ResourceResolver
        resourceResolver) {
        boolean retVal = false;
        Resource contentFragmentResource = resourceResolver.getResource(contentFragmentPath);
        if (null != contentFragmentResource) {
            //get cloud config resource
            Resource cloudConfigResource = getCloudConfigResourceAppliedOnResource(contentFragmentResource,
                MachineTranslationCloudConfig.class, null, resourceResolver);
            //check if translation is required
            if (null != cloudConfigResource) {
                MachineTranslationCloudConfig cloudConfig = cloudConfigResource.adaptTo(
                    MachineTranslationCloudConfig.class);
                if (null != cloudConfig) {
                    retVal = cloudConfig.isTranslateInlineMediaAssets();
                }
            }
        }
        return retVal;
    }

    private static boolean isTranslationOnUpdateOnlyDisabledForAssets(String assetPath, ResourceResolver
        resourceResolver) {
        boolean retVal = false;
        Resource assetResource = resourceResolver.getResource(assetPath);
        if (null != assetResource) {
            //get cloud config resource
            Resource cloudConfigResource = getCloudConfigResourceAppliedOnResource(assetResource,
                MachineTranslationCloudConfig.class, null, resourceResolver);
            if (null != cloudConfigResource) {
                MachineTranslationCloudConfig cloudConfig = cloudConfigResource.adaptTo(
                    MachineTranslationCloudConfig.class);
                if (null != cloudConfig) {
                    retVal = cloudConfig.isTranslationOnUpdateOnlyDisabledForAssets();
                }
            }
        }
        return retVal;
    }

    private static boolean isTargetPathInDraftState(Asset targetAsset) throws RepositoryException {
        boolean retVal = false;
        Resource targetAssetResource= targetAsset.adaptTo(Resource.class);
        if (targetAssetResource != null) {
            Resource contentResource = targetAssetResource.getChild(JcrConstants.JCR_CONTENT);
            contentResource = contentResource == null ? targetAssetResource : contentResource;
            Node contentNode = contentResource.adaptTo(Node.class);
            if (contentNode != null) {
                if (contentNode.hasProperty(ATTRIBUTE_CQ_TRANSLATION_STATUS)) {
                    Property property = contentNode.getProperty(ATTRIBUTE_CQ_TRANSLATION_STATUS);
                    if (property.getString().equals(TranslationConstants.TranslationStatus.DRAFT.toString())) {
                        retVal = true;
                    }
                } else {
                    retVal = true;
                }
            } else {
                log.warn("Content resource not adaptable to Node object: " + contentResource.getPath());
            }
        } else {
            log.warn("Asset is not adaptable to Resource object: " + targetAsset.getPath());
        }
        return retVal;
    }

    private static ContentState getContentStateForSmartTranslation(Asset sourceAsset ,Asset targetAsset,
        ResourceResolver resourceResolver) throws RepositoryException {
        ContentState contentState = ContentState.IS_TRANSLATED;
        boolean isSmartAssetUpdate = false;
        boolean isTranslationOnUpdateOnly = false;
        if (isSmartAssetUpdateRequired(sourceAsset, targetAsset)) {
            isSmartAssetUpdate = true;
        }
        if (isTranslationOnUpdateOnlyDisabledForAssets(targetAsset.getPath(), resourceResolver)) {
            isTranslationOnUpdateOnly = true;
        }
        Resource targetAssetResource= targetAsset.adaptTo(Resource.class);
        if (targetAssetResource != null) {
            Resource contentResource = targetAssetResource.getChild(JcrConstants.JCR_CONTENT);
            contentResource = contentResource == null ? targetAssetResource : contentResource;
            Node contentNode = contentResource.adaptTo(Node.class);
            if (contentNode != null) {
                if (contentNode.hasProperty(ATTRIBUTE_CQ_TRANSLATION_STATUS)) {
                    Property property = contentNode.getProperty(ATTRIBUTE_CQ_TRANSLATION_STATUS);
                    if (property.getString().equals(TranslationConstants.TranslationStatus.DRAFT.toString()) ||
                            isSmartAssetUpdate || isTranslationOnUpdateOnly) {
                        contentState = ContentState.IS_UPDATE_STATE;
                    }
                } else if (contentNode.hasProperty(ATTRIBUTE_CQ_TRANSLATION_LAST_UPDATE)) {
                    if (isSmartAssetUpdate) {
                        contentState = ContentState.IS_UPDATE_STATE;
                    } else if (!contentNode.hasProperty(CQ_TRANSLATION_SOURCE_JCR_UUID)) {
                        Node sourceAssetNode = sourceAsset.adaptTo(Node.class);
                        if ((sourceAssetNode != null && !sourceAssetNode.hasProperty(JCR_UUID)) || isTranslationOnUpdateOnly) {
                            contentState = ContentState.IS_UPDATE_STATE;
                        } else {
                            contentState = ContentState.IS_UNKNOWN;
                            log.error("Content resource language copy creation is in unknown state thus, skipped from processing: "
                                    + contentResource.getPath());
                        }
                    } else {
                        contentState = ContentState.IS_CREATE_STATE;
                    }
                } else {
                    log.warn("Content resource language copy is migrated content without any translation properties: "
                            + contentResource.getPath());
                    contentState = ContentState.IS_UPDATE_STATE;
                }
            } else {
                log.warn("Content resource not adaptable to Node object: " + contentResource.getPath());
            }
        } else {
            log.warn("Asset is not adaptable to Resource object: " + targetAsset.getPath());
        }
        return contentState;
    }

    private static Resource getContentResource(Resource resource) {
        if(resource != null) {
            boolean isContentNode = resource.getPath().equals(JcrConstants.JCR_CONTENT);
            Resource content;
            if (!isContentNode) {
                content = resource.getChild(JcrConstants.JCR_CONTENT);
            } else {
                content = resource;
            }
            return content;
        }
        return null;
    }

    private static Resource getCloudConfigResourceAppliedOnResource(Resource resource,
        Class cloudConfigClass, String strCloudConfigResourceType, ResourceResolver resourceResolver) {
        String cloudConfigPath = getCloudConfigPathAppliedOnResourceFromClassOrResourceType(resource, cloudConfigClass,
            strCloudConfigResourceType, resourceResolver);
        if (null != cloudConfigPath) {
            return resourceResolver.getResource(cloudConfigPath);
        }
        return null;
    }

    /**
     * Returns the path of cloud config applied on or inherited by a resource.
     */
    private static String getCloudConfigPathAppliedOnResourceFromClassOrResourceType(Resource resource,
        Class cloudConfigClass, String strCloudConfigResourceType, ResourceResolver resourceResolver) {

        if (resource == null || (cloudConfigClass == null && strCloudConfigResourceType == null)) {
            return null;
        }

        log.debug("In Function: getCloudConfigAppliedOnResource({})", resource.getPath());

        String strCloudConfigPath = null;
        String[] appliedCloudConfigs = null;
        if (resourceResolver != null) {
            Resource content = getContentResource(resource);
            if (content != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Resource: {}", resource.getPath());
                    log.debug("Content: {}", content.getPath());
                }

                boolean isContextAwareConfig = false;
                if (ResourceUtil.getValueMap(content).get(ATTRIBUTE_CA_CONFIG_PROPERTY) != null) {
                    isContextAwareConfig = true;
                }
                // First look for context aware cloud configurations
                if (isContextAwareConfig) {
                    try {
                        appliedCloudConfigs = getCAConfigs(resource);
                    } catch (Exception e) {
                        log.error("Failed to get configs.", e);
                    }
                }
                // if no configurations found, look for legacy cloud configurations
                if (appliedCloudConfigs == null || appliedCloudConfigs.length == 0) {
                    try {
                        appliedCloudConfigs = (String[]) ResourceUtil.getValueMap(content).get(
                                ATTRIBUTE_CLOUD_CONFIG_PROPERTY);
                    } catch (ClassCastException cce) {
                        log.debug("Trying to cast as String as String[] failed");
                        String singleValue = (String) ResourceUtil.getValueMap(content).get(
                                ATTRIBUTE_CLOUD_CONFIG_PROPERTY);

                        if (singleValue != null && !"".equals(singleValue)) {
                            appliedCloudConfigs = new String[1];
                            appliedCloudConfigs[0] = singleValue;
                        }
                    }
                }
                if (appliedCloudConfigs != null) {
                    for (String appliedCloudConfig : appliedCloudConfigs) {
                        Resource appliedConfig = resourceResolver.getResource(appliedCloudConfig);
                        if (cloudConfigClass != null) {
                            if (appliedConfig != null && appliedConfig.adaptTo(cloudConfigClass) != null) {
                                strCloudConfigPath = appliedCloudConfig;
                                log.debug("Found ObjectCloudConfig {}", strCloudConfigPath);
                                return strCloudConfigPath;
                            } else {
                                log.debug("Applied Cloud Config {} is not of type {}.", appliedCloudConfig,
                                    cloudConfigClass.toString());
                            }
                        } else {
                            Resource appliedConfigContent = getContentResource(appliedConfig);
                            if (appliedConfigContent != null) {
                                String strCurrentResourceType = appliedConfigContent.getResourceType();
                                if (strCloudConfigResourceType.equals(strCurrentResourceType)) {
                                    strCloudConfigPath = appliedCloudConfig;
                                    log.debug("Found ObjectCloudConfig {}", strCloudConfigPath);
                                    return strCloudConfigPath;
                                } else {
                                    log.debug("Applied Cloud Config {} is not of type {}.", appliedCloudConfig,
                                        strCloudConfigResourceType);
                                }
                            }
                        }
                    }
                }
            }
            //getCloudConfigPathApplied on parent resource
            strCloudConfigPath = getCloudConfigPathAppliedOnResourceFromClassOrResourceType(resource.getParent(),
                cloudConfigClass, strCloudConfigResourceType, resourceResolver);
        } else {
            log.warn("Failed to get resourceResolver");
        }

        return strCloudConfigPath;
    }

    private static String[] getCAConfigs(Resource resource) throws Exception {
        ArrayList caConfigs = new ArrayList();
        Conf conf = resource.adaptTo(Conf.class);
        if (conf != null) {
            // get TIF config
            Collection tifCollection = conf.getListResources(CACONFIG_TRANSLATIONCFG_PATH);
            if (null == tifCollection) {
                return new String[0];
            }
            Resource tif = getConfigFromCollection(tifCollection);
            if (tif != null) {
                caConfigs.add(tif.getPath());
            }
        }

        String[] caConfigsArray = new String[caConfigs.size()];
        return caConfigs.toArray(caConfigsArray);
    }

    private static Resource getConfigFromCollection(Collection tifCollection) {
        Iterator tifIterator = tifCollection.iterator();
        ArrayList tifList = new ArrayList();
        while (tifIterator.hasNext()) {
            tifList.add(tifIterator.next());
        }

        if (tifList.size() == 1) {
            return tifList.get(0);
        } else if (tifList.size() > 1) {
            // custom configuration
            for (int ndx = 0; ndx < tifList.size(); ndx++) {
                Resource currTif = tifList.get(ndx);
                if (currTif.getPath().startsWith(CACONFIG_ROOT) && !currTif.getPath().startsWith(CACONFIG_GLOBAL)) {
                    return currTif;
                }
            }

            String[] fallbackPaths = {CACONFIG_GLOBAL, "/apps"};

            // fallback from /conf/global, then apps
            // case of /libs taken care of in tifList.size() == 1
            for (String fallbackPath : fallbackPaths) {
                for (int ndx = 0; ndx < tifList.size(); ndx++) {
                    Resource currTif = tifList.get(ndx);
                    if (currTif.getPath().startsWith(fallbackPath)) {
                        return currTif;
                    }
                }
            }
        }

        return null;
    }

    private static void setLastTranslationUpdate(Resource resource) throws RepositoryException {
        if (resource != null) {
            Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT);
            if (contentResource != null) {
                Node sourceContentNode = contentResource.adaptTo(Node.class);
                Calendar lastModified = getNodeLastModifiedTime(contentResource);
                if (lastModified == null) {
                    lastModified = Calendar.getInstance();
                }
                if(sourceContentNode != null){
                    sourceContentNode.setProperty(ATTRIBUTE_CQ_TRANSLATION_LAST_UPDATE, lastModified);
                }
            }
        }
    }

    private static void setIsTranslationCreated(Resource resource) throws RepositoryException {
        if (resource != null) {
            Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT);
            if (contentResource != null) {
                Node contentNode = contentResource.adaptTo(Node.class);
                if(contentNode != null) {
                    contentNode.setProperty(ATTRIBUTE_CQ_TRANSLATION_CREATED, true);
                }
            }
        }
    }

    private static void setTransCreatedFlagToPathResource (String transCreatedPath, String pathAlreadyPresent, ResourceResolver resourceResolver)
        throws RepositoryException {
        Session session = resourceResolver.adaptTo(Session.class);
        while(transCreatedPath.length()>0) {
            String completePath = pathAlreadyPresent + transCreatedPath;
            Node tempNode = null;
            if (session.itemExists(completePath)) {
                tempNode = session.getNode(completePath);
            }
            if (tempNode != null){
                setIsTranslationCreated(resourceResolver.getResource(tempNode.getPath()));
            }
            transCreatedPath = transCreatedPath.substring(0,transCreatedPath.lastIndexOf("/"));
        }
    }

    private static String getTransCreatedPath(String pathToCheck, Session session) throws RepositoryException {
        String[] elements = Text.explode(pathToCheck, '/');
        Node dstParent = session.getNode(DamConstants.MOUNTPOINT_ASSETS);
        String transCreatedPath = "";
        for (int j = 0; j < elements.length; j++) {
            String name = elements[j];
            if (!dstParent.hasNode(name)) {
                transCreatedPath = transCreatedPath + "/" + name;
                break;
            } else {
                dstParent = dstParent.getNode(name);
            }
        }
        transCreatedPath = transCreatedPath == "" ? "" : transCreatedPath + "/" ;
        if(transCreatedPath!="") {
            transCreatedPath = pathToCheck.substring(pathToCheck.indexOf(transCreatedPath));
        }
        return transCreatedPath;
    }

    private static Calendar getNodeLastModifiedTime(Resource contentNode) {
        Calendar retVal = null;
        Calendar cqLastModified = getCalendarAttribute(contentNode, CQ_LASTMODIFIED);
        Calendar jcrLastModified = getCalendarAttribute(contentNode, JCR_LASTMODIFIED);
        if (cqLastModified != null && jcrLastModified != null) {
            if (cqLastModified.compareTo(jcrLastModified) > 0) {
                retVal = cqLastModified;
            } else {
                retVal = jcrLastModified;
            }
        } else if (jcrLastModified != null) {
            retVal = jcrLastModified;
        } else if (cqLastModified != null) {
            retVal = cqLastModified;
        }
        return retVal;
    }

    private static Calendar getCalendarAttribute(Resource resource, String strAttributeName) {
        ValueMap contentVM = resource.adaptTo(ValueMap.class);
        if (contentVM == null) {
            return null;
        } else {
            return contentVM.get(strAttributeName, Calendar.class);
        }
    }

    /**
     * Returns the destination language root for the given path by analyzing the path names starting at the root.
     *
     * @param srcLanguageRootParentPath source language root's parent path
     * @return the destination language root or null if not found
     */
    private static String getDestinationLanguageRoot(@Nonnull String srcLanguageRootParentPath, @Nonnull String
        destinationLanguage, @Nonnull ResourceResolver resourceResolver) {

        Resource langRootParent = resourceResolver.getResource(srcLanguageRootParentPath);
        if (null != langRootParent) {

            //language root based on path
            destinationLanguage = getDestinationLanguageWithAllowedDelimiters(srcLanguageRootParentPath,
                destinationLanguage, resourceResolver);
            String sameLevelDestinationRootPath = srcLanguageRootParentPath + "/" + destinationLanguage;
            if (null != resourceResolver.getResource(sameLevelDestinationRootPath)) {
                return sameLevelDestinationRootPath;
            }

            /**
             * Check for Language roots at two level
             */
            //go one level down
            Iterator iter = langRootParent.listChildren();
            while (iter.hasNext()) {
                Resource sibling = iter.next();
                Locale locale = getLocaleFromResource(sibling);
                if (locale == null) {
                    String retVal = checkAndGetLanguageRootsFromChildren(sibling, destinationLanguage);
                    if (null != retVal) {
                        return retVal;
                    }
                }
            }

            //one level up
            //find language roots which are not language countries
            Resource langRootGrandParent = langRootParent.getParent();
            ArrayList nonLangRootUncles = new ArrayList();
            boolean additionalLanguageRootsFound = false;
            if (langRootGrandParent != null) {
                Iterator langRootUncles = langRootGrandParent.listChildren();
                while (langRootUncles.hasNext()) {
                    Resource langRootUncle = langRootUncles.next();
                    if (langRootUncle.getName().equals(langRootParent.getName())) { //node comparison not working :(
                        //already checked
                        continue;
                    }

                    Locale gcLocale = getLocaleFromResource(langRootUncle);

                    //check that this is not a country node and locale is same
                    if (!isCountryNode(langRootUncle)) {
                        additionalLanguageRootsFound = true;
                        if (null != gcLocale) {
                            String gcLanguage = gcLocale.getLanguage().toString();
                            if (destinationLanguage.equals(gcLanguage)) {
                                return langRootUncle.getPath();
                            }
                        } else {
                            nonLangRootUncles.add(langRootUncle);
                        }
                    }
                }
            }

            if (additionalLanguageRootsFound) {
                //found roots one level up, need to go down on nonLangRootUncles.
                for (Resource nonLangRootUncle : nonLangRootUncles) {
                    String retVal = checkAndGetLanguageRootsFromChildren(nonLangRootUncle,
                        destinationLanguage);
                    if (null != retVal) {
                        return retVal;
                    }
                }
            }

            //fallback to language root based on path
            return sameLevelDestinationRootPath;


        }
        return null;
    }

    private static String checkAndGetLanguageRootsFromChildren(Resource resource, String destLanguage) {
        Iterator children = resource.listChildren();
        while (children.hasNext()) {
            Resource child = children.next();
            Locale childLocale = getLocaleFromResource(child);
            if (childLocale != null) {
                String childLanguage = childLocale.getLanguage().toString();
                if(childLanguage.equals(destLanguage)) {
                    return child.getPath();
                }
            }
        }
        return null;
    }

    private static Locale getLocaleFromResource(@Nonnull Resource res) {
        Language language = LanguageUtil.getLanguage(res.getName());
        if (language != null) {
            return language.getLocale();
        }
        return null;
    }

    private static boolean isCountryNode(Resource resource) {
        Iterator children = resource.listChildren();
        while (children.hasNext()) {
            Resource child = children.next();
            Locale gcLocale = getLocaleFromResource(child);
            if (gcLocale != null) {
                return true;
            }
        }
        return false;
    }

    public static ResourceResolver getUserResourceResolverFromUserId(SlingRepository slingRepository, ResourceResolverFactory resolverFactory, String userId, String serviceUser )
            throws LoginException, RepositoryException {
        Session initiatorSession = getUserSession(slingRepository, userId, serviceUser);
        return resolverFactory.getResourceResolver(Collections.singletonMap(org.apache.sling.jcr.resource.JcrResourceConstants.AUTHENTICATION_INFO_SESSION, (Object) initiatorSession));
    }

    private static Session getUserSession(SlingRepository slingRepository, String userId, String serviceUser) throws RepositoryException {
        final SimpleCredentials credentials = new SimpleCredentials(userId, new char[0]);
        Session session = slingRepository.impersonateFromService(serviceUser, credentials, null);
        try {
            session.getWorkspace().getObservationManager().setUserData(CHANGE_BY_TRANSLATION_WORKFLOW);
        } catch (NullPointerException | RepositoryException e) {
            log.error("Error while setting user data on session", e);
        }
        return session;
    }

    /**
     * This code should maintain parity with {@code com.adobe.cq.wcm.translation.impl.utils.CFEmbeddedAssetUtil}
     * Any changes made here should also be reflected in the other class as well
     */
    private static class CFEmbeddedAssetUtil {

        private static final Logger logger = LoggerFactory.getLogger(CFEmbeddedAssetUtil.class);

        /**
         * Returns inline media assets and referenced assets/CFs, together called embedded assets,
         * which are present inside a content Fragment.
         * 

* Use same value of isRecursive Parameter for single workflow execution. Mix and match of true and false * could result in unwanted results. *

* @param contentFragmentNodePath Path Of CF for which user needs to get embeddedAssets/referenced Content Fragments * @param resourceResolver ResourceResolver instance * @param isRecursive true if user wants to getEmbeddedAssets recursively. * @return embedded assets */ public static Set getEmbeddedAssets(String contentFragmentNodePath, ResourceResolver resourceResolver, boolean isRecursive) throws RepositoryException, IOException { logger.debug("In Function: getEmbeddedAssets"); Resource cfResource = resourceResolver.getResource(contentFragmentNodePath); if (cfResource == null) { logger.info("Resource not found at path: {}", contentFragmentNodePath); return Collections.emptySet(); } ContentFragment cf = cfResource.adaptTo(ContentFragment.class); if (cf == null) { logger.info("Content Fragment not found at path: {}", contentFragmentNodePath); return Collections.emptySet(); } HashSet embeddedAssets = new HashSet<>(); HashSet processedCFs = new HashSet<>(); processedCFs.add(contentFragmentNodePath); embeddedAssets.addAll(getEmbeddedAssetsForCFRecursive(contentFragmentNodePath, resourceResolver, processedCFs, isRecursive)); return embeddedAssets; } /** * * Replaces embedded assets and referenced assets/CFs which are present inside a content Fragment * with their destination language copies. *

* Use same value of isRecursive Parameter for single workflow execution. Mix and match of true and false * could result in unwanted results. *

* * @param currentNode Node in which user needs to replace embeddedAssetsReferences * @param destinationLanguage the target language code * @param resourceResolver ResourceResolver instance * @param isRecursive true if user wants to replace EmbeddedAssets recursively */ public static void updateEmbeddedAssetReferences(Node currentNode, String destinationLanguage, ResourceResolver resourceResolver, boolean isRecursive) throws RepositoryException, ContentFragmentException { replaceEmbeddedAssets(currentNode.getPath(), destinationLanguage, resourceResolver, isRecursive); } private static Set getEmbeddedAssetsForCFRecursive(String contentFragmentNodePath, ResourceResolver resourceResolver, HashSet processedCFs, boolean isRecursive) throws RepositoryException, IOException { logger.debug("In Function: getEmbeddedAssetsForCFRecursive"); HashSet embeddedAssets = new HashSet<>(); Resource cfResource = resourceResolver.getResource(contentFragmentNodePath); if (cfResource == null) { logger.info("Resource not found at path: {}", contentFragmentNodePath); return Collections.emptySet(); } ContentFragment cf = cfResource.adaptTo(ContentFragment.class); if (cf == null) { logger.info("Content Fragment not found at path: {}", contentFragmentNodePath); return Collections.emptySet(); } Iterator cfElements = cf.getElements(); while (cfElements.hasNext()) { ContentElement currentElement = cfElements.next(); embeddedAssets.addAll(getEmbeddedAssetsForCFElementAndItsReferences(cf, currentElement, resourceResolver, processedCFs, isRecursive)); } return embeddedAssets; } private static HashSet getEmbeddedAssetsForCFElementAndItsReferences(ContentFragment contentFragment, ContentElement contentElement, ResourceResolver resourceResolver, HashSet processedCFs, boolean isRecursive) throws IOException, RepositoryException { logger.debug("In Function: getEmbeddedAssetsForCFElementAndItsReferences"); HashSet embeddedAssets = new HashSet(); HashSet assetPaths = getEmbeddedAssetPaths(contentElement); for (String assetPath : assetPaths) { Resource resource = resourceResolver.getResource(decodeURL(assetPath)); if (null != resource) { Asset asset = resource.adaptTo(Asset.class); if (null != asset) { Node resourceNode = resource.adaptTo(Node.class); if (resourceNode != null && isContentFragment(resourceNode)) { if (processedCFs.contains(assetPath)) { continue; } else { processedCFs.add(assetPath); if (isRecursive) { embeddedAssets.addAll( getEmbeddedAssetsForCFRecursive(assetPath, resourceResolver, processedCFs, isRecursive)); } else { embeddedAssets.add(asset); } } } embeddedAssets.add(asset); } } } return embeddedAssets; } private static HashSet getEmbeddedAssetPaths(ContentElement contentElement) { logger.debug("In Function: getEmbeddedAssetPaths"); HashSet assets = new HashSet<>(); if (isElementFragmentOrContentReference(contentElement)) { assets.addAll(Arrays.asList(CFEmbeddedAssetUtil.ContentFragmentValueWrapper .getValue(contentElement))); Iterator elementVariations = contentElement.getVariations(); while (elementVariations.hasNext()) { ContentVariation currentVariation = elementVariations.next(); assets.addAll(Arrays.asList(ContentFragmentValueWrapper .getValue(currentVariation))); } return assets; } else { assets.addAll(getEmbeddedInlineMediaAssets(contentElement.getContent())); Iterator elementVariations = contentElement.getVariations(); while (elementVariations.hasNext()) { ContentVariation currentVariation = elementVariations.next(); assets.addAll(getEmbeddedInlineMediaAssets(currentVariation.getContent())); } return assets; } } private static void replaceEmbeddedAssets(String contentFragmentNodePath, String targetLanguageCode, ResourceResolver resourceResolver, boolean isRecursive) throws ContentFragmentException { logger.debug("In Function: replaceEmbeddedAssets"); Resource cfResource = resourceResolver.getResource(contentFragmentNodePath); if (cfResource == null) { logger.info("Resource not found at path: {}", contentFragmentNodePath); return; } ContentFragment cf = cfResource.adaptTo(ContentFragment.class); if (cf == null) { logger.info("Content Fragment not found at path: {}", contentFragmentNodePath); return; } HashSet processedAssets = new HashSet<>(); processedAssets.add(contentFragmentNodePath); replaceEmbeddedAssetsRecursive(contentFragmentNodePath, targetLanguageCode, resourceResolver, processedAssets, isRecursive); } private static class ContentFragmentValueWrapper { private static String[] getValue(ContentElement element) { String[] values = element.getValue().getValue(String[].class); return (null == values || (values.length == 1 && null == values[0])) ? new String[]{} : values; } private static String[] getValue(ContentVariation variation) { String[] values = variation.getValue().getValue(String[].class); return (null == values || (values.length == 1 && null == values[0])) ? new String[]{} : values; } private static void setValue(ContentElement element, String[] content) throws ContentFragmentException { FragmentData fragmentData = element.getValue(); updateFragmentData(fragmentData, content); element.setValue(fragmentData); } private static void setValue(ContentVariation variation, String[] content) throws ContentFragmentException { FragmentData fragmentData = variation.getValue(); updateFragmentData(fragmentData, content); variation.setValue(fragmentData); } private static void updateFragmentData(FragmentData fragmentData, String[] content) throws ContentFragmentException { if (fragmentData.getDataType().isMultiValue()) { fragmentData.setValue(content); } else { fragmentData.setValue(content[0]); } } } private static void replaceEmbeddedAssetsRecursive(String contentFragmentNodePath, String targetLanguageCode, ResourceResolver resourceResolver, HashSet processedCFs, boolean isRecursive) throws ContentFragmentException { logger.debug("In Function: replaceEmbeddedAssetsRecursion"); Resource cfResource = resourceResolver.getResource(contentFragmentNodePath); if (cfResource == null) { logger.info("Resource not found at path: {}", contentFragmentNodePath); return; } ContentFragment cf = cfResource.adaptTo(ContentFragment.class); if (cf == null) { logger.info("Content Fragment not found at path: {}", contentFragmentNodePath); return; } Iterator cfElemnemts = cf.getElements(); while (cfElemnemts.hasNext()) { ContentElement currentElement = cfElemnemts.next(); if (isElementFragmentOrContentReference(currentElement)) { String[] elementValues = ContentFragmentValueWrapper.getValue(currentElement); boolean bElementValuesUpdated = false; for (int ndx = 0; ndx < elementValues.length; ndx++) { if (hasLanguageRoot(elementValues[ndx])) { boolean isURLEncoded = isURLEncoded(elementValues[ndx]); String languageCopyPath = getLanguageCopyForEncodedURL(elementValues[ndx], targetLanguageCode, resourceResolver); if (languageCopyPath != null) { elementValues[ndx] = isURLEncoded ? encodeURL(languageCopyPath) : languageCopyPath; bElementValuesUpdated = true; if (!processedCFs.contains(languageCopyPath)) { Resource languageCopyResource = resourceResolver.getResource(languageCopyPath); if (languageCopyResource == null) { logger.warn("Content Reference not found at path: {}. " + "This Content Reference cannot be processed further for embedded assets.", languageCopyPath); } else { Node node = languageCopyResource.adaptTo(Node.class); if (node != null && isContentFragment(node)) { processedCFs.add(languageCopyPath); if (isRecursive) { replaceEmbeddedAssetsRecursive(languageCopyPath, targetLanguageCode, resourceResolver, processedCFs, isRecursive); } } } } } } } if (bElementValuesUpdated) { CFEmbeddedAssetUtil.ContentFragmentValueWrapper.setValue(currentElement, elementValues); } Iterator elementVariations = currentElement.getVariations(); while (elementVariations.hasNext()) { ContentVariation currentVariation = elementVariations.next(); String[] variationValues = CFEmbeddedAssetUtil.ContentFragmentValueWrapper.getValue(currentVariation); boolean bVariationValuesUpdated = false; for (int ndx = 0; ndx < variationValues.length; ndx++) { if (hasLanguageRoot(variationValues[ndx])) { boolean isURLEncoded = isURLEncoded(elementValues[ndx]); String languageCopyPath = getLanguageCopyForEncodedURL(variationValues[ndx], targetLanguageCode, resourceResolver); if (languageCopyPath != null) { variationValues[ndx] = isURLEncoded ? encodeURL(languageCopyPath) : languageCopyPath; bVariationValuesUpdated = true; if (!processedCFs.contains(languageCopyPath)) { Resource languageCopyResource = resourceResolver.getResource(languageCopyPath); if (languageCopyResource == null) { logger.warn("Content Reference not found at path: {}. " + "This Content Reference cannot be processed further for embedded assets.", languageCopyPath); } else { Node node = languageCopyResource.adaptTo(Node.class); if (node != null && isContentFragment(node)) { processedCFs.add(languageCopyPath); if (isRecursive) { replaceEmbeddedAssetsRecursive(languageCopyPath, targetLanguageCode, resourceResolver, processedCFs, isRecursive); } } } } } } } if (bVariationValuesUpdated) { ContentFragmentValueWrapper.setValue(currentVariation, variationValues); } } } else { String[] elementValues = CFEmbeddedAssetUtil.ContentFragmentValueWrapper .getValue(currentElement); boolean bElementValuesUpdated = false; for (int ndx = 0; ndx < elementValues.length; ndx++) { if (StringUtils.isNotBlank(elementValues[ndx])) { elementValues[ndx] = replaceEmbeddedInlineMediaAssetsInVariationData(elementValues[ndx], targetLanguageCode, resourceResolver); bElementValuesUpdated = true; } } if (bElementValuesUpdated) { CFEmbeddedAssetUtil.ContentFragmentValueWrapper.setValue(currentElement, elementValues); } Iterator elementVariations = currentElement.getVariations(); while (elementVariations.hasNext()) { ContentVariation currentVariation = elementVariations.next(); String[] variationValues = CFEmbeddedAssetUtil.ContentFragmentValueWrapper .getValue(currentVariation); boolean bVariationValuesUpdated = false; for (int ndx = 0; ndx < variationValues.length; ndx++) { if (StringUtils.isNotBlank(variationValues[ndx])) { variationValues[ndx] = replaceEmbeddedInlineMediaAssetsInVariationData(variationValues[ndx], targetLanguageCode, resourceResolver); bVariationValuesUpdated = true; } } if (bVariationValuesUpdated) { CFEmbeddedAssetUtil.ContentFragmentValueWrapper.setValue(currentVariation, variationValues); } } } Session session = resourceResolver.adaptTo(Session.class); try { // check if session is live and has any pending changes, then we'll save it if (session != null && session.isLive() && session.hasPendingChanges()) { session.save(); } } catch (Exception e) { // if failed before saving the session then refresh it (skip the current asset) if (session != null && session.isLive()) { try { session.refresh(false); //keepChanges = false (discard all changes to skip the current pending changes) } catch (RepositoryException re) { logger.error("error while refreshing the session: ", re); } } logger.error("Error while replacing the Language copy of Embedded Asset in content element: " + (String) currentElement.getName() + " in Content Fragment: " + contentFragmentNodePath + "{}", e); } } } private static String replaceEmbeddedInlineMediaAssetsInVariationData(String variationData, String targetLanguageCode, ResourceResolver resourceResolver) { logger.debug("In Function : replaceEmbeddedInlineMediaAssetsInVariationData"); String regEx = CFEmbeddedAssetUtil.InlineMediaAssetsPatternManager .getRegexForInlineMediaAssets(); Pattern pattern = Pattern.compile(regEx); Matcher matcher = pattern.matcher(variationData); int lastEnd = 0; StringBuilder updatedVariationData = new StringBuilder(variationData.length()); while (matcher.find()) { String sourceCopyPath = CFEmbeddedAssetUtil.InlineMediaAssetsPatternManager .getMatchingPath(matcher); sourceCopyPath = decodeURL(sourceCopyPath); String languageCopyPath = DamLanguageUtil .findLanguageCopyPathWithAutoCreatedRoots(sourceCopyPath, targetLanguageCode, resourceResolver); if (languageCopyPath == null) { continue; } languageCopyPath = encodeURL(languageCopyPath); CFEmbeddedAssetUtil.RegexIndex regexIndex = CFEmbeddedAssetUtil.InlineMediaAssetsPatternManager.replaceMatchingPath(matcher); updatedVariationData.append(variationData.substring(lastEnd, regexIndex.getStartIndex())) .append(languageCopyPath); lastEnd = regexIndex.getEndIndex(); } updatedVariationData.append(variationData.substring(lastEnd)); return updatedVariationData.toString(); } /** * To get non encoded language copy path for given source path and target language * @param encodedSourceCopyPath source path can be encoded path * @param targetLanguageCode target locale code * @param resourceResolver used to resolve source path to resource object * @return non encoded language copy path */ private static String getLanguageCopyForEncodedURL(String encodedSourceCopyPath, String targetLanguageCode, ResourceResolver resourceResolver) { logger.debug("In Function: replaceEmbeddedReferencedAssetsInVariationData"); String sourceCopyPath = decodeURL(encodedSourceCopyPath); String languageCopyPath = DamLanguageUtil .findLanguageCopyPathWithAutoCreatedRoots(sourceCopyPath, targetLanguageCode, resourceResolver); if (languageCopyPath == null) { logger.error("Language copy not found for {} with target language code {}", encodedSourceCopyPath, targetLanguageCode); return null; } return languageCopyPath; } private static boolean isURLEncoded(String url) { String decodedURL = decodeURL(url); return !url.equals(decodedURL); } private static String encodeURL(String url) { try { return URIUtil.encodePath(url); } catch (URIException e) { logger.error("could not encode url : {}", url, e); return url; } } private static String decodeURL(String url) { try { return URIUtil.decode(url); } catch (URIException e) { logger.error("could not decode url : {}", url, e); return url; } } private static boolean isElementFragmentOrContentReference(ContentElement contentElement) { String semanticType = contentElement.getValue().getDataType().getSemanticType(); return SemanticDataType.REFERENCE.equals(semanticType) || SemanticDataType.CONTENT_FRAGMENT.equals(semanticType); } private static Set getEmbeddedInlineMediaAssets(String variationData) { logger.debug("In Function: getEmbeddedInlineMediaAssets"); Set assetPaths = new HashSet<>(); String regEx = CFEmbeddedAssetUtil.InlineMediaAssetsPatternManager.getRegexForInlineMediaAssets(); Pattern pattern = Pattern.compile(regEx); Matcher matcher = pattern.matcher(variationData); while (matcher.find()) { assetPaths.add(CFEmbeddedAssetUtil.InlineMediaAssetsPatternManager.getMatchingPath(matcher)); } return assetPaths; } private static boolean hasLanguageRoot(@Nonnull String path) { return LanguageUtil.getLanguageRoot(path) != null; } private static class InlineMediaAssetsPatternManager { private static final String TOKEN_IMAGE_TAG = "img"; private static final String TOKEN_ATTRIBUTE_IMAGE = "src"; private static final String TOKEN_ANCHOR_TAG = "a"; private static final String TOKEN_ATTRIBUTE_LINK = "href"; private static String getRegexForInlineMediaAssets() { return regexForSrc() + "|" + regexForAnchor(); } private static String getMatchingPath(Matcher matcher) { String path = null; if(matcher.group(getRegexGroupSrc()) != null) { path = matcher.group(getRegexGroupSrc()); } else if (matcher.group(getRegexGroupAnchor()) != null) { path = matcher.group(getRegexGroupAnchor()); } return path; } private static CFEmbeddedAssetUtil.RegexIndex replaceMatchingPath(Matcher matcher) { int start = 0; int end = 0; if(matcher.group(getRegexGroupSrc()) != null) { start = matcher.start(getRegexGroupSrc()); end = matcher.end(getRegexGroupSrc()); } else if (matcher.group(getRegexGroupAnchor()) != null) { start = matcher.start(getRegexGroupAnchor()); end = matcher.end(getRegexGroupAnchor()); } CFEmbeddedAssetUtil.RegexIndex regexIndex = new CFEmbeddedAssetUtil.RegexIndex(start, end); return regexIndex; } private static String regexForSrc() { return getRegexForInlineMediaAssets(TOKEN_IMAGE_TAG, TOKEN_ATTRIBUTE_IMAGE); } private static String regexForAnchor() { return getRegexForInlineMediaAssets(TOKEN_ANCHOR_TAG, TOKEN_ATTRIBUTE_LINK); } private static int getRegexGroupSrc() { return 4; } private static int getRegexGroupAnchor() { return 11; } private static String getRegexForInlineMediaAssets(String tag, String attribute) { final String TOKEN_UNLIMITED_SPACE = "[\\n ]*"; final String TOKEN_ATTRIBUTE_KEY = "[a-zA-Z-]+"; final String TOKEN_SOURCE_ATTRIBUTE_VALUE = "\"" + TOKEN_UNLIMITED_SPACE + "([^>\" \\n]+)" + TOKEN_UNLIMITED_SPACE + "\""; final String TOKEN_ATTRIBUTE_VALUE = "\"" + TOKEN_UNLIMITED_SPACE + "([^>\"\\n]+)" + TOKEN_UNLIMITED_SPACE + "\""; final String TOKEN_UNLIMITED_KEY_VALUE = "((" + TOKEN_ATTRIBUTE_KEY + TOKEN_UNLIMITED_SPACE + "=" + TOKEN_UNLIMITED_SPACE + TOKEN_ATTRIBUTE_VALUE + ")* *)*"; final String TOKEN_SOURCE_KEY_VALUE = attribute + TOKEN_UNLIMITED_SPACE + "=" + TOKEN_UNLIMITED_SPACE + TOKEN_SOURCE_ATTRIBUTE_VALUE; final String regex = "<" + TOKEN_UNLIMITED_SPACE + tag + TOKEN_UNLIMITED_SPACE + TOKEN_UNLIMITED_KEY_VALUE + TOKEN_UNLIMITED_SPACE + TOKEN_SOURCE_KEY_VALUE + TOKEN_UNLIMITED_SPACE + TOKEN_UNLIMITED_KEY_VALUE + TOKEN_UNLIMITED_SPACE + "\\/?" + TOKEN_UNLIMITED_SPACE + ">"; return regex; } } private static class RegexIndex { private int startIndex; private int endIndex; RegexIndex(int startIndex, int endIndex) { this.startIndex = startIndex; this.endIndex = endIndex; } private int getStartIndex() { return startIndex; } private int getEndIndex() { return endIndex; } } private static boolean isContentFragment(Node resourceNode) { // return resource.adaptTo(ContentFragment.class) == null ? false : true; try { if (resourceNode.hasNode(JcrConstants.JCR_CONTENT)) { Node resourceNodeContent = resourceNode.getNode(JcrConstants.JCR_CONTENT); if (resourceNodeContent != null) { if (resourceNodeContent.hasProperty(CONTENT_FRAGMENT)) { return resourceNodeContent.getProperty(CONTENT_FRAGMENT) .getBoolean(); } } } } catch (Exception e) { logger.error("Unable to identify content fragment property for node", e); } return false; } } /** * This code should maintain parity with {@code com.adobe.cq.wcm.translation.impl.lock.LockProperties} * Any changes made here should also be reflected in the other class as well */ private static class LockProperties { private boolean isSessionScoped; private boolean isDeep; private int retryCount; /** * The time-out interval for lock in seconds. Taken for future use. */ private long timeOutInterval; private LockProperties(PropertyBuilder propertyBuilder) { this.isSessionScoped = propertyBuilder.isSessionScoped; this.isDeep = propertyBuilder.isDeep; this.timeOutInterval = propertyBuilder.timeOutInterval; this.retryCount = propertyBuilder.retryCount; } public static class PropertyBuilder { private boolean isSessionScoped = true; private boolean isDeep = false; private long timeOutInterval = Long.MAX_VALUE; private int retryCount = 5; public PropertyBuilder setIsSessionScoped(boolean isSessionScoped) { this.isSessionScoped = isSessionScoped; return this; } public PropertyBuilder setIsDeep(boolean deep) { this.isDeep = deep; return this; } public PropertyBuilder setTimeOutInterval(long timeOutInterval) { this.timeOutInterval = timeOutInterval; return this; } public PropertyBuilder setRetryCount(int retryCount) { this.retryCount = retryCount; return this; } public LockProperties build() { return new LockProperties(this); } } public boolean isDeep() { return isDeep; } public boolean isSessionScoped() { return isSessionScoped; } public long getTimeOutInterval() { return timeOutInterval; } public int getRetryCount() { return retryCount; } } /** * This code should maintain parity with {@code com.adobe.cq.wcm.translation.impl.lock.LockUtil} * Any changes made here should also be reflected in the other class as well */ private static class LockUtil { private static final Logger log = LoggerFactory.getLogger(LockUtil.class); private static final int RETRY_WAIT_TIME_MS = 500; protected static AuthorizationService authorizationService; /** * Block to initialize Authorization service */ static { Bundle bundle = FrameworkUtil.getBundle(LockUtil.class); if (bundle != null) { BundleContext bundleContext = bundle.getBundleContext(); if (bundleContext != null) { ServiceReference serviceReference = bundleContext.getServiceReference(AuthorizationService.class.getName()); if (serviceReference != null) { authorizationService = (AuthorizationService) bundleContext.getService(serviceReference); } } } } /** * Locks a resource with retry mechanism. * * @param resource resource to lock * @return true if the node was locked successfully, false otherwise * Throws AccessDeniedException if the current user does not have permission to lock the node. * Throws LockException if the node is already locked by another session. * Throws TranslationException if any other error occurs while locking the node. */ public static Lock lockResource(Resource resource, LockProperties lockProperties) throws AccessDeniedException, LockException, TranslationException { if (resource != null) { Session session = resource.getResourceResolver().adaptTo(Session.class); return lockContentPath(session, resource.getPath(), lockProperties); } else { throw new TranslationException("Failed to lock resource. Resource is null.", TranslationException.ErrorCode.GENERAL_EXCEPTION); } } /** * Locks a node with retry mechanism. * * @param node Node to be locked * @return true if the node was locked successfully, false otherwise * Throws AccessDeniedException if the current user does not have permission to lock the node. * Throws LockException if the node is already locked by another session. * Throws TranslationException if any other error occurs while locking the node. */ public static Lock lockNode(Node node, LockProperties lockProperties) throws AccessDeniedException, LockException, TranslationException { if (node != null) { String nodePath = getNodePath(node); Session session = getSessionFromNode(node); return lockContentPath(session, nodePath, lockProperties); } else { throw new TranslationException("Failed to lock resource. Resource is null.", TranslationException.ErrorCode.GENERAL_EXCEPTION); } } /** * Unlocks a node. * * @param node the node to be unlocked * @return true if the node was unlocked successfully, false if no lock is there on node. Throws exceptions in case of errors. * throws AccessDeniedException if the current user does not have permission to unlock the node. * throws TranslationException if any other error occurs while unlocking the node. */ public static boolean unlockNode(Node node) throws TranslationException, AccessDeniedException, LockException { if (node != null) { Session session = getSessionFromNode(node); return unlockContent(session, node); } else { throw new TranslationException("Failed to lock resource. Resource is null.", TranslationException.ErrorCode.GENERAL_EXCEPTION); } } /** * Unlocks a resource. * * @param resource the resource to be unlocked * @return true if the node was unlocked successfully, false if no lock is there on node. Throws exceptions in case of errors. * throws AccessDeniedException if the current user does not have permission to unlock the node. * throws TranslationException if any other error occurs while unlocking the node. */ public static boolean unlockResource(Resource resource) throws TranslationException, AccessDeniedException, LockException { if (resource != null) { Session session = resource.getResourceResolver().adaptTo(Session.class); Node contentNode = resource.adaptTo(Node.class); if (contentNode != null) { return unlockContent(session, contentNode); } else { String errorMsg = String.format("Failed to adapt resource [%s] to node.", resource.getPath()); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } else { throw new TranslationException("Failed to lock resource. Resource is null.", TranslationException.ErrorCode.GENERAL_EXCEPTION); } } /** * Checks if the node can be unlocked with same user or administrative access. * * @param node the node to be unlocked. * @return true if the node can be unlocked, false otherwise * Throws TranslationException if any error occurs while checking the lock. */ public static boolean canUnlockWithAdministrativeAccess(Node node) throws TranslationException, RepositoryException { Session session = getSessionFromNode(node); if (session != null) { if (canUnlock(node)) { return true; } else if (authorizationService.hasAdministrativeAccess(session)) { return true; } } return false; } /** * Checks if the node can be unlocked with same user * * @param node the node to be unlocked. * @return true if the node can be unlocked, false otherwise * Throws TranslationException if any error occurs while checking the lock. */ public static boolean canUnlock(Node node) throws TranslationException { Session session = getSessionFromNode(node); if (session!=null) { String lockOwner = getLockOwner(node); if (lockOwner.equals(session.getUserID())) { return true; } } return false; } /** * Gets the lock owner of a node. * * @param node the node to get the lock owner for. * @return the lock owner of the node * Throws TranslationException if any error occurs while getting the lock owner. */ public static String getLockOwner(Node node) throws TranslationException { Session session = getSessionFromNode(node); String nodePath = getNodePath(node); try { LockManager lockManager = getLockManager(session); Lock lock = lockManager.getLock(nodePath); return lock.getLockOwner(); } catch (RepositoryException e) { String errorMsg = String.format("Error while getting lock owner for node [%s]", nodePath); log.error(errorMsg); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } /** * Checks if the node is locked. * * @param node the node to be checked * @return true if the node is locked, false otherwise * Throws TranslationException if any error occurs while checking the lock. */ public static boolean isLocked(Node node) throws TranslationException { Session session = getSessionFromNode(node); String nodePath = getNodePath(node); if (session!=null) { LockManager lockManager = getLockManager(session); try { return lockManager.isLocked(nodePath); } catch (RepositoryException e) { String errorMsg = String.format("Error while checking if node [%s] is locked", nodePath); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } else { String errorMsg = String.format("Failed to check if node [%s] is locked. Session is null", nodePath); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } /** * Gets the lock of a node. * @param node the node to get the lock * @return the lock of the node * @throws TranslationException if any error occurs while getting the lock. */ public static Lock getLock(Node node) throws TranslationException { Session session = getSessionFromNode(node); String nodePath = getNodePath(node); if (session!=null) { LockManager lockManager = getLockManager(session); try { return lockManager.getLock(nodePath); } catch (RepositoryException e) { String errorMsg = String.format("Error while getting lock for node [%s]", nodePath); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } else { String errorMsg = String.format("Failed to get lock for node [%s]. Session is null", nodePath); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } /** * Adds a lock token to a node. * * @param node the node to add the lock token for. * Throws TranslationException if any error occurs while adding the lock token. */ public static void addNodeLockToken(Node node) throws TranslationException { Session session = getSessionFromNode(node); String nodePath = getNodePath(node); if (session!=null) { LockManager lockManager = getLockManager(session); try { lockManager.addLockToken(nodePath); } catch (RepositoryException e) { String errorMsg = String.format("Error while adding lock token for node [%s]", nodePath); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } else { String errorMsg = String.format("Failed to add lock token for node [%s]. Session is null", nodePath); throw new TranslationException(errorMsg, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } private static LockManager getLockManager(Session session) throws TranslationException { LockManager lockManager; try { lockManager = session.getWorkspace().getLockManager(); } catch (RepositoryException e) { String errorMessage = "Failed to get LockManager from session"; log.error(errorMessage, e); throw new TranslationException(errorMessage, e, TranslationException.ErrorCode.GENERAL_EXCEPTION); } return lockManager; } private static boolean unlockContent(Session session, Node contentNode) throws TranslationException, LockException, AccessDeniedException { if (session != null) { LockManager lockManager = getLockManager(session); String contentPath = getNodePath(contentNode); try { if (lockManager.isLocked(contentPath)) { lockManager.unlock(contentPath); return true; } else { log.warn("Node {} is not locked.", contentPath); return false; } } catch (AccessDeniedException e) { String errorMessage = String.format("Failed to unlock Node lock on node [%s]. Access denied to unlock node.", contentPath); log.error(errorMessage, e); throw e; } catch (RepositoryException e) { String errorMessage = String.format("Failed to unlock Node lock on node [%s].", contentPath); log.error(errorMessage); throw new TranslationException(errorMessage, e, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } else { throw new TranslationException("Failed to unlock node. Session is null.", TranslationException.ErrorCode.GENERAL_EXCEPTION); } } private static Lock lockContentPath(Session session, String contentPath, LockProperties lockProperties) throws AccessDeniedException, LockException, TranslationException { if (session != null) { LockManager lockManager = getLockManager(session); Node lockableNode = getLockableNode(contentPath, session); String lockablePath = getNodePath(lockableNode); Lock lock = obtainLock(session, lockablePath, lockProperties, lockManager); if (lock != null) { log.info("Node {} locked successfully.", contentPath); return lock; } else { throw new TranslationException("Failed to lock node " + contentPath, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } else { throw new TranslationException("Failed to lock node. Session is null.", TranslationException.ErrorCode.GENERAL_EXCEPTION); } } private static Lock obtainLock(Session session, String nodePath, LockProperties lockProperties, LockManager lockManager) throws AccessDeniedException, LockException, TranslationException { boolean isDeep = lockProperties.isDeep(); boolean isSessionScoped = lockProperties.isSessionScoped(); long timeOutInterval = lockProperties.getTimeOutInterval(); int retryCount = lockProperties.getRetryCount(); if (session != null && lockManager != null) { int retryAttempt = 0; do { try { if (session.itemExists(nodePath)) { Lock lock = lockManager.lock(nodePath, isDeep, isSessionScoped, timeOutInterval, session.getUserID()); session.save(); return lock; } } catch (LockException e) { String errorMessage = String.format("Taking lock on node [%s] failed. Node is already locked by another session", nodePath); log.error(errorMessage); if (retryAttempt > retryCount - 1) { throw e; } } catch (AccessDeniedException e) { String errorMessage = String.format("Taking lock on node [%s] failed. Access denied to lock node", nodePath); log.error(errorMessage); if (retryAttempt > retryCount - 1) { throw e; } } catch (RepositoryException e) { String errorMessage = String.format("Taking lock on node [%s] failed.", nodePath); log.error(errorMessage); if (retryAttempt > retryCount - 1) { throw new TranslationException("Failed to lock node " + nodePath, e, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } if (retryCount >= 1) { long waitMS = ((long) Math.pow(2, retryAttempt)) * RETRY_WAIT_TIME_MS; String retryMessage = String.format("Retry to take lock on node [%s]. Wait for [%s] ms. Retry count index: [%s].", nodePath, waitMS, retryAttempt + 1); log.error(retryMessage); try { Thread.sleep(waitMS); } catch (InterruptedException e1) { log.error("Thread is interrupted while waiting to take lock on node"); } } retryAttempt++; } while (retryAttempt <= retryCount); } return null; } private static Node getLockableNode(String nodePath, Session session) throws TranslationException { try { Node lockableNode = session.getNode(nodePath); if (lockableNode.canAddMixin(JcrConstants.MIX_LOCKABLE)) { lockableNode.addMixin(JcrConstants.MIX_LOCKABLE); session.save(); } return lockableNode; } catch (RepositoryException ex) { String errorMessage = String.format("Failed to get lockable node for the resource path [%s].", nodePath); log.error(errorMessage); throw new TranslationException(errorMessage, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } private static String getNodePath(Node node) throws TranslationException { try { return node.getPath(); } catch (RepositoryException e) { String errorMessage = "Failed to get path of lockable node"; log.error(errorMessage); throw new TranslationException(errorMessage, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } private static Session getSessionFromNode(Node node) throws TranslationException { try { return node.getSession(); } catch (RepositoryException e) { String errorMessage = "Failed to get session from node"; log.error(errorMessage); throw new TranslationException(errorMessage, TranslationException.ErrorCode.GENERAL_EXCEPTION); } } } // Methods copied from wcm-reactor and updated to read Rule File and get the Ready For Loc related tag info private static String getTranslationRulesFileLocation(ResourceResolver resolver, boolean bWrite) throws RepositoryException, PersistenceException { String strRetVal = null; // we need to check which file exists in priority order for (int index = 0; index < RULE_FILES_PRIORITY_ARRAY.length; index++) { String strCurrentPath = RULE_FILES_PRIORITY_ARRAY[index]; if (resolver.getResource(strCurrentPath) != null) { strRetVal = strCurrentPath; break; } } if (bWrite && !LEGACY_ETC_RULES_FILE_PATH.equals(strRetVal)) { strRetVal = CONF_RULES_FILE_PATH; // write to conf only // write to conf only if it is not etc path, other wise write to etc only } if (LEGACY_ETC_RULES_FILE_PATH.equals(strRetVal)) { log.info("falling back to {} path, which is obselete, please upgrade this path", strRetVal); } if (bWrite && null != strRetVal && resolver.getResource(strRetVal) == null) { Session session = resolver.adaptTo(Session.class); // we need to create parent folder structure. JcrUtils.getOrCreateByPath(Text.getRelativeParent(strRetVal, 1), false, JcrConstants.NT_FOLDER, JcrConstants.NT_FOLDER, session, true); Workspace workspace = session.getWorkspace(); workspace.copy(LIBS_RULES_FILE_PATH, strRetVal); resolver.commit(); } return strRetVal; } private static HashMap getCQTagMapFromRules(ResourceResolver resolver) throws RepositoryException, IOException { String ruleFilePath = getTranslationRulesFileLocation(resolver, false); Resource resource = resolver.getResource(ruleFilePath); if (resource != null) { javax.jcr.Node node = resource.adaptTo(javax.jcr.Node.class); if (node != null) { javax.jcr.Node jcrContent = node.getNode(JcrConstants.JCR_CONTENT); InputStream contentStream = jcrContent.getProperty(JcrConstants.JCR_DATA).getBinary().getStream(); try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(contentStream); final org.w3c.dom.Node rootNode = doc.getFirstChild(); final NodeList nodeList = rootNode.getChildNodes(); //need to maintain the order of the list of tags in the rule file HashMap contentFilterTagList = new LinkedHashMap<>(); for (int index = 0; index < nodeList.getLength(); index++) { org.w3c.dom.Node childNode = nodeList.item(index); if (childNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { if (ELEMENT_CONTENT_FILTER_NODE.equals(childNode.getNodeName())) { processContentFilterNode(childNode, contentFilterTagList); } } } return contentFilterTagList; } catch (Exception e) { log.error("Error in getting rfl tag maps from rules"); } } } else { log.debug("Resource not found in path: " + ruleFilePath); } return null; } private static void processContentFilterNode(org.w3c.dom.Node currentNode, HashMap contentFilterTagList) { if (currentNode != null) { String valueListStr = getElementAttribute((Element) currentNode, ATTRIBUTE_CONTENT_FILTER_VALUE, ""); Boolean createLangCopy = getElementAttribute((Element) currentNode, ATTRIBUTE_CONTENT_CREATE_LANG_COPY, true); if(!valueListStr.isEmpty()) { if(valueListStr.contains(",")){ String[] tagList = valueListStr.split(","); for(String tag: tagList){ contentFilterTagList.put(tag,createLangCopy); } } else { contentFilterTagList.put(valueListStr,createLangCopy); } } } } private static String getElementAttribute(Element element, String strAttributeName, String strDefaultValue) { return element.hasAttribute(strAttributeName) ? element.getAttribute(strAttributeName) : strDefaultValue; } private static boolean getElementAttribute(Element element, String strAttributeName, boolean bDefault) { String strAttributeValue = getElementAttribute(element, strAttributeName, null); boolean bRetVal = bDefault; if (strAttributeValue != null) { if (strAttributeValue.equalsIgnoreCase("true")) { bRetVal = true; } else if (strAttributeValue.equalsIgnoreCase("false")) { bRetVal = false; } } return bRetVal; } private static ArrayList getCqTagsValueList(Property prop) throws RepositoryException{ ArrayList tagList = new ArrayList<>(); // getting the tags from node if (prop.isMultiple()) { javax.jcr.Value[] val_array = prop.getValues(); if (val_array != null && val_array.length > 0) { for (int index = 0; index < val_array.length; index++) { javax.jcr.Value value = val_array[index]; String strTagName = value.getString(); if (!tagList.contains(strTagName)) { tagList.add(strTagName); } } } } return tagList; } private static boolean isNodeReadyForLocalization(Resource srcResource, ResourceResolver resolver){ try { ArrayList rflTagList = new ArrayList<>(); rflTagList.addAll(getCQTagMapFromRules(resolver).keySet()); if(rflTagList.isEmpty()){ return true; } if (null != srcResource.adaptTo(Asset.class)) { javax.jcr.Node assetSrcNode = srcResource.adaptTo(javax.jcr.Node.class); if (assetSrcNode.hasNode(JcrConstants.JCR_CONTENT)) { javax.jcr.Node nodeContent = assetSrcNode.getNode(JcrConstants.JCR_CONTENT); if (nodeContent != null) { if (nodeContent.hasNode(METADATA)) { javax.jcr.Node nodeMetadata = nodeContent.getNode(METADATA); if (nodeMetadata.hasProperty(CQ_TAG_NAME)) { Property prop = nodeMetadata.getProperty(CQ_TAG_NAME); ArrayList tagList = getCqTagsValueList(prop); for (String ruleTag : rflTagList) { if (tagList.contains(ruleTag)) { return false; } } } } } } } } catch (RepositoryException | IOException e) { log.error("Error in getting is node ready for localization"); } return true; } private static boolean isCreateLangCopyForRFLResource(Resource srcResource, ResourceResolver resolver) { try { HashMap rflTagMap = getCQTagMapFromRules(resolver); if(rflTagMap == null || rflTagMap.isEmpty()) { return true; } if (null != srcResource.adaptTo(Asset.class)) { javax.jcr.Node assetSrcNode = srcResource.adaptTo(javax.jcr.Node.class); if (assetSrcNode.hasNode(JcrConstants.JCR_CONTENT)) { javax.jcr.Node nodeContent = assetSrcNode.getNode(JcrConstants.JCR_CONTENT); if (nodeContent != null) { if (nodeContent.hasNode(METADATA)) { javax.jcr.Node nodeMetadata = nodeContent.getNode(METADATA); if (nodeMetadata.hasProperty(CQ_TAG_NAME)) { Property prop = nodeMetadata.getProperty(CQ_TAG_NAME); ArrayList tagList = getCqTagsValueList(prop); boolean retVal = true; for (String tag : rflTagMap.keySet()) { // get the value for last present tag on the rule since rflTagMap is LinkedHashMap if(tagList.contains(tag)){ retVal = rflTagMap.get(tag); } } return retVal; } } } } } } catch (RepositoryException | IOException e) { log.error("Error in getting is create lang copy for RFL tag"); } return true; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy