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 java.io.InputStream;
import java.io.IOException;

import java.util.ArrayList;
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.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;

import com.adobe.granite.asset.api.AssetRelation;
import com.adobe.granite.confmgr.Conf;
import com.adobe.granite.translation.core.MachineTranslationCloudConfig;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkflowData;
import com.adobe.granite.workflow.model.WorkflowModel;
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.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.util.ISO9075;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.jcr.resource.api.JcrResourceConstants;
import org.apache.sling.resource.collection.ResourceCollection;
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 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;

/**
 * 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 UPDATE_ASSET_WORKFLOW_MODEL = "/var/workflow/models/dam/update_asset";
    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

    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";


    /**
     * 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
    }

    /**
     * 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
     *
     * @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);
            contentPath = sourcePath.replaceFirst(parentOfRoot, "");
        } else {
            contentPath = sourcePath.replaceFirst(root, "");
            parentOfRoot = Text.getRelativeParent(root, 1);
        }
        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;

            try {
                if (contentPath.trim().length() > 0) {
                    String pathToCreate = Text.getRelativeParent(
                            targetPath, 1);
                    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);
                        }
                    }
                    addTranslationSourcePath(destinationResource, sourceResource.getPath());
                    setLastTranslationUpdate(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('/'));
                            }
                        }
                    }
                } else {
                    log.info(
                        "Could not create language copy for assets at path: " + targetPath +
                            ", resource already exists. Updating language copy.");
                    Asset sourceAsset = resourceResolver.getResource(sourcePath).adaptTo(Asset.class);
                    Asset targetAsset = resourceResolver.getResource(targetPath).adaptTo(Asset.class);
                    if (sourceAsset != null && targetAsset != null) {
                        if (isSmartAssetUpdateRequired(sourceAsset, targetAsset)) {
                            addSmartAssetUpdateSource(targetAsset, sourcePath);
                            addSmartAssetUpdateProperty(targetAsset, true);
                        } else {
                            addSmartAssetUpdateProperty(targetAsset, false);
                        }
                    }
                }
                createdCopies.add(targetPath);
            } catch (Exception e) {
                log.error(
                    "error while creating language copy for assets at path: "
                                + targetPath + "{}", e);
            }
        }
        return createdCopies;

    }

    private static void addTranslationSourcePath(Resource destinationResource, String translationSourcePath) throws
        RepositoryException {
        Node destNode = destinationResource.adaptTo(Node.class);
        if (destinationResource != null) {
            Node contentNode = destNode.getNode(JCR_CONTENT);
            contentNode.setProperty(ATTRIBUTE_CQ_TRANSLATION_SOURCE_PATH, translationSourcePath);
        }
    }

    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);
        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);
            contentPath = sourcePath.replaceFirst(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;

        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);
                        }
                    }
                    addTranslationSourcePath(destinationResource, sourceResource.getPath());
                    setLastTranslationUpdate(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('/'));
                        }
                    }
                }
                session.save();
            } else {
                createdCopy = targetPath;
            }
        } catch (Exception e) {
            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.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);
    }

    private static boolean isFolder(Resource resource){
        Node n = resource.adaptTo(Node.class);
        try {
            return n.isNodeType(NT_FOLDER);
        } 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());
        }
    }

    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);
            }

            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);

                    if (destinationAsset != null) {
                        destinationAsset.createRevision("", ASSET_VERSION_MESSAGE);
                        copyInsightData(destinationResource.getPath(), sourceResource.getPath(), resourceResolver,
                            pageManager);
                        deleteAllChildren(destinationResource, userSession);
                        copyAllChildren(sourceResource, destinationResource, pageManager, userSession);
                        
                        //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 to create
                        //renditions of the translated asset.
                        WorkflowSession workflowSession = resourceResolver.adaptTo(WorkflowSession.class);
                        WorkflowModel modelToStart = workflowSession.getModel(UPDATE_ASSET_WORKFLOW_MODEL);
                        WorkflowData workflowData = workflowSession.newWorkflowData("JCR_PATH", destinationPath);
                        workflowSession.startWorkflow(modelToStart, workflowData);
                    } 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);
                childNode.remove();
            }
        }
        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 {
        Node assetNode = asset.adaptTo(Node.class);
        Node jcrNode = assetNode.getNode(JCR_CONTENT);
        if (jcrNode.hasProperty(CONTENT_FRAGMENT)) {
            return jcrNode.getProperty(CONTENT_FRAGMENT).getBoolean();
        }
        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.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);
    }

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

        Node assetNode = destinationAsset.adaptTo(Node.class);
        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);
    }

    private static void addExtractMetadataPropertyForAsset(Asset asset, boolean bExtractMetadata) throws RepositoryException {
        Node assetNode = asset.adaptTo(Node.class);
        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);
    }

    /**
     * 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)) {
            return createLanguageCopyWithAssetRelationsForAsset(resourceResolver, pageManagerFactory,
                sourcePath, targetLanguageCodes);
        }

        //check for asset folder
        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)
        throws RepositoryException {
        String[] languageArray = {targetLanguageCode};
        List languageCopyList = createLanguageCopyWithAssetRelations(resourceResolver, pageManagerFactory,
            sourcePath, languageArray);
        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);
            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);
                }
            }
        }
    }

    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();
        String sourceResourcePath = sourceResource.getPath();
        if (null != sourceResource) {
            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);
                            Asset destinationAsset = resourceResolver.getResource(languageCopyPath).adaptTo(Asset.class);
                            addSmartAssetUpdateSource(destinationAsset, sourcePath);
                            addSmartAssetUpdateProperty(destinationAsset, true);
                            createdVariantCopies.add(languageCopyPath);
                            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);
                            com.adobe.granite.asset.api.Asset graniteDestinationAsset = variantLanguageCopyResource
                                    .adaptTo(com.adobe.granite.asset.api.Asset.class);
                            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);
                            break;
                        case NONE:
                            String tempLanguageCopyPath = findLanguageCopyPathWithAutoCreatedRoots(sourcePath,
                                targetLanguageCodes[languageIndex], resourceResolver);
                            Asset tempDestinationAsset = resourceResolver.getResource(tempLanguageCopyPath).adaptTo(
                                Asset.class);
                            addSmartAssetUpdateProperty(tempDestinationAsset, false);
                            createdVariantCopies.add(tempLanguageCopyPath);
                            break;
                    }
                }
            } else {
                    createdVariantCopies = createLanguageCopy(resourceResolver, pageManagerFactory, sourcePath,
                        targetLanguageCodes);
            }
            //todo add destination if required
            Asset sourceAsset = sourceResource.adaptTo(Asset.class);
            if(isContentFragment(sourceAsset)) {
                try {
                    createOrUpdateLanguageCopyForAssociatedContent(resourceResolver, pageManagerFactory, sourcePath,
                        targetLanguageCodes);
                } catch (Exception e) {
                    log.error("Could not create language copy for associated content. {}", e);
                }
            }
            if (isContentFragment(sourceAsset)) {
                try {
                    //commit changes to get variations by query. todo: remove once this is fixed
                    resourceResolver.commit();
                    createAndReplaceLanguageCopyForEmbeddedAssets(resourceResolver, pageManagerFactory, sourcePath,
                        targetLanguageCodes);
                } catch (Exception e) {
                    log.error("Could not create language copy for embedded assets. {}", e);
                }
            }
        }
        return createdVariantCopies;
    }

    private static void createAndReplaceLanguageCopyForEmbeddedAssets(ResourceResolver resourceResolver,
        PageManagerFactory pageManagerFactory, String sourcePath, String[] targetLanguageCodes)
      throws RepositoryException, IOException, WCMException {
        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);
                    if (bReplacementRequired) {
                        replaceEmbeddedAssets(languageCopyPath, targetLanguageCode, resourceResolver);
                    }
                } else {
                    //create language copies or mark for updates for source asset
                    createLanguageCopyForEmbeddedAssets(sourcePath, sourceLanguage, targetLanguageCode,
                        resourceResolver, pageManagerFactory);
                }
            }
        }
    }

    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 (getUpdateAssetNodeSourcePath(node) != null) {
                retVal = true;
            }
        }
        return retVal;
    }

    private static boolean createLanguageCopyForEmbeddedAssets(String contentFragmentPath, String sourceLanguageCode,
        String targetLanguageCode, ResourceResolver resourceResolver, PageManagerFactory pageManagerFactory)
      throws RepositoryException, IOException {
        HashSet embeddedAssets = getEmbeddedAssets(contentFragmentPath, resourceResolver);
        boolean bReplacementRequired = false;

        getLanguageRootLocale(contentFragmentPath);

        for (Asset embeddedAsset : embeddedAssets) {
            String embeddedAssetPath = embeddedAsset.getPath();
            String sourceEmbeddedAssetPath;

            if (!getLanguageRootLocale(embeddedAssetPath).equals(targetLanguageCode)) {
                sourceEmbeddedAssetPath = embeddedAssetPath;
                bReplacementRequired = true;
            } else {
                sourceEmbeddedAssetPath = findLanguageCopyPathWithAutoCreatedRoots(embeddedAssetPath, sourceLanguageCode,
                    resourceResolver);
            }

            createLanguageCopyWithAssetRelations(sourceEmbeddedAssetPath, targetLanguageCode, resourceResolver,
                    pageManagerFactory);
        }
        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);
            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.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);
        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);

            }
        }
    }

    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);
            if (null != sourceCollectionResource.adaptTo(Node.class) &&
                sourceCollectionResource.adaptTo(Node.class).hasProperty(JCR_TITLE)) {
                String sourceCollectionName = sourceCollectionResource.adaptTo(Node.class).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);
        replaceCollectionAssetLanguageCopies(collectionLanguageCopy, targetLanguageCode, resourceResolver);
    }

    private static void setProperty(Resource resource, String property, String value) throws RepositoryException {
        Node node = resource.adaptTo(Node.class);
        node.setProperty(property, 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);
        targetNode.setProperty(ATTRIBUTE_COLLECTION_SOURCE_LANGUAGE_COPY, sourceCollection.getPath());

        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);
            List relatedLinkedAssets = getRelatedAssets(relatedSourceResource, relationName);
            Asset destinationAsset = findLanguageCopyWithAutoCreatedRoots(graniteSourceAsset.getPath(),
                targetLanguageCode, resourceResolver);
            com.adobe.granite.asset.api.Asset graniteDestinationAsset = destinationAsset
                .adaptTo(com.adobe.granite.asset.api.Asset.class);
            createRelationLanguageCopy(relationName, graniteDestinationAsset, relatedLinkedAssets,
                targetLanguageCode, resourceResolver, pageManagerFactory);
        }
    }

    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) {

        if(getLanguageRootLocale(assetPath).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) ?
                    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) {
        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) {
        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)) {
                    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.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.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.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
                if (isContentFragment(destinationResource.adaptTo(Asset.class))) {
                    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);
                com.adobe.granite.asset.api.Asset graniteVariantAsset = variantResource.adaptTo(com.adobe.granite
                    .asset.api.Asset.class);
                adjustAssetRelations(graniteVariantAsset, relationName, LCofRelatedAsset, tempLCOfRelatedAsset);
                // Remove the others relation for the variant language copy
                removeAssetRelation(graniteVariantAsset, ATTRIBUTE_ASSET_OTHERS_RELATION);
            }
        }
    }

    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);
            }
            contentPath = assetPath.replaceFirst(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.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);
            }
            contentPath = folderPath.replaceFirst(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);
            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);
            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());
            Iterator collectionResourceIterator = getResourcesFromCollection(associatedContentResource);
            //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);
                }
            }
        }
        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);
            addAssetsFromCollection(rc, collectionAssets);
        }
    }

    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.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.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();
        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 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);
            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 HashSet getEmbeddedAssets(String contentFragmentNodePath, ResourceResolver resourceResolver)
      throws RepositoryException, IOException {
        log.debug("In Function: getEmbeddedAssets");
        HashSet embeddedAssets = new HashSet();
        NodeIterator ntFileNodes = getAllNtFilesAtPath(resourceResolver, contentFragmentNodePath, log);
        if (ntFileNodes != null) {
            while (ntFileNodes.hasNext()) {
                Node variationNode = ntFileNodes.nextNode();
                String mimeType = getMimeType(variationNode);
                String jcrDataProperty = JcrConstants.JCR_CONTENT + "/" + JcrConstants.JCR_DATA;
                if (mimeType.equals(MIME_TYPE_HTML) && variationNode.hasProperty(jcrDataProperty)) {
                    Property variationProperty = variationNode.getProperty(jcrDataProperty);
                    String variationData = getVariationData(variationProperty);
                    if (!StringUtils.isEmpty(variationData)) {
                        HashSet embeddedAssetsForVariation = getEmbeddedAssetsForVariation(variationData,
                            resourceResolver);
                        embeddedAssets.addAll(embeddedAssetsForVariation);
                    }
                }
            }
        }
        return embeddedAssets;
    }

    private static void replaceEmbeddedAssets(String contentFragmentNodePath, String targetLanguageCode,
        ResourceResolver resourceResolver) throws RepositoryException, IOException {
        log.debug("In Function: replaceEmbeddedAssets");
        NodeIterator ntFileNodes = getAllNtFilesAtPath(resourceResolver, contentFragmentNodePath, log);
        if (ntFileNodes != null) {
            while (ntFileNodes.hasNext()) {
                Node variationNode = ntFileNodes.nextNode();
                String mimeType = getMimeType(variationNode);
                String jcrDataProperty = JcrConstants.JCR_CONTENT + "/" + JcrConstants.JCR_DATA;
                if (mimeType.equals(MIME_TYPE_HTML) && variationNode.hasProperty(jcrDataProperty)) {
                    Property variationProperty = variationNode.getProperty(jcrDataProperty);
                    String variationData = getVariationData(variationProperty);
                    if (!StringUtils.isEmpty(variationData)) {
                        String replacementData = replaceEmbeddedAssetsForVariation(variationData, targetLanguageCode,
                            resourceResolver);
                        ValueFactory valueFactory = resourceResolver.adaptTo(Session.class).getValueFactory();
                        Value replacementValue = valueFactory.createValue(replacementData, PropertyType.BINARY);
                        variationProperty.setValue(replacementValue);
                    }
                }
            }
        }
    }

    private static String getVariationData(Property variationProperty) throws RepositoryException, IOException {
        InputStream is = null;
        String variationData = null;
        is = variationProperty.getValue().getBinary().getStream();
        try {
            variationData = IOUtils.toString(is, "UTF-8");
        } finally {
            is.close();
        }
        return variationData;
    }

    private static String getMimeType(Node variation) throws RepositoryException {
        String retVal = "";
        if (variation.hasNode(JcrConstants.JCR_CONTENT)) {
            Node contentNode = variation.getNode(JcrConstants.JCR_CONTENT);
            if (contentNode.hasProperty(JcrConstants.JCR_MIMETYPE)) {
                retVal = contentNode.getProperty(JcrConstants.JCR_MIMETYPE).getString();
            }
        }

        return retVal;
    }

    private static NodeIterator getAllNtFilesAtPath(ResourceResolver resolver, String path, Logger logger) {
        logger.trace("In function: getAllNtFilesAtPath path:{}", path);
        try {
            Session session = resolver.adaptTo(Session.class);
            QueryManager qm = session.getWorkspace().getQueryManager();
            QueryResult result;
            result =
                qm.createQuery("/jcr:root" + ISO9075.encodePath(path) + "//element(*, " + JcrConstants.NT_FILE + ")",
                    Query.XPATH).execute();
            return result.getNodes();
        } catch (RepositoryException e) {
            logger.error("Can't get nt:file from {}", path, e);
            return null;
        }
    }


    private static HashSet getEmbeddedAssetsForVariation(String variationData, ResourceResolver
        resourceResolver) {
        log.debug("In Function: getEmbeddedAssetsForVariation");
        HashSet embeddedAssets = new HashSet();
        HashSet assetPaths = getEmbeddedAssetPaths(variationData);
        for (String assetPath : assetPaths) {
            Resource resource = resourceResolver.getResource(decodeURL(assetPath));
            if (null != resource) {
                Asset asset = resource.adaptTo(Asset.class);
                if (null != asset) {
                    embeddedAssets.add(asset);
                }
            }
        }
        return embeddedAssets;
    }

    private static String decodeURL(String url) {
        try {
            return URIUtil.decode(url);
        } catch (URIException e) {
            log.error("could not decode url : {}", url, e);
            return url;
        }
    }

    private static String encodeURL(String url) {
        try {
            return URIUtil.encodePath(url);
        } catch (URIException e) {
            log.error("could not encode url : {}", url, e);
            return url;
        }
    }

    private static String replaceEmbeddedAssetsForVariation(String variationData, String targetLanguageCode,
        ResourceResolver resourceResolver) {
        log.debug("In Function: replaceEmbeddedAssetsForVariation");
        String regEx = getRegexForInlineMediaAssets();
        Pattern pattern = Pattern.compile(regEx);
        Matcher matcher = pattern.matcher(variationData);

        int lastEnd = 0;
        String updatedVariationData = "";
        while (matcher.find()) {
            String sourceCopyPath = matcher.group(getRegexGroupInlineMediaAssets());
            sourceCopyPath = decodeURL(sourceCopyPath);
            String languageCopyPath = DamLanguageUtil.findLanguageCopyPathWithAutoCreatedRoots(sourceCopyPath,
                targetLanguageCode,
                resourceResolver);
            languageCopyPath = encodeURL(languageCopyPath);
            int start = matcher.start(getRegexGroupInlineMediaAssets());
            int end = matcher.end(getRegexGroupInlineMediaAssets());
            updatedVariationData += variationData.substring(lastEnd, start) + languageCopyPath;
            lastEnd = end;
        }
        updatedVariationData += variationData.substring(lastEnd);

        return updatedVariationData;
    }

    private static HashSet getEmbeddedAssetPaths(String variationData) {
        log.debug("In Function: getEmbeddedAssetPaths");
        HashSet assetPaths = new HashSet();
        String regEx = getRegexForInlineMediaAssets();
        Pattern pattern = Pattern.compile(regEx);
        Matcher matcher = pattern.matcher(variationData);
        while (matcher.find()) {
            assetPaths.add(matcher.group(getRegexGroupInlineMediaAssets()));
        }
        return assetPaths;
    }

    private static String getRegexForInlineMediaAssets() {
        final String TOKEN_UNLIMITED_SPACE = "[\\n ]*";
        final String TOKEN_IMAGE_TAG = "img";
        final String TOKEN_ATTRIBUTE_KEY = "[a-zA-Z-]+"; //todo
        final String TOKEN_ATTRIBUTE_KEY_SOURCE = "src";
        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 = TOKEN_ATTRIBUTE_KEY_SOURCE + TOKEN_UNLIMITED_SPACE +
            "=" + TOKEN_UNLIMITED_SPACE + TOKEN_SOURCE_ATTRIBUTE_VALUE;

        final String regex = "<"
            + TOKEN_UNLIMITED_SPACE + TOKEN_IMAGE_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 int getRegexGroupInlineMediaAssets() {
        return 4;
    }

    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();
                }
                sourceContentNode.setProperty(ATTRIBUTE_CQ_TRANSLATION_LAST_UPDATE, lastModified);
            }
        }
    }

    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);
        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;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy