/*******************************************************************************
* Copyright 2017 Capital One Services, LLC and Bitwise, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*******************************************************************************/
package hydrograph.engine.core.xmlparser.subjob;
import hydrograph.engine.core.utilities.XmlUtilities;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPathExpressionException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The Class ParseSubjob.
*
* @author Bitwise
*/
public class ParseSubjob {
private static final String SUBJOB_OUTPUT = "subjobOutput";
private static final String SUBJOB_INPUT = "subjobInput";
private static final String ID = "id";
private static final Object INSOCKET = "inSocket";
private static final String FROMCOMPONENTID = "fromComponentId";
private static final String FROMSOCKETID = "fromSocketId";
private static final String TYPE = "xsi:type";
private static final String SUBJOB_COMPONENTS = "operations|inputs|outputs|commands";
private static final String BATCH = "batch";
private Document parentXmlDocument = null;
private Document subjobXmlDocument = null;
private String subjobName;
private String subjobBatch;
public ParseSubjob(Document parentjobXml, Document subjobXml,
String subjobName, String subjobBatch) {
this.parentXmlDocument = parentjobXml;
this.subjobXmlDocument = subjobXml;
this.subjobName = subjobName;
this.subjobBatch = subjobBatch;
}
public Document expandSubjob() {
if (!parentXmlDocument.equals(subjobXmlDocument)) {
Map<String, Map<String, String>> subjobMap = null;
renameComponentIDsInSubjob(subjobXmlDocument, subjobName);
subjobMap = getComponentToPortMappingFromSubjob(SUBJOB_OUTPUT);
Map<String, Map<String, String>> parentjobMap = getComponentToPortMappingFromParentjob(subjobName);
substituteComponentNamesInSubjob(subjobXmlDocument, parentjobMap,
SUBJOB_INPUT);
substituteComponentNamesInParentjob(parentXmlDocument, subjobMap,
subjobName);
removeSubjobCustomComponent(subjobXmlDocument, SUBJOB_INPUT);
removeSubjobCustomComponent(subjobXmlDocument, SUBJOB_OUTPUT);
removeSubjobComponentFromParentjob(parentXmlDocument, subjobName);
mergeParentjobAndSubjob();
updateBatchOfComponentsTillBatchLevelInExpandedXmlDocument();
}
return parentXmlDocument;
}
/**
* Updates the batch of components in the merged job till batchlevel. Batch level is the level of nesting of subjobs.
* This method do not validate or updates the batch of components based on the source components.
*/
private void updateBatchOfComponentsTillBatchLevelInExpandedXmlDocument() {
// batchLevel is the level of nesting of subjobs
int batchLevel = getBatchLevel(XmlUtilities.getXMLStringFromDocument(parentXmlDocument));
NodeList componentsWithBatchAttribute = null;
try {
componentsWithBatchAttribute = XmlUtilities
.getComponentsWithAttribute(parentXmlDocument, BATCH);
if (componentsWithBatchAttribute != null
&& componentsWithBatchAttribute.getLength() > 0) {
setBatchOfComponentsUptoBatchlevel(batchLevel,componentsWithBatchAttribute);
} else {
throw new RuntimeException("Component tag does not have '"+ BATCH +"' attribute.");
}
} catch (XPathExpressionException e) {
// this exception will never be thrown as XPATH is evaluted on the hardcoded value i.e BATCH
throw new RuntimeException(e);
}
}
/**updates batch of all the components in the expanded job by appending .0 to batch till batchLevel.
* @param batchLevel
* @param componentsWithBatchAttribute
*/
private void setBatchOfComponentsUptoBatchlevel(int batchLevel,
NodeList componentsWithBatchAttribute) {
for (int i = 0; i < componentsWithBatchAttribute.getLength(); i++) {
String batch = componentsWithBatchAttribute.item(i).getAttributes()
.getNamedItem(BATCH).getNodeValue();
if (batch.split("\\.").length < batchLevel) {
for (int j = 0; j < batchLevel; j++) {
batch = componentsWithBatchAttribute.item(i)
.getAttributes().getNamedItem(BATCH).getNodeValue();
if (batch.split("\\.").length < batchLevel) {
componentsWithBatchAttribute.item(i).getAttributes()
.getNamedItem(BATCH).setNodeValue(batch + ".0");
}
}
}
}
}
/**Returns true if component is of type subjobInput or subjobOutput
* @param nodeValue
* @return true if component type is subjobInput or subjobOutput
*/
private boolean isSubjobIOComponent(String nodeValue) {
return nodeValue.split(":")[1].matches(SUBJOB_INPUT + "|"
+ SUBJOB_OUTPUT);
}
private int getBatchLevel(String xmlAsString) {
Matcher m = Pattern.compile("batch\\s*=\\s*\"(.*?)\"", Pattern.DOTALL)
.matcher(xmlAsString);
int length = 0;
while (m.find()) {
if (m.group(1).split("\\.").length > length) {
length = m.group(1).split("\\.").length;
}
}
return length;
}
private void mergeParentjobAndSubjob() {
Node x = parentXmlDocument.importNode(
subjobXmlDocument.getFirstChild(), true);
while (x.hasChildNodes()) {
parentXmlDocument.getFirstChild().appendChild(x.getFirstChild());
}
}
private void renameComponentIDsInSubjob(Document subjobXmlDocument,
String subjobName) {
NodeList nodeList = subjobXmlDocument.getFirstChild().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
if (nodeList.item(i).hasAttributes()) {
Node componentNode = updateComponentIdAndBatch(nodeList.item(i));
updateInSocket(componentNode);
}
}
}
private void updateInSocket(Node componentNode) {
for (int j = 0; j < componentNode.getChildNodes().getLength(); j++) {
if (componentNode.getChildNodes().item(j).hasAttributes()) {
if (componentNode.getChildNodes().item(j).getNodeName()
.equals(INSOCKET)) {
String fromComponentId = componentNode.getChildNodes()
.item(j).getAttributes()
.getNamedItem(FROMCOMPONENTID).getNodeValue();
componentNode.getChildNodes().item(j).getAttributes()
.getNamedItem(FROMCOMPONENTID)
.setNodeValue(subjobName + "." + fromComponentId);
}
}
}
}
private Node updateComponentIdAndBatch(Node componentNode) {
String componentId = componentNode.getAttributes().getNamedItem(ID)
.getNodeValue();
componentNode.getAttributes().getNamedItem(ID)
.setNodeValue(subjobName + "." + componentId);
if (!isSubjobIOComponent(componentNode.getAttributes()
.getNamedItem(TYPE).getNodeValue())) {
if (componentNode.getAttributes().getNamedItem(BATCH) == null){
throw new RuntimeException("Batch attribute is not present for '"
+ componentNode.getAttributes()
.getNamedItem(TYPE).getNodeValue().split(":")[1] + "' component with Id '"
+ componentNode.getAttributes().getNamedItem(ID).getNodeValue() +"'");
} else {
String componentBatch = this.subjobBatch.split("\\.")[0]
+ "."
+ componentNode.getAttributes().getNamedItem(BATCH)
.getNodeValue();
componentNode.getAttributes().getNamedItem(BATCH)
.setNodeValue(componentBatch);
}
}
return componentNode;
}
private void removeSubjobCustomComponent(Document XmlDocument,
String subjobType) {
NodeList nodeList = XmlDocument.getFirstChild().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
if (nodeList.item(i).getNodeName().matches(SUBJOB_COMPONENTS)) {
String componentType = nodeList.item(i).getAttributes()
.getNamedItem(TYPE).getNodeValue().split(":")[1];
if (componentType.equals(subjobType)) {
Node parent = nodeList.item(i).getParentNode();
parent.removeChild(nodeList.item(i));
}
}
}
}
private void removeSubjobComponentFromParentjob(Document XmlDocument,
String subjobId) {
NodeList nodeList = XmlDocument.getFirstChild().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
if (nodeList.item(i).getNodeName().matches(SUBJOB_COMPONENTS)) {
String componentId = nodeList.item(i).getAttributes()
.getNamedItem(ID).getNodeValue();
if (componentId.equals(subjobId)) {
Node parent = nodeList.item(i).getParentNode();
parent.removeChild(nodeList.item(i));
}
}
}
}
private void substituteComponentNamesInParentjob(Document graphXml,
Map<String, Map<String, String>> componentToPortMap,
String subjobComponentId) {
NodeList nodeList = graphXml.getFirstChild().getChildNodes();
setComponentAndSocketId(nodeList, subjobComponentId, componentToPortMap);
}
private void substituteComponentNamesInSubjob(Document graphXml,
Map<String, Map<String, String>> componentToPortMap,
String componentType) {
String componentId = null;
NodeList nodeList = graphXml.getFirstChild().getChildNodes();
// get id of subjob-input component
for (int i = 0; i < nodeList.getLength(); i++) {
if (nodeList.item(i).getAttributes() != null)
if (nodeList.item(i).getAttributes().getNamedItem(TYPE) != null) {
String type = nodeList.item(i).getAttributes()
.getNamedItem(TYPE).getNodeValue().split(":")[1];
if (type.equals(componentType))
componentId = nodeList.item(i).getAttributes()
.getNamedItem(ID).getNodeValue();
}
}
// replace subjob-input component id with one from componentToPortMap
// in fromComponentId attribute
setComponentAndSocketId(nodeList, componentId, componentToPortMap);
}
private void setComponentAndSocketId(NodeList nodeList,
String subjobComponentId,
Map<String, Map<String, String>> componentToPortMap) {
for (int i = 0; i < nodeList.getLength(); i++) {
NodeList childNodes = nodeList.item(i).getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
if (childNodes.item(j) != null) {
if (childNodes.item(j).getNodeName().equals(INSOCKET)) {
String fromComponentId = childNodes.item(j)
.getAttributes().getNamedItem(FROMCOMPONENTID)
.getNodeValue();
String fromSockettId = childNodes.item(j)
.getAttributes().getNamedItem(FROMSOCKETID)
.getNodeValue();
if (fromComponentId.equals(subjobComponentId)) {
if (componentToPortMap.containsKey(fromSockettId)) {
Map<String, String> portToComponentMap = componentToPortMap
.get(fromSockettId);
Element e = (Element) childNodes.item(j);
for (String newFromSocketId : portToComponentMap
.keySet()) {
e.setAttribute(FROMSOCKETID,
newFromSocketId);
e.setAttribute(FROMCOMPONENTID,
portToComponentMap
.get(newFromSocketId));
}
}
}
}
}
}
}
}
private Map<String, Map<String, String>> getComponentToPortMappingFromSubjob(
String componentType) {
Map<String, Map<String, String>> socketToFromComponentMap = new HashMap<String, Map<String, String>>();
NodeList nodeList = subjobXmlDocument.getFirstChild().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
if (nodeList.item(i).getAttributes() != null)
if (nodeList.item(i).getAttributes().getNamedItem(TYPE) != null)
if (nodeList.item(i).getAttributes().getNamedItem(TYPE)
.getNodeValue().split(":")[1].equals(componentType)) {
NodeList childNodes = nodeList.item(i).getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
Map<String, String> fromComponentMap = new HashMap<String, String>();
if (childNodes.item(j) != null)
if (childNodes.item(j).getNodeName()
.equals(INSOCKET)) {
String socketId = childNodes.item(j)
.getAttributes().getNamedItem(ID)
.getNodeValue();
String fromComponentId = childNodes.item(j)
.getAttributes()
.getNamedItem(FROMCOMPONENTID)
.getNodeValue();
String fromSockettId = childNodes.item(j)
.getAttributes()
.getNamedItem(FROMSOCKETID)
.getNodeValue();
fromComponentMap.put(fromSockettId,
fromComponentId);
socketToFromComponentMap.put(socketId,
fromComponentMap);
}
}
}
}
return socketToFromComponentMap;
}
private Map<String, Map<String, String>> getComponentToPortMappingFromParentjob(
String componentName) {
Map<String, Map<String, String>> socketToFromComponentMap = new HashMap<String, Map<String, String>>();
NodeList nodeList = parentXmlDocument.getFirstChild().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
NodeList childNodes = nodeList.item(i).getChildNodes();
if (nodeList.item(i).getAttributes() != null)
if (nodeList.item(i).getAttributes().getNamedItem(ID) != null)
if (nodeList.item(i).getAttributes().getNamedItem(ID)
.getNodeValue().equals(componentName))
for (int j = 0; j < childNodes.getLength(); j++) {
Map<String, String> fromComponentMap = new HashMap<String, String>();
if (childNodes.item(j) != null)
if (childNodes.item(j).getNodeName()
.equals(INSOCKET)) {
String socketId = childNodes.item(j)
.getAttributes().getNamedItem(ID)
.getNodeValue();
String fromComponentId = childNodes.item(j)
.getAttributes()
.getNamedItem(FROMCOMPONENTID)
.getNodeValue();
String fromSockettId = childNodes.item(j)
.getAttributes()
.getNamedItem(FROMSOCKETID)
.getNodeValue();
fromComponentMap.put(fromSockettId,
fromComponentId);
socketToFromComponentMap.put(socketId,
fromComponentMap);
}
}
}
return socketToFromComponentMap;
}
}