/*
* 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.transformation.aspects.validation.rules;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xsd.XSDComponent;
import org.eclipse.xsd.XSDTypeDefinition;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.resource.EmfResource;
import org.teiid.designer.core.util.ModelContents;
import org.teiid.designer.core.validation.ValidationProblem;
import org.teiid.designer.core.validation.ValidationProblemImpl;
import org.teiid.designer.core.validation.ValidationResult;
import org.teiid.designer.metamodels.transformation.MappingClass;
import org.teiid.designer.metamodels.transformation.MappingClassColumn;
import org.teiid.designer.metamodels.transformation.MappingClassSet;
import org.teiid.designer.metamodels.transformation.SqlTransformationMappingRoot;
import org.teiid.designer.metamodels.transformation.TreeMappingRoot;
import org.teiid.designer.metamodels.xml.XmlContainerNode;
import org.teiid.designer.metamodels.xml.XmlElement;
import org.teiid.designer.metamodels.xml.util.XmlDocumentUtil;
import org.teiid.designer.query.IQueryFactory;
import org.teiid.designer.query.IQueryService;
import org.teiid.designer.query.sql.IGroupsUsedByElementsVisitor;
import org.teiid.designer.query.sql.lang.ICommand;
import org.teiid.designer.query.sql.lang.ICriteria;
import org.teiid.designer.query.sql.lang.IQuery;
import org.teiid.designer.query.sql.lang.ISetQuery;
import org.teiid.designer.query.sql.symbol.IGroupSymbol;
import org.teiid.designer.transformation.TransformationPlugin;
/**
* This validation rule applys aaditional validation checks for sql transformations whose targets are mapping classes. Checks
* applied: 1) ERROR -> If the query in the transformation editor is anything other than a Query or an UNION. 2) ERROR -> IF input
* parameters are used in Select clase of any of the Queries in the transformation. 3) WARNING -> IF input parameters are not used
* in the criteria of any of the queries in the transformation, for a recursive mapping class. This rule is called from
* SqlTransformationMappingRootRule while validating a transformation and assumes that the command on which its applying
* validation checks is valid.
*
* @since 8.0
*/
public class MappingClassTransformationValidationHelper {
/**
* Validate the command defining the mapping class.
*
* @since 4.2
*/
public void validate( final ICommand command,
final SqlTransformationMappingRoot transRoot,
final ValidationResult validationResult ) {
EObject targetObj = transRoot.getTarget();
if (!(targetObj instanceof MappingClass)) {
return;
}
MappingClass mappingClass = (MappingClass)targetObj;
// MyDefect : 17749 Added for recursive mapping class validation
if (mappingClass.isRecursionAllowed() && mappingClass.isRecursive() && mappingClass.eResource() instanceof EmfResource) {
validateRecursiveMappingClass(mappingClass, validationResult);
} else if (command instanceof IQuery) {
validate((IQuery)command, mappingClass, validationResult);
} else if (command instanceof ISetQuery) {
validate((ISetQuery)command, mappingClass, validationResult);
} else {
ValidationProblem errorProblem = new ValidationProblemImpl(
0,
IStatus.ERROR,
TransformationPlugin.Util.getString("MappingClassTransformationRule.Non-Query_NonUnion_transformation")); //$NON-NLS-1$
validationResult.addProblem(errorProblem);
}
}
public void validate( final IQuery query,
final MappingClass mappingClass,
final ValidationResult validationResult ) {
ValidationProblem problem2 = checkInputParamInCriteria(query, mappingClass);
if (problem2 != null) {
validationResult.addProblem(problem2);
return;
}
}
public void validate( final ISetQuery setQuery,
final MappingClass mappingClass,
final ValidationResult validationResult ) {
boolean hasInputParamInCriteria = false;
ValidationProblem inputCriteriaProblem = null;
for (Object query : setQuery.getQueryCommands()) {
if (query instanceof ISetQuery) {
validate((ISetQuery)query, mappingClass, validationResult);
return;
}
ValidationProblem problem2 = checkInputParamInCriteria((IQuery)query, mappingClass);
if (problem2 != null && !hasInputParamInCriteria) {
inputCriteriaProblem = problem2;
} else {
hasInputParamInCriteria = true;
}
}
// if there is no good criteria (using input params)
if (!hasInputParamInCriteria) {
validationResult.addProblem(inputCriteriaProblem);
}
}
/*
* WARNING -> IF input parameters are not used in the criteria of any of the queries in the transformation,
* for a recursive mapping class.
*/
ValidationProblem checkInputParamInCriteria( final IQuery query,
final MappingClass mappingClass ) {
IQueryService queryService = ModelerCore.getTeiidQueryService();
IQueryFactory factory = queryService.createQueryFactory();
IGroupsUsedByElementsVisitor groupsUsedByElementsVisitor = queryService.getGroupsUsedByElementsVisitor();
if (mappingClass.isRecursive()) {
boolean foundInParam = false;
ICriteria criteriaClause = query.getCriteria();
if (criteriaClause != null) {
Set<IGroupSymbol> groups = groupsUsedByElementsVisitor.findGroups(criteriaClause);
if (groups.contains(factory.createGroupSymbol("INPUT")) //$NON-NLS-1$
|| groups.contains(factory.createGroupSymbol("INPUTS"))) { //$NON-NLS-1$
foundInParam = true;
}
}
if (!foundInParam) {
return new ValidationProblemImpl(
0,
IStatus.WARNING,
TransformationPlugin.Util.getString("MappingClassTransformationRule.No_INPUT_Parameters_In_Criteria", mappingClass.getName())); //$NON-NLS-1$
}
}
return null;
}
/**
* MyDefect : 17749 Added method for recursive mapping class validation
*
* @param mc
* @param validationResult
* @since 4.3
*/
void validateRecursiveMappingClass( final MappingClass mc,
final ValidationResult validationResult ) {
// Apply test only to mapping classes that are marked as recursive ...
EmfResource eResource = (EmfResource)mc.eResource();
ModelContents contents = eResource.getModelContents();
if (contents != null) {
MappingClassSet mcset = (MappingClassSet)mc.eContainer();
// Get a list of all TreeMappingRoots for the XmlDocument instance that contains this MappingClass
// MappingClassSet.getTarget() returns a reference to its XmlDocument container
// TreeMappingRoot.getTarget() returns a reference to its XmlDocument container
List treeMappingRoots = contents.getTransformations(mcset.getTarget());
// Construct a map of XmlDocumentNode to MappingClass instances
Map xmlDocNodeToMappingClass = new HashMap();
for (Iterator iter = treeMappingRoots.iterator(); iter.hasNext();) {
Object obj = iter.next();
if (obj instanceof TreeMappingRoot) {
TreeMappingRoot tmr = (TreeMappingRoot)obj;
// TreeMappingRoot.getInputs() returns a reference to its source MappingClass
CoreArgCheck.isEqual(1, tmr.getInputs().size());
for (final Iterator outIter = tmr.getOutputs().iterator(); outIter.hasNext();) {
xmlDocNodeToMappingClass.put(outIter.next(), tmr.getInputs().get(0));
}
}
}
// For this MappingClass which is marked as recursive, try to find its recursion root.
// If one is not found then this is an error
for (Iterator iter = treeMappingRoots.iterator(); iter.hasNext();) {
Object obj = iter.next();
if (obj instanceof TreeMappingRoot) {
TreeMappingRoot tmr = (TreeMappingRoot)obj;
// Perform the validation checks using the TreeMappingRoot that references this MappingClass
if (tmr.getInputs().get(0) == mc) {
XmlElement element = (XmlElement)tmr.getOutputs().get(0);
MappingClass rootMappingClass = getRecusionRootMappingClass(element, xmlDocNodeToMappingClass);
if (rootMappingClass == null) {
// Mapping class is null for {0} class
String msg = TransformationPlugin.Util.getString("MappingClassTransformationRule.Mapping_Class_Is_Null_For_{0}_0", mc.getName()); //$NON-NLS-1$
ValidationProblem errorProblem = new ValidationProblemImpl(0, IStatus.ERROR, msg);
validationResult.addProblem(errorProblem);
}
// If a recursion root is found, check that its structure against the complimentary child mapping class
if (rootMappingClass != null) {
if (mc.getColumns().size() != rootMappingClass.getColumns().size()) {
// Mismatch number of column between mapping class {0} and {1}
String msg = TransformationPlugin.Util.getString("MappingClassTransformationRule.Mismatch_Number_Of_Column_{0}_AND_{1}_1", rootMappingClass.getName(), mc.getName()); //$NON-NLS-1$
ValidationProblem errorProblem = new ValidationProblemImpl(0, IStatus.ERROR, msg);
validationResult.addProblem(errorProblem);
continue;
}
List childColumns = mc.getColumns();
List rootColumns = rootMappingClass.getColumns();
for (int i = 0, n = rootColumns.size(); i < n; i++) {
MappingClassColumn rootCol = (MappingClassColumn)rootColumns.get(i);
MappingClassColumn childCol = (MappingClassColumn)childColumns.get(i);
if (!rootCol.getName().equalsIgnoreCase(childCol.getName())) {
// Mismatch column name between mapping class {0} and {1}
String rootColName = rootMappingClass.getName() + "." + rootCol.getName(); //$NON-NLS-1$
String childColName = mc.getName() + "." + childCol.getName(); //$NON-NLS-1$
String msg = TransformationPlugin.Util.getString("MappingClassTransformationRule.Mismatch_Number_Column_Name_{0}_AND_{1}_2", rootColName, childColName); //$NON-NLS-1$
ValidationProblem errorProblem = new ValidationProblemImpl(0, IStatus.ERROR, msg);
validationResult.addProblem(errorProblem);
break;
}
if (rootCol.getType() != childCol.getType()) {
// Mismatch column type between mapping class {0} and {1}
String rootColName = rootMappingClass.getName() + "." + rootCol.getName(); //$NON-NLS-1$
String childColName = mc.getName() + "." + childCol.getName(); //$NON-NLS-1$
String msg = TransformationPlugin.Util.getString("MappingClassTransformationRule.Mismatch_Column_Type_{0}_AND_{1}_3", rootColName, childColName); //$NON-NLS-1$
ValidationProblem errorProblem = new ValidationProblemImpl(0, IStatus.ERROR, msg);
validationResult.addProblem(errorProblem);
break;
}
}
}
}
}
}
}
}
/**
* @param xmlElement
* @param xmlDocNodeToMappingClass
* @return
* @since 4.3
*/
private MappingClass getRecusionRootMappingClass( XmlElement xmlElement,
final Map xmlDocNodeToMappingClass ) {
MappingClass mc = (MappingClass)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)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)xmlDocNodeToMappingClass.get(owner.eContainer());
if (mc != null) {
return mc;
}
}
}
}
owner = owner.eContainer();
}
}
return null;
}
}