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

com.adobe.aemds.guide.utils.DocumentDataMerger Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2014 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 may be covered by U.S. and Foreign Patents,
 * patents in process, 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.
 **************************************************************************/

package com.adobe.aemds.guide.utils;

import com.adobe.aemds.guide.service.GuideException;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.util.Map;

/**
 * @pad.exclude Exclude from Published API.
 */
public class DocumentDataMerger extends KeyValueDataMerger {

    protected XPath xPath;
    protected Document mergeReferenceDocument;
    protected Object currentRepeatNode;
    // current repeat path here is a relative path
    protected String currentRepeatPath;

    public DocumentDataMerger(JSONObject guideJson, Document mergeReferenceDocument, Map params) {
        super(guideJson, params);
        this.xPath = XPathFactory.newInstance().newXPath();
        setBindProperty();
        this.mergeReferenceDocument = mergeReferenceDocument;
        this.currentRepeatNode = this.mergeReferenceDocument.getDocumentElement();
        this.currentRepeatPath = "";
    }

    protected void setBindProperty() {
        this.bindProperty = GuideConstants.NAME;
    }

    protected String getComputedBindProperty(JSONObject jsonObject) throws JSONException {
        String bindProp = jsonObject.getString(this.bindProperty);
        if(currentRepeatPath.length() > 0){
            return currentRepeatPath + "/"+ bindProp;
        } else {
            return bindProp;
        }
    }
    protected boolean isInCurrentContext(String bindPath){
       return StringUtils.isNotEmpty(this.currentRepeatPath) && bindPath.startsWith(this.currentRepeatPath);
    }

    /**
     * Get the path relative to current context path (currentRepeatPath)
     * it can be an empty string which means that current node is not a descendant of context node
     * i.e. context does not contain it.
     * @param bindPath
     * @return relative path to the current repeat path for the given bind path
     */
    protected String getRelativePath(String bindPath){
        bindPath = StringUtils.substringAfter(bindPath, currentRepeatPath);
        bindPath = GuideUtils.removePrefix(bindPath, "/");
        return bindPath;
    }

    /**
     * Get value for current node relative to current context
     * This method is called while setting value for a field object and thus
     * is expected to return a primitive value.
     * @param relativePath
     * @param isRepeatChild
     * @param isRichText
     * @throws Exception
     */
    protected String matchPrimitive(String relativePath,boolean isRepeatChild, boolean isRichText) throws Exception {
        String nodeValue = null;
        Node dataNode = null;
        Object bindNode = isRepeatChild? this.currentRepeatNode : mergeReferenceDocument.getDocumentElement();
        if(StringUtils.isNotEmpty(relativePath)) {
            /* Extract the rich Text value of the field which is present as XML */
            if (isRichText) {
                nodeValue = XMLUtils.getXMLfromXsdDom((Element) xPath.evaluate(relativePath + "/*", bindNode, XPathConstants.NODE), true);
            }
            if (StringUtils.isEmpty(nodeValue)) {
                nodeValue = null;
                //get the node from xml
                dataNode = (Node) xPath.evaluate(relativePath, bindNode, XPathConstants.NODE);
            }
            if(dataNode != null) {
                nodeValue = dataNode.getTextContent();
            }
        }
        return nodeValue;
    }

    /**
     * Get value for current node relative to current context
     * This method is called while setting value for a field object and thus
     * is expected to return a primitive value.
     * @param relativePath
     * @param isRepeatChild
     * @throws Exception
     */
    protected String matchPrimitive(String relativePath,boolean isRepeatChild) throws Exception {
        return matchPrimitive(relativePath, isRepeatChild, false);
    }

    public void updateMergedJson(JSONObject jsonObject, String guideNodeClass) throws GuideException {
        String bindPath = null;
        boolean isRepeatChild = false;
        try {
            if (jsonObject.has(this.bindProperty)) {
                bindPath = getComputedBindProperty(jsonObject);
                if(isInCurrentContext(bindPath)){
                    // isRepeatChild does not carry any info that bindPath does not have, we can refactor this sometime.
                    isRepeatChild = true;
                    bindPath = getRelativePath(bindPath);
                }
                //Adding bindref to items of the bound composite field.
                if(GuideUtils.isGuideCompositeField(guideNodeClass) && !GuideUtils.isUnboundObj(jsonObject)) {
                    setCompositeFieldBindRef(jsonObject, bindPath);
                }
            }

            boolean isRichText = jsonObject.optBoolean(GuideConstants.RICHTEXT_ALLOWED);
            String value = matchPrimitive(bindPath, isRepeatChild, isRichText);
            updateJsonValue(jsonObject, value);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            throw new GuideException(e);
        }
    }

    /**
     * Get the current context
     */
    protected Object getCurrentContext(){
        return currentRepeatNode;
    }

    /**
     * Set the current context
     * @param ctx
     */
    protected void setCurrentContext(Object ctx){
        currentRepeatNode = ctx;
    }

