/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.xml.aspects.sql;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.mapping.Mapping;
import org.eclipse.xsd.XSDComponent;
import org.eclipse.xsd.XSDTypeDefinition;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.designer.metamodels.transformation.MappingClass;
import org.teiid.designer.metamodels.transformation.MappingClassColumn;
import org.teiid.designer.metamodels.transformation.StagingTable;
import org.teiid.designer.metamodels.transformation.TreeMappingRoot;
import org.teiid.designer.metamodels.xml.XmlContainerNode;
import org.teiid.designer.metamodels.xml.XmlDocumentEntity;
import org.teiid.designer.metamodels.xml.XmlElement;
import org.teiid.designer.metamodels.xml.util.XmlDocumentUtil;
/**
* XmlDocumentMappingHelper
*
* @since 8.0
*/
public class XmlDocumentMappingHelper {
private final List treeMappingRoots; // instances of TreeMappingRoot
private final Map xmlDocNodeToMappingClassColumn;
private final Map xmlDocNodeToMappingClass;
private final Map xmlDocNodeToStagingTables;
/**
* Construct an instance of XmlDocumentMappingHelper.
* @see #initialize()
*/
public XmlDocumentMappingHelper( final List treeMappingRoots ) {
CoreArgCheck.isNotNull(treeMappingRoots);
this.treeMappingRoots = treeMappingRoots;
this.xmlDocNodeToMappingClassColumn = new HashMap();
this.xmlDocNodeToMappingClass = new HashMap();
this.xmlDocNodeToStagingTables = new HashMap();
}
/**
* Method to initialize the helper. This method <i>must</i> be called before the
* {@link #getMappingClass(XmlDocumentEntity)} or {@link #getMappingClassColumn(XmlDocumentEntity)}
* methods (or they will always return null!).
*/
public void initialize() {
this.xmlDocNodeToMappingClass.clear();
this.xmlDocNodeToMappingClassColumn.clear();
// Iterate through the TreeMappingRoot recursively ...
final Iterator iter = this.treeMappingRoots.iterator();
while (iter.hasNext()) {
final TreeMappingRoot treeMappingRoot = (TreeMappingRoot)iter.next();
process(treeMappingRoot);
}
}
/**
* Method to obtain the {@link MappingClassColumn} that is mapped to the supplied
* {@link XmlDocumentEntity}.
* @param xmlNode the XML document node
* @return the mapping class column bound to the supplied node, or null if the
* node has no such binding
*/
public MappingClassColumn getMappingClassColumn( final XmlDocumentEntity xmlNode ) {
return (MappingClassColumn)this.xmlDocNodeToMappingClassColumn.get(xmlNode);
}
/**
* Method to obtain the {@link MappingClass} that is mapped to the supplied
* {@link XmlDocumentEntity}.
* @param xmlNode the XML document node
* @return the mapping class bound to the supplied node, or null if the
* node has no such binding
*/
public MappingClass getMappingClass( final XmlDocumentEntity xmlNode ) {
return (MappingClass)this.xmlDocNodeToMappingClass.get(xmlNode);
}
/**
* Method to obtain the {@link MappingClass} that is mapped to the supplied
* {@link XmlDocumentEntity}.
* @param xmlNode the XML document node
* @return the mapping class bound to the supplied node, or null if the
* node has no such binding
*/
public StagingTable[] getStagingTables( final XmlDocumentEntity xmlNode ) {
return (StagingTable[])this.xmlDocNodeToStagingTables.get(xmlNode);
}
/**
* Method to obtain the root {@link MappingClass} that compliments the
* mapping class bound to the supplied {@link XmlElement}. If the
* mapping class bound to this XML element is marked with recursionAllowed
* and recursive as true, then there should be an XML element upward within
* the parent hierarchy that is also bound to a mapping class. This
* complimentary mapping class represents the re-entrant point in the document
* for the recursion. Null will be returned if the supplied XML element
* is null, is not bound to a mapping class, the mapping class it is bound to is
* not marked as recursionAllowed and recursive, or the parent mapping class could
* not be found.
* @param element the XML element node
* @return the parent mapping class
*/
public MappingClass getRecusionRootMappingClass( final XmlElement xmlElement ) {
MappingClass mc = (MappingClass)this.xmlDocNodeToMappingClass.get(xmlElement);
// The mapping class must be marked for recursion before proceeding
if (mc != null && mc.isRecursionAllowed() && mc.isRecursive()) {
// Get the XSD type of the Xml element
final XSDComponent xsdComponent = xmlElement.getXsdComponent();
XSDTypeDefinition type = XmlDocumentUtil.findXSDType(xsdComponent);
// The search logic currently works by matching XSD types
if (xsdComponent == null) {
return null;
}
// Perform an upward search on the XML document trying to match XSD types
EObject owner = xmlElement.eContainer();
while (owner != null) {
if (owner instanceof XmlElement) {
// The XML element must be bound to a mapping class ...
XSDComponent ownerXsdComponent = ((XmlElement)owner).getXsdComponent();
XSDTypeDefinition ownerType = XmlDocumentUtil.findXSDType(ownerXsdComponent);
// If the types match then check if it is bound to a mapping class
if (type != null && type == ownerType) {
mc = (MappingClass)this.xmlDocNodeToMappingClass.get(owner);
if (mc != null) {
return mc;
}
// Check if the mapping class is bound to the parent container node
if (owner.eContainer() instanceof XmlContainerNode) {
mc = (MappingClass)this.xmlDocNodeToMappingClass.get(owner.eContainer());
if (mc != null) {
return mc;
}
}
}
}
owner = owner.eContainer();
}
}
return null;
}
/**
* Recursive method to process the supplied Mapping object and its nested mappings.
* This is the method that populates the XmlDocumentEntity-to-MappingClass and
* XmlDocumentEntity-to-MappingClassColumn maps.
* @param mapping the mapping to be processed; may not be null
*/
protected void process( final Mapping mapping ) {
final List inputs = mapping.getInputs();
final List outputs = mapping.getOutputs();
// If there is NOT at least one input and at least one output, then skip entirely ...
if ( inputs.isEmpty() || outputs.isEmpty() ) {
return;
}
// There may be more than one output (i.e., in the case of a choice, one mapping class column
// may be mapped to more than one XmlDocumentNode), but there should only be one input.
// (See defect 10880)
final Object mcObject = inputs.get(0);
final Iterator outputIter = outputs.iterator();
while (outputIter.hasNext()) {
final Object xmlDocNode = outputIter.next();
if ( mcObject instanceof StagingTable ) {
this.addStagingTableToMap(xmlDocNode,(StagingTable)mcObject);
} else if ( mcObject instanceof MappingClass ) {
this.xmlDocNodeToMappingClass.put(xmlDocNode,mcObject);
} else if ( mcObject instanceof MappingClassColumn ) {
this.xmlDocNodeToMappingClassColumn.put(xmlDocNode,mcObject);
}
}
// Process the nested mappings
final Iterator iter = mapping.getNested().iterator();
while (iter.hasNext()) {
final Mapping nested = (Mapping)iter.next();
process(nested);
}
}
private void addStagingTableToMap(final Object xmlDocNode, final StagingTable table) {
if (xmlDocNode == null || table == null) {
return;
}
StagingTable[] tables = (StagingTable[]) this.xmlDocNodeToStagingTables.get(xmlDocNode);
// Create a new StagingTable array if it does not yet exist
if (tables == null) {
tables = new StagingTable[]{table};
}
// Add the value to the existing array using the ArrayList utility class
else {
ArrayList tmp = new ArrayList();
tmp.addAll(Arrays.asList(tables));
tmp.add(table);
tables = new StagingTable[tmp.size()];
tmp.toArray(tables);
}
this.xmlDocNodeToStagingTables.put(xmlDocNode,tables);
}
}