
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