    /**
     * Get the path to current context node in the hierarchy
     * This is always maintained with CurrentContext
     */
    protected String getCurrentContextPath(){
        return currentRepeatPath;
    }
    /**
     * Set the path to current context node in the hierarchy
     * This is always maintained with CurrentContext
     */
    protected void setCurrentContextPath(String path){
        currentRepeatPath = path;
    }

    /**
     * Find the node in XML for the given relative path and full path
     * This method is called while resolving value for a non field object and thus is expected to
     * return a group/composite value
     * If relative path is empty, then we use full path to resolve node in XML using root element as reference
     * else we use relative path to resolve node in XML using current repeat node as reference
     * @param relativePath
     * @param fullPath
     * @throws Exception
     */
    protected Object matchComposite(String relativePath, String fullPath) throws Exception {
        NodeList currentRepeatNodeList = null;
        // If both belong to different path then relativeXpath would be "", in such case we should fall back to root
        // and get the bindPath relative to the root
        // Done to solve Issue: LC-3862520
        if(relativePath.length() == 0){
            // if the length is 0, get the root doc element
            Node node = mergeReferenceDocument.getDocumentElement();
            // note: if bind path is empty, this means there is some issue in adaptive forms created
            // we don't support unbound repeatable panel inside XSD based adaptive forms
            currentRepeatNodeList = (NodeList)xPath.evaluate(fullPath , node, XPathConstants.NODESET);
        } else {
            currentRepeatNodeList = (NodeList)xPath.evaluate(relativePath , currentRepeatNode, XPathConstants.NODESET);
        }
        return currentRepeatNodeList;
    }

    protected int getMatchCount(Object match){
        NodeList nodeList = (NodeList) match;
        return nodeList.getLength();
    }
    protected Object getMatchItem(Object match, int ith){
        NodeList nodeList = (NodeList) match;
        return nodeList.item(ith);
    }
    protected void resetContext(){
        currentRepeatNode = mergeReferenceDocument.getDocumentElement();
    }

    protected void mergeJSONObject(String objectKey, JSONObject jsonObject) throws Exception {
        boolean handleDefault = true;
        int minOccur = 1;
        int maxOccur = 1;
        if(jsonObject.has("minOccur")){
            minOccur = jsonObject.getInt("minOccur");
        }
        if(jsonObject.has("maxOccur")){
            maxOccur = jsonObject.getInt("maxOccur");
            if(maxOccur == -1){
                maxOccur = Integer.MAX_VALUE;
            }
        }
        if(minOccur != 1 || maxOccur != 1){
            Object originalRepeatNode = getCurrentContext();
            String originalXPath = getCurrentContextPath();
            String bindPath = getComputedBindProperty(jsonObject);
            String relativePath = getRelativePath(bindPath);
            Object currentMatch = matchComposite(relativePath, bindPath);
            setCurrentContextPath(bindPath);
            try {
                handleDefault = false;
                int originalRepeatCount = getMatchCount(currentMatch);
                //Repeat count must remain in range of minOccur and maxOccur
                int repeatCount = Math.max(originalRepeatCount, minOccur);
                repeatCount = Math.min(repeatCount, maxOccur);
                for (int i = 0; i < repeatCount; i++) {
                    if (originalRepeatCount <= i) {
                        // Need too review if prefill count is less than minOccur than should we add new instance on client or server
                        // to meet minOccur requirement
                        resetContext();
                    } else {
                        setCurrentContext(getMatchItem(currentMatch, i));

                    }
                    if (i > 0) {
                        String repeatKey = getUniqueKey(objectKey, jsonObject);
                        jsonWriter.key(repeatKey).object();
                    }
                    super.mergeJSONObject(objectKey, jsonObject);
                    if (i != repeatCount - 1) {
                        jsonWriter.endObject();
                    }
                }
            } finally {
                setCurrentContext(originalRepeatNode);
                setCurrentContextPath(originalXPath);
            }
        }
        if(handleDefault){
            boolean isUnBoundCompositeField = GuideUtils.isGuideCompositeField(jsonObject.optString(GuideConstants.GUIDE_NODE_CLASS, ""))
                                   && GuideUtils.isUnboundObj(jsonObject);
            Object originalRepeatNode = getCurrentContext();
            String originalXPath = getCurrentContextPath();

            if(isUnBoundCompositeField) {
                String tagName = jsonObject.optString(GuideConstants.NAME);
                Object node = xPath.evaluate(tagName, originalRepeatNode, XPathConstants.NODE);
                // node is null when the (reviewStatus and reviewDocument) or (fileattachment and comment)
                // tags are at the root level.
                // So, the context must be set to the composite field node only when the node is not null.
                if (node != null) {
                    setCurrentContext(node);
                }
                setCurrentContextPath(getComputedBindProperty(jsonObject));
            }
            super.mergeJSONObject(objectKey, jsonObject);

            if (isUnBoundCompositeField) {
                setCurrentContext(originalRepeatNode);
                setCurrentContextPath(originalXPath);
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy